Skip to main content

Neovim Keymap Inventory and Troubleshooting

This document expands on a personal note from 2023—a shortcut table I wrote so I would not forget it. The original note contained only five mappings and the one-line legend L: Leader Key, without distinguishing whether each mapping was a Neovim default or registered by a plugin. This document preserves the intent of that note while clearly identifying the owner of each mapping and providing both a procedure for verifying the mappings actually applied to the current session and a diagnostic sequence for mappings that do not work.

Scope and assumptions

  • The shortcut table below is a snapshot of an old local configuration. The same key may be bound to a completely different action depending on the Neovim distribution (LazyVim, NvChad, AstroNvim, and so on) or personal init.lua, so do not treat this table as fact for your own environment.
  • Neovim adds default mappings as versions advance. For example, the current stable release provides a default <C-W>d mapping that displays the diagnostic under the cursor in a floating window. This means that some actions in the table may now be available as built-in features without local mappings.
  • The verification commands assume Neovim 0.8 or later, where the Lua API has stabilized. If the output differs from this document, trust the :verbose output rather than the document.

Key notation

The original note represented the leader key as L, which caused two problems. First, L conflicts with a built-in Neovim Normal mode command (move the cursor to the last line on the screen, :h L). Second, the actual key for <Leader> defaults to backslash (\) and can be changed with g:mapleader, so there was no way to tell whether the letter L meant the literal input key or the leader. This document uses standard Vim/Neovim notation.

  • <Leader> — leader key (:h mapleader, default \)
  • <C-x> — Ctrl combination, <S-x> — Shift combination, <C-Space> — Ctrl+Space
  • <CR> — Enter, <Cmd> — notation for running an Ex command without opening the command line

Local configuration snapshot

The table below rewrites the mappings from the 2023 note in standard notation. Every entry is a local configuration snapshot, and you must use :verbose to identify which configuration file or plugin registered it. None is asserted to be a built-in Neovim feature.

ModeKeyIntended actionSuspected ownerClassification
Normal<Leader>fDisplay the diagnostic under the cursor in a floating windowMapping around vim.diagnostic.open_float() in the local LSP configurationLocal snapshot — owner must be verified
Normal<Leader><Leader>tToggle the file treeNvimTree/neo-tree family pluginLocal snapshot — owner must be verified
Normal<Leader><Leader>sLeap forward search (character-wise jump)leap.nvimLocal snapshot — owner must be verified
Normal<Leader>SLeap backward searchleap.nvimLocal snapshot — owner must be verified
Insert<C-Space>Manually invoke the completion popupnvim-cmp complete() bindingLocal snapshot — owner must be verified

The original note wrote the backward Leap mapping as L S, making it ambiguous whether the mapping was <Leader>S or <Leader><Leader>S. The table interprets it as <Leader>S, but verify the actual configuration with :verbose nmap <Leader>S.

Knowing the built-in alternatives keeps your workflow intact even in an environment where the local mappings have disappeared.

  • Diagnostic float — current stable default mapping <C-W>d (:h CTRL-W_d-default)
  • File navigation — built-in Netrw: :Explore, :Lexplore
  • Completion — built-in insert completion: <C-N>/<C-P> (keywords), <C-X><C-O> (omni)
  • s/S — built-ins in the substitute family (s is cl, and S is cc). leap.nvim works by overriding these keys, so they revert to their original actions in an environment where Leap is not loaded.

Official procedure for inspecting active mappings

"Mappings written in the configuration file" can differ from "mappings applied to the current session." A plugin may overwrite the same key later, or a buffer-local mapping may shadow a global mapping. The following commands are primary sources for the current session state.

CommandOutput
:nmapAll Normal mode mappings for the current buffer (global + buffer-local)
:imapAll Insert mode mappings
:verbose nmap <lhs>The mapping for that lhs and Last set from file:line — identifies its owner
:verbose imap <lhs>The same inspection for Insert mode
:nmap <buffer>Only buffer-local Normal mappings
:lua =vim.api.nvim_get_keymap('n')Returns global Normal mappings as a Lua table (for dumping/comparison)
:lua =vim.api.nvim_buf_get_keymap(0, 'n')Returns buffer-local mappings for the current buffer (0) as a Lua table

The standard sequence for identifying the owner starts by running :verbose nmap <lhs> in the buffer where the problem occurs. Last set from in the output points to the owning file and line. Empty output means there is no mapping in that mode, so the action is either a built-in command rather than a mapping, or a mapping in another mode. nvim_get_keymap() returns only global mappings, while nvim_buf_get_keymap() returns only buffer-local mappings; comparing them lets a script detect a buffer-local override.

Diagnosing mapping conflicts (shadowing)

When the same lhs is mapped twice, the mapping executed later wins. Lazy loading by a plugin manager often creates a situation where "my configuration definitely loads first, but the action is different." If your mapping is registered during initialization and then a plugin registers the same lhs when it loads, the plugin becomes the final owner. A buffer-local mapping shadows a global mapping inside that buffer. In a setup where an on_attach callback installs buffer-local mappings when an LSP attaches, the behavior may differ only in buffers with an attached LSP.

Diagnostic sequence:

  1. In the affected buffer, run :verbose nmap <lhs> — inspect the current owner and Last set from
  2. Run :nmap <buffer> — check whether a buffer-local mapping exists
  3. If Last set from points to your configuration, it is a load-order problem. If it points to a plugin directory, disable that plugin's default mapping option or defer your mapping until after the plugin loads
  4. unique = true on vim.keymap.set turns a conflict with an lhs that already exists at registration time into an error. It does not prevent a lazy-loaded plugin from overwriting the mapping later, so handle that conflict by disabling the plugin's default mapping or checking the owner again after its load hook

When <C-Space> does not work in the terminal

In traditional terminals, <C-Space> is often sent as the NUL (0x00) byte and cannot be distinguished from <C-@>. The terminal emulator may not send this byte at all, or the operating system may intercept it first. On macOS, for example, Ctrl+Space is assigned to "Select the previous input source" by default; unless that keyboard shortcut is disabled in System Settings, the key never reaches Neovim. Modifier combinations may also be lost inside tmux when extended keys are not configured.

The fastest way to inspect the transmission path is to examine the byte directly outside Neovim.

  1. Run cat -v in the shell and press Ctrl+Space. If ^@ appears, NUL is being transmitted; if nothing appears, the terminal or OS is swallowing it.
  2. Inside Neovim, press <C-V> in Insert mode and then <C-Space> to inspect the character actually inserted.
  3. tmux users should repeat the same test outside tmux to isolate whether tmux is a variable in the path.

There is no reason to insist on that lhs in an environment where it cannot be transmitted. Replace completion with the built-in <C-N>/<C-P> and <C-X><C-O>, or choose another lhs whose transmission is guaranteed. See :h tui for terminal key input behavior in general.

Registering mappings with Lua

For new mappings, vim.keymap.set is recommended over the :map family of Ex commands. Unlike :map, it defaults to noremap = true, preventing accidental recursive mappings, and stores a documentation string through desc. The desc appears directly in :map output and discovery UIs such as which-key.

-- Example: registration code that reproduces the intent of the local snapshot table
local map = vim.keymap.set

-- Normal: show the diagnostic under the cursor in a floating window
map('n', '<Leader>f', function()
vim.diagnostic.open_float({ border = 'rounded' })
end, { desc = 'Open diagnostic float', silent = true })

-- Insert: manually invoke the completion popup (requires nvim-cmp)
map('i', '<C-Space>', function()
require('cmp').complete()
end, { desc = 'Open completion popup' })

-- Normal: toggle the file tree — detect conflicts at registration time with unique
map('n', '<Leader><Leader>t', '<Cmd>NvimTreeToggle<CR>', {
desc = 'Toggle file tree',
unique = true,
})

Keeping the "Intended action" column in the snapshot table aligned with the actual desc lets :map output immediately reveal whether the document and configuration have diverged. The nvim-cmp example above is valid only in environments where that plugin is installed; if you use only built-in completion, the <C-Space> mapping itself is unnecessary.

Maintenance checklist

  • Always add desc to a new mapping, and run :verbose nmap <lhs> before adding it to check for conflicts
  • When possible, use unique = true to turn registration-time duplicates into errors, and check the owner again after lazy loading
  • Re-verify every lhs in the snapshot table with :verbose after upgrading Neovim or a plugin (new default mappings may be added, or plugin defaults may change)
  • Remove a mapping from this table at the same time it is removed from the configuration
  • After changing terminal emulators, use cat -v to recheck transmission of control keys such as <C-Space>
  • Remove local mappings that can be replaced by built-in defaults (for example, diagnostic float with <C-W>d)

Official references