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 **메서드가 정의되어 있어야 한다고 강제**하기
# 감사합니다