mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
fix(defaults): visual mode star (*,#) is fragile
Problem: Visual mode "*", "#" mappings don't work on text with "/", "\", "?", and newlines. Solution: Get the visual selection and escape it as a search pattern. Add functions vim.get_visual_selection and _search_for_visual_selection. Fix #21676
This commit is contained in:
committed by
Justin M. Keyes
parent
4e34ca8ae7
commit
abd380e28d
@ -1410,6 +1410,25 @@ deprecate({name}, {alternative}, {version}, {plugin}, {backtrace})
|
|||||||
Return: ~
|
Return: ~
|
||||||
Deprecated message, or nil if no message was shown.
|
Deprecated message, or nil if no message was shown.
|
||||||
|
|
||||||
|
*vim.get_visual_selection()*
|
||||||
|
get_visual_selection({list}, {append_empty})
|
||||||
|
Gets the content of the visual selection.
|
||||||
|
|
||||||
|
The result is either a string or, if {list} is `true`, a list of strings.
|
||||||
|
If not in any |visual-mode|, `nil` is returned.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {list} boolean|nil Return a list of strings instead of a
|
||||||
|
string. See |getreg()|. Defaults to `false`.
|
||||||
|
• {append_empty} boolean|nil Append an empty string to the result when
|
||||||
|
in |linewise-visual| mode and {list} is `true`. This
|
||||||
|
will preserve the trailing newline of the selection
|
||||||
|
when the result is concatenated with `"\n"`. Defaults
|
||||||
|
to `false`.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
string|table
|
||||||
|
|
||||||
inspect({object}, {options}) *vim.inspect()*
|
inspect({object}, {options}) *vim.inspect()*
|
||||||
Gets a human-readable representation of the given object.
|
Gets a human-readable representation of the given object.
|
||||||
|
|
||||||
|
@ -119,10 +119,11 @@ of these in your config by simply removing the mapping, e.g. ":unmap Y".
|
|||||||
nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>
|
nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>
|
||||||
inoremap <C-U> <C-G>u<C-U>
|
inoremap <C-U> <C-G>u<C-U>
|
||||||
inoremap <C-W> <C-G>u<C-W>
|
inoremap <C-W> <C-G>u<C-W>
|
||||||
xnoremap * y/\V<C-R>"<CR>
|
|
||||||
xnoremap # y?\V<C-R>"<CR>
|
|
||||||
nnoremap & :&&<CR>
|
nnoremap & :&&<CR>
|
||||||
<
|
<
|
||||||
|
Default mappings composed of Lua code are not listed above. Use ":map" to see
|
||||||
|
the |map-listing|.
|
||||||
|
|
||||||
DEFAULT AUTOCOMMANDS
|
DEFAULT AUTOCOMMANDS
|
||||||
*default-autocmds*
|
*default-autocmds*
|
||||||
Default autocommands exist in the following groups. Use ":autocmd! {group}" to
|
Default autocommands exist in the following groups. Use ":autocmd! {group}" to
|
||||||
|
@ -435,7 +435,7 @@ vim.cmd = setmetatable({}, {
|
|||||||
do
|
do
|
||||||
local validate = vim.validate
|
local validate = vim.validate
|
||||||
|
|
||||||
--@private
|
---@private
|
||||||
local function make_dict_accessor(scope, handle)
|
local function make_dict_accessor(scope, handle)
|
||||||
validate({
|
validate({
|
||||||
scope = { scope, 's' },
|
scope = { scope, 's' },
|
||||||
@ -535,6 +535,55 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
|
|||||||
return region
|
return region
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Gets the content of the visual selection.
|
||||||
|
---
|
||||||
|
--- The result is either a string or, if {list} is `true`, a list of strings.
|
||||||
|
--- If not in any |visual-mode|, `nil` is returned.
|
||||||
|
---
|
||||||
|
--- @param list boolean|nil
|
||||||
|
--- Return a list of strings instead of a string. See |getreg()|.
|
||||||
|
--- Defaults to `false`.
|
||||||
|
--- @param append_empty boolean|nil
|
||||||
|
--- Append an empty string to the result when in |linewise-visual| mode and {list} is `true`.
|
||||||
|
--- This will preserve the trailing newline of the selection when the result is concatenated with `"\n"`.
|
||||||
|
--- Defaults to `false`.
|
||||||
|
--- @return string|table
|
||||||
|
function vim.get_visual_selection(list, append_empty)
|
||||||
|
list = list or false
|
||||||
|
append_empty = append_empty or false
|
||||||
|
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
if mode ~= 'v' and mode ~= 'V' and mode:byte() ~= 22 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local reg_name_unnamed = '"'
|
||||||
|
local reg_name_yank = '0'
|
||||||
|
|
||||||
|
local reg_info_unnamed = vim.fn.getreginfo(reg_name_unnamed)
|
||||||
|
local reg_info_yank = vim.fn.getreginfo(reg_name_yank)
|
||||||
|
local opt_clipboard = vim.o.clipboard
|
||||||
|
local opt_report = vim.o.report
|
||||||
|
|
||||||
|
vim.o.clipboard = ''
|
||||||
|
vim.o.report = vim.v.maxcol
|
||||||
|
|
||||||
|
vim.api.nvim_feedkeys('y', 'nx', false)
|
||||||
|
|
||||||
|
local yanked = vim.fn.getreg(reg_name_yank, 1, list)
|
||||||
|
|
||||||
|
vim.fn.setreg(reg_name_unnamed, reg_info_unnamed)
|
||||||
|
vim.fn.setreg(reg_name_yank, reg_info_yank)
|
||||||
|
vim.o.clipboard = opt_clipboard
|
||||||
|
vim.o.report = opt_report
|
||||||
|
|
||||||
|
if list and append_empty and mode == 'V' then
|
||||||
|
table.insert(yanked, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
return yanked
|
||||||
|
end
|
||||||
|
|
||||||
--- Defers calling {fn} until {timeout} ms passes.
|
--- Defers calling {fn} until {timeout} ms passes.
|
||||||
---
|
---
|
||||||
--- Use to do a one-shot timer that calls {fn}
|
--- Use to do a one-shot timer that calls {fn}
|
||||||
@ -985,7 +1034,7 @@ end
|
|||||||
--- Defaults to "Nvim".
|
--- Defaults to "Nvim".
|
||||||
---@param backtrace boolean|nil Prints backtrace. Defaults to true.
|
---@param backtrace boolean|nil Prints backtrace. Defaults to true.
|
||||||
---
|
---
|
||||||
---@returns Deprecated message, or nil if no message was shown.
|
---@return Deprecated message, or nil if no message was shown.
|
||||||
function vim.deprecate(name, alternative, version, plugin, backtrace)
|
function vim.deprecate(name, alternative, version, plugin, backtrace)
|
||||||
local msg = ('%s is deprecated'):format(name)
|
local msg = ('%s is deprecated'):format(name)
|
||||||
plugin = plugin or 'Nvim'
|
plugin = plugin or 'Nvim'
|
||||||
@ -1010,9 +1059,28 @@ end
|
|||||||
function vim._init_default_mappings()
|
function vim._init_default_mappings()
|
||||||
-- mappings
|
-- mappings
|
||||||
|
|
||||||
--@private
|
---@private
|
||||||
|
local function _search_for_visual_selection(search_prefix)
|
||||||
|
if search_prefix ~= '/' and search_prefix ~= '?' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Escape these characters
|
||||||
|
local replacements = {
|
||||||
|
[search_prefix] = [[\]] .. search_prefix,
|
||||||
|
[ [[\]] ] = [[\\]],
|
||||||
|
['\t'] = [[\t]],
|
||||||
|
['\n'] = [[\n]],
|
||||||
|
}
|
||||||
|
local pattern = '[' .. table.concat(vim.tbl_keys(replacements), '') .. ']'
|
||||||
|
local visual_selection = vim.get_visual_selection(false)
|
||||||
|
local escaped_visual_selection = string.gsub(visual_selection, pattern, replacements)
|
||||||
|
local search_cmd = search_prefix .. [[\V]] .. escaped_visual_selection .. '\n'
|
||||||
|
vim.api.nvim_feedkeys(search_cmd, 'nx', true)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
local function map(mode, lhs, rhs)
|
local function map(mode, lhs, rhs)
|
||||||
vim.api.nvim_set_keymap(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' })
|
vim.keymap.set(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' })
|
||||||
end
|
end
|
||||||
|
|
||||||
map('n', 'Y', 'y$')
|
map('n', 'Y', 'y$')
|
||||||
@ -1020,8 +1088,12 @@ function vim._init_default_mappings()
|
|||||||
map('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>')
|
map('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>')
|
||||||
map('i', '<C-U>', '<C-G>u<C-U>')
|
map('i', '<C-U>', '<C-G>u<C-U>')
|
||||||
map('i', '<C-W>', '<C-G>u<C-W>')
|
map('i', '<C-W>', '<C-G>u<C-W>')
|
||||||
map('x', '*', 'y/\\V<C-R>"<CR>')
|
map('x', '*', function()
|
||||||
map('x', '#', 'y?\\V<C-R>"<CR>')
|
_search_for_visual_selection('/')
|
||||||
|
end)
|
||||||
|
map('x', '#', function()
|
||||||
|
_search_for_visual_selection('?')
|
||||||
|
end)
|
||||||
-- Use : instead of <Cmd> so that ranges are supported. #19365
|
-- Use : instead of <Cmd> so that ranges are supported. #19365
|
||||||
map('n', '&', ':&&<CR>')
|
map('n', '&', ':&&<CR>')
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user