Skip to content
Published on

마우스에 손을 대지 않는 즐거움: 터미널 생산성을 극대화하는 5가지 핵심 전략

Authors
  • Name
    Twitter

1. 도입: 왜 Vim + Tmux인가

1.1 컨텍스트 스위칭이라는 보이지 않는 비용

개발자의 하루를 떠올려 보자. 코드를 작성하다가 터미널로 전환하고, 테스트를 실행하다가 브라우저로 넘어가고, 다시 에디터로 돌아온다. 이 과정에서 마우스에 손이 닿는 순간마다 **컨텍스트 스위칭(Context Switching)**이 발생한다. 인지과학 연구에 따르면, 한 번의 컨텍스트 스위칭 후 원래 집중 상태로 복귀하는 데 평균 23분이 소요된다(University of California, Irvine 연구). 물론 창 전환 수준의 마이크로 스위칭은 23분까지는 아니지만, 하루에 수백 번 반복되면 누적 비용은 결코 무시할 수 없다.

문제의 본질은 **도구와 도구 사이의 틈새(seam)**에 있다. IDE에서 터미널로, 터미널에서 브라우저로, 브라우저에서 노트앱으로 — 각 전환마다 키보드에서 마우스로 손이 이동하고, 시선이 분산되고, 사고의 흐름이 끊어진다.

1.2 응집된 개발 환경이라는 해답

Vim과 Tmux의 조합은 이 문제에 대한 근본적인 해답을 제시한다.

┌─────────────────────────────────────────────────────────────┐
│                    터미널 에뮬레이터 (Ghostty)│  ┌──────────────────────────┬──────────────────────────────┐ │
│  │                          │                              │ │
│  │    Vim/Neovim (코드)Tmux Pane (서버 로그)    │ │
│  │                          │                              │ │
│  │    - 파일 탐색            │     $ tail -f app.log        │ │
│  │    - 코드 편집            │                              │ │
│  │    - LSP 자동완성         │                              │ │
│  │                          │                              │ │
│  ├──────────────────────────┤                              │ │
│  │                          │                              │ │
│  │    Vimux (테스트 실행)    │                              │ │
│  │                          │                              │ │
│  │    $ pytest -v tests/    │                              │ │
│  │    ✓ 12 passed           │                              │ │
│  │                          │                              │ │
│  └──────────────────────────┴──────────────────────────────┘ │
[0] dev* [1] server [2] db           youngjukim 15:42└─────────────────────────────────────────────────────────────┘

위 다이어그램이 보여주는 것처럼, Vim과 Tmux를 결합하면 코드 편집, 테스트 실행, 로그 모니터링, 서버 관리가 모두 하나의 터미널 안에서 이루어진다. 마우스 없이, 키보드만으로, 생각의 속도로 모든 작업을 수행할 수 있다.

1.3 키보드 중심 워크플로우의 철학

Vim과 Tmux가 공유하는 핵심 철학은 **"최소 저항의 경로(Path of Least Resistance)"**다.

패러다임GUI 중심 워크플로우Vim + Tmux 워크플로우
창 전환Alt-Tab / Cmd-Tab + 마우스 클릭Ctrl-h/j/k/l 즉시 이동
파일 탐색마우스로 폴더 트리 클릭:Telescope find_files / fzf
테스트 실행터미널 창 전환 → 명령 입력<Leader>tt 한 번
텍스트 복사마우스 드래그 → Ctrl-Cyy (Vim yank → 시스템 클립보드)
환경 복구앱 재실행, 창 재배치tmux attach (세션 그대로 복구)

이 글에서는 Vim과 Tmux를 진정한 통합 환경으로 만들어주는 5가지 핵심 전략을 하나씩 다룬다. 각 전략에는 즉시 적용 가능한 설정 파일과 실전 예제가 포함되어 있다.

1.4 이 글의 전제 조건

이 글을 최대한 활용하려면 다음 환경이 갖추어져 있어야 한다.

# 버전 확인
vim --version | head -1    # Vim 9.0+ 또는
nvim --version | head -1   # Neovim 0.10+
tmux -V                    # tmux 3.3+
git --version              # Git 2.40+

기본적인 Vim 모션(h/j/k/l, w/b/e, dd/yy/p)과 Tmux 기본 개념(세션, 윈도우, 페인)에 대한 이해가 있다고 가정한다. Tmux 기초가 필요하다면 Tmux 명령어 완벽 가이드를 먼저 참고하자.


2. 처음부터 설치하기: From Scratch 환경 구성

본격적인 전략을 다루기 전에, 완전히 새로운 환경에서 Vim + Tmux 개발 환경을 구축하는 과정을 정리한다.

2.1 터미널 에뮬레이터 설치: Ghostty

Ghostty는 Zig로 작성된 GPU 가속 터미널 에뮬레이터로, macOS에서는 Metal, Linux에서는 OpenGL을 사용한다. 2025년 공개 릴리스 이후 빠르게 커뮤니티의 주목을 받고 있다. 네이티브 UI 컴포넌트를 사용하므로 플랫폼에 자연스럽게 통합되며, Nerd Font를 기본 지원하고, Kitty Graphics Protocol을 통해 터미널 내 이미지 렌더링도 가능하다.

# macOS (Homebrew)
brew install --cask ghostty

# Linux (빌드가 필요할 수 있음 — 공식 패키지 확인)
# https://ghostty.org/docs 참조

Ghostty 설정 파일 (~/.config/ghostty/config):

# ~/.config/ghostty/config
font-family = Monaspace Neon
font-size = 14
font-feature = calt
font-feature = liga
font-feature = ss01
font-feature = ss02
font-feature = ss03
font-feature = ss09

theme = catppuccin-mocha
cursor-style = block
cursor-style-blink = false
shell-integration = zsh

window-padding-x = 8
window-padding-y = 4
window-decoration = true

# Tmux와 함께 사용 시 — Ghostty 자체 탭/분할보다 Tmux 사용 권장
keybind = super+t=unbind

2.2 폰트 설치: Monaspace

Monaspace는 GitHub Next가 개발한 모노스페이스 폰트 슈퍼패밀리로, 5가지 서체(Neon, Argon, Xenon, Radon, Krypton), Texture Healing 기술, 그리고 10개 그룹의 코딩 리가처를 제공한다.

# macOS
brew tap homebrew/cask-fonts
brew install font-monaspace

# Linux — 수동 설치
mkdir -p ~/.local/share/fonts
cd /tmp
wget https://github.com/githubnext/monaspace/releases/latest/download/monaspace-v1.101.zip
unzip monaspace-v1.101.zip -d monaspace
cp monaspace/monaspace-v1.101/fonts/otf/*.otf ~/.local/share/fonts/
fc-cache -fv

# Nerd Font 패치 버전 (아이콘이 필요한 경우)
brew install font-monaspace-nerd-font   # macOS

2.3 Neovim 설치

# macOS
brew install neovim

# Ubuntu / Debian (최신 버전)
sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt update && sudo apt install -y neovim

# Arch Linux
sudo pacman -S neovim

# AppImage (범용)
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
chmod u+x nvim.appimage
sudo mv nvim.appimage /usr/local/bin/nvim

2.4 Tmux 설치 및 TPM(Tmux Plugin Manager) 설정

# macOS
brew install tmux

# Ubuntu / Debian
sudo apt install -y tmux

# TPM 설치
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

TPM 설치 후 Tmux 내에서 prefix + I (대문자 I)를 누르면 플러그인이 자동 설치된다.

2.5 Neovim Kickstart로 시작하기

Kickstart.nvim은 Neovim 배포판(distribution)이 아니라, 자신만의 설정을 만들어나가기 위한 교육적 출발점이다. 단일 init.lua 파일로 구성되어 있어 모든 설정을 한눈에 파악할 수 있다.

# 기존 설정 백업 (있는 경우)
mv ~/.config/nvim{,.bak}
mv ~/.local/share/nvim{,.bak}
mv ~/.local/state/nvim{,.bak}
mv ~/.cache/nvim{,.bak}

# Kickstart 클론
git clone https://github.com/nvim-lua/kickstart.nvim.git ~/.config/nvim

# Neovim 실행 — 플러그인 자동 설치
nvim

Kickstart에는 다음이 기본 포함되어 있다:

  • lazy.nvim — 플러그인 매니저
  • telescope.nvim — 퍼지 파인더
  • treesitter — 구문 강조
  • LSP — 언어 서버 프로토콜 지원
  • nvim-cmp — 자동완성
  • which-key — 키바인딩 디스커버리

2.6 Starship 프롬프트 설치

# 설치
curl -sS https://starship.rs/install.sh | sh

# ~/.zshrc에 추가
echo 'eval "$(starship init zsh)"' >> ~/.zshrc

# 또는 ~/.bashrc에 추가
echo 'eval "$(starship init bash)"' >> ~/.bashrc

Starship 설정 파일 (~/.config/starship.toml):

# ~/.config/starship.toml

# 프롬프트 상단에 빈 줄 추가하지 않음
add_newline = false

# 프롬프트 형식 커스터마이징
format = """
$directory\
$git_branch\
$git_status\
$python\
$nodejs\
$golang\
$rust\
$kubernetes\
$docker_context\
$cmd_duration\
$line_break\
$character"""

[character]
success_symbol = "[➜](bold green)"
error_symbol = "[✗](bold red)"

[directory]
truncation_length = 3
truncation_symbol = "…/"
style = "bold cyan"

[git_branch]
format = "[$symbol$branch(:$remote_branch)]($style) "
symbol = " "
style = "bold purple"

[git_status]
format = '([\[$all_status$ahead_behind\]]($style) )'
style = "bold red"

[python]
format = '[${symbol}${pyenv_prefix}(${version} )(\($virtualenv\) )]($style)'
symbol = " "

[nodejs]
format = "[$symbol($version )]($style)"
symbol = " "

[golang]
format = "[$symbol($version )]($style)"
symbol = " "

[rust]
format = "[$symbol($version )]($style)"
symbol = " "

[kubernetes]
disabled = false
format = '[$symbol$context( \($namespace\))]($style) '
symbol = "⎈ "
style = "bold blue"

[docker_context]
format = "[$symbol$context]($style) "
symbol = " "

[cmd_duration]
min_time = 2_000
format = "[$duration]($style) "
style = "bold yellow"

3. [전략 1] 심리스 네비게이션 — vim-tmux-navigator

3.1 문제: 두 세계의 단절

Vim과 Tmux는 각각 독립적인 분할 시스템을 가지고 있다.

┌─────────────────────────────────────────┐
Tmux 세계                               │
│  ┌──────────────┬──────────────────────┐ │
│  │              │                      │ │
│  │  Vim 세계     │                      │ │
│  │  ┌────┬────┐ │    Tmux Pane 2       │ │
│  │  │Vim │Vim │     (shell)           │ │
│  │  │Split│Split│ │                      │ │
│  │  │ 12  │ │                      │ │
│  │  └────┴────┘ │                      │ │
│  │              │                      │ │
│  │  Tmux Pane 1 │                      │ │
│  └──────────────┴──────────────────────┘ │
└─────────────────────────────────────────┘

위 레이아웃에서 Vim Split 2에서 Tmux Pane 2로 이동하려면:

  1. Vim에서 나가기: Ctrl-w l (Vim의 오른쪽 분할로 이동... 하지만 더 이상 오른쪽 분할이 없다)
  2. Tmux 프리픽스 사용: Ctrl-b l (Tmux의 오른쪽 페인으로 이동)

"지금 내가 Vim 안에 있는가, Tmux 안에 있는가?"를 매번 판단해야 한다. 이 인지 부하는 생각의 흐름을 끊는다.

3.2 해결: vim-tmux-navigator

vim-tmux-navigator는 Chris Toomey가 만든 플러그인으로, Vim 분할과 Tmux 페인 사이의 경계를 완전히 투명하게 만든다. Ctrl-h/j/k/l이라는 일관된 단축키 하나로 Vim 안이든 Tmux 안이든 상관없이 원하는 방향으로 이동할 수 있다.

Vim/Neovim 설정 (vim-plug)

" ~/.vimrc 또는 ~/.config/nvim/init.vim

call plug#begin('~/.vim/plugged')

Plug 'christoomey/vim-tmux-navigator'

call plug#end()

" 줌 상태에서 네비게이션 비활성화 (선택)
let g:tmux_navigator_disable_when_zoomed = 1

" 줌 상태 유지하면서 이동 (선택)
" let g:tmux_navigator_preserve_zoom = 1

Neovim 설정 (lazy.nvim)

-- ~/.config/nvim/lua/plugins/tmux-navigator.lua

return {
  "christoomey/vim-tmux-navigator",
  cmd = {
    "TmuxNavigateLeft",
    "TmuxNavigateDown",
    "TmuxNavigateUp",
    "TmuxNavigateRight",
    "TmuxNavigatePrevious",
  },
  keys = {
    { "<C-h>", "<cmd>TmuxNavigateLeft<cr>",  desc = "Navigate Left" },
    { "<C-j>", "<cmd>TmuxNavigateDown<cr>",  desc = "Navigate Down" },
    { "<C-k>", "<cmd>TmuxNavigateUp<cr>",    desc = "Navigate Up" },
    { "<C-l>", "<cmd>TmuxNavigateRight<cr>", desc = "Navigate Right" },
  },
}

Tmux 설정 (.tmux.conf)

# ~/.tmux.conf — vim-tmux-navigator 연동

# TPM 사용 시
set -g @plugin 'christoomey/vim-tmux-navigator'

# 또는 수동 설정 (TPM 미사용 시)
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
    | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|l?n?vim?x?|fzf)(diff)?$'"

bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h'  'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j'  'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k'  'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l'  'select-pane -R'

tmux_version='$(tmux -V | sed -En "s/^tmux ([0-9]+(.[0-9]+)?).*/\1/p")'
if-shell -b '[ "$(echo "$tmux_version >= 3.0" | bc)" = 1 ]' \
    "bind-key -n 'C-\\' if-shell \"$is_vim\" 'send-keys C-\\\\'  'select-pane -l'"
if-shell -b '[ "$(echo "$tmux_version < 3.0" | bc)" = 1 ]' \
    "bind-key -n 'C-\\' if-shell \"$is_vim\" 'send-keys C-\\\\'  'select-pane -l'"

bind-key -T copy-mode-vi 'C-h' select-pane -L
bind-key -T copy-mode-vi 'C-j' select-pane -D
bind-key -T copy-mode-vi 'C-k' select-pane -U
bind-key -T copy-mode-vi 'C-l' select-pane -R

3.3 동작 원리

vim-tmux-navigator의 동작 원리는 다음과 같다.

  1. Ctrl-h를 누른다
  2. Tmux가 현재 페인의 프로세스를 확인한다 (is_vim 변수)
  3. Vim이 실행 중이면: 키 입력을 Vim에 전달 → Vim이 왼쪽 분할로 이동 시도
  4. Vim에서 더 이상 왼쪽 분할이 없으면 → Vim 플러그인이 Tmux 명령을 실행하여 왼쪽 Tmux 페인으로 이동
  5. Vim이 아니면: Tmux가 직접 왼쪽 페인으로 이동

결과적으로 사용자는 "왼쪽으로 가고 싶다 → Ctrl-h" 라는 단일 사고만 하면 된다. Vim 분할인지 Tmux 페인인지는 전혀 신경 쓸 필요가 없다.

3.4 Ctrl-l 화면 클리어 복구

Ctrl-l은 전통적으로 터미널 화면을 클리어하는 단축키다. vim-tmux-navigator가 이를 네비게이션에 사용하므로, 대안이 필요하다.

# ~/.tmux.conf — 화면 클리어 복구
# prefix + Ctrl-l 로 화면 클리어
bind C-l send-keys 'C-l'

이제 prefix + Ctrl-l로 화면을 클리어할 수 있다. 약간의 키 입력이 추가되지만, 네비게이션 일관성의 이점이 훨씬 크다.

3.5 흐름 상태를 유지하는 네비게이션

vim-tmux-navigator를 적용한 후의 워크플로우를 시각화하면 다음과 같다.

 사고의 흐름 (끊김 없음)
 ─────────────────────────────────────────────────────────

 "이 함수의 테스트 결과를 보자"
 Ctrl-j  (아래 Vimux 페인으로 이동 — Vim/Tmux 경계 투명)
 "테스트 통과했네. 로그를 보자"
 Ctrl-l  (오른쪽 로그 페인으로 이동 — 역시 경계 투명)
 "수정이 필요하다. 코드로 돌아가자"
 Ctrl-h  (왼쪽 Vim으로 복귀)
 코드 수정 계속...

 ─ 전체 과정에서 마우스를 한 번도 터치하지 않았다 ─

4. [전략 2] 직관적 패널 분할 — 시각적 메타포 매핑

4.1 문제: Tmux 기본 키바인딩의 비직관성

Tmux의 기본 페인 분할 키바인딩은 다음과 같다.

동작기본 키바인딩실제 의미
수평 분할 (위아래로 나눔)prefix + "따옴표? 왜?
수직 분할 (좌우로 나눔)prefix + %퍼센트? 왜?

이 키바인딩은 시각적 메타포가 전혀 없다. "가 수평선을 연상시키는가? %가 수직선을 떠올리게 하는가? 전혀 그렇지 않다. 매번 "수직 분할이 뭐였지... %인가 "인가..."를 머릿속에서 검색해야 한다. 이 **인지 부하(Cognitive Load)**는 미미해 보이지만, 하루에 수십 번 분할 작업을 하면 무시할 수 없다.

4.2 해결: 시각적 메타포 매핑

# ~/.tmux.conf — 직관적 패널 분할

# | 는 수직선처럼 보인다 → 수직 분할 (좌우로 나눔)
bind | split-window -h -c "#{pane_current_path}"

# - 는 수평선처럼 보인다 → 수평 분할 (위아래로 나눔)
bind - split-window -v -c "#{pane_current_path}"

# 기본 키바인딩 제거 (혼란 방지)
unbind '"'
unbind %

핵심은 -c "#{pane_current_path}" 옵션이다. 이것이 없으면 새 페인이 홈 디렉토리에서 열린다. 이 옵션을 추가하면 현재 작업 디렉토리를 유지한 채 분할된다 — 실전에서 극도로 중요한 설정이다.

4.3 시각적 메타포의 효과

 prefix + |                    prefix + -

 분할 전:                      분할 전:
 ┌──────────────┐              ┌──────────────┐
 │              │              │              │
 │              │              │              │
 │              │              │              │
 └──────────────┘              └──────────────┘

 분할 후:                      분할 후:
 ┌──────┬───────┐              ┌──────────────┐
 │      │       │              │              │
 │      │       │              ├──────────────┤
 │      │       │              │              │
 └──────┴───────┘              └──────────────┘

 | = 수직선 = 수직 분할         - = 수평선 = 수평 분할

더 이상 기억할 필요가 없다. 눈에 보이는 대로 동작한다.

4.4 실전 개발 레이아웃 패턴

자주 사용하는 개발 레이아웃 패턴들을 소개한다.

패턴 1: 코드 + 테스트 (TDD)

 ┌─────────────────────────┐
 │                         │
Vim (코드 편집) │                         │
 │                         │
 ├─────────────────────────┤
 │  테스트 실행 (Vimux) └─────────────────────────┘

 설정: prefix + - 한 번

패턴 2: 코드 + 터미널 + 로그 (풀스택)

 ┌──────────────────┬──────────────┐
 │                  │              │
Vim (코드)      │  서버 로그    │
 │                  │              │
 │                  │              │
 ├──────────────────┤              │
 │                  │              │
터미널 (git,    │              │
 │  빌드 등)        │              │
 └──────────────────┴──────────────┘

 설정: prefix + | → 왼쪽에서 prefix + -

패턴 3: 마이크로서비스 모니터링 (4분할)

 ┌──────────────────┬──────────────┐
 │                  │              │
 │  서비스 A 로그    │  서비스 B 로그│
 │                  │              │
 ├──────────────────┼──────────────┤
 │                  │              │
 │  서비스 C 로그    │  메트릭/htop  │
 │                  │              │
 └──────────────────┴──────────────┘

 설정: prefix + | → 각 페인에서 prefix + -

4.5 페인 크기 조절 단축키

분할 후 크기를 조절하는 것도 직관적으로 만들자.

# ~/.tmux.conf — 페인 크기 조절

# Vim 스타일 방향키로 크기 조절
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# -r 플래그: 반복 가능 (prefix 한 번 누른 후 연속 조절)

-r 플래그가 핵심이다. prefix + H를 누른 후 500ms 이내에 H를 다시 누르면 prefix 없이 반복된다. 빠르게 크기를 조절할 때 매우 편리하다.


5. [전략 3] Vimux — 초고속 피드백 루프

5.1 문제: 코드-실행-확인 루프의 비효율

전통적인 개발 워크플로우는 다음과 같다.

 코드 편집 (Vim)
 Vim 종료 또는 Ctrl-Z (중단)
 터미널에서 명령어 입력 (pytest, go test, npm test...)
 결과 확인
 다시 Vim으로 복귀 (fg 또는 vim 재실행)
 "아까 어디 수정하고 있었지..."

이 루프에서 코드 편집 → 실행 사이의 전환 비용이 크다. 특히 TDD(Test-Driven Development)에서는 이 루프가 수십 초 간격으로 반복되므로, 전환 비용의 누적이 치명적이다.

5.2 해결: Vimux — Vim 안에서 쉘 명령 실행

Vimux는 Vim에서 직접 Tmux 페인으로 명령어를 보내는 플러그인이다. Vim을 떠나지 않고, 코드를 보면서, 테스트를 실행하고, 결과를 확인할 수 있다.

설치 (vim-plug)

" ~/.vimrc
Plug 'preservim/vimux'

설치 (lazy.nvim)

-- ~/.config/nvim/lua/plugins/vimux.lua

return {
  "preservim/vimux",
  keys = {
    { "<Leader>vp", "<cmd>VimuxPromptCommand<cr>",   desc = "Vimux Prompt" },
    { "<Leader>vl", "<cmd>VimuxRunLastCommand<cr>",   desc = "Vimux Run Last" },
    { "<Leader>vi", "<cmd>VimuxInspectRunner<cr>",    desc = "Vimux Inspect" },
    { "<Leader>vz", "<cmd>VimuxZoomRunner<cr>",       desc = "Vimux Zoom" },
    { "<Leader>vc", "<cmd>VimuxCloseRunner<cr>",      desc = "Vimux Close" },
  },
  config = function()
    -- Vimux 페인 높이 (% 단위)
    vim.g.VimuxHeight = "30"
    -- 페인 방향 (수평 하단)
    vim.g.VimuxOrientation = "v"
    -- 사용할 러너 유형
    vim.g.VimuxRunnerType = "pane"
  end,
}

5.3 핵심 명령어

명령어기본 매핑설명
VimuxPromptCommand<Leader>vp명령어 입력 프롬프트
VimuxRunLastCommand<Leader>vl마지막 명령어 재실행
VimuxRunCommand("...")커스텀특정 명령어 직접 실행
VimuxInspectRunner<Leader>vi러너 페인으로 이동 (스크롤 가능)
VimuxZoomRunner<Leader>vz러너 페인 전체화면 토글
VimuxCloseRunner<Leader>vc러너 페인 닫기

5.4 언어별 TDD 워크플로우 설정

Python (pytest)

-- ~/.config/nvim/lua/plugins/vimux.lua 에 추가

-- Python: 현재 파일 테스트 실행
vim.keymap.set("n", "<Leader>tt", function()
  local file = vim.fn.expand("%")
  vim.fn.VimuxRunCommand("pytest -v " .. file)
end, { desc = "Run pytest on current file" })

-- Python: 현재 커서 위치의 테스트 함수만 실행
vim.keymap.set("n", "<Leader>ts", function()
  local file = vim.fn.expand("%")
  local line = vim.fn.line(".")
  -- pytest-picked 또는 라인 기반 선택
  vim.fn.VimuxRunCommand("pytest -v " .. file .. " --no-header -rN")
end, { desc = "Run pytest on current test" })

-- Python: 전체 테스트 스위트
vim.keymap.set("n", "<Leader>ta", function()
  vim.fn.VimuxRunCommand("pytest -v --tb=short")
end, { desc = "Run all tests" })

Go

-- Go: 현재 패키지 테스트
vim.keymap.set("n", "<Leader>tt", function()
  vim.fn.VimuxRunCommand("go test -v ./...")
end, { desc = "Run Go tests" })

-- Go: 현재 파일의 패키지만 테스트
vim.keymap.set("n", "<Leader>tf", function()
  local dir = vim.fn.expand("%:h")
  vim.fn.VimuxRunCommand("go test -v ./" .. dir .. "/...")
end, { desc = "Run Go tests in current package" })

-- Go: 벤치마크
vim.keymap.set("n", "<Leader>tb", function()
  vim.fn.VimuxRunCommand("go test -bench=. -benchmem ./...")
end, { desc = "Run Go benchmarks" })

Node.js (Jest)

-- Jest: 현재 파일 테스트
vim.keymap.set("n", "<Leader>tt", function()
  local file = vim.fn.expand("%")
  vim.fn.VimuxRunCommand("npx jest " .. file .. " --verbose")
end, { desc = "Run Jest on current file" })

-- Jest: watch 모드
vim.keymap.set("n", "<Leader>tw", function()
  local file = vim.fn.expand("%")
  vim.fn.VimuxRunCommand("npx jest " .. file .. " --watch")
end, { desc = "Run Jest in watch mode" })

-- Jest: 전체
vim.keymap.set("n", "<Leader>ta", function()
  vim.fn.VimuxRunCommand("npx jest --verbose")
end, { desc = "Run all Jest tests" })

Rust

-- Rust: cargo test
vim.keymap.set("n", "<Leader>tt", function()
  vim.fn.VimuxRunCommand("cargo test")
end, { desc = "Run cargo test" })

-- Rust: cargo test with output
vim.keymap.set("n", "<Leader>to", function()
  vim.fn.VimuxRunCommand("cargo test -- --nocapture")
end, { desc = "Run cargo test with output" })

-- Rust: cargo clippy
vim.keymap.set("n", "<Leader>tc", function()
  vim.fn.VimuxRunCommand("cargo clippy -- -W clippy::all")
end, { desc = "Run cargo clippy" })

5.5 Vimux TDD 워크플로우 시각화

  "찾고, 읽고, 실행하는" 단일 컨텍스트
  ──────────────────────────────────────

  ┌─────────────────────────────────┐
Vim (코드 편집)  │                                 │
  │  def add(a, b):return a + b  ← 수정 중    │
  │                                 │
~~  ├─────────────────────────────────┤
Vimux Runner (테스트 결과)  │                                 │
  │  $ pytest -v test_calc.py  │  test_add PASSED ✓              │
  │  test_subtract FAILED ✗         │
1 failed, 1 passed             │
  └─────────────────────────────────┘

  <Leader>tt → 테스트 실행 (시선 이동만, 손 이동 없음)
  <Leader>vl → 마지막 명령 재실행 (수정 후 즉시 검증)

핵심은 눈은 아래로 잠깐, 손은 키보드 위에 그대로라는 점이다. 코드 편집과 테스트 실행 사이의 전환 비용이 사실상 제로가 된다.


6. [전략 4] 시스템 클립보드 동기화

6.1 문제: 터미널과 OS 사이의 보이지 않는 벽

Vim에서 yy로 복사한 텍스트를 브라우저에 Cmd-V로 붙여넣으려 하면... 아무것도 붙여넣어지지 않는다. Vim의 레지스터와 OS의 시스템 클립보드는 기본적으로 별개의 세계이기 때문이다.

Tmux는 여기에 한 겹의 복잡성을 더한다. Tmux의 복사 모드에서 선택한 텍스트는 Tmux 버퍼에 들어가지만, 역시 시스템 클립보드와는 무관하다.

 ┌──────────┐    ┌──────────┐    ┌──────────┐
Vim      │    │ Tmux     │    │ OSRegister │ ✗  │ Buffer   │ ✗  │ Clipboard│
  ("a, "b) (paste   │     (Cmd-V) │          │←──→│  buffer) │←──→│          │
 └──────────┘    └──────────┘    └──────────┘
          단절           단절

 목표: 세 시스템 모두 동기화

6.2 해결: 플랫폼별 클립보드 설정

macOS

macOS는 pbcopy / pbpaste 유틸리티가 기본 제공되므로 설정이 비교적 간단하다.

Tmux 설정:

# ~/.tmux.conf — macOS 클립보드 동기화

# set-clipboard 활성화
set -s set-clipboard on

# 복사 모드에서 선택 후 시스템 클립보드에 복사
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"

# 마우스 드래그 후 자동 복사 (마우스 사용 시)
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"

# Enter 키로도 클립보드 복사
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "pbcopy"

Neovim 설정:

-- ~/.config/nvim/init.lua 또는 lua/config/options.lua

-- 시스템 클립보드를 unnamed plus 레지스터로 사용
vim.opt.clipboard = "unnamedplus"

-- macOS에서는 이것만으로 충분하다.
-- Neovim이 자동으로 pbcopy/pbpaste를 감지한다.

Linux (X11 — xclip)

# xclip 설치
sudo apt install -y xclip   # Debian/Ubuntu
sudo pacman -S xclip         # Arch

# ~/.tmux.conf
set -s set-clipboard on
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
-- Neovim (Linux X11)
vim.opt.clipboard = "unnamedplus"

-- 클립보드 프로바이더 명시 (선택사항)
vim.g.clipboard = {
  name = "xclip",
  copy = {
    ["+"] = "xclip -selection clipboard",
    ["*"] = "xclip -selection primary",
  },
  paste = {
    ["+"] = "xclip -selection clipboard -o",
    ["*"] = "xclip -selection primary -o",
  },
  cache_enabled = 0,
}

Linux (Wayland — wl-clipboard)

# wl-clipboard 설치
sudo apt install -y wl-clipboard   # Debian/Ubuntu
sudo pacman -S wl-clipboard         # Arch

# ~/.tmux.conf
set -s set-clipboard on
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "wl-copy"
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "wl-copy"
-- Neovim (Wayland)
vim.opt.clipboard = "unnamedplus"

vim.g.clipboard = {
  name = "wl-clipboard",
  copy = {
    ["+"] = "wl-copy",
    ["*"] = "wl-copy --primary",
  },
  paste = {
    ["+"] = "wl-paste --no-newline",
    ["*"] = "wl-paste --no-newline --primary",
  },
  cache_enabled = 0,
}

6.3 SSH 원격 환경에서의 클립보드 동기화

SSH로 원격 서버에 접속한 상태에서는 클립보드 동기화가 한층 더 까다로워진다. OSC 52 이스케이프 시퀀스를 활용하면 원격 서버의 Tmux에서도 로컬 시스템 클립보드로 복사가 가능하다.

# ~/.tmux.conf (원격 서버)

# OSC 52를 통한 클립보드 동기화
set -s set-clipboard on
set -g allow-passthrough on

# 터미널 에뮬레이터가 OSC 52를 지원해야 한다
# Ghostty, Alacritty, WezTerm, iTerm2 등이 지원
-- Neovim (원격 서버) — OSC 52 지원
-- Neovim 0.10+ 에서는 기본 내장

vim.opt.clipboard = "unnamedplus"

-- OSC 52가 자동 감지되지 않을 때 명시적 설정
vim.g.clipboard = {
  name = "OSC 52",
  copy = {
    ["+"] = require("vim.ui.clipboard.osc52").copy("+"),
    ["*"] = require("vim.ui.clipboard.osc52").copy("*"),
  },
  paste = {
    ["+"] = require("vim.ui.clipboard.osc52").paste("+"),
    ["*"] = require("vim.ui.clipboard.osc52").paste("*"),
  },
}

6.4 통합 후 워크플로우

클립보드 동기화가 완료되면 다음과 같은 자연스러운 워크플로우가 가능해진다.

 ┌──────────┐    ┌──────────┐    ┌──────────┐
Vim      │    │ Tmux     │    │ OSRegister │ ✓  │ Buffer   │ ✓  │ Clipboard│
 │ yy/p     │◄──►│ copy-mode│◄──►│ Cmd-V └──────────┘    └──────────┘    └──────────┘
        동기화           동기화

 Vim에서 yy → 브라우저에서 Cmd-V 브라우저에서 Cmd-CVim에서 p ✓
 Tmux copy-mode → Slack에 붙여넣기 ✓

도구 간 장벽이 완전히 사라진다. 코드 스니펫을 Vim에서 복사하여 Slack에 공유하거나, 브라우저에서 에러 메시지를 복사하여 Vim에 붙여넣는 것이 마우스 없이도 자연스럽게 이루어진다.


7. [전략 5] Dotfiles와 개발자의 정체성

7.1 설정 파일 관리 = 전문성의 증명

Dotfiles(점으로 시작하는 설정 파일들 — .vimrc, .tmux.conf, .zshrc, .gitconfig 등)는 단순한 설정 파일이 아니다. 이것은 개발자가 수년간 축적한 워크플로우 최적화의 결정체다.

잘 관리된 Dotfiles 저장소는 다음을 말해준다.

  • 이 개발자가 어떤 도구를 사용하는지
  • 어떤 워크플로우를 선호하는지
  • 생산성에 얼마나 진지하게 투자하는지
  • 새로운 머신을 얼마나 빠르게 셋업할 수 있는지

7.2 Dotfiles 관리 도구 비교

방법 1: Git Bare Repository (최소주의)

# 초기 설정
git init --bare $HOME/.dotfiles
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles config --local status.showUntrackedFiles no

# .bashrc 또는 .zshrc에 alias 추가
echo "alias dotfiles='git --git-dir=\$HOME/.dotfiles/ --work-tree=\$HOME'" >> ~/.zshrc

# 파일 추가
dotfiles add ~/.tmux.conf
dotfiles add ~/.config/nvim/init.lua
dotfiles commit -m "Add tmux and neovim config"
dotfiles remote add origin git@github.com:username/dotfiles.git
dotfiles push -u origin main

# 새 머신에서 복원
git clone --bare git@github.com:username/dotfiles.git $HOME/.dotfiles
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles checkout

장점: 외부 의존성 없음, Git만 있으면 됨 단점: 충돌 관리 번거로움, 머신별 분기 어려움

방법 2: GNU Stow (심볼릭 링크)

# 설치
sudo apt install stow   # Debian/Ubuntu
brew install stow        # macOS

# 디렉토리 구조
~/dotfiles/
├── tmux/
│   └── .tmux.conf
├── nvim/
│   └── .config/
│       └── nvim/
│           └── init.lua
├── zsh/
│   └── .zshrc
└── starship/
    └── .config/
        └── starship.toml

# 심볼릭 링크 생성
cd ~/dotfiles
stow tmux      # ~/.tmux.conf → ~/dotfiles/tmux/.tmux.conf
stow nvim      # ~/.config/nvim/init.lua → ~/dotfiles/nvim/.config/nvim/init.lua
stow zsh       # ~/.zshrc → ~/dotfiles/zsh/.zshrc
stow starship  # ~/.config/starship.toml → ~/dotfiles/starship/.config/starship.toml

# 심볼릭 링크 해제
stow -D tmux

장점: 간단하고 직관적, 패키지별 관리 단점: 템플릿/시크릿 관리 기능 없음

방법 3: chezmoi (현대적 엔터프라이즈)

chezmoi는 가장 기능이 풍부한 Dotfiles 관리 도구다. 템플릿, 시크릿 관리, 머신별 분기, 암호화를 지원한다.

# 설치
brew install chezmoi         # macOS
sudo apt install chezmoi     # Debian/Ubuntu (snap도 가능)
sh -c "$(curl -fsLS get.chezmoi.io)"  # 범용

# 초기화
chezmoi init

# 파일 추가
chezmoi add ~/.tmux.conf
chezmoi add ~/.config/nvim/init.lua
chezmoi add ~/.config/starship.toml

# 편집
chezmoi edit ~/.tmux.conf

# 변경 사항 미리보기
chezmoi diff

# 적용
chezmoi apply

# Git 저장소로 푸시
chezmoi cd  # chezmoi 소스 디렉토리로 이동
git add . && git commit -m "Update dotfiles" && git push

# 새 머신에서 한 줄로 복원
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply username

chezmoi 템플릿 기능 — 머신별 설정 분기:

# ~/.local/share/chezmoi/dot_tmux.conf.tmpl

# 공통 설정
set -g default-terminal "tmux-256color"
set -g mouse on

# 클립보드 — OS별 분기
{{ if eq .chezmoi.os "darwin" -}}
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
{{ else if eq .chezmoi.os "linux" -}}
{{   if env "WAYLAND_DISPLAY" -}}
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "wl-copy"
{{   else -}}
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
{{   end -}}
{{ end -}}

7.3 Dotfiles 관리 도구 비교표

항목Git BareGNU Stowchezmoi
의존성Git만Git + StowGit + chezmoi
학습 곡선낮음낮음중간
템플릿없음없음Go 템플릿
시크릿 관리없음없음age, gpg, 1Password 등
머신별 분기Git 브랜치 수동수동자동 (OS, hostname 등)
원라인 복원가능 (스크립트)가능chezmoi init --apply
추천 대상미니멀리스트중간 규모멀티 머신 관리

7.4 왜 Vim + Tmux가 여전히 골드 스탠다드인가

Zellij를 비롯한 현대적 대안들이 등장했지만, Vim + Tmux 조합이 여전히 골드 스탠다드인 이유가 있다.

Zellij — 매력적이지만 아직은 아닌 이유

Zellij는 Rust로 작성된 현대적 터미널 멀티플렉서로, 모드 기반 네비게이션과 실시간 키바인딩 툴바를 제공하여 초보자 친화적이다. 하지만:

  • 생태계 미성숙: Tmux의 방대한 플러그인 생태계(TPM, vim-tmux-navigator 등)에 비해 제한적
  • Vim 통합: vim-tmux-navigator 수준의 심리스 네비게이션이 아직 발전 중
  • 원격 서버: 대부분의 서버에 Tmux는 기본 설치되어 있지만 Zellij는 아직 아님
  • 스크립팅: Tmux의 성숙한 명령어 체계에 비해 제한적
  • 메모리: 유사 조건에서 약 22MB (Tmux 대비 높음)

그렇다고 Zellij를 무시해서는 안 된다. 초보자이거나 로컬 개발 환경만 사용한다면 Zellij의 직관적 UI는 충분히 매력적이다.

7.5 터미널 에뮬레이터 비교

항목GhosttyAlacrittyWezTermKittyiTerm2
언어ZigRustRustC/PythonObj-C
GPU 가속Metal/OpenGLOpenGLOpenGLOpenGLMetal
네이티브 UIOXXXO (macOS)
설정텍스트 파일YAML → TOMLLuaconf 파일GUI
리가처OXOOO
이미지 렌더링Kitty protocolXOOO
탭/분할O (네이티브)XOOO
macOSOOOOO
LinuxOOOOX
Windows계획중OOXX
Nerd Font 기본OXXXX
특징제로 설정 시작극도의 미니멀Lua 스크립팅내장 멀티플렉서macOS 최적화

Tmux와 함께 사용할 때의 추천: Tmux가 멀티플렉싱을 담당하므로, 터미널 에뮬레이터 자체의 탭/분할 기능은 불필요하다. GPU 가속, 폰트 렌더링, 안정성에 집중하자. Ghostty는 제로 설정으로 시작하면서도 Nerd Font 기본 지원, Metal 가속, Kitty 프로토콜 등 필요한 모든 기능을 갖추고 있어 Tmux와의 궁합이 뛰어나다.


8. 완성된 설정 파일 (Copy-Paste Ready)

8.1 완성된 .tmux.conf

# ============================================================
# .tmux.conf — 생산성 극대화를 위한 완성 설정
# ============================================================

# ─── 기본 설정 ─────────────────────────────────────────────

# 256 컬러 및 True Color 지원
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"
set -ag terminal-overrides ",*256col*:Tc"

# 히스토리 크기
set -g history-limit 50000

# 이스케이프 시퀀스 대기 시간 최소화 (Vim에서 중요)
set -sg escape-time 0

# 반복 키 대기 시간
set -g repeat-time 500

# 포커스 이벤트 활성화 (Vim autoread 등에 필요)
set -g focus-events on

# 인덱스 1부터 시작 (0은 키보드에서 멀다)
set -g base-index 1
setw -g pane-base-index 1

# 윈도우 번호 자동 재배열
set -g renumber-windows on

# 마우스 지원
set -g mouse on

# Vi 키 모드
setw -g mode-keys vi

# ─── 프리픽스 키 변경 ──────────────────────────────────────

# Ctrl-a 가 Ctrl-b 보다 손이 편하다
unbind C-b
set -g prefix C-a
bind C-a send-prefix

# ─── 직관적 패널 분할 [전략 2] ─────────────────────────────

bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
unbind '"'
unbind %

# 새 윈도우도 현재 경로 유지
bind c new-window -c "#{pane_current_path}"

# ─── 페인 크기 조절 ──────────────────────────────────────

bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# ─── vim-tmux-navigator [전략 1] ───────────────────────────

# TPM으로 설치하는 경우 아래 한 줄이면 충분
set -g @plugin 'christoomey/vim-tmux-navigator'

# Ctrl-l 화면 클리어 복구
bind C-l send-keys 'C-l'

# ─── 복사 모드 및 클립보드 [전략 4] ─────────────────────────

# Vi 스타일 복사 모드
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle
bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel

# 시스템 클립보드 동기화
set -s set-clipboard on

# macOS
if-shell "uname | grep -q Darwin" {
  bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
  bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "pbcopy"
  bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
}

# Linux (X11)
if-shell "[ -n \"$DISPLAY\" ] && command -v xclip > /dev/null" {
  bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
  bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xclip -selection clipboard"
}

# Linux (Wayland)
if-shell "[ -n \"$WAYLAND_DISPLAY\" ] && command -v wl-copy > /dev/null" {
  bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "wl-copy"
  bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "wl-copy"
}

# ─── 상태바 커스터마이징 ──────────────────────────────────

set -g status-position bottom
set -g status-style "bg=#1e1e2e,fg=#cdd6f4"
set -g status-left-length 40
set -g status-right-length 80

set -g status-left "#[fg=#1e1e2e,bg=#89b4fa,bold]  #S #[fg=#89b4fa,bg=#1e1e2e] "
set -g status-right "#[fg=#a6adc8] %Y-%m-%d  %H:%M #[fg=#1e1e2e,bg=#a6e3a1,bold]  #H "

# 윈도우 상태바
setw -g window-status-format "#[fg=#6c7086] #I:#W "
setw -g window-status-current-format "#[fg=#1e1e2e,bg=#cba6f7,bold] #I:#W "

# 페인 테두리
set -g pane-border-style "fg=#313244"
set -g pane-active-border-style "fg=#89b4fa"

# ─── 유틸리티 ────────────────────────────────────────────

# 설정 리로드
bind r source-file ~/.tmux.conf \; display-message "Config reloaded!"

# 세션 전환
bind s choose-tree -Zs

# 동기 입력 토글 (여러 페인에 같은 입력)
bind S setw synchronize-panes \; display-message "Synchronize #{?synchronize-panes,ON,OFF}"

# ─── TPM (Tmux Plugin Manager) ───────────────────────────

set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'

# tmux-resurrect: Neovim 세션 복구
set -g @resurrect-strategy-nvim 'session'

# tmux-continuum: 자동 저장
set -g @continuum-restore 'on'
set -g @continuum-save-interval '15'

# TPM 초기화 (반드시 맨 마지막에)
run '~/.tmux/plugins/tpm/tpm'

8.2 완성된 Neovim init.lua (Vim + Tmux 통합 부분)

아래는 kickstart.nvim을 기반으로 Vim + Tmux 통합에 핵심적인 설정만 발췌한 것이다.

-- ============================================================
-- Neovim init.lua — Vim + Tmux 통합 핵심 설정
-- kickstart.nvim 기반
-- ============================================================

-- ─── 기본 옵션 ──────────────────────────────────────────

vim.g.mapleader = " "        -- Leader = 스페이스
vim.g.maplocalleader = " "

vim.opt.number = true         -- 줄 번호
vim.opt.relativenumber = true -- 상대 줄 번호
vim.opt.signcolumn = "yes"    -- 사인 컬럼 항상 표시
vim.opt.cursorline = true     -- 현재 줄 강조
vim.opt.termguicolors = true  -- True Color
vim.opt.scrolloff = 8         -- 스크롤 여유
vim.opt.sidescrolloff = 8
vim.opt.wrap = false          -- 줄 바꿈 비활성화

-- 검색
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.hlsearch = true
vim.opt.incsearch = true

-- 들여쓰기
vim.opt.expandtab = true
vim.opt.shiftwidth = 2
vim.opt.tabstop = 2
vim.opt.smartindent = true

-- 시스템 클립보드 동기화 [전략 4]
vim.opt.clipboard = "unnamedplus"

-- 파일 변경 자동 감지 (Tmux focus-events와 연동)
vim.opt.autoread = true
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter" }, {
  command = "checktime",
})

-- ─── lazy.nvim 부트스트랩 ────────────────────────────────

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

-- ─── 플러그인 ──────────────────────────────────────────

require("lazy").setup({

  -- vim-tmux-navigator [전략 1]
  {
    "christoomey/vim-tmux-navigator",
    cmd = {
      "TmuxNavigateLeft",
      "TmuxNavigateDown",
      "TmuxNavigateUp",
      "TmuxNavigateRight",
      "TmuxNavigatePrevious",
    },
    keys = {
      { "<C-h>", "<cmd>TmuxNavigateLeft<cr>",  desc = "Navigate Left" },
      { "<C-j>", "<cmd>TmuxNavigateDown<cr>",  desc = "Navigate Down" },
      { "<C-k>", "<cmd>TmuxNavigateUp<cr>",    desc = "Navigate Up" },
      { "<C-l>", "<cmd>TmuxNavigateRight<cr>", desc = "Navigate Right" },
    },
  },

  -- Vimux [전략 3]
  {
    "preservim/vimux",
    keys = {
      { "<Leader>vp", "<cmd>VimuxPromptCommand<cr>",   desc = "Vimux Prompt" },
      { "<Leader>vl", "<cmd>VimuxRunLastCommand<cr>",   desc = "Vimux Run Last" },
      { "<Leader>vi", "<cmd>VimuxInspectRunner<cr>",    desc = "Vimux Inspect" },
      { "<Leader>vz", "<cmd>VimuxZoomRunner<cr>",       desc = "Vimux Zoom" },
      { "<Leader>vc", "<cmd>VimuxCloseRunner<cr>",      desc = "Vimux Close" },
    },
    config = function()
      vim.g.VimuxHeight = "30"
      vim.g.VimuxOrientation = "v"
      vim.g.VimuxRunnerType = "pane"

      -- 언어별 테스트 실행 매핑
      -- Python
      vim.keymap.set("n", "<Leader>tp", function()
        vim.fn.VimuxRunCommand("pytest -v " .. vim.fn.expand("%"))
      end, { desc = "Run pytest" })

      -- Go
      vim.keymap.set("n", "<Leader>tg", function()
        vim.fn.VimuxRunCommand("go test -v ./...")
      end, { desc = "Run go test" })

      -- Node.js
      vim.keymap.set("n", "<Leader>tj", function()
        vim.fn.VimuxRunCommand("npx jest " .. vim.fn.expand("%") .. " --verbose")
      end, { desc = "Run jest" })

      -- Rust
      vim.keymap.set("n", "<Leader>tr", function()
        vim.fn.VimuxRunCommand("cargo test")
      end, { desc = "Run cargo test" })

      -- 범용: 마지막 테스트 재실행
      vim.keymap.set("n", "<Leader>tl", function()
        vim.fn.VimuxRunLastCommand()
      end, { desc = "Run last test" })
    end,
  },

  -- Telescope (퍼지 파인더)
  {
    "nvim-telescope/telescope.nvim",
    branch = "0.1.x",
    dependencies = { "nvim-lua/plenary.nvim" },
    keys = {
      { "<Leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find Files" },
      { "<Leader>fg", "<cmd>Telescope live_grep<cr>",  desc = "Live Grep" },
      { "<Leader>fb", "<cmd>Telescope buffers<cr>",    desc = "Buffers" },
      { "<Leader>fh", "<cmd>Telescope help_tags<cr>",  desc = "Help Tags" },
    },
  },

  -- Treesitter (구문 강조)
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = {
          "lua", "vim", "vimdoc", "python", "go",
          "javascript", "typescript", "rust", "bash",
          "json", "yaml", "toml", "markdown",
        },
        highlight = { enable = true },
        indent = { enable = true },
      })
    end,
  },

  -- 테마
  {
    "catppuccin/nvim",
    name = "catppuccin",
    priority = 1000,
    config = function()
      require("catppuccin").setup({
        flavour = "mocha",
        transparent_background = false,
        integrations = {
          telescope = true,
          treesitter = true,
          which_key = true,
        },
      })
      vim.cmd.colorscheme("catppuccin")
    end,
  },

  -- which-key (키바인딩 디스커버리)
  {
    "folke/which-key.nvim",
    event = "VeryLazy",
    config = function()
      require("which-key").setup()
    end,
  },
})

-- ─── 추가 키매핑 ────────────────────────────────────────

-- ESC로 검색 하이라이트 제거
vim.keymap.set("n", "<Esc>", "<cmd>nohlsearch<cr>")

-- 분할 창 이동 (vim-tmux-navigator와 중복되므로 필요 없지만 참고용)
-- vim.keymap.set("n", "<C-h>", "<C-w>h")
-- vim.keymap.set("n", "<C-j>", "<C-w>j")
-- vim.keymap.set("n", "<C-k>", "<C-w>k")
-- vim.keymap.set("n", "<C-l>", "<C-w>l")

-- 비주얼 모드에서 선택한 블록 이동
vim.keymap.set("v", "J", ":m '>+1<cr>gv=gv")
vim.keymap.set("v", "K", ":m '<-2<cr>gv=gv")

-- 화면 중앙 유지
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")
vim.keymap.set("n", "n", "nzzzv")
vim.keymap.set("n", "N", "Nzzzv")

9. 생산성 측정: 워크플로우 비교

9.1 작업별 소요 시간 비교

다음은 일상적인 개발 작업에서 GUI 중심 워크플로우와 Vim + Tmux 워크플로우의 체감 소요 시간을 비교한 것이다. (숙련자 기준, 마이크로초~초 단위의 누적 효과)

작업GUI 중심Vim + Tmux절감
파일 열기3-5초 (폴더 탐색, 클릭)0.5-1초 (<Leader>ff)~80%
코드 → 터미널 전환2-3초 (Alt-Tab, 마우스)0.2초 (Ctrl-j)~90%
테스트 실행3-5초 (터미널 전환, 명령 입력)0.3초 (<Leader>tt)~90%
텍스트 복사 → 다른 앱2-4초 (마우스 드래그, Ctrl-C)0.5초 (yy → Cmd-V)~80%
환경 복구 (재부팅 후)5-15분 (앱 재실행, 창 재배치)10초 (tmux attach)~99%
창 레이아웃 구성1-3분 (마우스로 드래그, 리사이즈)10초 (prefix + |, -)~90%

9.2 하루 누적 효과

하루 8시간 개발 기준으로 각 작업의 빈도를 가정하면:

 ┌────────────────────────────────────────────────────┐
 │  일일 컨텍스트 스위칭 누적 비용 추정                  │
 ├────────────────────────────────────────────────────┤
 │                                                    │
 │  파일 열기: 50/일 × 3초 절감 = 150 (2.5) │  창 전환:  200/일 × 2초 절감 = 400 (6.7) │  테스트:    30/일 × 3초 절감 = 90  (1.5) │  복사/붙여넣기: 40/일 × 2초 절감 = 80 (1.3) │  기타 조작: 100/일 × 1초 절감 = 100 (1.7) │                                                    │
 │  ─────────────────────────────────────────────      │
 │  일일 절감:14 (순수 동작 시간)+ 인지 부하 감소에 의한 집중력 유지: 측정 불가        │
+ 흐름 상태(Flow State) 유지 효과: 측정 불가         │
 │                                                    │
연간 (250 근무일):58시간                        │
= 7.25 근무일                                      │
 └────────────────────────────────────────────────────┘

14분이라는 숫자는 순수 동작 시간만 계산한 것이다. 실제로 더 중요한 것은 인지 부하 감소흐름 상태 유지 효과인데, 이는 정량화하기 어렵지만 체감적으로 훨씬 크다. 마우스에 손을 뻗지 않는다는 것은 사고의 흐름이 끊기지 않는다는 것이고, 이것이 생산성의 실질적인 핵심이다.


10. 트러블슈팅 및 자주 묻는 질문

10.1 vim-tmux-navigator가 느리다

일부 환경에서 vim-tmux-navigator의 is_vim 체크가 지연을 유발할 수 있다. 이 경우 플러그인 없이 직접 구현하는 방법이 있다.

# ~/.tmux.conf — 경량 대안 (플러그인 없이)
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
    | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|l?n?vim?x?)(diff)?$'"

bind-key -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L"
bind-key -n C-j if-shell "$is_vim" "send-keys C-j" "select-pane -D"
bind-key -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U"
bind-key -n C-l if-shell "$is_vim" "send-keys C-l" "select-pane -R"

Neovim 측 설정 (Lua, 플러그인 없이):

-- Neovim 측 경량 구현
local function navigate(direction)
  local nr = vim.fn.winnr()
  vim.cmd("wincmd " .. direction)
  if nr == vim.fn.winnr() then
    local tmux_dir = ({ h = "L", j = "D", k = "U", l = "R" })[direction]
    vim.fn.system("tmux select-pane -" .. tmux_dir)
  end
end

vim.keymap.set("n", "<C-h>", function() navigate("h") end)
vim.keymap.set("n", "<C-j>", function() navigate("j") end)
vim.keymap.set("n", "<C-k>", function() navigate("k") end)
vim.keymap.set("n", "<C-l>", function() navigate("l") end)

10.2 Tmux에서 색상이 깨진다

# ~/.tmux.conf
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"

# Ghostty 사용 시
set -ag terminal-overrides ",xterm-ghostty:RGB"

10.3 Neovim에서 ESC 반응이 느리다

Tmux의 escape-time 설정이 원인이다.

# ~/.tmux.conf
set -sg escape-time 0   # 기본값은 500ms — 반드시 0으로

10.4 클립보드가 SSH에서 동작하지 않는다

OSC 52 지원을 확인하자.

# 터미널 에뮬레이터의 OSC 52 지원 확인 (로컬)
printf '\033]52;c;%s\a' $(echo -n "test" | base64)
# 이후 Cmd-V로 "test"가 붙여넣어지면 OSC 52 지원됨

# Tmux 설정
set -s set-clipboard on
set -g allow-passthrough on

10.5 Tmux 세션이 재부팅 후 사라진다

tmux-resurrecttmux-continuum 플러그인을 사용하자. (위 8.1 완성된 .tmux.conf에 포함)


11. 결론: 최소 저항의 경로

11.1 "1초의 동작" 질문

개발 환경을 최적화할 때 스스로에게 던져야 할 질문이 하나 있다.

"이 동작에 1초 이상 걸리는가?"

만약 그렇다면, 그것은 자동화하거나 단축키로 만들 수 있는 후보다. Vim과 Tmux의 조합은 이 질문에 대한 체계적인 답변 시스템이다.

  • 파일을 열려면?<Leader>ff (1초 미만)
  • 옆 패널로 이동하려면?Ctrl-h/j/k/l (0.2초)
  • 테스트를 실행하려면?<Leader>tt (0.3초)
  • 텍스트를 복사하려면?yy (0.1초, 시스템 클립보드 자동 동기화)
  • 어제 작업 환경을 복구하려면?tmux attach (1초)

11.2 최소 저항의 경로

물은 항상 최소 저항의 경로를 따라 흐른다. 개발자의 워크플로우도 마찬가지다. 특정 동작의 저항(마찰)이 크면 그 동작을 피하게 되고, 저항이 작으면 자연스럽게 그 경로를 따르게 된다.

Vim + Tmux는 코드 편집 → 실행 → 확인 → 수정이라는 개발의 핵심 루프에서 저항을 극한까지 줄인다. 마우스에 손을 뻗는 것, 터미널 창을 Alt-Tab으로 찾는 것, 명령어를 다시 타이핑하는 것 — 이 모든 미세한 저항들이 사라질 때, 개발자는 생각의 속도로 코딩할 수 있게 된다.

11.3 시작을 위한 로드맵

모든 것을 한 번에 도입할 필요는 없다. 단계적으로 접근하자.

 Week 1: Tmux 기본 + 직관적 패널 분할 (|, -)
 Week 2: vim-tmux-navigator 설정 (Ctrl-h/j/k/l)
 Week 3: Vimux 설정 + 자주 쓰는 테스트 명령어 매핑
 Week 4: 클립보드 동기화 + Dotfiles Git 관리 시작
 Month 2+: 자신만의 워크플로우 미세 조정
 : "마우스에 손을 대지 않는 즐거움"

매주 하나의 전략만 도입해도 한 달이면 완전한 Vim + Tmux 통합 환경이 완성된다. 중요한 것은 한 번에 모든 것을 바꾸려 하지 않는 것이다. 각 전략이 자연스러워진 후 다음 전략으로 넘어가자.

마우스에 손을 대지 않는 것은 단순한 기술적 선택이 아니다. 그것은 생각과 실행 사이의 거리를 줄이는 것이고, 궁극적으로 개발자로서의 표현력을 극대화하는 것이다.


12. 참고 자료