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:
zeertzjq
2025-04-04 09:50:29 +08:00
committed by GitHub
4 changed files with 127 additions and 43 deletions

View File

@@ -640,6 +640,12 @@ vim.hl.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts})
• {timeout}? (`integer`, default: -1 no timeout) Time in ms
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*

View File

@@ -119,10 +119,6 @@ OPTIONS
• Setting |hidden-options| now gives an error. In particular, setting
'noshellslash' is now only allowed on Windows.
PLUGINS
• TODO
TREESITTER
• |Query:iter_matches()| correctly returns all matching nodes in a match
@@ -136,12 +132,11 @@ TREESITTER
if no languages are explicitly registered.
• |vim.treesitter.language.add()| returns `true` if a parser was loaded
successfully and `nil,errmsg` otherwise instead of throwing an error.
• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse
the tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua
• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse the
tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua
local p = vim.treesitter.get_parser(0, 'c')
p:parse()
• |vim.treesitter.get_parser()| expects its buffer to be loaded.
<
TUI
@@ -176,7 +171,6 @@ API
aligned text that truncates before covering up buffer text.
• `virt_lines_overflow` field accepts value `scroll` to enable horizontal
scrolling for virtual lines with 'nowrap'.
• |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight
DEFAULTS
@@ -242,8 +236,7 @@ EDITOR
• |hl-ComplMatchIns| shows matched text of the currently inserted completion.
• |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
• |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
@@ -302,6 +295,8 @@ LUA
• |vim.fs.relpath()| gets relative path compared to base path.
• |vim.fs.dir()| and |vim.fs.find()| can now follow symbolic links,
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.
OPTIONS
@@ -312,10 +307,8 @@ OPTIONS
• 'messagesopt' configures |:messages| and |hit-enter| prompt.
• 'tabclose' controls which tab page to focus when closing a tab page.
• 'eventignorewin' to persistently ignore events in a window.
• 'winborder' sets the default border for |floating-windows|
• 'winborder' add bold style.
• 'winborder' sets the default border for |floating-windows|.
• 'winborder' "bold" style.
• |g:clipboard| accepts a string name to force any builtin clipboard tool.
PERFORMANCE

View File

@@ -17,9 +17,6 @@ M.priorities = {
user = 200,
}
local range_timer --- @type uv.uv_timer_t?
local range_hl_clear --- @type fun()?
--- @class vim.hl.range.Opts
--- @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 finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
---@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)
opts = opts or {}
local regtype = opts.regtype or 'v'
@@ -108,38 +109,38 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
end
end
if range_timer and not range_timer:is_closing() then
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
local extmarks = {} --- @type integer[]
for _, res in ipairs(region) do
local start_row = res[1][2] - 1
local start_col = res[1][3] - 1
local end_row = res[2][2] - 1
local end_col = res[2][3]
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
hl_group = higroup,
end_row = end_row,
end_col = end_col,
priority = priority,
strict = false,
})
table.insert(
extmarks,
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
hl_group = higroup,
end_row = end_row,
end_col = end_col,
priority = priority,
strict = false,
})
)
end
local range_hl_clear = function()
for _, mark in ipairs(extmarks) do
api.nvim_buf_del_extmark(bufnr, ns, mark)
end
end
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
local yank_timer --- @type uv.uv_timer_t?
local yank_hl_clear --- @type fun()?
local yank_ns = api.nvim_create_namespace('nvim.hlyank')
--- 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 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 } })
M.range(bufnr, yank_ns, higroup, "'[", "']", {
yank_timer, yank_hl_clear = M.range(bufnr, yank_ns, higroup, "'[", "']", {
regtype = event.regtype,
inclusive = true,
priority = opts.priority or M.priorities.user,

View File

@@ -139,6 +139,80 @@ describe('vim.hl.range', function()
|
]])
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)
describe('vim.hl.on_yank', function()
@@ -184,9 +258,9 @@ describe('vim.hl.on_yank', function()
eq({ win }, api.nvim__ns_get(ns).wins)
command('wincmd w')
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()
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)
@@ -209,9 +283,9 @@ describe('vim.hl.on_yank', function()
eq({ win }, api.nvim__ns_get(ns).wins)
command('wincmd w')
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()
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)
@@ -246,5 +320,9 @@ describe('vim.hl.on_yank', function()
{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)