Skip to content

refactor: redesign cursor API and update all doc annotations - #2

Merged
xvzc merged 13 commits into
mainfrom
refactor/cursor-exec-api-v2
Apr 21, 2026
Merged

refactor: redesign cursor API and update all doc annotations#2
xvzc merged 13 commits into
mainfrom
refactor/cursor-exec-api-v2

Conversation

@xvzc

@xvzc xvzc commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace __call metamethod with explicit :exec(bufnr) method on cursor — makes the lazy query pattern explicit and self-documenting
  • Simplify :exec() return type from { node, range }[] to plain TSNode[], since TSNode already exposes :range()
  • Convert all doc example blocks from --- Example:\n--->lua format to @usage [[ ]] annotation format (lemmy-help compatible)
  • Fix all filetype test files to follow actual/expected naming convention
  • Rename doc/bufsitter.txtdoc/bufsitter.nvim.txt
  • Remove redundant config.lua module

Test plan

  • All 163 tests pass (make test)
  • StyLua formatting check passes (stylua . --check)
  • GitHub Actions CI passes

- Replace __call metamethod with explicit exec(bufnr) method on cursor
- Simplify exec result from {node, range}[] to plain TSNode[] since TSNode already has :range()
- Convert all doc example blocks from --- Example: --->lua format to @Usage [[ ]] annotation format
- Fix all filetype test files to follow actual/expected naming convention
- Rename doc/bufsitter.txt to doc/bufsitter.nvim.txt
- Remove redundant config.lua module
@xvzc
xvzc requested a review from Copilot April 21, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the bufsitter cursor execution API (explicit :exec(bufnr) and TSNode[] returns), updates IO/ref/scratch modules and tests accordingly, and migrates documentation to lemmy-help–generated annotations with CI checks.

Changes:

  • Redesign cursor evaluation to use explicit :exec(bufnr) and return TSNode[] (range via node:range()).
  • Update IO/scratch/ref implementations + expand test suite with filetype fixtures (markdown/go/typst).
  • Regenerate/help-tag documentation and add CI tooling (docs check, commitlint, Nix shell).

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/minimal_init.lua Minimal Neovim init for running Plenary tests.
tests/helpers.lua Shared buffer helpers for tests (create buffers from files, cleanup, TS root).
tests/filetypes/typst/typ_spec.lua Typst traversal/IO integration coverage using new cursor API.
tests/filetypes/typst/sample.typ.tree Typst fixture parse tree snapshot.
tests/filetypes/typst/sample.typ Typst fixture document.
tests/filetypes/markdown/sample.md.tree Markdown fixture parse tree snapshot.
tests/filetypes/markdown/sample.md Markdown fixture document.
tests/filetypes/markdown/markdown_spec.lua Markdown traversal + IO integration tests.
tests/filetypes/go/sample.go.tree Go fixture parse tree snapshot.
tests/filetypes/go/sample.go Go fixture source used by traversal + IO tests.
tests/filetypes/go/go_spec.lua Go traversal + IO integration tests.
tests/bufsitter/scratch_spec.lua Scratch buffer lifecycle tests.
tests/bufsitter/ref_spec.lua Ref string generation tests (buffer + visual selection).
tests/bufsitter/io_spec.lua IO select/insert/delete/replace/clear + error handling tests.
tests/bufsitter/cursor_spec.lua Cursor chaining semantics + integration tests.
tests/bufsitter/config_spec.lua Config defaults and deep-merge behavior tests.
shell.nix Developer environment (Lua, stylua, lemmy-help, commitlint).
lua/bufsitter/scratch.lua Scratch buffer/window implementation + lemmy-help annotations.
lua/bufsitter/ref.lua Buffer/selection reference string generation + docs.
lua/bufsitter/io.lua Cursor-driven buffer IO implementation + docs.
lua/bufsitter/init.lua Plugin config defaults + setup + docs entrypoint.
lua/bufsitter/cursor.lua New cursor implementation with lazy traversal + :exec(bufnr).
doc/tags Updated help tags for renamed help file.
doc/bufsitter.nvim.txt Generated help documentation (lemmy-help output).
Makefile Add dependency setup, parser install, test/docs/check-docs targets.
.stylua.toml StyLua formatting configuration.
.gitignore Ignore local deps/cache/dev artifacts.
.github/workflows/ci.yml CI: commitlint, tests, docs diff check.
.envrc direnv integration for Nix shell.
.commitlintrc.js Commitlint conventional commits configuration.
.claude/settings.json Local automation hooks for tests/docs/commitlint.
.claude/rules/testing.md Testing guidelines for contributors/automation.
.claude/rules/security.md Security guidelines (avoid hardcoded absolute paths).
.claude/rules/formatting.md Formatting guidelines (StyLua).
.claude/rules/documentation.md Lua annotation + doc example conventions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile
Comment on lines +28 to +32
test: _install-parsers
nvim \
--headless \
-u tests/minimal_init.lua \
-c "PlenaryBustedDirectory tests/ { minimal_init = 'tests/minimal_init.lua' }"

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the test recipe, the continuation lines (--headless, -u ..., -c ...) appear to be indented with spaces rather than a leading tab. In Makefiles every recipe line must begin with a tab, otherwise make test can fail with a “missing separator”/parse error. Ensure each of these lines is part of the recipe (tab-indented), or rewrite this as a single tab-indented shell line.

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/scratch.lua
local wopts = vim.tbl_deep_extend("force", self._win_opts, win_opts or {})

if self:is_visible() then
vim.api.nvim_win_set_buf(self._winid, self._bufnr)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch:show() merges win_opts into wopts, but when the window is already visible it only reattaches the buffer and never applies the new window config. This makes the documented usage s:show({ width = 100, height = 30 }) misleading because it won’t actually resize/reposition an existing window. Consider calling nvim_win_set_config(self._winid, wopts) (or update the docs to state that win_opts are only used on first open).

Suggested change
vim.api.nvim_win_set_buf(self._winid, self._bufnr)
vim.api.nvim_win_set_buf(self._winid, self._bufnr)
vim.api.nvim_win_set_config(self._winid, wopts)

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/cursor.lua Outdated
end
end
return result
end)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base:children() creates a new cursor step but does not pass prev into new_multi(...), so _prev is nil for the resulting cursor. That effectively disables :or_else() for chains where children() is the step that might be empty, even though or_else is documented as a general “fallback to previous step”. Pass prev as the second argument to new_multi here to preserve the fallback chain.

Suggested change
end)
end, prev)

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/cursor.lua Outdated
end
end
return result
end)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi:parents() returns new_multi(...) without setting _prev, so :or_else() cannot fall back from a parents() step to the prior cursor step. If or_else is meant to work consistently across traversal steps, this should pass prev into new_multi(..., prev) like filter() does.

Suggested change
end)
end, prev)

Copilot uses AI. Check for mistakes.
Comment thread shell.nix Outdated
];
shellHook = # sh
''
export name="nix:promdown.nvim"

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shellHook exports name="nix:promdown.nvim", which doesn’t match this plugin/repo name (bufsitter.nvim). If this variable is used by tooling (prompts, logging, etc.), it can cause confusion or mislabeling. Consider updating it to nix:bufsitter.nvim (or removing it if unused).

Suggested change
export name="nix:promdown.nvim"
export name="nix:bufsitter.nvim"

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +35
Username *string `json:"username"`
Email string `json:"email"`

// 3. Collections: Slices and Maps
Roles []string `json:"roles"`
Settings map[string]string `json:"settings"`

// 4. Nested Anonymous Struct
Address struct {
City string `json:"city"`
ZipCode int `json:"zip_code"`
} `json:"address"`

// 5. Interface member (for polymorphism tests)
Permissions any `json:"permissions"`

// 6. Channel for concurrency tests

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sample file contains whitespace-only lines (e.g. the blank lines at 21 and 34 include spaces). Trailing whitespace makes diffs noisy and can cause formatting/lint issues; consider converting these to truly empty lines and updating the corresponding expected test fixtures if needed.

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/cursor.lua Outdated
local idx = n > 0 and n or (#nodes + n + 1)
local node = nodes[idx]
return node and { node } or {}
end)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi:nth() returns a SingleCursor via new_single(...) but doesn’t set _prev (it doesn’t pass prev). This means cursor.root():children():filter(fn):first():or_else() (shown in the Single:or_else() docs) won’t actually fall back, because first()/nth() breaks the _prev chain. Pass prev into new_single(..., prev) so or_else works after nth/first/last as documented.

Suggested change
end)
end, prev)

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/io.lua Outdated
contents
)
else
vim.api.nvim_buf_set_text(bufnr, er, ec, er, ec, contents)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cursor-based insert with inline=true + prepend=false, the code inserts at (er, ec) directly. For Treesitter ranges, ec == 0 commonly means the end is exclusive at the start of row er, so this will insert at the beginning of the next line rather than at the end of the node’s last line. Use the existing clamp_end(bufnr, er, ec) (or equivalent) before calling nvim_buf_set_text in this branch.

Suggested change
vim.api.nvim_buf_set_text(bufnr, er, ec, er, ec, contents)
local end_row, end_col = clamp_end(bufnr, er, ec)
vim.api.nvim_buf_set_text(
bufnr,
end_row,
end_col,
end_row,
end_col,
contents
)

Copilot uses AI. Check for mistakes.
Comment thread lua/bufsitter/init.lua
Comment on lines +4 to +10
---@brief [[
--- Treesitter-powered buffer manipulation for Neovim.
---
--- Quick start:
--->lua
--- require("bufsitter").setup()
---<

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says doc example blocks were converted to @usage [[ ... ]] for lemmy-help compatibility, but the module ---@brief here still uses the old --->lua ... ---< example block. Update this block to the new @usage style (or adjust the PR description if brief-block examples are intentionally left as-is).

Copilot uses AI. Check for mistakes.
xvzc added 12 commits April 21, 2026 21:13
- cursor: pass prev to new_multi/new_single in children(), parents(), nth()
  so or_else() works correctly after those steps
- io: apply clamp_end before inline append insert to handle ec=0 correctly
- scratch: apply win_opts to existing window via nvim_win_set_config in show()
- shell.nix: fix plugin name nix:promdown.nvim -> nix:bufsitter.nvim
- sample.go: remove trailing whitespace on blank lines
- resolve width/height as ratio of editor size when value is 0–1
- compute centered row/col at show() time when not explicitly set
- remove fixed row/col from default config
- default width/height changed to 0.6/0.4
- remove unused VimResized autocmd code
…d io comments

- scratch: add min_width/min_height support (absolute or 0-1 ratio) to win.opts
- scratch: expose row/col as explicit nil defaults; add bufsitter.config.scratch.win.opts type
- io: fix cursor-based operations inserting after trailing blank lines by adding
  resolve_end() with trim_end option (default true) across select/insert/delete/replace
- io: annotate all internal logic with inline comments
@xvzc
xvzc merged commit 99a28a5 into main Apr 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants