mirror of
https://github.com/neovim/neovim.git
synced 2026-09-03 12:50:36 +00:00
Merge #41599 from justinmk/mchammer
This commit is contained in:
@@ -217,6 +217,8 @@ The following new features were added.
|
||||
• Navigating the |jumplist| with CTRL+O, CTRL+I behaves more intuitively
|
||||
when deleting buffers, and avoids "invalid buffer" cases. #25461
|
||||
• |:fclose| command.
|
||||
• v_Q-default and |v_@-default| repeat a register for each line of a linewise
|
||||
visual selection.
|
||||
• Clicking on a tabpage in the tabline with the middle mouse button closes it.
|
||||
• |:checkhealth| buffer can be opened in a split window using modifiers like
|
||||
|:vertical|, |:horizontal| and |:botright|.
|
||||
|
||||
@@ -327,16 +327,15 @@ q Stops recording.
|
||||
==============================================================================
|
||||
Multiple cursors *mcursor* *multicursor*
|
||||
|
||||
You can place extra cursors ("multicursors") in a buffer, to repeat operations
|
||||
at each cursor, as-you-type. This is like a live |macro|, but unlike macros (a
|
||||
stream of keys without any context), actions (|CmdAtom|) replay "semantically".
|
||||
You can place extra cursors ("multicursor") in a buffer, to repeat operations
|
||||
at each cursor as-you-type. Unlike a |macro| (a stream of keys without any
|
||||
context), multicursor replays actions "semantically" (|CmdAtom|).
|
||||
|
||||
- Mappings and Normal-mode operators are applied at each cursor.
|
||||
- Normal-mode commands, Mappings, and even Macros (|@|) replay at each cursor.
|
||||
- Insert-mode edits appear at each cursor as you type.
|
||||
- Motions (|q=|) and Visual-mode sequences replay at each cursor: "viweex"
|
||||
re-executes the selection there, so the extents are per-cursor (each
|
||||
cursor's own word, block, …).
|
||||
- Macros (|@|) replay at each cursor.
|
||||
- Motions (|q=|) and Visual sequences replay at each cursor: "viweex"
|
||||
re-executes the selection so the extents are per-cursor (each cursor's own
|
||||
word, block, …).
|
||||
- Registers are cursor-local. Each cursor reads/writes its own registers.
|
||||
The yanks are joined (linewise) when the multicursor session ends.
|
||||
- Undo is atomic: |u| reverts the aggregate edits from all cursors at once.
|
||||
@@ -371,8 +370,8 @@ COMMANDS
|
||||
Q Toggles a multicursor at the current cursor position.
|
||||
Disables follow-mode |q=|.
|
||||
|
||||
With a [count]: places a cursor at every match (|gn|)
|
||||
of the last search pattern. Example:
|
||||
[count]Q Places a cursor at every match (|gn|) of the last
|
||||
search pattern. Example:
|
||||
1. Search for something ("*", "/pattern<CR>", …).
|
||||
2. Type "1Q".
|
||||
|
||||
@@ -413,6 +412,25 @@ g CTRL-A During a multicursor session, inserts an ascending
|
||||
[C Jump to the [count]'th previous cursor.
|
||||
|
||||
|
||||
EXAMPLES *mcursor-examples*
|
||||
|
||||
Example: a "follow-once" mapping: "q-{motion}" replays {motion} at every
|
||||
cursor, then disables follow-mode again. >lua
|
||||
|
||||
vim.keymap.set('n', 'q-', function()
|
||||
vim.cmd('normal! 1q=')
|
||||
vim.api.nvim_create_autocmd('CmdAtom', {
|
||||
callback = function(ev)
|
||||
if ev.data.lhs == 'q-' then
|
||||
return -- Skip the mapping itself.
|
||||
end
|
||||
vim.cmd('normal! 2q=')
|
||||
return true -- Delete the handler.
|
||||
end,
|
||||
})
|
||||
end)
|
||||
<
|
||||
|
||||
LIMITATIONS *mcursor-limitations*
|
||||
|
||||
- Extra cursors on the same line can be lost or misplaced by an edit that
|
||||
|
||||
@@ -143,7 +143,7 @@ static const char e_missing_argument_str[]
|
||||
// they still work when the runtime files can't be found.
|
||||
|
||||
static const char *highlight_init_both[] = {
|
||||
"Cursor guifg=bg guibg=fg",
|
||||
"Cursor guifg=bg guibg=fg cterm=reverse",
|
||||
"CursorLineNr gui=bold cterm=bold",
|
||||
"Ignore guifg=bg ctermfg=0",
|
||||
"PmenuMatch gui=bold cterm=bold",
|
||||
@@ -153,7 +153,7 @@ static const char *highlight_init_both[] = {
|
||||
"TabLineSel guifg=fg guibg=bg gui=bold cterm=nocombine",
|
||||
"TermCursor gui=reverse cterm=reverse",
|
||||
"Underlined gui=underline cterm=underline",
|
||||
"lCursor guifg=bg guibg=fg",
|
||||
"lCursor guifg=bg guibg=fg cterm=reverse",
|
||||
|
||||
// UI
|
||||
"default link CursorIM Cursor",
|
||||
|
||||
@@ -223,8 +223,8 @@ bool mc_replaying(void)
|
||||
/// position (e.g. "o" on the line above).
|
||||
/// @param no_undo Transient mark, undo must not restore it (a restored right-gravity mark
|
||||
/// drifts when undo re-inserts text at its position).
|
||||
static void mc_point_mark_set(buf_T *buf, uint32_t ns, uint32_t *mark, pos_T pos, bool watched,
|
||||
bool right_gravity, bool no_undo)
|
||||
static void mc_mark_set(buf_T *buf, uint32_t ns, uint32_t *mark, pos_T pos, bool watched,
|
||||
bool right_gravity, bool no_undo)
|
||||
{
|
||||
DecorInline decor = DECOR_INLINE_INIT;
|
||||
if (watched) {
|
||||
@@ -237,7 +237,7 @@ static void mc_point_mark_set(buf_T *buf, uint32_t ns, uint32_t *mark, pos_T pos
|
||||
/// Creates or updates the extmark tracking a multicursor position.
|
||||
static void mc_mark_upd(buf_T *buf, uint32_t *mark, pos_T pos)
|
||||
{
|
||||
mc_point_mark_set(buf, mc_ns(), mark, pos, true, true, false);
|
||||
mc_mark_set(buf, mc_ns(), mark, pos, true, true, false);
|
||||
}
|
||||
|
||||
/// Gets the position tracked by `mark`, adjusted for buffer edits since the mark was set.
|
||||
@@ -258,7 +258,7 @@ static bool mc_mark_get(buf_T *buf, uint32_t ns, uint32_t mark, pos_T *pos)
|
||||
/// across a cascade, the preview-apply cursor).
|
||||
static void mc_track_upd(buf_T *buf, uint32_t *mark, pos_T pos)
|
||||
{
|
||||
mc_point_mark_set(buf, mc_session_ns(), mark, pos, false, true, true);
|
||||
mc_mark_set(buf, mc_session_ns(), mark, pos, false, true, true);
|
||||
}
|
||||
|
||||
/// Enters a replay sandbox, so keys fed (cascaded) per-cursor do not modify pending typeahead nor
|
||||
@@ -1091,7 +1091,7 @@ void mc_vsel_refresh(void)
|
||||
}
|
||||
// Selection-end cursor; the anchor extmark stays put (the eventual replay position).
|
||||
uint32_t cmark = 0;
|
||||
mc_point_mark_set(curbuf, mc_vcur_ns(), &cmark, curwin->w_cursor, true, false, true);
|
||||
mc_mark_set(curbuf, mc_vcur_ns(), &cmark, curwin->w_cursor, true, false, true);
|
||||
mc_vsel_buf = curbuf->handle;
|
||||
Visual.active = false;
|
||||
}
|
||||
@@ -1231,7 +1231,6 @@ bool mc_follow_toggle(long count0)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
smsg(0, _("multicursor: follow motion %s"), mc_follow_motion ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1345,7 +1344,7 @@ void mc_ns_cleared(buf_T *buf, uint32_t ns_id)
|
||||
continue;
|
||||
}
|
||||
uint32_t mark = 0;
|
||||
mc_point_mark_set(buf, mc_last_ns(), &mark, ctx->pos, false, true, false);
|
||||
mc_mark_set(buf, mc_last_ns(), &mark, ctx->pos, false, true, false);
|
||||
}
|
||||
mc_dedupe(); // Cleanup.
|
||||
if (kv_size(mc_cursors) == 0) {
|
||||
|
||||
@@ -434,7 +434,7 @@ describe('multicursor', function()
|
||||
line11 |
|
||||
line12 |
|
||||
line13 |
|
||||
multicursor: ...ow motion off |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
@@ -2468,10 +2468,10 @@ describe('multicursor', function()
|
||||
screen:expect({ any = '1×' })
|
||||
feed('jQ')
|
||||
screen:expect({ any = '2×' })
|
||||
command('silent normal! 1q=') -- Follow mode: "=" prefix (silent to avoid the q= message).
|
||||
feed('l') -- Tickle showcmd redraw.
|
||||
feed('q=') -- Follow mode: "=" prefix.
|
||||
feed('l') -- Tickle showcmd redraw.
|
||||
screen:expect({ any = '=2×' })
|
||||
command('silent normal! 2q=')
|
||||
feed('q=') -- Follow mode off.
|
||||
feed('h') -- Tickle showcmd redraw.
|
||||
clear_cursors() -- cleared with the cursors
|
||||
feed('<Esc>') -- the indicator refreshes on the next command
|
||||
@@ -2492,13 +2492,37 @@ describe('multicursor', function()
|
||||
screen:expect({ any = '1×' })
|
||||
feed('jQ')
|
||||
screen:expect({ any = '2×' })
|
||||
command('silent normal! 1q=') -- Follow mode: "=" prefix (silent to avoid the q= message).
|
||||
feed('q=') -- Follow mode: "=" prefix.
|
||||
feed('l') -- Tickle showcmd redraw.
|
||||
screen:expect({ any = '=2×' })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('workflows', function()
|
||||
it('follow-once mapping', function()
|
||||
n.exec_lua([[
|
||||
vim.keymap.set('n', 'q-', function()
|
||||
vim.cmd('normal! 1q=')
|
||||
vim.api.nvim_create_autocmd('CmdAtom', {
|
||||
callback = function(ev)
|
||||
if ev.data.lhs == 'q-' then
|
||||
return
|
||||
end
|
||||
vim.cmd('normal! 2q=')
|
||||
return true
|
||||
end,
|
||||
})
|
||||
end)
|
||||
]])
|
||||
cursors({ ' aaa', ' bbb', ' ccc' })
|
||||
feed('q-')
|
||||
feed('^') -- Cascades: every cursor moves to the first non-blank.
|
||||
eq({ { 0, 2 }, { 1, 2 } }, anchors())
|
||||
feed('l') -- Follow already ended: primary only.
|
||||
eq({ { 0, 2 }, { 1, 2 } }, anchors())
|
||||
eq(4, fn.col('.'))
|
||||
end)
|
||||
|
||||
it('split visual selection into line cursors', function()
|
||||
-- {Visual}Q
|
||||
fn.setline(1, { 'aaaa', 'bbbb', 'cc', 'dddd' })
|
||||
|
||||
@@ -242,7 +242,10 @@ describe('highlight defaults', function()
|
||||
|
||||
it('Cursor after `:hi clear|syntax reset` #6508', function()
|
||||
command('highlight clear|syntax reset')
|
||||
eq('guifg=bg guibg=fg', eval([[matchstr(execute('hi Cursor'), '\v(gui|cterm).*$')]]))
|
||||
eq(
|
||||
'cterm=reverse guifg=bg guibg=fg',
|
||||
eval([[matchstr(execute('hi Cursor'), '\v(gui|cterm).*$')]])
|
||||
)
|
||||
end)
|
||||
|
||||
it('Whitespace highlight', function()
|
||||
|
||||
Reference in New Issue
Block a user