mirror of
https://github.com/neovim/neovim.git
synced 2025-10-26 12:27:24 +00:00
Merge pull request #33295 from siddhantdev/backport-33283-to-release-0.11
feat(vim.hl): allow multiple timed highlights simultaneously #33283
This commit is contained in:
@@ -640,6 +640,12 @@ vim.hl.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts})
|
|||||||
• {timeout}? (`integer`, default: -1 no timeout) Time in ms
|
• {timeout}? (`integer`, default: -1 no timeout) Time in ms
|
||||||
before highlight is cleared
|
before highlight is cleared
|
||||||
|
|
||||||
|
Return (multiple): ~
|
||||||
|
(`uv.uv_timer_t?`) range_timer A timer which manages how much time the
|
||||||
|
highlight has left
|
||||||
|
(`fun()?`) range_clear A function which allows clearing the highlight
|
||||||
|
manually. nil is returned if timeout is not specified
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
VIM.DIFF *vim.diff*
|
VIM.DIFF *vim.diff*
|
||||||
|
|||||||
@@ -119,10 +119,6 @@ OPTIONS
|
|||||||
• Setting |hidden-options| now gives an error. In particular, setting
|
• Setting |hidden-options| now gives an error. In particular, setting
|
||||||
'noshellslash' is now only allowed on Windows.
|
'noshellslash' is now only allowed on Windows.
|
||||||
|
|
||||||
PLUGINS
|
|
||||||
|
|
||||||
• TODO
|
|
||||||
|
|
||||||
TREESITTER
|
TREESITTER
|
||||||
|
|
||||||
• |Query:iter_matches()| correctly returns all matching nodes in a match
|
• |Query:iter_matches()| correctly returns all matching nodes in a match
|
||||||
@@ -136,12 +132,11 @@ TREESITTER
|
|||||||
if no languages are explicitly registered.
|
if no languages are explicitly registered.
|
||||||
• |vim.treesitter.language.add()| returns `true` if a parser was loaded
|
• |vim.treesitter.language.add()| returns `true` if a parser was loaded
|
||||||
successfully and `nil,errmsg` otherwise instead of throwing an error.
|
successfully and `nil,errmsg` otherwise instead of throwing an error.
|
||||||
• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse
|
• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse the
|
||||||
the tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua
|
tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua
|
||||||
local p = vim.treesitter.get_parser(0, 'c')
|
local p = vim.treesitter.get_parser(0, 'c')
|
||||||
p:parse()
|
p:parse()
|
||||||
• |vim.treesitter.get_parser()| expects its buffer to be loaded.
|
• |vim.treesitter.get_parser()| expects its buffer to be loaded.
|
||||||
<
|
|
||||||
|
|
||||||
TUI
|
TUI
|
||||||
|
|
||||||
@@ -176,7 +171,6 @@ API
|
|||||||
aligned text that truncates before covering up buffer text.
|
aligned text that truncates before covering up buffer text.
|
||||||
• `virt_lines_overflow` field accepts value `scroll` to enable horizontal
|
• `virt_lines_overflow` field accepts value `scroll` to enable horizontal
|
||||||
scrolling for virtual lines with 'nowrap'.
|
scrolling for virtual lines with 'nowrap'.
|
||||||
• |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight
|
|
||||||
|
|
||||||
DEFAULTS
|
DEFAULTS
|
||||||
|
|
||||||
@@ -242,8 +236,7 @@ EDITOR
|
|||||||
• |hl-ComplMatchIns| shows matched text of the currently inserted completion.
|
• |hl-ComplMatchIns| shows matched text of the currently inserted completion.
|
||||||
• |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
|
• |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
|
||||||
• |gO| now works in `help`, `checkhealth`, and `markdown` buffers.
|
• |gO| now works in `help`, `checkhealth`, and `markdown` buffers.
|
||||||
• Jump between sections in `help` and `checkhealth` buffers with `[[` and
|
• Jump between sections in `help` and `checkhealth` buffers with `[[` and `]]`.
|
||||||
`]]`.
|
|
||||||
|
|
||||||
EVENTS
|
EVENTS
|
||||||
|
|
||||||
@@ -302,6 +295,8 @@ LUA
|
|||||||
• |vim.fs.relpath()| gets relative path compared to base path.
|
• |vim.fs.relpath()| gets relative path compared to base path.
|
||||||
• |vim.fs.dir()| and |vim.fs.find()| can now follow symbolic links,
|
• |vim.fs.dir()| and |vim.fs.find()| can now follow symbolic links,
|
||||||
the behavior can be turn on using the new `follow` option.
|
the behavior can be turn on using the new `follow` option.
|
||||||
|
• |vim.hl.range()| now has a optional `timeout` field which allows for multiple
|
||||||
|
timed highlights.
|
||||||
• |vim.text.indent()| indents/dedents text.
|
• |vim.text.indent()| indents/dedents text.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
@@ -312,10 +307,8 @@ OPTIONS
|
|||||||
• 'messagesopt' configures |:messages| and |hit-enter| prompt.
|
• 'messagesopt' configures |:messages| and |hit-enter| prompt.
|
||||||
• 'tabclose' controls which tab page to focus when closing a tab page.
|
• 'tabclose' controls which tab page to focus when closing a tab page.
|
||||||
• 'eventignorewin' to persistently ignore events in a window.
|
• 'eventignorewin' to persistently ignore events in a window.
|
||||||
• 'winborder' sets the default border for |floating-windows|
|
• 'winborder' sets the default border for |floating-windows|.
|
||||||
|
• 'winborder' "bold" style.
|
||||||
• 'winborder' add bold style.
|
|
||||||
|
|
||||||
• |g:clipboard| accepts a string name to force any builtin clipboard tool.
|
• |g:clipboard| accepts a string name to force any builtin clipboard tool.
|
||||||
|
|
||||||
PERFORMANCE
|
PERFORMANCE
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ M.priorities = {
|
|||||||
user = 200,
|
user = 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
local range_timer --- @type uv.uv_timer_t?
|
|
||||||
local range_hl_clear --- @type fun()?
|
|
||||||
|
|
||||||
--- @class vim.hl.range.Opts
|
--- @class vim.hl.range.Opts
|
||||||
--- @inlinedoc
|
--- @inlinedoc
|
||||||
---
|
---
|
||||||
@@ -47,6 +44,10 @@ local range_hl_clear --- @type fun()?
|
|||||||
---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()|
|
---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()|
|
||||||
---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
|
---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
|
||||||
---@param opts? vim.hl.range.Opts
|
---@param opts? vim.hl.range.Opts
|
||||||
|
--- @return uv.uv_timer_t? range_timer A timer which manages how much time the
|
||||||
|
--- highlight has left
|
||||||
|
--- @return fun()? range_clear A function which allows clearing the highlight manually.
|
||||||
|
--- nil is returned if timeout is not specified
|
||||||
function M.range(bufnr, ns, higroup, start, finish, opts)
|
function M.range(bufnr, ns, higroup, start, finish, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local regtype = opts.regtype or 'v'
|
local regtype = opts.regtype or 'v'
|
||||||
@@ -108,24 +109,14 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if range_timer and not range_timer:is_closing() then
|
local extmarks = {} --- @type integer[]
|
||||||
range_timer:close()
|
|
||||||
assert(range_hl_clear)
|
|
||||||
range_hl_clear()
|
|
||||||
end
|
|
||||||
|
|
||||||
range_hl_clear = function()
|
|
||||||
range_timer = nil
|
|
||||||
range_hl_clear = nil
|
|
||||||
pcall(vim.api.nvim_buf_clear_namespace, bufnr, ns, 0, -1)
|
|
||||||
pcall(vim.api.nvim__ns_set, { wins = {} })
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, res in ipairs(region) do
|
for _, res in ipairs(region) do
|
||||||
local start_row = res[1][2] - 1
|
local start_row = res[1][2] - 1
|
||||||
local start_col = res[1][3] - 1
|
local start_col = res[1][3] - 1
|
||||||
local end_row = res[2][2] - 1
|
local end_row = res[2][2] - 1
|
||||||
local end_col = res[2][3]
|
local end_col = res[2][3]
|
||||||
|
table.insert(
|
||||||
|
extmarks,
|
||||||
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
|
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
|
||||||
hl_group = higroup,
|
hl_group = higroup,
|
||||||
end_row = end_row,
|
end_row = end_row,
|
||||||
@@ -133,13 +124,23 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
|
|||||||
priority = priority,
|
priority = priority,
|
||||||
strict = false,
|
strict = false,
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local range_hl_clear = function()
|
||||||
|
for _, mark in ipairs(extmarks) do
|
||||||
|
api.nvim_buf_del_extmark(bufnr, ns, mark)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if timeout ~= -1 then
|
if timeout ~= -1 then
|
||||||
range_timer = vim.defer_fn(range_hl_clear, timeout)
|
local range_timer = vim.defer_fn(range_hl_clear, timeout)
|
||||||
|
return range_timer, range_hl_clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local yank_timer --- @type uv.uv_timer_t?
|
||||||
|
local yank_hl_clear --- @type fun()?
|
||||||
local yank_ns = api.nvim_create_namespace('nvim.hlyank')
|
local yank_ns = api.nvim_create_namespace('nvim.hlyank')
|
||||||
|
|
||||||
--- Highlight the yanked text during a |TextYankPost| event.
|
--- Highlight the yanked text during a |TextYankPost| event.
|
||||||
@@ -179,8 +180,14 @@ function M.on_yank(opts)
|
|||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
if yank_timer and not yank_timer:is_closing() then
|
||||||
|
yank_timer:close()
|
||||||
|
assert(yank_hl_clear)
|
||||||
|
yank_hl_clear()
|
||||||
|
end
|
||||||
|
|
||||||
vim.api.nvim__ns_set(yank_ns, { wins = { winid } })
|
vim.api.nvim__ns_set(yank_ns, { wins = { winid } })
|
||||||
M.range(bufnr, yank_ns, higroup, "'[", "']", {
|
yank_timer, yank_hl_clear = M.range(bufnr, yank_ns, higroup, "'[", "']", {
|
||||||
regtype = event.regtype,
|
regtype = event.regtype,
|
||||||
inclusive = true,
|
inclusive = true,
|
||||||
priority = opts.priority or M.priorities.user,
|
priority = opts.priority or M.priorities.user,
|
||||||
|
|||||||
@@ -139,6 +139,80 @@ describe('vim.hl.range', function()
|
|||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('shows multiple highlights with different timeouts simultaneously', function()
|
||||||
|
local timeout1 = 300
|
||||||
|
local timeout2 = 600
|
||||||
|
exec_lua(function()
|
||||||
|
local ns = vim.api.nvim_create_namespace('')
|
||||||
|
vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout1 })
|
||||||
|
vim.hl.range(0, ns, 'Search', { 2, 6 }, { 3, 5 }, { timeout = timeout2 })
|
||||||
|
end)
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
{10:^asdfghjkl}{100:$} |
|
||||||
|
{10:«口=口»}{100:$} |
|
||||||
|
{10:qwertyuiop}{100:$} |
|
||||||
|
{10:口口=口口}{1:$} |
|
||||||
|
zxcvbnm{1:$} |
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
timeout = timeout1 / 3,
|
||||||
|
})
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
^asdfghjkl{1:$} |
|
||||||
|
«口=口»{1:$} |
|
||||||
|
qwerty{10:uiop}{100:$} |
|
||||||
|
{10:口口}=口口{1:$} |
|
||||||
|
zxcvbnm{1:$} |
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
timeout = timeout1 + ((timeout2 - timeout1) / 3),
|
||||||
|
})
|
||||||
|
screen:expect([[
|
||||||
|
^asdfghjkl{1:$} |
|
||||||
|
«口=口»{1:$} |
|
||||||
|
qwertyuiop{1:$} |
|
||||||
|
口口=口口{1:$} |
|
||||||
|
zxcvbnm{1:$} |
|
||||||
|
|
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('allows cancelling a highlight that has not timed out', function()
|
||||||
|
exec_lua(function()
|
||||||
|
local timeout = 3000
|
||||||
|
local range_timer
|
||||||
|
local range_hl_clear
|
||||||
|
local ns = vim.api.nvim_create_namespace('')
|
||||||
|
range_timer, range_hl_clear = vim.hl.range(
|
||||||
|
0,
|
||||||
|
ns,
|
||||||
|
'Search',
|
||||||
|
{ 0, 0 },
|
||||||
|
{ 4, 0 },
|
||||||
|
{ timeout = timeout }
|
||||||
|
)
|
||||||
|
if range_timer and not range_timer:is_closing() then
|
||||||
|
range_timer:close()
|
||||||
|
assert(range_hl_clear)
|
||||||
|
range_hl_clear()
|
||||||
|
range_hl_clear() -- Exercise redundant call
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
screen:expect({
|
||||||
|
grid = [[
|
||||||
|
^asdfghjkl{1:$} |
|
||||||
|
«口=口»{1:$} |
|
||||||
|
qwertyuiop{1:$} |
|
||||||
|
口口=口口{1:$} |
|
||||||
|
zxcvbnm{1:$} |
|
||||||
|
|
|
||||||
|
]],
|
||||||
|
unchanged = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('vim.hl.on_yank', function()
|
describe('vim.hl.on_yank', function()
|
||||||
@@ -184,9 +258,9 @@ describe('vim.hl.on_yank', function()
|
|||||||
eq({ win }, api.nvim__ns_get(ns).wins)
|
eq({ win }, api.nvim__ns_get(ns).wins)
|
||||||
command('wincmd w')
|
command('wincmd w')
|
||||||
eq({ win }, api.nvim__ns_get(ns).wins)
|
eq({ win }, api.nvim__ns_get(ns).wins)
|
||||||
-- Use a new vim.hl.range() call to cancel the previous timer
|
-- Use a new vim.hl.on_yank() call to cancel the previous timer
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
vim.hl.range(0, ns, 'Search', { 0, 0 }, { 0, 0 }, { timeout = 0 })
|
vim.hl.on_yank({ timeout = 0, on_macro = true, event = { operator = 'y' } })
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -209,9 +283,9 @@ describe('vim.hl.on_yank', function()
|
|||||||
eq({ win }, api.nvim__ns_get(ns).wins)
|
eq({ win }, api.nvim__ns_get(ns).wins)
|
||||||
command('wincmd w')
|
command('wincmd w')
|
||||||
eq({ win }, api.nvim__ns_get(ns).wins)
|
eq({ win }, api.nvim__ns_get(ns).wins)
|
||||||
-- Use a new vim.hl.range() call to cancel the previous timer
|
-- Use a new vim.hl.on_yank() call to cancel the previous timer
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
vim.hl.range(0, ns, 'Search', { 0, 0 }, { 0, 0 }, { timeout = 0 })
|
vim.hl.on_yank({ timeout = 0, on_macro = true, event = { operator = 'y' } })
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -246,5 +320,9 @@ describe('vim.hl.on_yank', function()
|
|||||||
{1:~ }|
|
{1:~ }|
|
||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
|
-- Use a new vim.hl.on_yank() call to cancel the previous timer
|
||||||
|
exec_lua(function()
|
||||||
|
vim.hl.on_yank({ timeout = 0, on_macro = true, event = { operator = 'y' } })
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user