From 2ea6c02c698e82f73a430bb4f46a5a432c61f6d0 Mon Sep 17 00:00:00 2001 From: Jonathan Apodaca Date: Sun, 7 Sep 2025 18:05:38 -0600 Subject: [PATCH] (renderer) add more tests; fix bugs in text-change --- lua/u/buffer.lua | 2 +- lua/u/renderer.lua | 134 ++++++++++++++++++++++++++++------------- spec/renderer_spec.lua | 35 +++++++++++ 3 files changed, 129 insertions(+), 42 deletions(-) diff --git a/lua/u/buffer.lua b/lua/u/buffer.lua index 0305349..f749181 100644 --- a/lua/u/buffer.lua +++ b/lua/u/buffer.lua @@ -68,7 +68,7 @@ function Buffer:motion(motion, opts) return Range.from_motion(motion, opts) end ---- @param event string|string[] +--- @param event vim.api.keyset.events|vim.api.keyset.events[] --- @diagnostic disable-next-line: undefined-doc-name --- @param opts vim.api.keyset.create_autocmd function Buffer:autocmd(event, opts) diff --git a/lua/u/renderer.lua b/lua/u/renderer.lua index aa68168..e644a54 100644 --- a/lua/u/renderer.lua +++ b/lua/u/renderer.lua @@ -1,5 +1,15 @@ function _G.URendererOpFuncSwallow() end +local ENABLE_LOG = false + +local function log(...) + if not ENABLE_LOG then return end + + local f = assert(io.open(vim.fs.joinpath(vim.fn.stdpath 'log', 'u.renderer.log'), 'a+')) + f:write(os.date() .. '\t' .. vim.inspect { ... } .. '\n') + f:close() +end + local M = {} local H = {} @@ -251,8 +261,12 @@ function Renderer:render(tree) -- {{{ -- to do that, forwarding the event to an operatorfunc that does -- nothing: if result == '' then - vim.go.operatorfunc = 'v:lua.URendererOpFuncSwallow' - return 'g@ ' + if mode == 'i' then + return '' + else + vim.go.operatorfunc = 'v:lua.URendererOpFuncSwallow' + return 'g@ ' + end end return result end, { buffer = self.bufnr, expr = true, replace_keycodes = true }) @@ -272,6 +286,7 @@ function Renderer:render(tree) -- {{{ self.old = self.curr self.curr = { lines = lines, extmarks = extmarks } self:_reconcile() + vim.cmd.doautocmd { args = { 'User', 'Renderer:' .. tostring(self.bufnr) .. ':render' } } end -- }}} --- @private @@ -362,6 +377,7 @@ function Renderer:_on_text_changed() -- {{{ local l, c = unpack(vim.api.nvim_win_get_cursor(0)) l = l - 1 -- make it actually 0-based local pos_infos = self:get_tags_at({ l, c }, 'i') + log('_on_text_changed', { cursor_0_0 = { l, c }, pos_infos = pos_infos }) for _, pos_info in ipairs(pos_infos) do local extmark_inf = pos_info.extmark local tag = pos_info.tag @@ -374,6 +390,21 @@ function Renderer:_on_text_changed() -- {{{ --- @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 + log('_on_text_changed: fetched current extmark for pos_info', { + pos_info = pos_info, + curr_extmark = { + start_row0 = start_row0, + start_col0 = start_col0, + end_row0 = end_row0, + end_col0 = end_col0, + details = details, + }, + }) + + if start_row0 == end_row0 and start_col0 == end_col0 then + on_change '' + return + end local buf_max_line0 = math.max(1, vim.api.nvim_buf_line_count(self.bufnr) - 1) if end_row0 > buf_max_line0 then @@ -388,25 +419,37 @@ function Renderer:_on_text_changed() -- {{{ or '' end_col0 = last_line:len() end + log('_on_text_changed: after position correction', { + curr_extmark = { + start_row0 = start_row0, + start_col0 = start_col0, + end_row0 = end_row0, + end_col0 = end_col0, + }, + }) if start_row0 == end_row0 and start_col0 == end_col0 then on_change '' - else - local pos1 = { self.bufnr, start_row0 + 1, start_col0 + 1 } - local pos2 = { self.bufnr, end_row0 + 1, end_col0 } - local ok, lines = pcall(vim.fn.getregion, pos1, pos2, { type = 'v' }) - if not ok then - vim.api.nvim_echo({ - { '(u.nvim:getregion:invalid-pos) ', 'ErrorMsg' }, - { - '{ start, end } = ' .. vim.inspect({ pos1, pos2 }, { newline = ' ', indent = '' }), - }, - }, true, {}) - error(lines) - end - local text = table.concat(lines, '\n') - on_change(text) + return end + + local pos1 = { self.bufnr, start_row0 + 1, start_col0 + 1 } + local pos2 = { self.bufnr, end_row0 + 1, end_col0 } + local ok, lines = pcall(vim.fn.getregion, pos1, pos2, { type = 'v' }) + if not ok then + log('_on_text_changed: getregion: invalid-pos ', { + { pos1, pos2 }, + }) + vim.api.nvim_echo({ + { '(u.nvim:getregion:invalid-pos) ', 'ErrorMsg' }, + { + '{ start, end } = ' .. vim.inspect({ pos1, pos2 }, { newline = ' ', indent = '' }), + }, + }, true, {}) + error(lines) + end + local text = table.concat(lines, '\n') + on_change(text) end end end -- }}} @@ -428,33 +471,42 @@ function Renderer:_debug() -- {{{ vim.api.nvim_buf_delete(info_bufnr, { force = true }) end + local function autocmd_callback() + if vim.api.nvim_get_current_win() ~= prev_w then return end + + local l, c = unpack(vim.api.nvim_win_get_cursor(0)) + l = l - 1 -- make it actually 0-based + + local info = { + cursor = { + pos = { l, c }, + tags = self:get_tags_at { l, c }, + extmarks = vim.api.nvim_buf_get_extmarks( + self.bufnr, + self.ns, + { l, c }, + { l, c }, + { details = true, overlap = true } + ), + }, + computed = { + extmarks = self.curr.extmarks, + }, + } + vim.api.nvim_buf_set_lines(info_bufnr, 0, -1, true, vim.split(vim.inspect(info), '\n')) + end + table.insert( ids, vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { - callback = function() - if vim.api.nvim_get_current_win() ~= prev_w then return end - - local l, c = unpack(vim.api.nvim_win_get_cursor(0)) - l = l - 1 -- make it actually 0-based - - local info = { - cursor = { - pos = { l, c }, - tags = self:get_tags_at { l, c }, - extmarks = vim.api.nvim_buf_get_extmarks( - self.bufnr, - self.ns, - { l, c }, - { l, c }, - { details = true, overlap = true } - ), - }, - computed = { - extmarks = self.curr.extmarks, - }, - } - vim.api.nvim_buf_set_lines(info_bufnr, 0, -1, true, vim.split(vim.inspect(info), '\n')) - end, + callback = autocmd_callback, + }) + ) + table.insert( + ids, + vim.api.nvim_create_autocmd({ 'User' }, { + pattern = 'Renderer:' .. tostring(self.bufnr) .. ':render', + callback = autocmd_callback, }) ) table.insert( diff --git a/spec/renderer_spec.lua b/spec/renderer_spec.lua index a4d5833..5c4c670 100644 --- a/spec/renderer_spec.lua +++ b/spec/renderer_spec.lua @@ -201,6 +201,41 @@ describe('Renderer', function() assert.are.same(buf:all():text(), 'bleh') assert.are.same(captured_changed_text, 'bleh') + + vim.fn.setreg('"', '') + vim.cmd [[normal! ggdG]] + -- We'll call the handler ourselves: + r:_on_text_changed() + + assert.are.same(buf:all():text(), '') + assert.are.same(captured_changed_text, '') + end) + + withbuf({}, function() + local Buffer = require 'u.buffer' + local buf = Buffer.current() + local r = R.Renderer.new(0) + --- @type string? + local captured_changed_text = nil + r:render { + 'prefix:', + R.h('text', { + on_change = function(txt) captured_changed_text = txt end, + }, { + 'one', + }), + 'suffix', + } + + vim.fn.setreg('"', 'bleh') + vim.api.nvim_win_set_cursor(0, { 1, 9 }) + vim.cmd [[normal! vhhd]] + -- For some reason, the autocmd does not fire in the busted environment. + -- We'll call the handler ourselves: + r:_on_text_changed() + + assert.are.same(buf:all():text(), 'prefix:suffix') + assert.are.same(captured_changed_text, '') end) end)