aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiddhant Agarwal <68201519+siddhantdev@users.noreply.github.com>2025-04-03 19:56:56 +0530
committerGitHub <noreply@github.com>2025-04-03 07:26:56 -0700
commiteae2d3b145c724e8c801c7c22a2ccc3bde5534eb (patch)
treed30812a2e6b14b82aab0eb05085f424abaf84c04
parent974a3aa2c4c34f97ce015466a7dfc5fd727fccc2 (diff)
downloadrneovim-eae2d3b145c724e8c801c7c22a2ccc3bde5534eb.tar.gz
rneovim-eae2d3b145c724e8c801c7c22a2ccc3bde5534eb.tar.bz2
rneovim-eae2d3b145c724e8c801c7c22a2ccc3bde5534eb.zip
feat(vim.hl): allow multiple timed highlights simultaneously #33283
Problem: Currently vim.hl.range only allows one timed highlight. Creating another one, removes the old one. Solution: vim.hl.range now returns a timer and a function. The timer keeps track of how much time is left in the highlight and the function allows you to clear it, letting the user decide what to do with old highlights.
-rw-r--r--runtime/doc/lua.txt6
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/lua/vim/hl.lua57
-rw-r--r--test/functional/lua/hl_spec.lua74
4 files changed, 113 insertions, 26 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index be118cf790..c580c55a5e 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -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*
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 72ab3373f7..72d394dd68 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -104,7 +104,7 @@ The following new features were added.
API
-• todo
+• |vim.hl.range()| now allows multiple timed highlights
DEFAULTS
diff --git a/runtime/lua/vim/hl.lua b/runtime/lua/vim/hl.lua
index a2b06c9727..d15ee1fd10 100644
--- a/runtime/lua/vim/hl.lua
+++ b/runtime/lua/vim/hl.lua
@@ -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,
diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua
index 12be01e0a5..e3c6c706c7 100644
--- a/test/functional/lua/hl_spec.lua
+++ b/test/functional/lua/hl_spec.lua
@@ -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()