mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
fix(defaults): improve visual search mappings #32378
Problem: The behavior of the visual search mappings aren't consistent with their normal mode counterparts. - The count isn't considered - Searching with an empty selection will match every character in the buffer - Searching backwards only jumps back when the cursor is positioned at the start of the selection. Solution: - Issue `n` `v:count1` times - Error out and exit visual mode when the selection is empty - Detect when the cursor is not at the start of the selection, and adjust the count accordingly Also, use the search register instead of the more error-prone approach of feeding the entire search string as an expression
This commit is contained in:
@@ -32,27 +32,53 @@ do
|
|||||||
---
|
---
|
||||||
--- See |v_star-default| and |v_#-default|
|
--- See |v_star-default| and |v_#-default|
|
||||||
do
|
do
|
||||||
local function _visual_search(cmd)
|
local function _visual_search(forward)
|
||||||
assert(cmd == '/' or cmd == '?')
|
assert(forward == 0 or forward == 1)
|
||||||
local chunks =
|
local pos = vim.fn.getpos('.')
|
||||||
vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
|
local vpos = vim.fn.getpos('v')
|
||||||
|
local mode = vim.fn.mode()
|
||||||
|
local chunks = vim.fn.getregion(pos, vpos, { type = mode })
|
||||||
local esc_chunks = vim
|
local esc_chunks = vim
|
||||||
.iter(chunks)
|
.iter(chunks)
|
||||||
:map(function(v)
|
:map(function(v)
|
||||||
return vim.fn.escape(v, cmd == '/' and [[/\]] or [[?\]])
|
return vim.fn.escape(v, [[\]])
|
||||||
end)
|
end)
|
||||||
:totable()
|
:totable()
|
||||||
local esc_pat = table.concat(esc_chunks, [[\n]])
|
local esc_pat = table.concat(esc_chunks, [[\n]])
|
||||||
local search_cmd = ([[%s\V%s%s]]):format(cmd, esc_pat, '\n')
|
if #esc_pat == 0 then
|
||||||
return '\27' .. search_cmd
|
vim.api.nvim_echo({ { 'E348: No string under cursor' } }, true, { err = true })
|
||||||
|
return '<Esc>'
|
||||||
|
end
|
||||||
|
local search = [[\V]] .. esc_pat
|
||||||
|
|
||||||
|
vim.fn.setreg('/', search)
|
||||||
|
vim.fn.histadd('/', search)
|
||||||
|
vim.v.searchforward = forward
|
||||||
|
|
||||||
|
-- The count has to be adjusted when searching backwards and the cursor
|
||||||
|
-- isn't positioned at the beginning of the selection
|
||||||
|
local count = vim.v.count1
|
||||||
|
if forward == 0 then
|
||||||
|
local _, line, col, _ = unpack(pos)
|
||||||
|
local _, vline, vcol, _ = unpack(vpos)
|
||||||
|
if
|
||||||
|
line > vline
|
||||||
|
or mode == 'v' and line == vline and col > vcol
|
||||||
|
or mode == 'V' and col ~= 1
|
||||||
|
or mode == '\22' and col > vcol
|
||||||
|
then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return '<Esc>' .. count .. 'n'
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.keymap.set('x', '*', function()
|
vim.keymap.set('x', '*', function()
|
||||||
return _visual_search('/')
|
return _visual_search(1)
|
||||||
end, { desc = ':help v_star-default', expr = true, replace_keycodes = false })
|
end, { desc = ':help v_star-default', expr = true })
|
||||||
vim.keymap.set('x', '#', function()
|
vim.keymap.set('x', '#', function()
|
||||||
return _visual_search('?')
|
return _visual_search(0)
|
||||||
end, { desc = ':help v_#-default', expr = true, replace_keycodes = false })
|
end, { desc = ':help v_#-default', expr = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Map Y to y$. This mimics the behavior of D and C. See |Y-default|
|
--- Map Y to y$. This mimics the behavior of D and C. See |Y-default|
|
||||||
|
@@ -119,7 +119,9 @@ describe('default', function()
|
|||||||
[[testing <CR> /?\!3]],
|
[[testing <CR> /?\!3]],
|
||||||
[[testing <CR> /?\!4]],
|
[[testing <CR> /?\!4]],
|
||||||
})
|
})
|
||||||
n.feed('gg0vf!o*')
|
n.feed('gg0vf!')
|
||||||
|
n.poke_eventloop()
|
||||||
|
n.feed('*')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
{3:testing <CR> /?\!}1 |
|
{3:testing <CR> /?\!}1 |
|
||||||
{4:^testing <CR> /?\!}2 |
|
{4:^testing <CR> /?\!}2 |
|
||||||
@@ -127,7 +129,7 @@ describe('default', function()
|
|||||||
{3:testing <CR> /?\!}4 |
|
{3:testing <CR> /?\!}4 |
|
||||||
{1:~ }|*2
|
{1:~ }|*2
|
||||||
{2:[No Name] [+] 2,1 All}|
|
{2:[No Name] [+] 2,1 All}|
|
||||||
/\Vtesting <CR> \/?\\! [2/4] |
|
/\Vtesting <CR> /?\\! [2/4] |
|
||||||
]])
|
]])
|
||||||
n.feed('n')
|
n.feed('n')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
@@ -137,9 +139,11 @@ describe('default', function()
|
|||||||
{3:testing <CR> /?\!}4 |
|
{3:testing <CR> /?\!}4 |
|
||||||
{1:~ }|*2
|
{1:~ }|*2
|
||||||
{2:[No Name] [+] 3,1 All}|
|
{2:[No Name] [+] 3,1 All}|
|
||||||
/\Vtesting <CR> \/?\\! [3/4] |
|
/\Vtesting <CR> /?\\! [3/4] |
|
||||||
]])
|
]])
|
||||||
n.feed('G0vf!o#')
|
n.feed('G0vf!')
|
||||||
|
n.poke_eventloop()
|
||||||
|
n.feed('#')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
{3:testing <CR> /?\!}1 |
|
{3:testing <CR> /?\!}1 |
|
||||||
{3:testing <CR> /?\!}2 |
|
{3:testing <CR> /?\!}2 |
|
||||||
|
Reference in New Issue
Block a user