better extmark inclusivity
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m21s

This commit is contained in:
Jonathan Apodaca 2025-06-17 23:37:16 -06:00
parent 28714fb51b
commit 103270a241
4 changed files with 52 additions and 17 deletions

View File

@ -10,9 +10,6 @@ local Renderer = require('u.renderer').Renderer
local h = require('u.renderer').h
local tracker = require 'u.tracker'
-- Utility to trim brackets from strings:
local function trimb(s) return (s:gsub('^%[(.*)%]$', '%1')) end
-- Create a new, temporary, buffer to the side:
vim.cmd.vnew()
vim.bo.buftype = 'nofile'
@ -21,13 +18,13 @@ vim.bo.buflisted = false
local renderer = Renderer.new()
-- Create two signals:
local s_name = tracker.create_signal '[whoever-you-are]'
local s_age = tracker.create_signal '[ideally-a-number]'
local s_name = tracker.create_signal 'whoever-you-are'
local s_age = tracker.create_signal 'ideally-a-number'
-- We can create derived information from the signals above. Say we want to do
-- some validation on the input for `age`: we can do that with a memo:
local s_age_info = tracker.create_memo(function()
local age_raw = trimb(s_age:get())
local age_raw = s_age:get()
local age_digits = age_raw:match '^%s*(%d+)%s*$'
local age_n = age_digits and tonumber(age_digits) or nil
return {
@ -59,9 +56,8 @@ tracker.create_effect(function()
on_change = function(text) s_name:set(text) end,
}, name),
},
'\n',
{
'Age: ',
'\nAge: ',
h.Structure({
on_change = function(text) s_age:set(text) end,
}, age),
@ -72,7 +68,7 @@ tracker.create_effect(function()
-- Show the values of the signals here, too, so that we can see the
-- reactivity in action. If you change the values in the tags above, you
-- can see the changes reflected here immediately.
{ 'Hello, "', trimb(name), '"!' },
{ 'Hello, "', name, '"!' },
--
-- A more complex example: we can do much more complex rendering, based on

View File

@ -382,6 +382,8 @@ function Range:save_to_extmark()
end_col = 0
end
local id = vim.api.nvim_buf_set_extmark(r.start.bufnr, NS, r.start.lnum - 1, r.start.col - 1, {
right_gravity = false,
end_right_gravity = true,
end_row = end_row,
end_col = end_col,
})

View File

@ -28,7 +28,6 @@ M.h = setmetatable({}, {
}
end,
__index = function(_, name)
-- vim.print('dynamic hl ' .. name)
return function(attributes, children)
return M.h('text', vim.tbl_deep_extend('force', { hl = name }, attributes), children)
end
@ -324,6 +323,13 @@ function Renderer:_reconcile() -- {{{
id = extmark.id,
end_row = extmark.stop[1],
end_col = extmark.stop[2],
-- If we change the text starting from the beginning (where the extmark
-- is), we don't want the extmark to move to the right.
right_gravity = false,
-- If we change the text starting from the end (where the end extmark
-- is), we don't want the extmark to move to stay stationary: we want
-- it to move to the right.
end_right_gravity = true,
}, extmark.opts)
)
end
@ -385,7 +391,7 @@ function Renderer:_on_text_changed()
local end_row0, end_col0 = details.end_row, details.end_col
if start_row0 == end_row0 and start_col0 == end_col0 then
-- Invalid extmark
on_change ''
else
local lines = vim.fn.getregion(
{ self.bufnr, start_row0 + 1, start_col0 + 1 },
@ -405,9 +411,11 @@ end
---
--- @private (private for now)
--- @param pos0 [number; number]
--- @param mode string?
--- @return { extmark: u.renderer.RendererExtmark; tag: u.renderer.Tag; }[]
function Renderer:get_pos_infos(pos0) -- {{{
function Renderer:get_pos_infos(pos0, mode) -- {{{
local cursor_line0, cursor_col0 = pos0[1], pos0[2]
if not mode then mode = vim.api.nvim_get_mode().mode end
-- The cursor (block) occupies **two** extmark spaces: one for it's left
-- edge, and one for it's right. We need to do our own intersection test,
@ -437,10 +445,23 @@ function Renderer:get_pos_infos(pos0) -- {{{
--- @param ext u.renderer.RendererExtmark
:filter(function(ext)
if ext.stop[1] ~= nil and ext.stop[2] ~= nil then
return cursor_line0 >= ext.start[1]
and cursor_col0 >= ext.start[2]
and cursor_line0 <= ext.stop[1]
and cursor_col0 < ext.stop[2]
-- If we've "ciw" and "collapsed" an extmark onto the cursor,
-- the cursor pos will equal the exmark's start AND end. In this
-- case, we want to include the extmark.
return (
cursor_line0 == ext.start[1]
and cursor_col0 == ext.start[2]
and cursor_line0 == ext.stop[1]
and cursor_col0 == ext.stop[2]
)
or (
cursor_line0 >= ext.start[1]
and cursor_col0 >= ext.start[2]
and cursor_line0 <= ext.stop[1]
-- In insert mode, the cursor is "thin", so <= to compensate:
-- In normal mode, the cursor is "wide", so < to compensate:
and (mode == 'i' and cursor_col0 <= ext.stop[2] or cursor_col0 < ext.stop[2])
)
else
return true
end

View File

@ -103,11 +103,27 @@ describe('Renderer', function()
}
local pos_infos = r:get_pos_infos { 0, 2 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
pos_infos = r:get_pos_infos { 0, 4 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
pos_infos = r:get_pos_infos { 0, 5 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup2')
assert.are.same(pos_infos[1].extmark.start, { 0, 5 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 11 })
-- In insert mode, bounds are eagerly included:
pos_infos = r:get_pos_infos({ 0, 5 }, 'i')
assert.are.same(#pos_infos, 2)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[2].tag.attributes.hl, 'HighlightGroup2')
end)
end)