From 856bc6d284f6c9f31f21d01e69b44457502a773f Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Sun, 17 May 2026 16:39:25 +0800 Subject: [PATCH] fix(range): handle inclusive/exclusive positions on multibyte characters --- runtime/lua/vim/range.lua | 14 ++++++++------ test/functional/lua/range_spec.lua | 12 ++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/runtime/lua/vim/range.lua b/runtime/lua/vim/range.lua index dd22d1151e..e069df4a3a 100644 --- a/runtime/lua/vim/range.lua +++ b/runtime/lua/vim/range.lua @@ -150,11 +150,12 @@ end ---@param buf integer ---@return integer, integer local function to_inclusive_pos(buf, row, col) + local line = get_line(buf, row) if col > 0 then - col = col - 1 + col = col + vim.str_utf_start(line, col) - 1 elseif col == 0 and row > 0 then row = row - 1 - col = #get_line(buf, row) + col = #line > 0 and #line + vim.str_utf_start(line, #line) - 1 or 0 end return row, col @@ -165,11 +166,12 @@ end ---@param buf integer ---@return integer, integer local function to_exclusive_pos(buf, row, col) - if col >= #get_line(buf, row) then + local line = get_line(buf, row) + if col >= #line then row = row + 1 col = 0 else - col = col + 1 + col = col + vim.str_utf_end(line, col + 1) + 1 end return row, col @@ -179,7 +181,7 @@ end ---@param r1 vim.Range ---@param r2 vim.Range function M.__lt(r1, r2) - if r1:is_empty() then + if r1:is_empty() or r2:is_empty() then return cmp_pos(r1[3], r1[4], r2[1], r2[2]) ~= 1 end @@ -191,7 +193,7 @@ end ---@param r1 vim.Range ---@param r2 vim.Range function M.__le(r1, r2) - if r1:is_empty() then + if r1:is_empty() or r2:is_empty() then return cmp_pos(r1[3], r1[4], r2[1], r2[2]) ~= 1 end diff --git a/test/functional/lua/range_spec.lua b/test/functional/lua/range_spec.lua index bb2235d6c6..d165080d81 100644 --- a/test/functional/lua/range_spec.lua +++ b/test/functional/lua/range_spec.lua @@ -80,6 +80,18 @@ describe('vim.range', function() }, range) end) + it('converts between inclusive mark ranges ending on multibyte characters', function() + insert('🙂') + + local range, mark_range = exec_lua(function() + vim.o.selection = 'inclusive' + local range = vim.range.mark(0, 1, 0, 1, 0) + return { range[1], range[2], range[3], range[4] }, { range:to_mark() } + end) + eq({ 0, 0, 0, 4 }, range) + eq({ 1, 0, 1, 0 }, mark_range) + end) + it('checks whether a range contains a position', function() eq( true,