mirror of
https://github.com/neovim/neovim.git
synced 2026-09-03 21:00:34 +00:00
fix(multicursor): abandoned Visual selection does not move cursors #41670
Problem:
"v{motion}<Esc>" moves the primary cursor to the selection end, but the
other cursors snap back to their anchors unless follow-mode ("q=") is
on. The selection preview showed them at the selection end.
Solution:
Replay the abandoned selection at every cursor, not only in follow-mode.
An abandoned typed selection now always emits a "visual" CmdAtom.
This commit is contained in:
@@ -1310,9 +1310,9 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
vatom.state |= kVatomVoid;
|
||||
}
|
||||
} else if (old->visual.active) {
|
||||
if (user && old->follow && atom_visual_replayable() && kv_size(vatom.atoms) > 0) {
|
||||
// Follow-motion ("q="): a selection abandoned without an operator (<Esc>, "v" toggle) still
|
||||
// moved the primary cursor to the selection end: replay it at every cursor.
|
||||
if (user && atom_visual_replayable() && kv_size(vatom.atoms) > 0) {
|
||||
// Cursor moved to selection-end after ESC/"v"-toggle. Even though this is technically
|
||||
// a "motion", it's more intuitive to always replay it? #41631
|
||||
char suffix[2] = { ESC, NUL };
|
||||
atom_visual_end_suffix(xstrdup(suffix), NULL, false);
|
||||
} else {
|
||||
|
||||
@@ -849,11 +849,15 @@ describe('CmdAtom', function()
|
||||
feed(',D')
|
||||
eq(before, #atoms())
|
||||
atom('w', 'dw', ',Dw') -- The supplied motion finishes the operation
|
||||
-- Same for a mapping that opens Visual mode: "<Esc>" abandons the selection and closes it.
|
||||
-- Same for a mapping that opens Visual mode: ESC closes the selection (cursor stays on the
|
||||
-- selection end, where it moved during the Visual sequence).
|
||||
feed(',V')
|
||||
eq(before + 1, #atoms())
|
||||
feed('<Esc>')
|
||||
eq({ type = 'mapping', lhs = k(',V<Esc>') }, pick(atom_last(), 'type', 'lhs', 'keys'))
|
||||
eq(
|
||||
{ type = 'visual', lhs = k(',V<Esc>'), keys = k('v<Esc>') },
|
||||
pick(atom_last(), 'type', 'lhs', 'keys')
|
||||
)
|
||||
-- A mapping whose trailing prefix is completed by TYPED keys ("," + "w")
|
||||
-- ends where its own keys stop: each `lhs` owns only what it produced.
|
||||
command('set notimeout')
|
||||
|
||||
@@ -1533,10 +1533,11 @@ describe('multicursor', function()
|
||||
{1:~ }|*2
|
||||
{5:-- VISUAL --} |
|
||||
]])
|
||||
-- <Esc>: cursors move to the selection-end, like the primary.
|
||||
feed('<Esc>')
|
||||
screen:expect([[
|
||||
{17:l}ongword x |
|
||||
{17:a}b y |
|
||||
longword {17:x} |
|
||||
ab {17:y} |
|
||||
medium ^z |
|
||||
{1:~ }|*2
|
||||
|
|
||||
@@ -1557,7 +1558,8 @@ describe('multicursor', function()
|
||||
{5:-- VISUAL LINE --} |
|
||||
]])
|
||||
feed('<Esc>')
|
||||
feed('3G0l')
|
||||
clear_cursors()
|
||||
cursors({ 'aaaa', 'bbbb', 'cccc', 'dddd' }, 'Q2jl')
|
||||
feed('<C-v>jl') -- blockwise: primary (3,1)-(4,2), fake (1,0)-(2,1)
|
||||
screen:expect([[
|
||||
{17:aa}aa |
|
||||
@@ -1605,11 +1607,19 @@ describe('multicursor', function()
|
||||
})
|
||||
end)
|
||||
|
||||
it('<Esc> discards the pending visual atom', function()
|
||||
it('ESC moves cursors to selection-end', function()
|
||||
cursors({ 'abc', 'def' }, 'Qj')
|
||||
feed('viw<Esc>')
|
||||
feed('x') -- cascades normally; no stray visual replay
|
||||
eq({ 'bc', 'de' }, get_lines())
|
||||
eq({ 'ab', 'de' }, get_lines())
|
||||
-- Without follow-mode a plain motion ("0") stays primary-only.
|
||||
-- But the selection itself moves every cursor to its selection-end.
|
||||
clear_cursors()
|
||||
cursors({ 'aaa bbb ccc', 'ddd eee fff', 'ggg hhh iii' }, '4lQjQj')
|
||||
feed('viw<Esc>x')
|
||||
eq({ 'aaa bb ccc', 'ddd ee fff', 'ggg hh iii' }, get_lines())
|
||||
feed('0viwe<Esc>x')
|
||||
eq({ 'aaa bb cc', 'ddd ee ff', 'ggg h iii' }, get_lines())
|
||||
end)
|
||||
|
||||
it('cursor displays at each selection end; o swaps it', function()
|
||||
@@ -1807,19 +1817,6 @@ describe('multicursor', function()
|
||||
eq({ '1', 'a2', 'b1', '2' }, get_lines())
|
||||
end)
|
||||
|
||||
it('abandoned visual selection moves cursors to their selection ends', function()
|
||||
cursors({ 'aaa bbb ccc', 'ddd eee fff', 'ggg hhh iii' }, '4lQjQj')
|
||||
feed('q=')
|
||||
feed('viw<Esc>') -- Selection end: the last char of each cursor's word.
|
||||
feed('x')
|
||||
eq({ 'aaa bb ccc', 'ddd ee fff', 'ggg hh iii' }, get_lines())
|
||||
-- No follow: <Esc> discards, other cursors stay; "x" cascades.
|
||||
feed('q=')
|
||||
feed('0viw<Esc>')
|
||||
feed('x')
|
||||
eq({ 'aaa bbccc', 'ddd eefff', 'gg hh iii' }, get_lines())
|
||||
end)
|
||||
|
||||
it('per-cursor curswant is kept over short lines', function()
|
||||
fn.setline(1, { 'ABCDEF', 'xy', 'GHIJKL', 'MNOPQR', 'zw', 'STUVWX' })
|
||||
feed('gg04l') -- Cursor at (1,4).
|
||||
|
||||
Reference in New Issue
Block a user