mirror of
https://github.com/neovim/neovim.git
synced 2026-11-02 20:30:40 -06:00
feat(input)!: CmdAtom event #41297
Problem:
There is no unified notion of a "user action".
Vim processes input by one-char-at-a-time, and mostly throws away any
hints it might gather about the user's action, with one exception: it
stores the last _edit_ action (the "redo buffer", encoded as
unstructured `["x][v][count]body` bytes).
Plugins can only observe individual keys (vim.on_key) and high-level
effects (TextChanged, CursorMoved).
Solution:
- Users can subscribe to `CmdAtom` events to handle any user action.
- Event is deferred; handlers cannot cancel or interfere with user
actions.
- Capture `CmdSpec` from the normal/insert/visual subsystems.
- typeahead/readahead stay unstructured (`buffheader_T`): they are key
streams, not commands.
- the redo/record buffers become `StringBuilder`: fewer
allocations/copies.
- Repurpose the input/redo engine to accept `CmdSpec` objects.
"atom": one repeatable unit of user input, as a resolved (post-mapping)
keysequence plus structured fields. Only user actions, not `:normal`,
API calls, or non-"t" `feedkeys`.
BREAKING: dot-repeat of an Insert session, replays the entire session
including cursor-moves (:help ins-repeat).
BREAKING: dot-repeat of a Visual operation, replays the selection
instead of operating on a fixed-size region.
This commit is contained in:
1 parent
485ae7e31a
commit
64a301184e
63 files changed
+3577
-909
No files matched your search
@@ -419,6 +419,85 @@ ChanOpen Just after a channel was opened.
|
||||
Sets these |v:event| keys:
|
||||
info as from |nvim_get_chan_info()|
|
||||
|
||||
*CmdAtom*
|
||||
CmdAtom After a user action (an input "atom"): any
|
||||
motion, operator, insert session, Visual-mode
|
||||
keysequence, Ex cmdline (":cnext<CR>"),
|
||||
mapping (and its sub-atoms), or scroll/mouse.
|
||||
|
||||
Only for user input, not programmatic input:
|
||||
INPUT ATOM ~
|
||||
typed keys yes
|
||||
|nvim_input()| yes
|
||||
|nvim_feedkeys()| with "t" (see note)
|
||||
dot-repeat |.| yes
|
||||
mapping, typed "@q" yes
|
||||
|:normal| no
|
||||
API requests no, lol
|
||||
"@q" fed by a script no
|
||||
"multicursor" replays no
|
||||
aborted operation no
|
||||
terminal-mode keys no
|
||||
mouse drag/release no
|
||||
Hydrogen yes
|
||||
|
||||
Note: keys fed from a typed mapping's own
|
||||
execution (e.g. its Lua callback) count as
|
||||
the mapping's expansion and fold into its
|
||||
atom, "t" or not.
|
||||
Fired at the next event-loop tick, not
|
||||
synchronously.
|
||||
<amatch> (the pattern) is the atom type.
|
||||
|
||||
The |event-data| has these fields; a field
|
||||
that does not apply is omitted (type, keys,
|
||||
changed, and cascade are always present):
|
||||
- arg: Typed operand of `cmd`: the "x" of "fx",
|
||||
the replacement char of |r|.
|
||||
|key-notation|, like `cmd`.
|
||||
- atoms: The constituent atoms of a composite
|
||||
(multi-command mapping, Visual sequence),
|
||||
in-order. A consumer of e.g. "motion" atoms
|
||||
may want to inspect these "children".
|
||||
- cascade: Queued for "multicursor" replay.
|
||||
- changed: Buffer was changed.
|
||||
- cmd: Command/motion/object name: "w", "fx" is
|
||||
"f", "iw", "gJ", "<Home>", …. |key-notation|
|
||||
(not raw bytes).
|
||||
- count: Effective |count|; omitted if none.
|
||||
- keys: Resolved (post-mapping) keysequence,
|
||||
raw internal bytes (not |key-notation|):
|
||||
feed directly to |feedkeys()| or
|
||||
|nvim_feedkeys()| (mode "n") to replay. Use
|
||||
|keytrans()| to key-notation. Empty for
|
||||
a mapping whose commands have no replayable
|
||||
keys (|<Cmd>|/Lua commands).
|
||||
- lhs: LHS (user input). Raw bytes, like `keys`.
|
||||
- motionforce |forced-motion|: "v", "V", or
|
||||
"<C-V>" (|key-notation|).
|
||||
- operator: Operator: "d", "g@", "zf", ….
|
||||
|key-notation|, like `cmd`.
|
||||
- pending: What is awaiting input after
|
||||
a mapping: "operator", "visual".
|
||||
- reg: Register name.
|
||||
- text: Payload text: inserted text of an
|
||||
insert session (after its last cursor-move,
|
||||
like |quote.|), Ex cmdline, or search.
|
||||
Literal text, not key encoding. Examples:
|
||||
- typed "iab<Esc>" → text="ab"
|
||||
- typed ":cnext<CR>" → text="cnext"
|
||||
- typed "iab<Left>c<Esc>" → text="c"
|
||||
- type: "command", "ex", "insert", "jump",
|
||||
"mapping", "motion", "mouse", "operator",
|
||||
"scroll", "visual".
|
||||
- "command": neither edits nor moves the
|
||||
cursor (undo, folds, CTRL-W commands, …).
|
||||
- "jump": moves the cursor via
|
||||
absolute/shared navigation state
|
||||
(|jumplist|, marks, |star|).
|
||||
- "mouse"/"scroll" atoms are emit-only:
|
||||
never cascaded, not replayable.
|
||||
|
||||
*CmdlineChanged*
|
||||
CmdlineChanged After EVERY change inside command line. Also
|
||||
triggered during mappings! Use |<Cmd>| instead
|
||||
|
||||
@@ -169,6 +169,39 @@ Other references:
|
||||
- https://github.com/neovim/neovim/pull/18375
|
||||
- https://github.com/neovim/neovim/pull/21605
|
||||
|
||||
==============================================================================
|
||||
State *dev-state*
|
||||
|
||||
"Editor state" is represented by these (sometimes overlapping) concepts:
|
||||
|
||||
- shada (`src/nvim/shada.c`): user data: registers, marks, …. Msgpack format.
|
||||
- sessions/views (`src/nvim/ex_session.c`): windows/layout, per-window
|
||||
cursor/options, CWD, …. Vimscript format: commands, not merged data.
|
||||
Overlaps shada for the buffer list and global variables.
|
||||
- context (`src/nvim/context.c`): |nvim_get_context()|.
|
||||
Currently interfaces shada and other state. Needs more thought.
|
||||
- scoped execution ("temporary state"): `ctx_switch()`, `vim._with()`,
|
||||
|nvim_win_call()|, |nvim_buf_call()|, `cmdmod_T`. Temporary switches, not
|
||||
snapshots. The C mechanisms overlap with each other; `vim._with()` is the
|
||||
unified interface.
|
||||
- reentrancy protection: save/restore around nested execution. `InsState`,
|
||||
`RedoState`.
|
||||
- TODO: ad hoc pairs remain for typeahead (`tasave_T`), |v:event|, view
|
||||
state, cmdline, with inconsistent nesting semantics (stack vs refcount vs
|
||||
single-shot).
|
||||
|
||||
LONG-TERM VISION(?): shada is the authority on all user-data state, sessions
|
||||
on layout state? New state kinds extend the shada schema instead of adding
|
||||
a format or an in-memory sidecar. Eliminate context.c?
|
||||
|
||||
LUA FUNCTIONS ACROSS PROCESSES
|
||||
|
||||
The test harness transfers Lua closures between Nvim instances
|
||||
(`test/functional/testnvim/exec_lua.lua`). This works ONLY between same-build
|
||||
instances (LuaJIT bytecode not portable across versions/builds) and only for
|
||||
trusted peers. Upvalues are limited to msgpack-able values (not
|
||||
function/userdata; tables lose metatables and identity).
|
||||
|
||||
==============================================================================
|
||||
Filesystem *dev-filesystem*
|
||||
|
||||
@@ -502,6 +535,109 @@ When doing an `i` or `a` command, `normal_cmd()` will call the `edit()` function
|
||||
It contains a loop that waits for the next character and handles it. It
|
||||
returns when leaving Insert mode.
|
||||
|
||||
==============================================================================
|
||||
Multicursor *dev-multicursor* *dev-cmdatom*
|
||||
|
||||
Multicursor is essentially a bunch of extmarks that "cascade" the user action
|
||||
(|CmdAtom|) driven by the primary cursor.
|
||||
|
||||
An "atom" is any user action, as a resolved ("elemental", post-mapping)
|
||||
keysequence, the same material ("redobuf") replayed by dot-repeat
|
||||
(|single-repeat|). Whereas a macro is a series of meaningless characters
|
||||
whose meaning is context-dependent, assigned at the moment of execution, an
|
||||
atom scopes an input sequence to one, semantic, user action. Examples: "x" is
|
||||
captured as "dl", an insert session as `ciwfoo<Esc>`, a Visual-mode edit as
|
||||
`viweed`.
|
||||
|
||||
CONCEPTS
|
||||
|
||||
- ATOM: A repeatable unit of user input: the resolved keysequence, plus
|
||||
structured fields (`CmdSpec`). Every user action emits a |CmdAtom| event.
|
||||
- SPAN: Fragment of an Insert or Visual sequence, cascades but does not emit
|
||||
a CmdAtom.
|
||||
- COMPOSITE: An atom composed of subatoms (`CmdAtom.atoms`): a mapping/macro's
|
||||
commands, or a Visual sequence.
|
||||
- INSERT-SESSION: All changes from an insert-mode session, until <Esc>.
|
||||
- INSERTION: An `Ins.start`..cursor "undo unit" within a session.
|
||||
- INSERT-CASCADE: Replay an insert-session span-by-span as it is typed (each
|
||||
span is packaged as a quasi-session: `i` + keys + `<Esc>`), via nested
|
||||
`edit()`. Pending literal text is a PREVIEW until its span completes.
|
||||
- REPLAY: Execute keys (dot-repeat, or atom).
|
||||
- CASCADE: Replay queued atoms at the "clock edge" at every cursor. Visual
|
||||
cascade "dry-runs" spans to display the selection.
|
||||
- VOID: When a pending (Visual) atom's keys were tainted/poisoned (by: mouse,
|
||||
`gv`, a scroll that drags the cursor, …), thus not replayable.
|
||||
|
||||
IMPLEMENTATION
|
||||
|
||||
Capture happens at the `atom_xx()` hooks.
|
||||
- `prep_redo()` marks a redoable command (operators, `r`, `J`, `p`, …). Then
|
||||
`atom_cmd_end()` takes the final redobuff as the atom, (including
|
||||
payloads, e.g. `d/pat<CR>`). Prep-exempt commands (yank, `D`, folds) are
|
||||
reconstructed instead, in `atom_capture_op()`.
|
||||
- `atom_cmd_start()` delimits a command in `normal_execute()`. Everything not
|
||||
covered above is decided here (motions, jumps, scrolls, mouse).
|
||||
- `atom_ins_start()` delimits an insert session.
|
||||
- `atom_map_start()`, `atom_macro_start()` delimits a composite.
|
||||
- `atom_visual_end()` completes the pending Visual atom, `viweex` => `viweed`.
|
||||
<Esc> discards it; non-replayable commands VOID it.
|
||||
- `atom_cmdline_set()` gets an invoked cmdline payload (e.g. `/pat<CR>` is
|
||||
a motion atom, `:cnext<CR>` is an "ex" atom.)
|
||||
|
||||
DOT-REPEAT
|
||||
|
||||
The redo buffer itself is the pending change atom (`RedoBuf`); the register,
|
||||
Visual flag and count are FIELDS, and the command body is bytes appended
|
||||
during execution by the `redo_append_xx()` family (Vim's `AppendToRedobuff*`).
|
||||
The old `["x][v][count]body` byte encoding no longer exists; `prep_redo()`
|
||||
stores the prefix structurally, and consumers recompose it at the boundaries
|
||||
(`redo_keys()`, `start_redo()`, `atom_from_redo()`).
|
||||
|
||||
THE CASCADE
|
||||
|
||||
Cursors are `Context` entries in `mc_cursors`, tracked as extmarks. The
|
||||
extmark is authoritative: it follows edits, and user code may delete cursors
|
||||
by deleting their extmarks. Cursor-local state is swapped in on replay, so
|
||||
e.g. each cursor reads/writes its own registers.
|
||||
|
||||
CmdAtoms queued for cascade (`g_atoms`) are replayed at every cursor on the
|
||||
"clock edge": the completion of a toplevel `normal_execute()`. While
|
||||
a mapping is executing (its keys are still in typebuf) the edge is deferred
|
||||
(subatoms queue/accumulate).
|
||||
|
||||
Undo is buffer-global and applied in bulk. Each cascade adds exactly one undo
|
||||
state, and undo/redo is itself never cascaded (`u_doit()` calls `atom_op_global_set()`).
|
||||
|
||||
The insert-cascade PREVIEW/COMMIT model (why text is previewed instead of
|
||||
replayed) is documented at `mc_ins_span`. Sessions that cannot insert-cascade
|
||||
(count `3iZ`, Replace mode, blockwise `CTRL-V c`) are captured at <Esc>.
|
||||
- See `CmdAtomType` and `atom_key_class()` to understand how input is
|
||||
classified to decide whether and how it can be replayed.
|
||||
|
||||
RENDERING
|
||||
|
||||
Display is owned by `runtime/lua/vim/_core/mcursor.lua`. the C core maintains
|
||||
the model, as extmarks:
|
||||
|
||||
- "nvim.multicursor": the tracking marks (cursor positions).
|
||||
- "nvim.multicursor.cursor": selection-END display cursors, during Visual mode
|
||||
(each cursor displays at its own selection end). Consumers (UIs) that
|
||||
display cursors use these positions
|
||||
while any exist, instead of the tracking marks.
|
||||
- "nvim.multicursor.visual": the pending selection ranges.
|
||||
|
||||
GUI SUPPORT
|
||||
|
||||
A GUI gets a working display for free, via cell highlights. If it wants to
|
||||
draw "real" cursors, it is expected to:
|
||||
- receive positions via the `win_extmark` UI event: the tracking marks are
|
||||
ui_watched, so visible cursor positions are pushed per-redraw. (Known
|
||||
issue: win_extmark does not give a removal signal; re-query when in doubt.)
|
||||
- or query the namespaces above with |nvim_buf_get_extmarks()|; prefer the
|
||||
"nvim.multicursor.cursor" positions while any exist (in-progress Visual
|
||||
selection).
|
||||
- render selections from "nvim.multicursor.visual", or simply keep their
|
||||
"hl-MultiCursorVisual" grid highlights.
|
||||
|
||||
==============================================================================
|
||||
|
||||
|
||||
@@ -343,9 +343,13 @@ out of Insert mode. This is very handy if you prefer to use the Insert mode
|
||||
all the time, just like editors that don't have a separate Normal mode. You
|
||||
can use CTRL-O if you want to map a function key to a command.
|
||||
|
||||
*ins-repeat*
|
||||
The changes (inserted or deleted characters) before and after these keys can
|
||||
be undone separately. Only the last change can be redone and always behaves
|
||||
like an "i" command.
|
||||
be undone separately. Relative cursor-moves (the cursor keys, <S-Left>,
|
||||
<Home>, CTRL-G j, …) are part of the insert: |.| repeats the whole insert,
|
||||
including cursor-moves. After a jump (mouse, scroll, <PageUp>, …) only the
|
||||
last change can be redone, and it behaves like an "i" command. The |quote.|
|
||||
register always holds just the text typed after the last cursor-move.
|
||||
|
||||
char action ~
|
||||
-----------------------------------------------------------------------
|
||||
@@ -445,6 +449,8 @@ will be repeatable by using |.| to the expected
|
||||
|
||||
Lorem ipsum (dolor)
|
||||
|
||||
Cursor keys are captured by |.| (|ins-repeat|), so these mappings are only
|
||||
needed to keep the cursor-move from starting a new undo block.
|
||||
|
||||
Using CTRL-O splits undo: the text typed before and after it is undone
|
||||
separately. If you want to avoid this (e.g., in a mapping) you might be able
|
||||
|
||||
@@ -122,6 +122,20 @@ EDITOR
|
||||
cmdwin via |c_CTRL-F|.
|
||||
• Behavior of |:restart| changed. Use "!" (|:restart!|) to get the old behavior.
|
||||
• |ZR| now performs |:restart|. Add a count to change the behavior.
|
||||
• |ins-repeat| replays the full Insert session, including cursor-moves.
|
||||
Undo and |quote.| still restart at each cursor-move. Use |i_CTRL-O| to get
|
||||
the old behavior: >vim
|
||||
inoremap <Left> <C-o><Left>
|
||||
inoremap <Right> <C-o><Right>
|
||||
• |visual-repeat| is "semantic" instead of "fixed-size". See
|
||||
|visual-fixed-size| if you prefer Vim's "fixed-size" Visual-repeat.
|
||||
• |Q| adds a |multicursor| instead of replaying the last recorded register.
|
||||
To get the old behavior: >lua
|
||||
vim.keymap.set('n', 'Q', function()
|
||||
local reg = vim.fn.reg_recorded()
|
||||
return reg == '' and '' or ('@' .. reg)
|
||||
end, { expr = true })
|
||||
<
|
||||
|
||||
EVENTS
|
||||
|
||||
@@ -297,6 +311,8 @@ EVENTS
|
||||
• |:delmarks| now triggers the |MarkSet| autocommand with line==col==0, same
|
||||
as |nvim_buf_del_mark()|
|
||||
• |ChanClose| is triggered after a channel is closed, before it is removed.
|
||||
• |CmdAtom| fires after each atomic user action (motion, mapping, operator,
|
||||
insert session, visual, Ex command, …).
|
||||
• |SessionWritePre| event emits just before |:mksession|.
|
||||
• |TextPutPre| and |TextPutPost| are triggered before/after putting text.
|
||||
• |TabMoved| is triggered when tabs are reordered.
|
||||
|
||||
@@ -2570,6 +2570,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
|ChanClose|,
|
||||
|ChanInfo|,
|
||||
|ChanOpen|,
|
||||
|CmdAtom|,
|
||||
|CmdUndefined|,
|
||||
|CmdlineChanged|,
|
||||
|CmdlineEnter|,
|
||||
|
||||
+38
-5
@@ -1,7 +1,7 @@
|
||||
*repeat.txt* Nvim
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
NVIM REFERENCE MANUAL
|
||||
|
||||
|
||||
Repeating commands, Vim scripts and debugging *repeating*
|
||||
@@ -19,6 +19,9 @@ Single repeats *single-repeat*
|
||||
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.
|
||||
@@ -27,12 +30,34 @@ 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.
|
||||
|
||||
Note that when repeating a command that used a Visual selection, the same SIZE
|
||||
of area is used, see |visual-repeat|.
|
||||
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.
|
||||
|
||||
*@:*
|
||||
@: Repeat last command-line [count] times.
|
||||
Repeating a Visual-mode command re-executes the captured keysequence,
|
||||
selection included. See |visual-repeat|.
|
||||
|
||||
*motion-repeat*
|
||||
Example: a "," mapping that repeats the last motion, like |;| but for any
|
||||
motion ("3w", "fx", "/pat<CR>"): remember the last "motion" |CmdAtom| and
|
||||
replay its keys: >lua
|
||||
local last ---@type string?
|
||||
vim.api.nvim_create_autocmd('CmdAtom', {
|
||||
pattern = 'motion',
|
||||
callback = function(ev)
|
||||
last = ev.data.keys
|
||||
end,
|
||||
})
|
||||
vim.keymap.set('n', ',', function()
|
||||
-- CmdAtom is deferred: schedule the replay after any pending event,
|
||||
-- so `last` is fresh when "," follows a motion.
|
||||
vim.schedule(function()
|
||||
if last then
|
||||
vim.api.nvim_feedkeys(last, 'n', false) -- "n": already resolved
|
||||
end
|
||||
end)
|
||||
end)
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
Multiple repeats *multi-repeat*
|
||||
@@ -156,6 +181,9 @@ Q Repeat the last recorded register [count] times.
|
||||
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
|
||||
@@ -180,6 +208,11 @@ Q Repeat the last recorded register [count] times.
|
||||
:[addr]@@ Repeat the previous :@{register}. First set cursor at
|
||||
line [addr] (default is current line).
|
||||
|
||||
==============================================================================
|
||||
Multiple cursors *mcursor* *multicursor*
|
||||
|
||||
todo
|
||||
|
||||
==============================================================================
|
||||
Using Vim scripts *using-scripts*
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@ you never want any default mappings, call |:mapclear| early in your config.
|
||||
- <C-L> |CTRL-L-default|
|
||||
- & |&-default|
|
||||
- - |dir-mappings|
|
||||
- Q |v_Q-default|
|
||||
- @ |v_@-default|
|
||||
- # |v_#-default|
|
||||
- * |v_star-default|
|
||||
@@ -232,6 +231,7 @@ New Features *nvim-features*
|
||||
MAJOR COMPONENTS
|
||||
|
||||
- API |API|
|
||||
- Command atoms |CmdAtom|
|
||||
- Job control |job-control|
|
||||
- LSP framework |lsp|
|
||||
- Lua scripting |lua| |-l|
|
||||
@@ -335,6 +335,10 @@ Commands:
|
||||
- |:uptime|
|
||||
|
||||
Editor:
|
||||
- |ins-repeat| replays the full Insert session, including cursor-moves:
|
||||
"iab<Left>c<Esc>." produces "acb", not "c".
|
||||
- |visual-repeat| is "semantic" instead of "fixed-size". If you prefer Vim's
|
||||
"fixed-size" Visual-repeat, see |visual-fixed-size|.
|
||||
- Interactive |Ex-mode| is implemented as persistent, insert-mode |cmdwin|.
|
||||
- |prompt-buffer| supports multiline input/paste, undo/redo, and o/O normal
|
||||
commands.
|
||||
@@ -402,7 +406,9 @@ Input/Mappings:
|
||||
|
||||
Normal commands:
|
||||
- |gO| shows a filetype-defined "outline" of the current buffer.
|
||||
- |Q| replays the last recorded macro instead of switching to Ex mode.
|
||||
- |gQ| restores |multicursor|s.
|
||||
- |Q| adds a |multicursor| ([count]Q: at each match; {Visual}Q: at each
|
||||
selected line |v_Q|) instead of switching to Ex mode.
|
||||
- [count]q: enters |Ex-mode|.
|
||||
- |ZR| performs |:restart|
|
||||
- |v_al| selects the whole buffer; |v_il| selects the current line without
|
||||
|
||||
+33
-11
@@ -359,17 +359,39 @@ See |v_b_r_example|.
|
||||
==============================================================================
|
||||
6. Repeating *visual-repeat*
|
||||
|
||||
When repeating a Visual mode operator, the operator will be applied to the
|
||||
same amount of text as the last time:
|
||||
- Linewise Visual mode: The same number of lines.
|
||||
- Blockwise Visual mode: The same number of lines and columns.
|
||||
- Normal Visual mode within one line: The same number of characters.
|
||||
- Normal Visual mode with several lines: The same number of lines, in the
|
||||
last line the same number of characters as in the last line the last time.
|
||||
The start of the text is the Cursor position. If the "$" command was used as
|
||||
one of the last commands to extend the highlighted text, the repeating will
|
||||
be applied up to the rightmost column of the longest line. Any count passed
|
||||
to the `.` command is not used.
|
||||
Repeating a Visual operation works "semantically": |.| replays the captured
|
||||
keysequence (Visual selection plus operator) at the cursor: "viwd" then "."
|
||||
deletes the word at the cursor, whatever its size. (Note: this is unlike Vim,
|
||||
where Visual-repeat works on the same fixed-size region. If you prefer that
|
||||
behavior, see |visual-fixed-size|.)
|
||||
|
||||
A [count] given to "." is prepended to the replayed keys. If the selection was
|
||||
extended by a command that cannot be replayed (mouse, search, Ex command,
|
||||
scroll), "." instead applies the operator to a same-size area, as if "1v"
|
||||
(|v|) preceded it.
|
||||
|
||||
*visual-fixed-size*
|
||||
Example: To mimic Vim's fixed-size Visual dot-repeat: after a Visual
|
||||
operation, reselect the fixed-size region ("1v", |v|) and applies the
|
||||
operator: >lua
|
||||
|
||||
local vop ---@type string?
|
||||
vim.api.nvim_create_autocmd('CmdAtom', {
|
||||
---@param ev {data: vim.event.cmdatom.data}
|
||||
callback = function(ev)
|
||||
if ev.data.type == 'visual' then
|
||||
vop = ev.data.atoms and ev.data.atoms[#ev.data.atoms].keys or nil
|
||||
elseif ev.data.changed then
|
||||
vop = nil -- the last change is no longer the Visual one
|
||||
end
|
||||
end,
|
||||
})
|
||||
vim.keymap.set('n', '.', function()
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_feedkeys(vop and ('1v' .. vop) or '.', 'n', false)
|
||||
end)
|
||||
end)
|
||||
<
|
||||
|
||||
Visual mode |default-mappings| "@" and "Q" repeat a register for all selected
|
||||
lines if the selection is linewise. See |v_@-default| and |v_Q-default| for
|
||||
|
||||
+1
@@ -105,6 +105,7 @@ error('Cannot require a meta file')
|
||||
--- |'ChanClose'
|
||||
--- |'ChanInfo'
|
||||
--- |'ChanOpen'
|
||||
--- |'CmdAtom'
|
||||
--- |'CmdUndefined'
|
||||
--- |'CmdlineChanged'
|
||||
--- |'CmdlineEnter'
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
-- See also `vim.api.keyset.events` in `api_keysets.gen.lua`.
|
||||
error('Cannot require a meta file')
|
||||
|
||||
--- Data for the CmdAtom event.
|
||||
--- @class vim.event.cmdatom.data
|
||||
--- @field arg? string Typed operand of `cmd` ("fx" => "x").
|
||||
--- @field atoms? vim.event.cmdatom.data[] Subatoms of a composite (mapping, Visual sequence).
|
||||
--- @field cascade boolean Queued for multicursor replay.
|
||||
--- @field changed boolean Changed the buffer.
|
||||
--- @field cmd? string Command/motion/object name ("w", "f", "iw", "gJ").
|
||||
--- @field count? integer Effective count.
|
||||
--- @field keys string Resolved keysequence, raw bytes: feed to nvim_feedkeys() to replay.
|
||||
--- @field lhs? string Mapping LHS or macro register ("gj", "@q"). Raw bytes, like `keys`.
|
||||
--- @field motionforce? 'v'|'V'|'<C-V>' forced-motion type.
|
||||
--- @field operator? string Operator name ("d", "g~", "g@"). key-notation.
|
||||
--- @field pending? 'operator'|'visual' Mapping ended mid-operation.
|
||||
--- @field reg? string Register name.
|
||||
--- @field text? string Inserted text, or the Ex/search cmdline.
|
||||
--- @field type 'command'|'ex'|'insert'|'jump'|'mapping'|'motion'|'mouse'|'operator'|'scroll'|'visual'
|
||||
|
||||
--- @class vim.event.lspattach.data
|
||||
--- @field client_id integer
|
||||
|
||||
|
||||
Generated
+1
@@ -2193,6 +2193,7 @@ vim.go.ei = vim.go.eventignore
|
||||
--- `ChanClose`,
|
||||
--- `ChanInfo`,
|
||||
--- `ChanOpen`,
|
||||
--- `CmdAtom`,
|
||||
--- `CmdUndefined`,
|
||||
--- `CmdlineChanged`,
|
||||
--- `CmdlineEnter`,
|
||||
|
||||
Reference in new issue
Block a user