From 9383f0b3ef020c58563aba540e7f3fd18458349e Mon Sep 17 00:00:00 2001 From: Jonathan Apodaca Date: Mon, 6 Apr 2026 17:37:29 -0600 Subject: [PATCH] add some extmark-edge-case tests from morph.nvim --- lua/u.lua | 4 ++-- spec/u_spec.lua | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lua/u.lua b/lua/u.lua index 0740f24..8055f20 100644 --- a/lua/u.lua +++ b/lua/u.lua @@ -360,7 +360,7 @@ function Extmark.from_range(range, nsid) local r = range:to_charwise() local stop = r.stop or r.start local end_row = stop.lnum - 1 - local end_col = stop.col + local end_col = r:is_empty() and (r.start.col - 1) or stop.col if range.mode == 'V' then end_row = end_row + 1 end_col = 0 @@ -702,7 +702,7 @@ end function Range:to_charwise() local r = self:clone() r.mode = 'v' - if r.stop:is_col_max() then r.stop = r.stop:as_real() end + if r.stop and r.stop:is_col_max() then r.stop = r.stop:as_real() end return r end diff --git a/spec/u_spec.lua b/spec/u_spec.lua index ebc3efc..e0a5f8b 100644 --- a/spec/u_spec.lua +++ b/spec/u_spec.lua @@ -1370,6 +1370,61 @@ describe('Extmark additional coverage', function() end) end) +describe('Extmark edge cases', function() + it('tracks multi-byte characters correctly', function() + withbuf({ 'πŸš€πŸŒŸ hello δ½ ε₯½δΈ–η•Œ' }, function() + -- The string is 27 bytes: πŸš€(4) + 🌟(4) + space(1) + hello(5) + space(1) + δ½ (3) + ε₯½(3) + δΈ–(3) + η•Œ(3) + local r = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 27), 'v') + local ext = r:save_to_extmark() + local ext_range = ext:range() + assert.are.same('πŸš€πŸŒŸ hello δ½ ε₯½δΈ–η•Œ', ext_range:text()) + end) + end) + + it('clamps start position after buffer shrink', function() + withbuf({ 'line 1', 'line 2', 'line 3', '' }, function() + local r = Range.from_buf_text() + local ext = r:save_to_extmark() + + -- Delete last line + vim.api.nvim_buf_set_lines(0, 3, 4, true, {}) + + -- Get range from extmark - should clamp to valid buffer + local ext_range = ext:range() + assert.is_true(ext_range.stop.lnum <= 3) + end) + end) + + it('handles zero-width extmark (empty range)', function() + withbuf({ 'hello world' }, function() + local r = Range.new(Pos.new(nil, 1, 1), nil, 'v') + local ext = r:save_to_extmark() + local ext_range = ext:range() + assert.is_true(ext_range:is_empty()) + end) + end) + + it('handles extmark at buffer start', function() + withbuf({ 'first', 'second', 'third' }, function() + local r = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 5), 'v') + local ext = r:save_to_extmark() + local ext_range = ext:range() + assert.are.same(1, ext_range.start.lnum) + assert.are.same(1, ext_range.start.col) + end) + end) + + it('handles extmark at buffer end', function() + withbuf({ 'first', 'second', 'third' }, function() + local r = Range.new(Pos.new(nil, 3, 1), Pos.new(nil, 3, 5), 'v') + local ext = r:save_to_extmark() + local ext_range = ext:range() + assert.are.same(3, ext_range.stop.lnum) + assert.are.same(5, ext_range.stop.col) + end) + end) +end) + describe('utils', function() local u = require 'u'