Neovim으로
생산성 퀀텀점프하기
(그리고 다루지 못했던 얘기)
by @kodingwarrior
이번 세션에서는
Neovim으로 어떻게
작업환경을 구성하는지
소개하려고 합니다.
# 이번 발표에서 다룰 내용
* Chapter 1 : 의미론 단위로 많은 일들을 쳐내기
* Chapter 2 : CLI 환경과 친해지기
* Chapter 3 : 나만의 맞춤형 환경 구성하기
# 이번 발표에서 다룰 내용
* 특정 플러그인을 사용하는 방법 (X)
* Neovim을 어떻게 하면 내 workflow에 녹아낼지 인사이트 제공하기 (O)
# Chapter 1
# 의미론 단위의
# 많은 일들을 쳐내기
# 우리는 일을 어떻게 처리하는가?
먼저 우리가 소스코드를 편집할 때 하는 행동들을 atmoic하게 쪼개봅시다.
* **Read** : 우리가 계속 개발하면서 인식 체계에 정보가 전달되는 것
* 커서의 위치, 프로젝트의 전반적인 아키텍쳐(디렉토리 구조), diagnostics
* **Write** : 소스코드를 작성하기 위해서 커서가 위치한 곳에 글자를 삽입
* Input mode, paste, snippet, replace
* **Move** : 커서를 이동 (h/j/k/l/w/W/e/E/f/F/%/gg)
* **Select** : 영역을 선택 (마우스로 드래그 or Visual 모드에서 커서를 이동)
* **Delete** : 삭제 (backspace, Delete)
---
# 우리는 일을 어떻게 처리하는가?
* 우리는 정보를 인식하고, 그 정보를 기반으로 어떻게 행동할지를 결정.
* 어떤 단위적인 작업을 하던, 시간은 똑같이 흐른다.
* keystroke를 타이핑하는 동안에도
* 키보드에서 마우스로 손목이 옮겨가는 동안에도
* 마우스를 움직이는 동안에도
* 어떤 덩어리 단위의 작업을 한다면, **덩어리 단위의 작업을 하는데 드는 속도를 최적화**할 수 있지 않을까?
* 프로그램으로 치면 **인스트럭션의 실행 횟수**를 줄이는 것
---
# 그렇다면 어떻게 최적화하지?
* Read 과정에서 생기는 **인지부하**를 줄이기
* Syntax highlighting
* docstring 안에 작성된 샘플코드의 syntax highlighting
* **컨텍스트 스위칭 비용** 줄이기
* Read -> Move -> Write -> Read -> Move -> Delete -> ...
* 가능하면 Context를 왔다갔다하는 시간을 줄여야 함
* ex. 커서가 올바른 위치에 있나? 충분히 삭제를 했나?
* **적은 수의 keystroke**로 많은 일을 하도록 만들기 **(머슬메모리)**
* Repetition, Keymap, Snippet, Macro, DSL
---
# 의미론 단위의 일을 쳐내기 (1)
* 적당한 단위의 작업을 처리하는 방식을 단순화된 형태로 내재화하는 것
* 기초
* 단어 단위로 커서를 점프 **w**, 오른쪽/왼쪽 방향으로 글자 탐색 **F**/**f**
* 239번 라인으로 이동 **:239** (stacktrace를 근거로 오류 원인 분석)
* 응용
* 괄호 안의 글자를 비우기
* 선택한 영역에 있는 특정 패턴을 다른 패턴으로 치환하기
* 클래스/함수/모듈의 정의를 클립보드에 복사하기
---
# 의미론 단위의 일을 쳐내기 (2)
* 괄호 안의 글자를 비우기
* VSCode -- **마우스 드래그 + backspace**, 혹은 backspace 여러번
* Vim -- **ci[**, **ci(**, **ci{**, **ci"** (text object)
* 선택한 영역에 있는 특정 패턴을 다른 패턴으로 치환하기
* Vim -- **:'<,'>s/pattern/target/g** (Visual mode)
---
# 의미론 단위로 일을 쳐내기 (3)
* 클래스/함수/모듈의 정의를 클립보드에 복사하기
* VSCode -- **마우스로 드래그**하면서 **스크롤** 내리고, **Ctrl+C** 입력
* Vim (Visual mode) -- **머슬 메모리가 함께라면 엄청 빠름**
* 커서를 정의 부분으로 옮긴다. (**검색**) ex. **/function_name**
* 커서를 행의 맨끝으로 옮긴다. (**$**)
* 커서가 가리키는 괄호의 짝이 맞는 부분으로 커서를 이동한다 (**%**)
* 클립보드 복사 (**:'<'>w! pbcopy**)
# Snippet
* 방대하고 반복적인 코드를 최소한의 타이핑으로 빠르게 찍어내기
* verbose한 코드를 작성하는 것에서 생기는 인지부하를 최소화
* TextMate 표준 / LuaSnip / Ultisnips
---
# Snippet (ex. html)
* Emmet : HTML 마크업을 빠르게 찍어낼 수 있게 해줌
```html
<!-- body#slides>section.slide*7 -->
<body id="slides">
<section class="slide"></section>
<section class="slide"></section>
<section class="slide"></section>
<section class="slide"></section>
<section class="slide"></section>
<section class="slide"></section>
<section class="slide"></section>
</body>
```
---
# Snippet (ex. flutter)
* stl
```dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
```
---
# Snippet (ex. flutter)
* stf
```dart
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
// ignore: library_private_types_in_public_api
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Container();
}
}
```
---
# Macro (keystroke의 자동화)
[![함께 보시죠](https://storage.googleapis.com/memedex-bucket/macro-magic.png)](https://www.youtube.com/shorts/VWihUa8mUKI)
---
# 그 외에도....
* Metaprogramming (DSL)
* 번거로움을 줄이기 위해 적당한 레벨에서 추상화
* 엄밀하게 짠다면 더럽게 짜야하는 경우...... 🤯🤯
* ex. JSX(React.createElement) / Declarative UI
* Scaffolding
* 적당히 구조가 잡혀있는 코드를 자동 생성하기
* Skeleton
* 이미 구조가 잡혀있는 프로젝트를 만들기 (개발환경 세팅을 자동화)
# Chapter 2
# CLI 환경과 친해지기
---
# Chapter 2
# CLI 환경과 친해지기
# Terminal / Neovim
# Neovim을 키는 과정
* Step 1 : 터미널 에뮬레이터를 킨다
* Step 2 : 명령어를 타이핑할 수 있는 쉘이 열린다.
* Step 3 : 작업하고자 하는 프로젝트의 경로를 찾아간다.
* Step 4 : 현재 위치한 경로를 nvim 커맨드로 연다.
---
# Neovim을 키는 과정
* Step 1 : 터미널 에뮬레이터를 킨다 (iterm, konsole, wezterm, ...)
* Step 2 : 명령어를 타이핑할 수 있는 쉘이 열린다. (zsh, bash)
* Step 3 : 작업하고자 하는 프로젝트의 경로를 찾아간다. (cd xxxx)
* Step 4 : 현재 위치한 경로를 nvim 커맨드로 연다. (nvim .)
---
# Neovim을 키는 과정
* **Step 1 : 터미널 에뮬레이터를 킨다 (iterm, konsole, wezterm, ...)**
* Step 2 : 명령어를 타이핑할 수 있는 쉘이 열린다. (zsh, bash)
* Step 3 : 작업하고자 하는 프로젝트의 경로를 찾아간다. (cd xxxx)
* Step 4 : 현재 위치한 경로를 nvim 커맨드로 연다. (nvim .)
---
# Wezterm
* GPU 가속을 지원하는 터미널 에뮬레이터
* neovim처럼 **lua로 커스터마이징**이 가능
* 어떤 폰트를 사용할 것인지
* 폰트 사이즈는 몇으로 할 것인지
* 탭의 위치를 바꾸는 키맵
---
# Wezterm (배경 투명도 조절)
* For UI 작업하는 빈도가 잦은 개발자
* 모니터를 분할해서 사용하는 것이 국룰 (Web Browser / IDE)
* 하지만... 밖에서 작업한다면? 모니터 하나로만 작업해야한다면?
---
# Wezterm (배경 투명도 조절)
* 요즘 시대엔.. **HMR(Hot Module Reloading)** 이라는 게 있다.....
* 소스코드를 편집하면 브라우저에 바로 변경사항이 반영됨
* 배경 투명도 조절이 되는 터미널과 함께라면?
* **브라우저 창 위에 터미널 창을 띄워서 작업**할 수 있다.
* 터미널에서 편집 시, 백그라운드의 브라우저에 변경사항이 즉시 반영
---
# Wezterm (배경 투명도 조절)
![wezterm 투명도](https://storage.googleapis.com/memedex-bucket/wezterm-transparent.png)
---
# Wezterm (탭 이름 변경하기)
* A 프로젝트 / B 프로젝트 / 저널링 / 블로그
* 불가피하게 여러개의 작업 환경으로 멀티태스킹 해야할 때...
* 내가 어떤 환경을 작업하고 있는지 신경쓰는 **인지부하**를 줄여줌
---
# Wezterm (탭 이름 변경하기)
![wezterm 탭 이름 변경](https://storage.googleapis.com/memedex-bucket/wezterm-rename.png)
---
# Neovim을 키는 과정
* Step 1 : 터미널 에뮬레이터를 킨다 (iterm, konsole, wezterm, ...)
* Step 2 : 명령어를 타이핑할 수 있는 쉘이 열린다. (zsh, bash)
* **Step 3 : 작업하고자 하는 프로젝트의 경로를 찾아간다. (cd xxxx)**
* Step 4 : 현재 위치한 경로를 nvim 커맨드로 연다. (nvim .)
---
# Tmux
* 터미널 세션을 좀 더 가성비있게 쥐어짜낼 수 있는 CLI 도구
* **Session** / **Window** / **Pane**
* **다중 작업 관리** - 하나의 **Session** 안에서 여러개의 **Window**로 분할
* **창 분할** - **Window** 내부의 **Pane**을 수직/수평으로 분할
* **수직 분할** - **Ctrl+b** + **%**
* **수평 분할** - **Ctrl+b** + **"**
* **분할된 pane간 이동** - **Ctrl+b** + **(h/j/k/l)**
---
# Tmuxinator
* yaml 설정 파일을 통해, **프로젝트별로 tmux 세션을 관리**
* yaml 파일에 tmux 세션이 띄워지는 조건을 선언 후, **tmuxinator start xxxx(프로젝트 이름)** 명령어로 실행
* tmux session에서 표시되는 프로젝트의 이름
* **어느 디렉토리**를 기준으로 스크립트를 실행하는지 (pwd)
* **각각의 window 마다 어떤 프로세스를 띄울지** (혹은 어떤 환경변수를 세팅할지)
* **각각의 window를 어떻게 분할할지**
* yaml 파일을 파라미터로 넘겨줄때는 **tmuxinator start -p xyz.yml**
---
# Tmuxinator
![tmux](https://storage.googleapis.com/memedex-bucket/tmux-example.png)
---
# Tmuxinator
* Wezterm과 역할을 의도적으로 분리
* **Tmuxinator** : 한 프로젝트를 작업하는데 필요한 모든 프로세스
* **작업 단위, 프로세스 단위 분리**
* 백엔드 / 프론트엔드 / 서버 프로세스(localhost) + 빌드 스크립트
* **Wezterm**
* **워크스페이스 단위의 분리**
* A 프로젝트 / B 프로젝트 / 저널링,TIL / 블로그
---
# Tmuxinator 실행 화면
![tmuxinator](https://storage.googleapis.com/memedex-bucket/tmuxinator.png)
---
# Neovim을 키는 과정
* Step 1 : 터미널 에뮬레이터를 킨다 (iterm, konsole, wezterm, ...)
* **Step 2 : 명령어를 타이핑할 수 있는 쉘이 열린다. (zsh, bash)**
* Step 3 : 작업하고자 하는 프로젝트의 경로를 찾아간다. (cd xxxx)
* Step 4 : 현재 위치한 경로를 nvim 커맨드로 연다. (nvim .)
---
# Neovim의 Terminal 모드
* Neovim 버퍼 안에서... 터미널을 띄울 수 있다고...?
* Command 모드의 한계
* 실시간 상호작용이 포함되는 CLI 툴을 사용하기 어려움 (Github CLI)
---
# Neovim의 Terminal 모드
* 터미널 명령어 사용하는게 불가피할때 좀 더 높은 자유도를 제공해줌
* 터미널 에뮬레이터에서 CLI 명령어를 실행하기 위해 탭을 따로 띄울 필요가 없음
* **Neovim 인스턴스의 버퍼 안에서 해결하면 그만**이니까!
* **alias 세팅해놓은 것도 잘 먹힘**
* **상호작용이 필요한 CLI 프로그램**도 잘 돌아감.
* Github CLI, ssh, flutter build, terraform, ...
---
# Github CLI Copilot (inside Neovim)
![copilot cli](https://storage.googleapis.com/memedex-bucket/github-copilot-cli.jpeg)
# Chapter 3
# 나만의 맞춤형 환경 구성하기
# 저의 dotfiles를 소개합니다
* Timeslot Alarm
* Prompt engineering
* Switching Theme
---
# 저의 dotfiles를 소개합니다
* **Timeslot Alarm**
* Prompt engineering
* Switching Theme
---
# Timeslot Alarm
* context - **오늘, 또 일을 미루고 말았다** (**나카지마 사토시**/북클라우드)
![책 표지](https://storage.googleapis.com/memedex-bucket/timemanagement-book.jpeg)
---
# Timeslot Alarm
* 컨텍스트 스위칭이 빈번하게 일어나면 생산성이 나빠질 수 밖에 없다고 강조함
* A 작업 20분 > B 작업 20분 > C 작업 20분 > ... (3번 반복)
* **컨텍스트 스위칭 대략 9회**
* A 작업 60분 > B 작업 60분 > C 작업 60분
* **컨텍스트 스위칭 2회**
* 멀티태스킹이 불가피하다면, 시간을 **적당한 블록 단위로 분할**
---
# Timeslot Alarm
![타임슬롯 예시](https://storage.googleapis.com/memedex-bucket/timeslot.jpeg)
---
# Timeslot Alarm
![코드 예시](https://storage.googleapis.com/memedex-bucket/timeslot-source.png)
---
# Timeslot Alarm
![알림 띄우기](https://storage.googleapis.com/memedex-bucket/timeslot-applied.png)
---
# 저의 dotfiles를 소개합니다
* Timeslot Alarm
* **Prompt engineering**
* Switching Theme
---
# Prompt engineering
프롬프트를 스크립트로 말아넣는 과정에는 **NeoAI.nvim**를 사용했음
* 커밋 메시지를 자동생성하기
* 커밋 메시지 수정
* 브라우저 안 키고, ChatGPT 띄우기
---
# Prompt engineering (커밋 메시지 생성)
**:InjectCommitMessage**
**프롬프트**
* **75자 이내의 짧고 간단한 git commit 메시지**를 작성해주세요.
* 변경사항은 **git diff --cached**와 같습니다.
---
# Prompt engineering (커밋 메시지 수정)
**`:'<,'>TextifyCommitMessage`**
**프롬프트**
* 여기에 적어놓은 커밋 메시지가 문법적으로 불완전합니다.
* 이 메시지를 **좀 더 가독성 있고, 명확하게** 고쳐주세요.
* **75자 이내의 짧고 간단한 git commit 메시지**를 작성해주세요.
* 변경사항은 **git diff --cached**와 같습니다.
---
# Prompt engineering (ChatGPT)
* ChatGPT : 무한정으로 대화를 주고받을 수 있음
* OpenAI API : Context length limit
![chatgpt](https://storage.googleapis.com/memedex-bucket/neoai-example.png)
---
# 저의 dotfiles를 소개합니다
* Timeslot Alarm
* Prompt engineering
* **Switching Theme**
---
# Switching Theme
* 풀스택 개발자라면 멀티태스킹하는건 불가피한 일...
* 백엔드 / 프론트엔드 / 백오피스, 번갈아가면서 작업하는 일이 잦음
* 시각적으로도 **어떤 부분을 작업하고 있는지 명확한 구분이 필요**했음.
* 미세하게 인지부하를 줄이기
---
# Switching Theme
![nordic](https://storage.googleapis.com/memedex-bucket/theme-nordic.png)
---
# Switching Theme
![Tokyonight](https://storage.googleapis.com/memedex-bucket/theme-tokyonight.png)
---
# Switching Theme
![Catppuccin](https://storage.googleapis.com/memedex-bucket/theme-catppuccin.png)
---
# Switching Theme
![Melange](https://storage.googleapis.com/memedex-bucket/theme-melange.png)
---
# Switching Theme
* Neovim 프로세스를 키기 전에 환경변수로 어떤 Theme를 쓸 지 선언
* Neovim 프로세스가 띄워지면, 어떤 Theme를 사용할지 런타임에서 결정
```lua
local current_theme = os.getenv("NEOVIM_THEME")
local available_themes = {}
for _, theme in ipairs({
"melange",
"catppuccin",
"tokyonight",
"nordic",
}) do
available_themes[theme] = true
end
if current_theme == nil then
vim.cmd.colorscheme("melange")
elseif available_themes[current_theme] ~= nil then
vim.cmd.colorscheme(current_theme)
else
vim.cmd.colorscheme("melange")
end
```
---
# Switching Theme
이제 이걸 tmuxinator에다가 응용해볼까요?
```yaml
# Tmuxinator configuration for Memedex project Application
name: memedex
root: ~/projects/
windows:
- backend:
layout: main-vertical
panes:
- cd ./memedex-api/ && export NEOVIM_THEME=melange && vim .
- frontend:
layout: main-vertical
panes:
- cd ./memedex-front/ && export NEOVIM_THEME=tokyonight && vim .
- backoffice:
layout: main-vertical
panes:
- cd ./memedex-backoffice/ && export NEOVIM_THEME=nordic && vim .
- debugging:
layout: main-vertical
panes:
- cd ./memedex-api/ && export IS_MARIADB_DOCKERIZED=true && poetry run python manage.py runserver
- export TOSS_CLIENT_KEY=test_ck_xxxxxxxx && cd ./memedex-front/ && yarn dev
- export TOSS_CLIENT_KEY=test_ck_xxxxxxxx && cd ./memedex-backoffice/ && yarn dev
- cd ./memedex-api/ && export IS_MARIADB_DOCKERIZED=true && poetry run python manage.py shell
- cd ./memedex-api/ && docker-compose up
```
# 결론
* 생산성 개선은 내가 어떤 행위에서 불필요하게 시간을 소모하는지 분석하는 메타인지에서 시작합니다.
* 컨텍스트 스위칭 비용 및 인지부하를 줄이기 위한 시도도 생산성 개선에 큰 기여를 합니다.
* 터미널로 할 수 있는 것들은 생각보다 많습니다. 특히, Neovim과 CLI 조합은 환상적입니다.
* 읽어보면 좋은 글
* [Typing Fast is Latency, Not Throughput](https://two-wrongs.com/typing-fast-is-about-latency-not-throughput)
* Part 1 내용이 궁금하신 분은 [slides.kodingwarrior.dev](https://slides.kodingwarrior.dev) 에서 구경할 수 있습니다 👀👀
---
# 분량상 다루지 못해서 아쉬운 주제들 (1)
하지만..... 관련 내용을 찾아보시면 도움은 됩니다.
* **set relativenumber**를 다들 선호할 수 밖에 없는 이유 (**repetition**)
* h/j/k/l 가 얼마나 근본이 있는 키 조합인지
* Vim 사용자들 간 암묵적인 약속으로서의 h/j/k/l
* 명령모드에서 % 사용하기
* **:! chezmoi add %** / **:! yarn lint %** / ...
* vim-fugitive 가 얼마나 훌륭한 플러그인인지 데모로 소개하기
* 각 파일의 변경사항을 **=** 로 펼치기, **Hunk 단위로 잘게 쪼개서** 커밋
---
# 분량상 다루지 못해서 아쉬운 주제들 (2)
* language server, formatter, linter를 잘 활용하기
* **Hover tooltip**
* 함수를 호출하는 부분에 마우스 올려대기 vs 노멀 모드에서 K 입력
* **Docstring 작성의 필요성**
* Go to definition 하는데서 생기는 인지부하 최소화
* Linter rule을 마우스 우클릭 안하고도 빠르게 Disable하기
* Sub-typing 을 이용한 **제약사항 강제**
* Form 클래스에서 상속받는 모든 클래스는 onSubmit, canSubmit, invalidate **메서드가 정의되어 있어야 한다고 강제**하기