mirror of
https://github.com/neovim/neovim.git
synced 2026-09-03 12:50:36 +00:00
Problem:
An atom queued in buffer A cascades on B's cursors if the mapping ends
in B ("nnoremap X x:bnext<CR>").
Solution:
Check the atom's origin buffer (`CmdAtom.origin.buf`).
Note: This does not preclude mappings etc from doing work in temporary
throwaway buffers, as long as they return to the origin buffer.
460 lines
17 KiB
Plaintext
460 lines
17 KiB
Plaintext
*repeat.txt* Nvim
|
|
|
|
|
|
NVIM REFERENCE MANUAL
|
|
|
|
|
|
Repeating commands *repeating*
|
|
|
|
Chapter 26 of the user manual introduces repeating |usr_26.txt|.
|
|
|
|
Type |gO| to see the table of contents.
|
|
|
|
==============================================================================
|
|
Single repeat *single-repeat*
|
|
|
|
*.*
|
|
. Repeat last change, with count replaced with [count].
|
|
Also repeat a yank command, when the 'y' flag is
|
|
included in 'cpoptions'. Does not repeat a
|
|
command-line command.
|
|
|
|
*@:*
|
|
@: Repeat last command-line [count] times.
|
|
|
|
Simple changes can be repeated with the "." command. Without a count, the
|
|
count of the last change is used. If you enter a count, it will replace the
|
|
last one. |v:count| and |v:count1| will be set.
|
|
|
|
If the last change included a specification of a numbered register, the
|
|
register number will be incremented. See |redo-register| for an example how
|
|
to use this.
|
|
|
|
Repeating an insert |ins-repeat| re-executes the whole session, including
|
|
non-jump cursor-moves: "iab<Left>c<Esc>." produces "acb", not "c". Note that
|
|
jumps (mouse, |i_<PageUp>|, …) split an insert session.
|
|
|
|
Repeating a Visual-mode command re-executes the captured keysequence,
|
|
selection included. See |visual-repeat|.
|
|
|
|
==============================================================================
|
|
Semantic repeat *action-repeat* *cmdatom* *excalibur*
|
|
|
|
Every user action emits a |CmdAtom| event. This avoids the need for plugins to
|
|
"announce" the repeatable unit, thus plugins like vim-repeat aren't needed.
|
|
|
|
`CmdAtom.lhs` is the high-level user input collected during an action,
|
|
including getchar() input. This is signficant: it reflects the semantic
|
|
intent. `CmdAtom.keys` reveals the low-level internal commands that were
|
|
produced.
|
|
|
|
INPUT (`lhs`) RESOLUTION (`keys`) ~
|
|
dw "dw" Not translated.
|
|
zfa{ "zfa{" Not an edit (`changed=false`).
|
|
x "dl" Translated builtin.
|
|
,d "dl" Mapping `:nnoremap ,d x`.
|
|
@q "dl" Macro `@q = "x"`.
|
|
<F6> "dlw" Composite `:nnoremap <F6> xw`:
|
|
`type='mapping'`, subatoms "dl" and "w"
|
|
concat to the composite keys.
|
|
,Dw "dw" Incomplete mapping: ended mid-
|
|
operation (`:nnoremap ,D d`),
|
|
captures its continuation ("w").
|
|
ds) ":call …<NL>)" "Payload" mapping (e.g. vim-surround
|
|
"ds'" reads |getchar()|).
|
|
]q nil Lua mapping.
|
|
V<C-E>d "" Unreplayable: viewport-dependent
|
|
Visual sequence; `lhs` is only
|
|
a hint/label.
|
|
|
|
To repeat an atom, feed `keys` (mode "n"), or `lhs` (mode "m") if `keys=nil`. >lua
|
|
|
|
local function replay(a)
|
|
vim.api.nvim_feedkeys(a.keys or a.lhs, a.keys and 'n' or 'm', false)
|
|
end
|
|
<
|
|
*motion-repeat*
|
|
Example: This "," mapping repeats the last motion ("zj", "3w", "fx",
|
|
"/pat<CR>", "]c", any mapping/command that sets `moved=true`). >lua
|
|
|
|
local last ---@type vim.event.cmdatom.data?
|
|
vim.api.nvim_create_autocmd('CmdAtom', {
|
|
callback = function(ev)
|
|
-- Skip edits, and the "," mapping itself.
|
|
local motion = ev.data.moved or ev.match == 'motion'
|
|
if motion and not (ev.data.changed or ev.data.lhs == ',') then
|
|
last = ev.data
|
|
end
|
|
end,
|
|
})
|
|
vim.keymap.set('n', ',', function()
|
|
-- CmdAtom is deferred; schedule the replay, in case "," follows a motion.
|
|
vim.schedule(function()
|
|
if last then
|
|
vim.api.nvim_feedkeys(last.keys or last.lhs, last.keys and 'n' or 'm', false)
|
|
end
|
|
end)
|
|
end)
|
|
<
|
|
*edit-repeat*
|
|
Example: This "." mapping repeats ANY edit (except undo/redo), including
|
|
operations provided by plugins, without the need for "announcement" via
|
|
vim-repeat or similar. >lua
|
|
|
|
local last ---@type vim.event.cmdatom.data?
|
|
vim.api.nvim_create_autocmd('CmdAtom', {
|
|
callback = function(ev)
|
|
local is_redo_or_undo = ev.data.changed and (ev.data.undoseq or 0) <= (vim.b[ev.buf].maxseq or 0)
|
|
vim.b[ev.buf].maxseq = math.max(vim.b[ev.buf].maxseq or 0, ev.data.undoseq or 0)
|
|
if ev.data.changed and not is_redo_or_undo and ev.data.lhs ~= '.' then
|
|
last = ev.data
|
|
end
|
|
end,
|
|
})
|
|
vim.keymap.set('n', '.', function()
|
|
-- Multicursors: degrade to builtin "." (cascades).
|
|
local mc = vim.api.nvim_create_namespace('nvim.multicursor')
|
|
if #vim.api.nvim_buf_get_extmarks(0, mc, 0, -1, { limit = 1 }) > 0 then
|
|
vim.api.nvim_feedkeys('.', 'n', false)
|
|
return
|
|
end
|
|
-- CmdAtom is deferred; schedule the replay, in case "." follows an edit.
|
|
vim.schedule(function()
|
|
if last then
|
|
vim.api.nvim_feedkeys(last.keys or last.lhs, last.keys and 'n' or 'm', false)
|
|
end
|
|
end)
|
|
end)
|
|
<
|
|
*cmdatom-macro*
|
|
Example: This "[count]<Space>" mapping saves the last [count] atoms as an
|
|
editable macro: "2<Space>" opens the |cmdwin| with the last [count] atoms
|
|
listed (you can delete/edit steps if you want), <Enter> saves it. "<Space>"
|
|
without a count replays the macro. >lua
|
|
|
|
-- Track the last 20 atoms.
|
|
local atom_ring = {} ---@type vim.event.cmdatom.data[]
|
|
vim.api.nvim_create_autocmd('CmdAtom', {
|
|
callback = function(ev)
|
|
-- Skip this mapping itself, and cmdwin edits.
|
|
if ev.data.lhs ~= ' ' and vim.fn.getcmdwintype() == '' then
|
|
atom_ring[#atom_ring + 1] = ev.data
|
|
if #atom_ring > 20 then
|
|
table.remove(atom_ring, 1)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
-- [count]<space> shows a cmdwin where the user can edit/save the last [count] atoms as a "macro".
|
|
-- <space> (no count) replays it.
|
|
vim.keymap.set('n', '<Space>', function()
|
|
local count = vim.v.count
|
|
-- CmdAtom is deferred; schedule it so pending events land in the ring first.
|
|
vim.schedule(function()
|
|
count = math.min(count, #atom_ring)
|
|
if count == 0 then -- Replay the saved macro.
|
|
for _, step in ipairs(vim.g.atom_macro or {}) do
|
|
vim.api.nvim_feedkeys(vim.keycode(step.keys or step.lhs), step.keys and 'n' or 'm', false)
|
|
end
|
|
return
|
|
end
|
|
local parts = {}
|
|
for i = #atom_ring - count + 1, #atom_ring do
|
|
local a = atom_ring[i]
|
|
local keys = a.keys or ('%s%s'):format(a.count or '', a.lhs)
|
|
local field = a.keys and 'keys' or 'lhs'
|
|
parts[#parts + 1] = ('{%s=%q},'):format(field, vim.fn.keytrans(keys))
|
|
end
|
|
local cmd = ('lua vim.g.atom_macro = { %s }'):format(table.concat(parts, ' '))
|
|
-- Draft it on the cmdline; CTRL-F opens the cmdwin to edit it.
|
|
vim.api.nvim_feedkeys((':%s%s'):format(cmd, vim.keycode('<C-f>')), 'n', false)
|
|
end)
|
|
end)
|
|
<
|
|
*restore-undo-cursor*
|
|
By default, |undo| returns the cursor to its original position. If you don't
|
|
like that, you can use this snippet to get Vim's behavior instead: >lua
|
|
|
|
vim.api.nvim_create_autocmd('CmdAtom', {
|
|
callback = function(ev)
|
|
-- Undo/redo: the buffer changed to an already-seen undo state.
|
|
local undid = ev.data.changed and (ev.data.undoseq or 0) <= (vim.b[ev.buf].maxseq or 0)
|
|
vim.b[ev.buf].maxseq = math.max(vim.b[ev.buf].maxseq or 0, ev.data.undoseq or 0)
|
|
if undid then
|
|
vim.cmd('normal! `[') -- Start of the changed text.
|
|
end
|
|
end,
|
|
})
|
|
<
|
|
|
|
==============================================================================
|
|
Multiple repeat *multi-repeat*
|
|
|
|
*:g* *:global* *E148*
|
|
:[range]g[lobal]/{pattern}/[cmd]
|
|
Execute the Ex command [cmd] (default ":p") on the
|
|
lines within [range] where {pattern} matches.
|
|
|
|
:[range]g[lobal]!/{pattern}/[cmd]
|
|
Execute the Ex command [cmd] (default ":p") on the
|
|
lines within [range] where {pattern} does NOT match.
|
|
|
|
*:v* *:vglobal*
|
|
:[range]v[global]/{pattern}/[cmd]
|
|
Same as :g!.
|
|
|
|
Example: >
|
|
:g/^Obsolete/d _
|
|
Using the underscore after `:d` avoids clobbering registers or the clipboard.
|
|
This also makes it faster.
|
|
|
|
Instead of the '/' which surrounds the {pattern}, you can use any other
|
|
single byte character, but not an alphabetic character, '\', '"', '|' or '!'.
|
|
This is useful if you want to include a '/' in the search pattern or
|
|
replacement string.
|
|
|
|
For the definition of a pattern, see |pattern|.
|
|
|
|
NOTE [cmd] may contain a range; see |collapse| and |edit-paragraph-join| for
|
|
examples.
|
|
|
|
The global commands work by first scanning through the [range] lines and
|
|
marking each line where a match occurs (for a multi-line pattern, only the
|
|
start of the match matters).
|
|
In a second scan the [cmd] is executed for each marked line, as if the cursor
|
|
was in that line. For ":v" and ":g!" the command is executed for each not
|
|
marked line. If a line is deleted its mark disappears.
|
|
The default for [range] is the whole buffer (1,$). Use "CTRL-C" to interrupt
|
|
the command. If an error message is given for a line, the command for that
|
|
line is aborted and the global command continues with the next marked or
|
|
unmarked line.
|
|
*E147*
|
|
When the command is used recursively, it only works on one line. Giving a
|
|
range is then not allowed. This is useful to find all lines that match a
|
|
pattern and do not match another pattern: >
|
|
:g/found/v/notfound/{cmd}
|
|
This first finds all lines containing "found", but only executes {cmd} when
|
|
there is no match for "notfound".
|
|
|
|
Any Ex command can be used, see |ex-cmd-index|. To execute a Normal mode
|
|
command, you can use the `:normal` command: >
|
|
:g/pat/normal {commands}
|
|
Make sure that {commands} ends with a whole command, otherwise Vim will wait
|
|
for you to type the rest of the command for each match. The screen will not
|
|
have been updated, so you don't know what you are doing. See |:normal|.
|
|
|
|
The undo/redo command will undo/redo the whole global command at once.
|
|
The previous context mark will only be set once (with "''" you go back to
|
|
where the cursor was before the global command).
|
|
|
|
The global command sets both the last used search pattern and the last used
|
|
substitute pattern (this is vi compatible). This makes it easy to globally
|
|
replace a string: >
|
|
:g/pat/s//PAT/g
|
|
This replaces all occurrences of "pat" with "PAT". The same can be done with: >
|
|
:%s/pat/PAT/g
|
|
Which is two characters shorter!
|
|
|
|
==============================================================================
|
|
Complex repeat *complex-repeat*
|
|
|
|
*q* *recording* *macro*
|
|
q{0-9a-zA-Z"} Record typed characters into register {0-9a-zA-Z"}
|
|
(uppercase to append). The 'q' command is disabled
|
|
while executing a register, and it doesn't work inside
|
|
a mapping and |:normal|.
|
|
|
|
Note: If the register being used for recording is also
|
|
used for |y| and |p| the result is most likely not
|
|
what is expected, because the put will paste the
|
|
recorded macro and the yank will overwrite the
|
|
recorded macro.
|
|
|
|
Note: The recording happens while you type, replaying
|
|
the register happens as if the keys come from a
|
|
mapping. This matters, for example, for undo, which
|
|
only syncs when commands were typed.
|
|
|
|
q Stops recording.
|
|
Implementation note: The 'q' that stops recording is
|
|
not stored in the register, unless it was the result
|
|
of a mapping
|
|
|
|
*@*
|
|
@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} [count]
|
|
times. Note that register '%' (name of the current
|
|
file) and '#' (name of the alternate file) cannot be
|
|
used.
|
|
The register is executed like a mapping, that means
|
|
that the difference between 'wildchar' and 'wildcharm'
|
|
applies, and undo might not be synced in the same way.
|
|
For "@=" you are prompted to enter an expression. The
|
|
result of the expression is then executed.
|
|
See also |@:|.
|
|
|
|
*@@* *E748*
|
|
@@ Repeat the previous @{0-9a-z":*} [count] times.
|
|
|
|
*v_@-default*
|
|
{Visual}@{0-9a-z".=*+} In linewise Visual mode, execute the contents of the
|
|
{Visual}@@ register for each selected line.
|
|
See |visual-repeat|, |default-mappings|.
|
|
|
|
*:@*
|
|
:[addr]@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} as an
|
|
Ex command. First set cursor at line [addr] (default
|
|
is current line). When the last line in the register
|
|
does not have a <CR> it will be added automatically
|
|
when the 'e' flag is present in 'cpoptions'.
|
|
For ":@=" the last used expression is used. The
|
|
result of evaluating the expression is executed as an
|
|
Ex command.
|
|
Mappings are not recognized in these commands.
|
|
When the |line-continuation| character (\) is present
|
|
at the beginning of a line in a linewise register,
|
|
then it is combined with the previous line. This is
|
|
useful for yanking and executing parts of a Vim
|
|
script.
|
|
|
|
*:@:*
|
|
:[addr]@: Repeat last command-line. First set cursor at line
|
|
[addr] (default is current line).
|
|
|
|
:[addr]@ *:@@*
|
|
:[addr]@@ Repeat the previous :@{register}. First set cursor at
|
|
line [addr] (default is current line).
|
|
|
|
==============================================================================
|
|
Multiple cursors *mcursor* *multicursor*
|
|
|
|
You can place extra cursors ("multicursor") in a buffer, to repeat operations
|
|
at each cursor as-you-type. Unlike a |macro| (a stream of keys without any
|
|
context), multicursor replays actions "semantically" (|CmdAtom|).
|
|
|
|
- Normal-mode commands, Mappings, and even Macros (|@|) replay at each cursor.
|
|
- Insert-mode edits appear at each cursor as you type.
|
|
- Motions (|q=|) and Visual sequences replay at each cursor: "viweex"
|
|
re-executes the selection so the extents are per-cursor (each cursor's own
|
|
word, block, …).
|
|
- Registers are cursor-local. Each cursor reads/writes its own registers.
|
|
The yanks are joined (linewise) when the multicursor session ends.
|
|
- Undo is atomic: |u| reverts the aggregate edits from all cursors at once.
|
|
- Folds are ignored during replay. Edits replay within folds ("dd" deletes the
|
|
line at cursor, not the entire folded contents). Motions step into folds
|
|
instead of skipping over them. The primary cursor keeps the usual
|
|
|fold-behavior|.
|
|
- Cursors are per-buffer, and cascade only while their buffer is current.
|
|
- CTRL-C interrupts the cascade.
|
|
|
|
Example: place a cursor on every matching pattern: >
|
|
:g/pattern/normal! nQ
|
|
<
|
|
Example: place a cursor at each |quickfix| item, or a range: >
|
|
:cdo normal! Q
|
|
:2,4cdo normal! Q
|
|
<
|
|
Example: place a cursor at every match, using the API: >lua
|
|
for _, m in ipairs(vim.fn.matchbufline('%', [[pattern]], 1, '$')) do
|
|
vim.api.nvim_mcursor(0, { m.lnum, m.byteidx })
|
|
end
|
|
<
|
|
Plugins can place cursors with |nvim_mcursor()| and inspect user actions via
|
|
the |CmdAtom| event.
|
|
|
|
The cursor positions are tracked as |extmarks| in the "nvim.multicursor"
|
|
namespace. Query them with |nvim_buf_get_extmarks()|; deleting an extmark
|
|
deletes the cursor. You can also use |:marks| to peek at cursors: >
|
|
:marks nvim.multicursor
|
|
<
|
|
Multicursors are presented as |hl-MCursor| highlights, unless your (terminal)
|
|
UI supports the Kitty multiple-cursors protocol.
|
|
|
|
|
|
COMMANDS
|
|
*Q*
|
|
Q Toggles a multicursor at the current cursor position.
|
|
Disables follow-mode |q=|.
|
|
|
|
[count]Q Places a cursor at every match (|gn|) of the last
|
|
search pattern. Example:
|
|
1. Search for something ("*", "/pattern<CR>", …).
|
|
2. Type "1Q".
|
|
|
|
*mcursor-mouse* *<C-LeftMouse>*
|
|
<C-LeftMouse> Toggles a multicursor at the click position, without
|
|
moving the primary cursor (or changing windows). Does
|
|
NOT disable follow-mode |q=|. No-op in Insert-mode.
|
|
|
|
*v_Q*
|
|
{Visual}Q Places a cursor on each line of the Visual selection.
|
|
Enables follow-mode |q=|.
|
|
|
|
*q=*
|
|
q= Toggles follow-mode: motions (not jumps/scrolls) are
|
|
replayed per-cursor. Cursors arriving at the same
|
|
position (e.g. "G") are merged (deduplicated).
|
|
|
|
Use [count] to force the mode instead of toggling:
|
|
"1q=" on, "2q=" off.
|
|
|
|
*mcursor-clear*
|
|
CTRL-L Clears multicursors in the current buffer.
|
|
|CTRL-L-default|
|
|
|
|
Cursors can also be cleared by clearing the namespace: >lua
|
|
local mc_ns = vim.api.nvim_create_namespace('nvim.multicursor')
|
|
vim.api.nvim_buf_clear_namespace(0, mc_ns, 0, -1)
|
|
<
|
|
*gQ*
|
|
gQ Restores the previous multicursors.
|
|
|
|
*g_CTRL-A*
|
|
g CTRL-A During a multicursor session, inserts an ascending
|
|
number ("counter") at each cursor, so a column of
|
|
cursors becomes 1, 2, 3, …. Use [count] to choose the
|
|
initial number.
|
|
|
|
*]C*
|
|
]C Jump to the [count]'th next cursor.
|
|
|
|
*[C*
|
|
[C Jump to the [count]'th previous cursor.
|
|
|
|
|
|
EXAMPLES *mcursor-examples*
|
|
|
|
Example: a "follow-once" mapping: "q-{motion}" replays {motion} at every
|
|
cursor, then disables follow-mode again. >lua
|
|
|
|
vim.keymap.set('n', 'q-', function()
|
|
vim.cmd('normal! 1q=')
|
|
vim.api.nvim_create_autocmd('CmdAtom', {
|
|
callback = function(ev)
|
|
if ev.data.lhs == 'q-' then
|
|
return -- Skip the mapping itself.
|
|
end
|
|
vim.cmd('normal! 2q=')
|
|
return true -- Delete the handler.
|
|
end,
|
|
})
|
|
end)
|
|
<
|
|
|
|
LIMITATIONS *mcursor-limitations*
|
|
|
|
- Extra cursors on the same line can be lost or misplaced by an edit that
|
|
shifts columns (e.g. inserting text): the cursors shift each other.
|
|
- Undo restores the buffer, not (per-cursor) registers.
|
|
- Time-travel undo (|g-|, |g+|, |:earlier|) clears all cursors.
|
|
- Reloading/unloading a buffer (|:edit!|, 'autoread') clears its cursors
|
|
(extmarks limitation).
|
|
- An operator replayed at a cursor inside a closed fold applies to the whole
|
|
fold, like any operator (|fold-behavior|).
|
|
- |Q| is not allowed while recording or executing a macro.
|
|
- An 'operatorfunc' that caches its |getchar()| input instead of re-reading it
|
|
misbehaves at the extra cursors: the unconsumed input runs as a normal-mode
|
|
key.
|
|
|
|
|
|
vim:tw=78:ts=8:noet:ft=help:norl:
|