mirror of
https://github.com/neovim/neovim.git
synced 2026-09-03 12:50:36 +00:00
fix(multicursor): atom may cascade in another buffer #41624
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.
This commit is contained in:
@@ -343,6 +343,7 @@ context), multicursor replays actions "semantically" (|CmdAtom|).
|
||||
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: >
|
||||
|
||||
@@ -498,8 +498,7 @@ static char *atom_composite_lhs(void)
|
||||
return keys.items;
|
||||
}
|
||||
|
||||
/// Queues an LHS-replay atom: a mapping that edited invisibly (:normal/:call, "ds'") re-runs
|
||||
/// per cursor from its LHS + payload keys. Cascade only.
|
||||
/// Queues an internal-only atom for mcursor cascade (no emit).
|
||||
void atom_lhs_replay_queue(void)
|
||||
{
|
||||
kv_push(g_atoms, ((CmdAtom){ .type = kAMapping, .keys = atom_composite_lhs(), .remap = true }));
|
||||
@@ -972,7 +971,7 @@ static bool atom_visual_end_suffix(char *suffix, const CmdSpec *spec, bool redoa
|
||||
redo_append_str(suffix, -1);
|
||||
}
|
||||
if (!atom_is_user_cmd() || !(vatom.state & kVatomTyped)) {
|
||||
// Not user input (":normal! vjd", fed keys): the redo prep above is the only effect; no emit.
|
||||
// Internal input (":norm! vjd", fed keys): the redo prep above is the only effect; no emit.
|
||||
xfree(vkeys);
|
||||
xfree(suffix);
|
||||
atom_visual_reset();
|
||||
|
||||
@@ -468,6 +468,13 @@ static void mc_cascade(void)
|
||||
|
||||
// Replay each atom at each cursor (nested ":norm! xx" queues multiple atoms per clock edge).
|
||||
for (size_t ai = 0; ai < kv_size(g_atoms); ai++) {
|
||||
CmdAtom *atom = &kv_A(g_atoms, ai);
|
||||
if (atom->origin.buf.br_buf != NULL
|
||||
&& (!bufref_valid(&atom->origin.buf) || atom->origin.buf.br_buf != curbuf)) {
|
||||
// Cascade only in the atom's origin buffer (a mapping may switch buffers).
|
||||
// Assume untagged atoms (atom_lhs_replay_queue()) are current-buffer.
|
||||
continue;
|
||||
}
|
||||
for (size_t ci = 0; ci < kv_size(mc_cursors); ci++) {
|
||||
// Replays consume `typebuf` only, so check for CTRL-C in OS/RPC input here.
|
||||
line_breakcheck();
|
||||
@@ -499,9 +506,8 @@ void mc_clock_edge(bool map_edit)
|
||||
{
|
||||
if (map_edit && !atom_composite_queued() && kv_size(g_atoms) == 0
|
||||
&& atom_composite_active() && mc_buf_has_cursors(curbuf)) {
|
||||
// A payload mapping (vim-surround "ds'"/"S") edited the buffer via :normal/:call; invisible
|
||||
// to atom capture, so nothing was queued. Fallback to LHS-replay: re-run the mapping at each
|
||||
// cursor by replaying its trigger plus the keys its getchar() read (atom_lhs_replay_queue()).
|
||||
// A payload mapping (vim-surround "ds'"/"S") edited the buffer via :norm/:call, invisible to
|
||||
// atom capture, so nothing was queued. Fallback to LHS-replay.
|
||||
atom_lhs_replay_queue();
|
||||
}
|
||||
if (mc_buf_has_cursors(curbuf) && kv_size(g_atoms) > 0) {
|
||||
|
||||
@@ -930,6 +930,34 @@ describe('multicursor', function()
|
||||
eq({ 'xx', 'yy' }, api.nvim_buf_get_lines(buf_b, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('atom queued in one buffer does not cascade in another buffer', function()
|
||||
command('set hidden')
|
||||
cursors({ 'aaa', 'bbb' }, 'Qj')
|
||||
local buf_a = api.nvim_get_current_buf()
|
||||
command('enew')
|
||||
fn.setline(1, { 'ccc', 'ddd' })
|
||||
feed('gg0Qj')
|
||||
local buf_b = api.nvim_get_current_buf()
|
||||
command('buffer #')
|
||||
command('nnoremap X x:bnext<CR>')
|
||||
feed('2G0X')
|
||||
-- The "x" atom was captured in A but resolves in B. It should not cascade to B's cursors.
|
||||
-- A's cursors are skipped, except the edit done by the primary cursor.
|
||||
eq({ a = { 'aaa', 'bb' }, b = { 'ccc', 'ddd' } }, {
|
||||
a = api.nvim_buf_get_lines(buf_a, 0, -1, true),
|
||||
b = api.nvim_buf_get_lines(buf_b, 0, -1, true),
|
||||
})
|
||||
-- Same-buffer edit still cascades.
|
||||
feed('2G0x')
|
||||
eq({ 'cc', 'dd' }, api.nvim_buf_get_lines(buf_b, 0, -1, true))
|
||||
|
||||
-- TEMPORARY buffer-switch is allowed. A mapping may do work in a throwaway buffer, then apply
|
||||
-- edits to curbuf.
|
||||
command('nnoremap Y :new<CR>ihello<Esc>:bwipe!<CR>x')
|
||||
feed('2G0Y')
|
||||
eq({ 'c', 'd' }, api.nvim_buf_get_lines(buf_b, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('edits cascade from any window showing the buffer (:split)', function()
|
||||
-- Cascade is conditional on the buffer, not the window.
|
||||
cursors({ 'aaa', 'bbb' }, 'Q')
|
||||
|
||||
Reference in New Issue
Block a user