watch text-changes with signals
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m23s

This commit is contained in:
Jonathan Apodaca 2025-06-12 23:16:28 -06:00
parent 9054f4453f
commit 9bd0ff2088
2 changed files with 49 additions and 85 deletions

View File

@ -22,29 +22,10 @@ tracker.create_effect(function()
-- constructed with `h(...)` calls). To help organize the markup, text and -- constructed with `h(...)` calls). To help organize the markup, text and
-- tags can be nested in tables at any depth. Line breaks must be specified -- tags can be nested in tables at any depth. Line breaks must be specified
-- manually, with '\n'. -- manually, with '\n'.
local text_ref = ui_buf.renderer:create_ref()
ui_buf:render { ui_buf:render {
'Reactive Counter Example\n', 'Reactive Counter Example\n',
'========================\n\n', '========================\n\n',
{
'Text field: ',
h('text', { hl = 'DiffAdd', ref = text_ref }, { '[]' }),
'\n',
h('text', {
hl = 'DiffAdd',
nmap = {
['<CR>'] = function()
vim.notify(text_ref:text())
return ''
end,
},
}, ' Submit '),
},
'\n',
'\n',
{ 'Counter: ', tostring(count), '\n' }, { 'Counter: ', tostring(count), '\n' },
'\n', '\n',

View File

@ -1,3 +1,5 @@
local Signal = require('u.tracker').Signal
local M = {} local M = {}
local H = {} local H = {}
@ -33,52 +35,6 @@ M.h = setmetatable({}, {
end, end,
}) })
-- TagRef {{{
--- @class u.renderer.TagRef
--- @field tag? u.renderer.Tag
--- @field renderer u.renderer.Renderer
local TagRef = {}
TagRef.__index = TagRef
function TagRef:extmark()
if not self.tag then return {} end
--- @type u.renderer.RendererExtmark?
local extmark_inf = vim
.iter(self.renderer.curr.extmarks)
--- @param x u.renderer.RendererExtmark
:find(function(x) return x.tag == self.tag end)
if not extmark_inf then return {} end
local extmark = vim.api.nvim_buf_get_extmark_by_id(
self.renderer.bufnr,
self.renderer.ns,
extmark_inf.id,
{ details = true }
)
return extmark
end
function TagRef:range()
local extmark = self:extmark()
if not extmark then return nil end
local Range = require 'u.range'
return Range.from_extmark(self.renderer.bufnr, extmark)
end
function TagRef:lines()
local range = self:range()
if not range then return {} end
return range:lines()
end
function TagRef:text()
local range = self:range()
if not range then return '' end
return range:text()
end
-- }}}
-- Renderer {{{ -- Renderer {{{
--- @class u.renderer.RendererExtmark --- @class u.renderer.RendererExtmark
--- @field id? number --- @field id? number
@ -124,11 +80,15 @@ function Renderer.new(bufnr) -- {{{
old = { lines = {}, extmarks = {} }, old = { lines = {}, extmarks = {} },
curr = { lines = {}, extmarks = {} }, curr = { lines = {}, extmarks = {} },
}, Renderer) }, Renderer)
vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, {
buffer = bufnr,
callback = function() self:_on_text_changed() end,
})
return self return self
end -- }}} end -- }}}
function Renderer:create_ref() return setmetatable({ renderer = self }, TagRef) end
--- @param opts { --- @param opts {
--- tree: u.renderer.Tree; --- tree: u.renderer.Tree;
--- on_tag?: fun(tag: u.renderer.Tag, start0: [number, number], stop0: [number, number]): any; --- on_tag?: fun(tag: u.renderer.Tag, start0: [number, number], stop0: [number, number]): any;
@ -276,14 +236,13 @@ function Renderer:render(tree) -- {{{
tag.attributes.extmark.hl_group = tag.attributes.extmark.hl_group or hl tag.attributes.extmark.hl_group = tag.attributes.extmark.hl_group or hl
end end
local extmark_opts = tag.attributes.extmark local extmark_opts = tag.attributes.extmark or {}
-- Set any necessary keymaps: -- Set any necessary keymaps:
for _, mode in ipairs { 'i', 'n', 'v', 'x', 'o' } do for _, mode in ipairs { 'i', 'n', 'v', 'x', 'o' } do
for lhs, _ in pairs(tag.attributes[mode .. 'map'] or {}) do for lhs, _ in pairs(tag.attributes[mode .. 'map'] or {}) do
-- Force creating an extmark if there are key handlers. To accurately -- Force creating an extmark if there are key handlers. To accurately
-- sense the bounds of the text, we need an extmark: -- sense the bounds of the text, we need an extmark:
extmark_opts = extmark_opts or {}
vim.keymap.set( vim.keymap.set(
mode, mode,
lhs, lhs,
@ -293,15 +252,6 @@ function Renderer:render(tree) -- {{{
end end
end end
-- Check for refs:
if tag.attributes.ref and getmetatable(tag.attributes.ref) == TagRef then
--- @type u.renderer.TagRef
local ref = tag.attributes.ref
ref.tag = tag
extmark_opts = extmark_opts or {}
end
if extmark_opts then
table.insert(extmarks, { table.insert(extmarks, {
start = start0, start = start0,
stop = stop0, stop = stop0,
@ -309,7 +259,6 @@ function Renderer:render(tree) -- {{{
tag = tag, tag = tag,
}) })
end end
end
end, -- }}} end, -- }}}
} }
@ -404,6 +353,40 @@ function Renderer:_expr_map_callback(mode, lhs) -- {{{
return cancel and '' or lhs return cancel and '' or lhs
end -- }}} end -- }}}
function Renderer:_on_text_changed()
--- @type integer, integer
local l, c = unpack(vim.api.nvim_win_get_cursor(0))
l = l - 1 -- make it actually 0-based
local pos_infos = self:get_pos_infos { l, c }
for _, pos_info in ipairs(pos_infos) do
local extmark_inf = pos_info.extmark
local tag = pos_info.tag
if tag.attributes.signal and getmetatable(tag.attributes.signal) == Signal then
--- @type u.Signal
local signal = tag.attributes.signal
local extmark =
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, self.ns, extmark_inf.id, { details = true })
--- @type integer, integer, vim.api.keyset.extmark_details
local start_row0, start_col0, details = unpack(extmark)
local end_row0, end_col0 = details.end_row, details.end_col
if start_row0 == end_row0 and start_col0 == end_col0 then
-- Invalid extmark
else
local lines = vim.fn.getregion(
{ self.bufnr, start_row0 + 1, start_col0 + 1 },
{ self.bufnr, end_row0 + 1, end_col0 },
{ type = 'v' }
)
local text = table.concat(lines, '\n')
if text ~= signal:get() then signal:schedule_set(text) end
end
end
end
end
--- Returns pairs of extmarks and tags associate with said extmarks. The --- Returns pairs of extmarks and tags associate with said extmarks. The
--- returned tags/extmarks are sorted smallest (innermost) to largest --- returned tags/extmarks are sorted smallest (innermost) to largest
--- (outermost). --- (outermost).