diff options
-rw-r--r-- | runtime/doc/lua.txt | 4 | ||||
-rw-r--r-- | runtime/lua/vim/highlight.lua | 55 | ||||
-rw-r--r-- | test/functional/lua/highlight_spec.lua | 83 |
3 files changed, 127 insertions, 15 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 3d8453c5a2..64a3014388 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -646,8 +646,8 @@ vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts}) • {finish} (`integer[]|string`) End of region as a (line, column) tuple or string accepted by |getpos()| • {opts} (`table?`) A table with the following fields: - • {regtype}? (`string`, default: `'charwise'`) Type of - range. See |setreg()| + • {regtype}? (`string`, default: `'v'` i.e. charwise) Type + of range. See |getregtype()| • {inclusive}? (`boolean`, default: `false`) Indicates whether the range is end-inclusive • {priority}? (`integer`, default: diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index f278bd357f..89298ce568 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -20,8 +20,8 @@ M.priorities = { --- @class vim.highlight.range.Opts --- @inlinedoc --- ---- Type of range. See [setreg()] ---- (default: `'charwise'`) +--- Type of range. See [getregtype()] +--- (default: `'v'` i.e. charwise) --- @field regtype? string --- --- Indicates whether the range is end-inclusive @@ -49,20 +49,49 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local priority = opts.priority or M.priorities.user local scoped = opts._scoped or false - -- TODO: in case of 'v', 'V' (not block), this should calculate equivalent - -- bounds (row, col, end_row, end_col) as multiline regions are natively - -- supported now - local region = vim.region(bufnr, start, finish, regtype, inclusive) - for linenr, cols in pairs(region) do - local end_row - if cols[2] == -1 then - end_row = linenr + 1 - cols[2] = 0 + local pos1 = type(start) == 'string' and vim.fn.getpos(start) + or { bufnr, start[1] + 1, start[2] + 1, 0 } + local pos2 = type(finish) == 'string' and vim.fn.getpos(finish) + or { bufnr, finish[1] + 1, finish[2] + 1, 0 } + + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) + pos1[2] = math.min(pos1[2], buf_line_count) + pos2[2] = math.min(pos2[2], buf_line_count) + + if pos1[2] <= 0 or pos1[3] <= 0 or pos2[2] <= 0 or pos2[3] <= 0 then + return + end + + vim.api.nvim_buf_call(bufnr, function() + local max_col1 = vim.fn.col({ pos1[2], '$' }) + pos1[3] = math.min(pos1[3], max_col1) + local max_col2 = vim.fn.col({ pos2[2], '$' }) + pos2[3] = math.min(pos2[3], max_col2) + end) + + local region = vim.fn.getregionpos(pos1, pos2, { + type = regtype, + exclusive = not inclusive, + eol = true, + }) + -- For non-blockwise selection, use a single extmark. + if regtype == 'v' or regtype == 'V' then + region = { { region[1][1], region[#region][2] } } + end + + 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] + if regtype == 'V' then + end_row = end_row + 1 + end_col = 0 end - api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], { + api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, { hl_group = higroup, end_row = end_row, - end_col = cols[2], + end_col = end_col, priority = priority, strict = false, scoped = scoped, diff --git a/test/functional/lua/highlight_spec.lua b/test/functional/lua/highlight_spec.lua index c9f2d0a47f..8f099ac233 100644 --- a/test/functional/lua/highlight_spec.lua +++ b/test/functional/lua/highlight_spec.lua @@ -1,5 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') local exec_lua = n.exec_lua local eq = t.eq @@ -9,6 +10,88 @@ local command = n.command local clear = n.clear local api = n.api +describe('vim.highlight.range', function() + local screen + + before_each(function() + clear() + screen = Screen.new(60, 6) + screen:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.Blue, background = Screen.colors.Yellow, bold = true }, + }) + screen:attach() + api.nvim_set_option_value('list', true, {}) + api.nvim_set_option_value('listchars', 'eol:$', {}) + api.nvim_buf_set_lines(0, 0, -1, true, { + 'asdfghjkl', + '«口=口»', + 'qwertyuiop', + '口口=口口', + 'zxcvbnm', + }) + end) + + it('works with charwise selection', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 1, 5 }, { 3, 10 }) + ]]) + screen:expect([[ + ^asdfghjkl{1:$} | + «口{10:=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口}口{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) + + it('works with linewise selection', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { regtype = 'V' }) + ]]) + screen:expect([[ + {10:^asdfghjkl}{100:$} | + {10:«口=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口口}{100:$} | + {10:zxcvbnm}{100:$} | + | + ]]) + end) + + it('works with blockwise selection', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 4 }, { regtype = '\022' }) + ]]) + screen:expect([[ + {10:^asdf}ghjkl{1:$} | + {10:«口=}口»{1:$} | + {10:qwer}tyuiop{1:$} | + {10:口口}=口口{1:$} | + {10:zxcv}bnm{1:$} | + | + ]]) + end) + + it('works with blockwise selection with width', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 4 }, { 4, 7 }, { regtype = '\0226' }) + ]]) + screen:expect([[ + ^asdf{10:ghjkl}{1:$} | + «口={10:口»}{1:$} | + qwer{10:tyuiop}{1:$} | + 口口{10:=口口}{1:$} | + zxcv{10:bnm}{1:$} | + | + ]]) + end) +end) + describe('vim.highlight.on_yank', function() before_each(function() clear() |