Files
neovim/runtime/doc/repeat.txt
Justin M. Keyes 6423657352 feat(cmdatom)!: eliminate the need for vim-repeat #41414
Problem:
- `lhs` is not fully realized. E.g. for a "payload" mapping
  `lhs` omits the `getchar()` payload during a mapping (vim-surround
  `ds'` reports `lhs="ds"`). This means plugins like vim-repeat are
  still needed...
- The "delta" fields of a CmdAtom are calculated too late.
  - `<abuf>` and `changed` check whatever (wrong) buffer a command
    (":bnext") might land in.
  - CTRL-W_w between two windows on the same buffer reports type="motion".

Solution:
- `CmdOrigin` samples (buf/win/cursor/changedtick) at each "scope" entry
  (CmdFrame, composite, Visual session, insert session).
- `dd<C-w>l` reports `changed=true` for the buffer it edited, regardless
  of where the cursor ends up.
- New fields:
  - `pos`: cursor position at command start.
  - `moved`: indicates whether the cursor moved (in same buffer).
  - `undoseq`: undo state at settlement.
- lhs now includes the payload: "ds)" reports lhs="ds)" instead of "ds".
  - Easy for users to "replay" any atom.
- Rename: type "command" => "normal", "ex" => "excmd"; `arg` => `cmdarg`
- Drop `cascade` field (no reason to expose it)
2026-08-21 13:42:17 -04:00

288 lines
10 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 *cmdatom* *action-repeat*
The |CmdAtom| event is published on every user action. This avoids the need
for plugins to "announce" the repeatable unit, thus plugins like vim-repeat
aren't needed.
*excalibur*
`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.
ds) ":call …<CR>" "Payload" mapping (reads |getchar()|
e.g. vim-surround "ds'"), `remap=true`
]q "" Lua mapping, `remap=true`
,D "" Incomplete mapping (ended mid-
operation) `:nnoremap ,D d`,
`pending='operator'`, the NEXT atom
("dw") completes it
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"). Empty `keys`
without `remap` means the atom is unreplayable. >lua
local function replay(a)
if not a.remap and a.keys == '' then
return -- Unreplayable Visual op.
end
vim.api.nvim_feedkeys(a.remap and a.lhs or a.keys, a.remap and 'm' or 'n', false)
end
<
*motion-repeat*
Example: This "," mapping repeats the last "motion" ("zj", "3w", "fx",
"/pat<CR>", "]c") by checking `(moved and not changed)`. Drop the
`not ev.data.moved` condition to repeat any non-edit ("zz", "zfa{", CTRL-W_w): >lua
local last ---@type vim.event.cmdatom.data?
vim.api.nvim_create_autocmd('CmdAtom', {
callback = function(ev)
if not ev.data.moved or ev.data.changed or ev.data.lhs == ',' then
return -- Skip non-motions, edits, and this mapping itself.
end
last = ev.data
end,
})
vim.keymap.set('n', ',', function()
-- CmdAtom is deferred: schedule the replay, in case "," follows a motion.
vim.schedule(function()
if last then
replay(last)
end
end)
end)
<
*restore-undo-cursor*
Example: Restore cursor position after undo. Works for |u|, "3u", |CTRL-R|,
":undo N", |g-| and any mapping: >lua
local seen = {} ---@type table<integer, table>
vim.api.nvim_create_autocmd('CmdAtom', {
callback = function(ev)
local seq = ev.data.undoseq
if not seq then
return
end
local s = seen[ev.buf] or {}
seen[ev.buf] = s
-- Note: g- :earlier may cross undo-tree branches, "best effort" in that case.
if s.prev and seq < s.prev and s[seq + 1] then
vim.api.nvim_win_set_cursor(0, s[seq + 1]) -- Undo: first abandoned state.
elseif s.prev and seq > s.prev and s[seq] then
vim.api.nvim_win_set_cursor(0, s[seq]) -- Redo: revisiting a known seq.
elseif ev.data.changed and not s[seq] then
s[seq] = ev.data.pos -- New edit.
end
s.prev = seq
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|.
*Q*
Q Repeat the last recorded register [count] times.
See |reg_recorded()|.
*v_Q-default*
{Visual}Q In linewise Visual mode, repeat the last recorded
register for each selected line.
See |visual-repeat|, |default-mappings|.
*v_Q*
{Visual}Q Place a cursor on each line of the Visual selection.
*:@*
:[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*
todo
vim:tw=78:ts=8:noet:ft=help:norl: