Neovim 키맵 인벤토리와 트러블슈팅
이 문서는 2023년에 작성한 개인 메모, 즉 "까먹지 않으려고 적어둔 단축키 표"를 확장한 것이다.
원본 메모는 다섯 개의 매핑과 L: Leader Key라는 한 줄짜리 범례가 전부였고, 각 매핑이
Neovim 기본 기능인지 플러그인이 등록한 것인지 구분이 없었다. 이 문서는 그 메모의 의도를
보존하되, 각 매핑의 소유자를 명확히 표시하고, 현재 세션에 실제로 적용된 매핑을 스스로
검증하는 절차와, 매핑이 동작하지 않을 때의 진단 순서를 함께 정리하는 것을 목표로 한다.
범위와 전제
- 아래 단축키 표는 과거 로컬 설정의 스냅샷이다. Neovim 배포판(LazyVim, NvChad,
AstroNvim 등)이나 개인
init.lua구성에 따라 같은 키가 전혀 다른 동작에 연결되어 있을 수 있으므로, 이 표를 그대로 자기 환경에 적용되는 사실로 간주하면 안 된다. - Neovim은 버전이 올라가며 기본 매핑이 추가된다. 예를 들어 현재 stable은
<C-W>d로 커서 위치의 진단(diagnostic)을 floating window에 표시하는 기본 매핑을 제공한다. 표의 동작 일부는 최신 버전에서는 로컬 매핑 없이 내장 기능으로 사용할 수 있다는 뜻이다. - 검증 명령은 Lua API가 안정화된 Neovim 0.8 이상을 기준으로 한다. 출력이 이 문서와
다르다면 문서가 아니라
:verbose계열 출력을 기준으로 삼는다.
키 표기법
원본 메모는 리더 키를 L로 표기했는데 두 가지 문제가 있었다. 첫째, L은 Neovim 내장
Normal 모드 명령(커서를 화면 마지막 줄로 이동, :h L)과 충돌한다. 둘째, <Leader>의
실제 키는 기본값이 백슬래시(\)이고 g:mapleader로 변경할 수 있으므로, 문자 L이 실제
입력 키인지 리더인지 구분할 수 없었다. 이 문서는 Vim/Neovim 표준 표기를 사용한다.
<Leader>— 리더 키 (:h mapleader, 기본값\)<C-x>— Ctrl 조합,<S-x>— Shift 조합,<C-Space>— Ctrl+Space<CR>— Enter,<Cmd>— 명령행을 열지 않고 Ex 명령을 실행하는 표기
로컬 설정 스냅샷
아래 표는 2023년 메모의 매핑을 표준 표기로 옮긴 것이다. 전부 로컬 설정 스냅샷이며, 어느
설정 파일이나 플러그인이 등록했는지는 반드시 :verbose로 확인해야 한다. Neovim 내장
기능으로 단정하지 않는다.
| Mode | 키 | 의도한 동작 | 추정 소유자 | 분류 |
|---|---|---|---|---|
| Normal | <Leader>f | 커서 위치 diagnostic을 floating window로 표시 | 로컬 LSP 설정에서 vim.diagnostic.open_float()를 감싼 매핑 | 로컬 스냅샷 — 소유자 확인 필요 |
| Normal | <Leader><Leader>t | 파일 트리 토글 | NvimTree/neo-tree 계열 플러그인 | 로컬 스냅샷 — 소유자 확인 필요 |
| Normal | <Leader><Leader>s | Leap 정방향 탐색 (글자 단위 점프) | leap.nvim | 로컬 스냅샷 — 소유자 확인 필요 |
| Normal | <Leader>S | Leap 역방향 탐색 | leap.nvim | 로컬 스냅샷 — 소유자 확인 필요 |
| Insert | <C-Space> | 자동완성 팝업 수동 호출 | nvim-cmp의 complete() 바인딩 | 로컬 스냅샷 — 소유자 확인 필요 |
원본 메모의 역방향 Leap 표기는 L S로, <Leader>S인지 <Leader><Leader>S인지 모호했다.
위 표는 <Leader>S로 해석했으나 실제 설정은 :verbose nmap <Leader>S로 확인한다.
내장 대안을 알아두면 로컬 매핑이 사라진 환경에서도 작업 흐름이 끊기지 않는다.
- 진단 float — 현재 stable의 기본 매핑
<C-W>d(:h CTRL-W_d-default) - 파일 탐색 — 내장 Netrw:
:Explore,:Lexplore - 자동완성 — 내장 insert completion:
<C-N>/<C-P>(키워드),<C-X><C-O>(omni) s/S— 내장으로는 substitute 계열(s는cl,S는cc)이다. leap.nvim은 이 키를 덮어쓰는 방식으로 동작하므로, leap이 로드되지 않은 환경에서는 원래 동작으로 돌아간다.
실제 매핑을 확인하는 공식 절차
"설정 파일에 적어둔 매핑"과 "현재 세션에 적용된 매핑"은 다를 수 있다. 플러그인이 나중에 같은 키를 덮어쓰거나, 버퍼 로컬 매핑이 글로벌 매핑을 가릴 수 있기 때문이다. 다음 명령들이 현재 세션의 상태를 보여주는 1차 소스다.
| 명령 | 출력 |
|---|---|
:nmap | 현재 버퍼 기준 Normal 모드 매핑 전체 (글로벌 + 버퍼 로컬) |
:imap | Insert 모드 매핑 전체 |
:verbose nmap <lhs> | 해당 lhs의 매핑과 Last set from 파일:라인 — 소유자 특정 |
:verbose imap <lhs> | Insert 모드에 대한 동일한 검사 |
:nmap <buffer> | 버퍼 로컬 Normal 매핑만 필터 |
:lua =vim.api.nvim_get_keymap('n') | 글로벌 Normal 매핑을 Lua 테이블로 반환 (덤프/비교용) |
:lua =vim.api.nvim_buf_get_keymap(0, 'n') | 현재 버퍼(0)의 버퍼 로컬 매핑을 Lua 테이블로 반환 |
소유자를 특정하는 표준 순서는 이렇다. 문제가 재현되는 버퍼에서 :verbose nmap <lhs>를
실행하면, 출력의 Last set from이 소유 파일과 라인을 가리킨다. 출력이 비어 있으면 해당
모드에 매핑이 없다는 뜻이므로, 그 동작은 매핑이 아니라 내장 명령이거나 다른 모드의 매핑이다.
nvim_get_keymap()은 글로벌만, nvim_buf_get_keymap()은 버퍼 로컬만 반환하므로 둘을
비교하면 버퍼 로컬 오버라이드 여부를 스크립트로 확인할 수 있다.
매핑 충돌(섀도잉) 진단
같은 lhs를 두 번 매핑하면 나중에 실행된 쪽이 이긴다. 플러그인 매니저의 지연 로딩(lazy
loading) 때문에 "내 설정이 분명히 먼저 로드되는데 동작이 다르다"는 상황이 흔하다. 내 매핑이
초기화 단계에서 등록된 뒤, 어떤 플러그인이 로드되면서 같은 lhs를 다시 등록하면 플러그인이
최종 소유자가 된다. 버퍼 로컬 매핑은 해당 버퍼 안에서 글로벌 매핑을 가린다. LSP가 attach될
때 on_attach 콜백이 버퍼 로컬 매핑을 심는 구성이라면, LSP가 붙은 버퍼에서만 동작이
달라지는 식이다.
진단 순서:
- 문제 버퍼에서
:verbose nmap <lhs>— 현재 소유자와Last set from확인 :nmap <buffer>— 버퍼 로컬 매핑 존재 여부 확인Last set from이 내 설정이면 로드 순서 문제이고, 플러그인 디렉터리면 해당 플러그인의 기본 매핑 옵션을 끄거나 내 매핑을 플러그인 로드 이후로 미룬다vim.keymap.set의unique = true는 등록 시점에 이미 존재하는 lhs와의 충돌을 에러로 만든다. 이후 lazy-loaded plugin이 덮어쓰는 경우까지 막지는 않으므로, 그 충돌은 플러그인의 기본 매핑을 끄거나 load hook 이후에 소유자를 다시 확인해 관리한다
터미널에서 <C-Space>가 동작하지 않을 때
<C-Space>는 전통적인 터미널에서 NUL(0x00) 바이트로 전송되며 <C-@>와 구분되지 않는
경우가 많다. 터미널 에뮬레이터가 이 바이트를 아예 보내지 않거나, 운영체제가 먼저 가로채는
경우도 있다. 대표적으로 macOS는 "이전 입력 소스 선택" 단축키가 기본으로 Ctrl+Space에
할당되어 있어, 시스템 설정의 키보드 단축키에서 이를 해제하지 않으면 Neovim까지 키가
도달하지 않는다. tmux 안에서 사용하는 경우에도 extended keys 설정이 없으면 수정자 조합이
손실될 수 있다.
전송 경로를 확인하는 가장 빠른 방법은 Neovim 밖에서 바이트를 직접 보는 것이다.
- 셸에서
cat -v를 실행하고 Ctrl+Space를 누른다.^@가 보이면 NUL이 전송되는 것이고, 아무것도 보이지 않으면 터미널이나 OS가 삼키는 것이다. - Neovim 안에서는 Insert 모드에서
<C-V>를 누른 뒤<C-Space>를 눌러 실제로 삽입되는 문자를 확인한다. - tmux 사용자는 tmux 바깥에서 같은 테스트를 반복해 tmux가 경로상의 변수인지 분리한다.
전송이 불가능한 환경이라면 그 lhs를 고집할 이유가 없다. 내장 <C-N>/<C-P>와
<C-X><C-O>로 자동완성을 대체하거나, 전송이 보장되는 다른 lhs를 선택하는 편이 낫다.
터미널 키 입력 전반의 동작은 :h tui를 참조한다.
Lua로 매핑 등록하기
새 매핑은 Ex 명령 :map 계열보다 vim.keymap.set으로 등록하는 것이 권장된다. :map과
달리 noremap = true가 기본값이라 재귀 매핑 사고를 피할 수 있고, desc로 문서화 문자열을
함께 저장할 수 있기 때문이다. desc는 :map 출력과 which-key 같은 탐색 UI에 그대로
표시된다.
-- 예시: 로컬 스냅샷 표의 의도를 재현하는 등록 코드
local map = vim.keymap.set
-- Normal: 커서 아래 diagnostic을 floating window로
map('n', '<Leader>f', function()
vim.diagnostic.open_float({ border = 'rounded' })
end, { desc = 'diagnostic float 열기', silent = true })
-- Insert: 자동완성 팝업 수동 호출 (nvim-cmp 설치 전제)
map('i', '<C-Space>', function()
require('cmp').complete()
end, { desc = '자동완성 팝업 열기' })
-- Normal: 파일 트리 토글 — unique로 충돌을 설정 시점에 감지
map('n', '<Leader><Leader>t', '<Cmd>NvimTreeToggle<CR>', {
desc = '파일 트리 토글',
unique = true,
})
스냅샷 표의 "의도한 동작" 열을 실제 desc와 일치시켜 두면, 문서와 설정이 어긋났는지를
:map 출력만으로 바로 알 수 있다. 위 nvim-cmp 예시는 해당 플러그인이 설치된 환경에서만
유효하며, 내장 completion만 쓰는 환경이라면 <C-Space> 매핑 자체가 불필요하다.
유지보수 체크리스트
- 새 매핑을 추가할 때
desc를 반드시 달고, 추가 전:verbose nmap <lhs>로 충돌 여부 확인 - 가능하면
unique = true로 등록 시점의 중복을 에러로 승격하고, lazy load 뒤에도 소유자 재확인 - Neovim 본체나 플러그인 업그레이드 후 스냅샷 표의 각 lhs를
:verbose로 재검증 (새 기본 매핑이 생기거나 플러그인 기본값이 바뀔 수 있음) - 설정에서 제거한 매핑은 이 문서의 표에서도 같은 시점에 제거
- 터미널 에뮬레이터를 바꾸면
<C-Space>등 제어키 전송을cat -v로 재확인 - 내장 기본 매핑으로 대체 가능한 로컬 매핑은 정리 (예: 진단 float은
<C-W>d)
관련 문서
- Tools:: 인덱스 — 이 섹션의 다른 도구 노트
- Vim에서 한글이 깨질 때 — 같은 에디터 계열의 트러블슈팅
- iTerm2 shell integration — macOS 터미널 환경, 키 전송 문제의 배경
공식 레퍼런스
- map.txt —
:h map—:map계열 명령,<Leader>,<unique>,<buffer>정의 :h mapleader— 리더 키의 기본값과 변경 방법vim.keymap.set()— Lua 매핑 APInvim_get_keymap()/nvim_buf_get_keymap()— 글로벌/버퍼 로컬 매핑 조회 API- diagnostic.txt —
:h vim.diagnostic—open_float()와 기본 매핑<C-W>d(CTRL-W_d-default) - TUI —
:h tui— 터미널 키 입력과 수정자 전송 동작