mirror of
https://github.com/neovim/neovim.git
synced 2026-09-02 12:23:50 +00:00
Problem: Undo places the cursor wherever the cursor happened to sit at "save time" (`uh_cursor` is sampled lazily on the first change). Examples: - `i` preserves, but `a` does not - `diw`, `atest<Esc>`, `d^` abandon the original position - `D`, `o` restore it (by accident). Solution: `composite` tracks the pending atom (and its `origin`) across frames. A `stuffed` continuation frame inherits the `origin` + prepped redo. Store `origin` info in the undo header, so undo can restore it. - Not for a mid-command undo break (i_CTRL-G_u). - Undoing a mapping restores where the mapping started (which technically may be different than where the "edit" started).
341 lines
13 KiB
Plaintext
341 lines
13 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*
|
|
|
|
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.
|
|
|
|
`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 = vim.fn.undotree(ev.buf).seq_last
|
|
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()
|
|
-- 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 = vim.fn.undotree(ev.buf).seq_last
|
|
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|.
|
|
|
|
*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:
|