Notable keybindings
The keybinding philosophy covers how the
keyspace is organised, and :help noethervim-keymaps lists every binding the
distribution sets. This page covers a handful of individual
choices that are interesting to single out and make copiable.
Every snippet here is standalone. None of it depends on NoetherVim, and none of it depends on a plugin unless the entry says so.
j and k know the difference between a hop and a jump
Section titled “j and k know the difference between a hop and a jump”Wrapped lines force a choice. Map j to gj and short movements inside a
wrapped paragraph behave the way the screen looks, but 10j now counts screen
rows instead of lines, and none of it enters the jumplist, so <C-o> cannot
take you back. Leave j alone and every wrapped paragraph becomes a single
unnavigable line.
The split is by count. A bare press or a small count moves by visual line. A count above five sets a jumplist mark first and then moves by logical line, because a movement that large is a jump, and jumps should be undoable.
local function vline_move(key) local n = vim.v.count if n > 5 then return "m'" .. n .. key -- set jump mark, then logical-line move elseif n > 0 then return n .. "g" .. key -- counted visual-line move else return "g" .. key -- single visual-line hop endend
vim.keymap.set("n", "j", function() return vline_move("j") end, { expr = true })vim.keymap.set("n", "k", function() return vline_move("k") end, { expr = true })The threshold of five is arbitrary and worth tuning. Set it to whatever number of lines you can cross without losing your place.
n always goes forward
Section titled “n always goes forward”After /pattern, n goes down. After ?pattern, n goes up. This is
consistent behaviour, but if you work on something else and then
press n you often forget which key opened the search. This keymaps makes the direction
of the key be a property of the key:
vim.keymap.set({ "n", "v" }, "n", function() return vim.v.searchforward == 1 and "n" or "N" end, { expr = true, silent = true })vim.keymap.set({ "n", "v" }, "N", function() return vim.v.searchforward == 1 and "N" or "n" end, { expr = true, silent = true })? still searches backward, namely it starts the search upward. It just no longer
inverts the two keys you press afterwards.
Extending Z to a grid of keys
Section titled “Extending Z to a grid of keys”Vim ships ZZ (write and quit) and ZQ (quit, discarding changes). Two keys
that read as arbitrary until you notice they are the two corners of a table:
- Rows are how much you are willing to lose. Columns are what you are closing.
| this window | everything | this buffer | |
|---|---|---|---|
| force, discard changes | ZQ :q! |
ZW :qa! |
ZE :bd! |
| refuse if dirty | ZA :q |
ZS :qa |
ZD :bd |
| save first | ZZ :x |
ZX :wa|qa! |
ZC :w|bd |
Concretely:
vim.keymap.set("n", "ZA", "<cmd>q<cr>")vim.keymap.set("n", "ZS", "<cmd>qa<cr>")vim.keymap.set("n", "ZW", "<cmd>qa!<cr>")vim.keymap.set("n", "ZX", "<cmd>wa<bar>qa!<cr>")vim.keymap.set("n", "ZD", "<cmd>bdelete<cr>")vim.keymap.set("n", "ZE", "<cmd>bdelete!<cr>")vim.keymap.set("n", "ZC", "<cmd>write<bar>bdelete<cr>")Arrow keys resize the window they are in
Section titled “Arrow keys resize the window they are in”The arrow keys duplicate hjkl and sit far enough from home row that they see
little use as motions. That leaves four keys free, and four more with shift.
The direction rule matters more than the binding. Vim’s own :resize grows the
current window by preferring its right or bottom border, and falls back to the
opposite border once the window is against the screen edge, so an arrow binding
built on it reverses direction in that case. These bind the arrow to the
direction the border moves instead:
<Right>pushes the right edge rightward,<S-Right>pulls the left edge rightward, and the same for the other three axes.- Each operation is a no-op when there is no neighbour on the moving edge, rather than resizing the opposite side.
local function neighbor(dir) local cur, nbr = vim.fn.winnr(), vim.fn.winnr("1" .. dir) return nbr ~= cur and vim.fn.win_getid(nbr) or nilend
vim.keymap.set("n", "<Right>", function() if neighbor("l") then vim.fn.win_move_separator(0, 2) endend)vim.keymap.set("n", "<Left>", function() local w = neighbor("h") if w then vim.fn.win_move_separator(w, -2) endend)vim.keymap.set("n", "<Down>", function() if neighbor("j") then vim.fn.win_move_statusline(0, 2) endend)vim.keymap.set("n", "<Up>", function() local w = neighbor("k") if w then vim.fn.win_move_statusline(w, -2) endend)win_move_separator() and win_move_statusline() treat the resize as a drag
of the border between two windows, which is why the neighbour check is
required: on the bottom-most window, win_move_statusline() takes its rows
from 'cmdheight' instead.
NoetherVim adds one branch on top. When the tab holds a single non-floating
window, or the cursor is inside a float, there is nothing to resize against, so
the arrows fall through to hjkl motion. Counts pass through with them, so
5<Down> still moves five lines.
<Esc> dismisses what is on screen
Section titled “<Esc> dismisses what is on screen”<Esc> leaves the current mode. In normal mode it does nothing, so it is free
to carry a second meaning: clear whatever the screen is still holding.
Search highlighting, notification toasts and LSP hover floats each have their own way out. This routes all of them through one key.
vim.keymap.set({ "n", "v" }, "<Esc>", function() vim.cmd.stopinsert() vim.cmd.noh()
-- vim.lsp.util sets this on the source buffer to the float's winid local float = vim.b.lsp_floating_preview if float and vim.api.nvim_win_is_valid(float) then pcall(vim.api.nvim_win_close, float, true) end
if package.loaded["snacks"] then require("snacks").notifier.hide() end if package.loaded["notify"] then require("notify").dismiss() endend, { silent = true })One constraint keeps the list from growing without bound: <Esc> may dismiss
things and may not change the buffer, so anything it closes can be brought back
by repeating whatever opened it.
Keeping the unnamed register off the clipboard
Section titled “Keeping the unnamed register off the clipboard”Setting clipboard=unnamedplus sends every yank to the system clipboard, and
sends x, dd, c and every other delete-shaped operation there too. Copy
something in a browser, delete a line in Neovim to make room for it, and the
paste is gone.
Neovim leaves 'clipboard' empty by default. What is left is to build explicit
bridges, so that reaching the clipboard on purpose takes one key rather than a
register prefix, and to stop transient edits from overwriting the unnamed
register:
-- Transient edits should not cost you the registervim.keymap.set("n", "s", '"_s') -- substitute char, keep the registervim.keymap.set("v", "p", '"_dP') -- paste over a selection, keep the register
-- Explicit bridges, in both directionsvim.keymap.set({ "n", "v" }, "<Leader>y", '"*y')vim.keymap.set("n", "<Leader>Y", '"*yy')vim.keymap.set("n", "<Leader>p", '"*p')vim.keymap.set("n", "<Leader>P", '"*P')vim.keymap.set("v", "Y", '"*y')vim.keymap.set("v", "P", '"_d"*P') -- clipboard in, both registers intactVisual p shows the difference most directly. Selecting a word and pasting
over it normally moves the replaced text into the unnamed register, so pasting
over a second word inserts the word that was just overwritten.
; opens the command line
Section titled “; opens the command line”: and ; are the same physical key, and by default ; repeats the
last f / t motion. Consider the following remapping:
vim.keymap.set({ "n", "v" }, ";", ":")The trade depends on how much you use f{char} with ; to repeat: if you use
it often, keep ; and put : somewhere else. NoetherVim ships this mapping,
since entering the commandline is one of the most common actions and making so making it as
effortless as possible outweighs the default;. :help noethervim-semicolon gives the one-line revert.
The smaller ones
Section titled “The smaller ones”gC: inverts comments line by line in visual mode. The builtingcoperator picks one direction for the whole range based on the majority state, so a half-commented block becomes fully commented or fully uncommented.gCtoggles each line on its own, leaving a mixed selection with every line in the opposite state.|,_and+: splits shaped like what they make.|splits vertically,_splits horizontally,+opens a tab. They displace “go to screen column”, “down N-1 lines to first non-blank” and “down one line to first non-blank”. All three open an empty scratch buffer rather than a second view of the current one, which is what you want when the split is for something new;<C-w>vand<C-w>sstill give you the second view. Leave the scratch untouched and it disappears with the window; type in it and Neovim will not let you abandon it unsaved.-: highlights every instance of the word under the cursor and reports how many there are, leaving the cursor where it was.*does the highlight but moves to the next match.<c-l>in insert mode in a buffer with writing (latex, markdown, etc.) will auto-fix the last spelling mistake: if you wrotehelllo world |and press<c-l>when at|then it will give youhello world |and bring you back to the same cursor position.[fand]f: previous and next file in the current directory, alphabetically, wrapping at the ends. Useful for numbered or dated files: chapters, notes, migrations.ilandal: the line text objects Vim leaves out.ilruns from the first non-blank character to the last, sodilclears a line’s contents without touching its indentation andcilretypes it in place.alis the whole line up to the break, sodalempties a line and keeps it, whereddwould take the line with it, andyalputs the line in a register as text, ready forpto drop it inside another line instead of below it.zvandzx:zz10<C-e>andzz10<C-y>. Centre the cursor line, then scroll the window ten lines, leaving the cursor on the same buffer line.<C-w>t: a twelve-line terminal along the bottom that toggles rather than stacking a new buffer per press, so the shell from five minutes ago is still there.<Esc><Esc>leaves terminal mode.'timeoutlen'is global and defaults to a full second, which would hold a single<Esc>back for that long before passing it to the program in the terminal, so NoetherVim drops it to 150ms onTermEnterand restores it onTermLeave.- Oil’s yank family: case picks the destination.
yp/yd/ynyank the full path, the parent directory and the bare filename;Yp/Yd/Yndo the same into the system clipboard. ycandyC: nvim-surround’syssandySSunder single letters. Those two are the only mappings in the plugin that double a letter.- Command-line
<C-o>: jumps to the start of the line, prefixes it withRedir, and returns to the end, so:hi<C-o><CR>puts the output in a scratch buffer rather than the pager. The keymap is"<c-b>Redir <c-e>"; the:Redircommand behind it is NoetherVim’s, and covers both:commandsand!shell.<C-l>inserts the current file’s directory, and<C-y>copies the command line itself to the clipboard. - Select mode: letters replace the selection instead of running commands,
<Esc>twice returns to normal, and<C-a>jumps past the end of what was selected. Select mode is where a snippet placeholder leaves you. - J/K: This one is probably the most controversial one on this list, but these bindings have
survived my config from the time they were added a few months into me getting into vim. In normal
mode,
Jmaps toj<c-e>andKmaps tok<c-y>. In normal-mode,<c-e>and<c-y>move the buffer down/up while keeping your cursor position the same, so combining withjandkmoves both the cursor and the buffer.Kis by default the hover key when inspecting code: I remapped it toLwhose default behaviour is to go to the end of the line.
Everything else
Section titled “Everything else”:help noethervim-keymaps is the full list, including the prefix namespaces
and a table of every Vim default the distribution shadows. Inside a running
Neovim, <Space>? shows the same thing read from live state, and <CR> on any
line jumps to where that keymap is defined.