From db391c5715e76b5e393d6fcc5e98a64390bb7b6f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 8 Mar 2026 14:09:36 -0400 Subject: [PATCH] feat(buffer): track line changes via `on_bytes` to keep `_meta` aligned Problem: `_meta` is a positional array keyed by line number. Line insertions and deletions during editing desync it from actual buffer content, breaking `get_fold()`, cursor-based task lookups, and extmark re-application. Solution: attach an `on_bytes` callback that adjusts `_meta` on line insertions/deletions and tracks dirty rows. Remove the manual `_meta` insert from `open_line()` since `on_bytes` now handles it. Reset dirty rows on each full render. --- lua/pending/buffer.lua | 49 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index da3caca..367a8bb 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -26,6 +26,10 @@ local _initial_fold_loaded = false local _filter_predicates = {} ---@type table local _hidden_ids = {} +---@type table +local _dirty_rows = {} +---@type boolean +local _on_bytes_active = false ---@return pending.LineMeta[] function M.meta() @@ -95,6 +99,48 @@ function M.clear_marks(b) vim.api.nvim_buf_clear_namespace(bufnr, ns_inline, 0, -1) end +---@return table +function M.dirty_rows() + return _dirty_rows +end + +---@return nil +function M.clear_dirty_rows() + _dirty_rows = {} +end + +---@param bufnr integer +---@return nil +function M.attach_bytes(bufnr) + if _on_bytes_active then + return + end + _on_bytes_active = true + vim.api.nvim_buf_attach(bufnr, false, { + on_bytes = function(_, buf, _, start_row, _, _, old_end_row, _, _, new_end_row, _, _) + if buf ~= task_bufnr then + _on_bytes_active = false + return true + end + local delta = new_end_row - old_end_row + if delta > 0 then + for _ = 1, delta do + table.insert(_meta, start_row + 2, { type = 'task' }) + end + elseif delta < 0 then + for _ = 1, -delta do + if _meta[start_row + 2] then + table.remove(_meta, start_row + 2) + end + end + end + for r = start_row + 1, start_row + 1 + math.max(0, new_end_row) do + _dirty_rows[r] = true + end + end, + }) +end + ---@return nil function M.persist_folds() log.debug( @@ -208,7 +254,6 @@ function M.open_line(above) local insert_row = above and (row - 1) or row vim.bo[bufnr].modifiable = true vim.api.nvim_buf_set_lines(bufnr, insert_row, insert_row, false, { '- [ ] ' }) - table.insert(_meta, insert_row + 1, { type = 'task' }) vim.api.nvim_win_set_cursor(0, { insert_row + 1, 6 }) vim.cmd('startinsert!') end @@ -452,6 +497,7 @@ function M.render(bufnr) end _meta = line_meta + _dirty_rows = {} snapshot_folds(bufnr) vim.bo[bufnr].modifiable = true @@ -511,6 +557,7 @@ function M.open() if not (task_bufnr and vim.api.nvim_buf_is_valid(task_bufnr)) then task_bufnr = vim.api.nvim_create_buf(true, false) set_buf_options(task_bufnr) + M.attach_bytes(task_bufnr) end vim.cmd('botright new')