Skip to content

Commit a68b879

Browse files
zeertzjqhuangyingw
authored andcommitted
refactor(lua): rewrite vim.highlight.range() (neovim#28986)
- Use getregionpos(). - Use a single extmark for non-blockwise selection.
1 parent 562d118 commit a68b879

File tree

3 files changed

+127
-15
lines changed

3 files changed

+127
-15
lines changed

Diff for: runtime/doc/lua.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -646,8 +646,8 @@ vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts})
646646
{finish} (`integer[]|string`) End of region as a (line, column)
647647
tuple or string accepted by |getpos()|
648648
{opts} (`table?`) A table with the following fields:
649-
{regtype}? (`string`, default: `'charwise'`) Type of
650-
range. See |setreg()|
649+
{regtype}? (`string`, default: `'v'` i.e. charwise) Type
650+
of range. See |getregtype()|
651651
{inclusive}? (`boolean`, default: `false`) Indicates
652652
whether the range is end-inclusive
653653
{priority}? (`integer`, default:

Diff for: runtime/lua/vim/highlight.lua

+42-13
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ M.priorities = {
2020
--- @class vim.highlight.range.Opts
2121
--- @inlinedoc
2222
---
23-
--- Type of range. See [setreg()]
24-
--- (default: `'charwise'`)
23+
--- Type of range. See [getregtype()]
24+
--- (default: `'v'` i.e. charwise)
2525
--- @field regtype? string
2626
---
2727
--- Indicates whether the range is end-inclusive
@@ -49,20 +49,49 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
4949
local priority = opts.priority or M.priorities.user
5050
local scoped = opts._scoped or false
5151

52-
-- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
53-
-- bounds (row, col, end_row, end_col) as multiline regions are natively
54-
-- supported now
55-
local region = vim.region(bufnr, start, finish, regtype, inclusive)
56-
for linenr, cols in pairs(region) do
57-
local end_row
58-
if cols[2] == -1 then
59-
end_row = linenr + 1
60-
cols[2] = 0
52+
local pos1 = type(start) == 'string' and vim.fn.getpos(start)
53+
or { bufnr, start[1] + 1, start[2] + 1, 0 }
54+
local pos2 = type(finish) == 'string' and vim.fn.getpos(finish)
55+
or { bufnr, finish[1] + 1, finish[2] + 1, 0 }
56+
57+
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
58+
pos1[2] = math.min(pos1[2], buf_line_count)
59+
pos2[2] = math.min(pos2[2], buf_line_count)
60+
61+
if pos1[2] <= 0 or pos1[3] <= 0 or pos2[2] <= 0 or pos2[3] <= 0 then
62+
return
63+
end
64+
65+
vim.api.nvim_buf_call(bufnr, function()
66+
local max_col1 = vim.fn.col({ pos1[2], '$' })
67+
pos1[3] = math.min(pos1[3], max_col1)
68+
local max_col2 = vim.fn.col({ pos2[2], '$' })
69+
pos2[3] = math.min(pos2[3], max_col2)
70+
end)
71+
72+
local region = vim.fn.getregionpos(pos1, pos2, {
73+
type = regtype,
74+
exclusive = not inclusive,
75+
eol = true,
76+
})
77+
-- For non-blockwise selection, use a single extmark.
78+
if regtype == 'v' or regtype == 'V' then
79+
region = { { region[1][1], region[#region][2] } }
80+
end
81+
82+
for _, res in ipairs(region) do
83+
local start_row = res[1][2] - 1
84+
local start_col = res[1][3] - 1
85+
local end_row = res[2][2] - 1
86+
local end_col = res[2][3]
87+
if regtype == 'V' then
88+
end_row = end_row + 1
89+
end_col = 0
6190
end
62-
api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], {
91+
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
6392
hl_group = higroup,
6493
end_row = end_row,
65-
end_col = cols[2],
94+
end_col = end_col,
6695
priority = priority,
6796
strict = false,
6897
scoped = scoped,

Diff for: test/functional/lua/highlight_spec.lua

+83
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local t = require('test.testutil')
22
local n = require('test.functional.testnvim')()
3+
local Screen = require('test.functional.ui.screen')
34

45
local exec_lua = n.exec_lua
56
local eq = t.eq
@@ -9,6 +10,88 @@ local command = n.command
910
local clear = n.clear
1011
local api = n.api
1112

13+
describe('vim.highlight.range', function()
14+
local screen
15+
16+
before_each(function()
17+
clear()
18+
screen = Screen.new(60, 6)
19+
screen:add_extra_attr_ids({
20+
[100] = { foreground = Screen.colors.Blue, background = Screen.colors.Yellow, bold = true },
21+
})
22+
screen:attach()
23+
api.nvim_set_option_value('list', true, {})
24+
api.nvim_set_option_value('listchars', 'eol:$', {})
25+
api.nvim_buf_set_lines(0, 0, -1, true, {
26+
'asdfghjkl',
27+
'«口=口»',
28+
'qwertyuiop',
29+
'口口=口口',
30+
'zxcvbnm',
31+
})
32+
end)
33+
34+
it('works with charwise selection', function()
35+
exec_lua([[
36+
local ns = vim.api.nvim_create_namespace('')
37+
vim.highlight.range(0, ns, 'Search', { 1, 5 }, { 3, 10 })
38+
]])
39+
screen:expect([[
40+
^asdfghjkl{1:$} |
41+
«口{10:=口»}{100:$} |
42+
{10:qwertyuiop}{100:$} |
43+
{10:口口=口}口{1:$} |
44+
zxcvbnm{1:$} |
45+
|
46+
]])
47+
end)
48+
49+
it('works with linewise selection', function()
50+
exec_lua([[
51+
local ns = vim.api.nvim_create_namespace('')
52+
vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { regtype = 'V' })
53+
]])
54+
screen:expect([[
55+
{10:^asdfghjkl}{100:$} |
56+
{10:«口=口»}{100:$} |
57+
{10:qwertyuiop}{100:$} |
58+
{10:口口=口口}{100:$} |
59+
{10:zxcvbnm}{100:$} |
60+
|
61+
]])
62+
end)
63+
64+
it('works with blockwise selection', function()
65+
exec_lua([[
66+
local ns = vim.api.nvim_create_namespace('')
67+
vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 4 }, { regtype = '\022' })
68+
]])
69+
screen:expect([[
70+
{10:^asdf}ghjkl{1:$} |
71+
{10:«口=}口»{1:$} |
72+
{10:qwer}tyuiop{1:$} |
73+
{10:口口}=口口{1:$} |
74+
{10:zxcv}bnm{1:$} |
75+
|
76+
]])
77+
end)
78+
79+
it('works with blockwise selection with width', function()
80+
exec_lua([[
81+
local ns = vim.api.nvim_create_namespace('')
82+
vim.highlight.range(0, ns, 'Search', { 0, 4 }, { 4, 7 }, { regtype = '\0226' })
83+
]])
84+
screen:expect([[
85+
^asdf{10:ghjkl}{1:$} |
86+
«口={10:口»}{1:$} |
87+
qwer{10:tyuiop}{1:$} |
88+
口口{10:=口口}{1:$} |
89+
zxcv{10:bnm}{1:$} |
90+
|
91+
]])
92+
end)
93+
end)
94+
1295
describe('vim.highlight.on_yank', function()
1396
before_each(function()
1497
clear()

0 commit comments

Comments
 (0)