feat: support comments in ordinary source buffers

This commit is contained in:
2026-10-22 22:30:16 +02:00
parent ac5c64981d
commit e6d94dcb41
16 changed files with 1005 additions and 600 deletions
+19 -15
View File
@@ -482,27 +482,29 @@ Collapsed folders show a green **`✓`** when every changed file beneath them is
Review marks are saved with the selected local review and restored by immutable per-file Before/After identities and file paths. Changes to unrelated files or commit metadata do not clear them; changes to either side's bytes, final newline, file mode, or rename paths require re-review. They survive filtering, file navigation, lazy loading, tabs, split/unified/orientation changes, and pinned checkout-only transitions. Editing a writable After file clears only that file's mark and saves the removal; unchanged refreshes and saves preserve it. Marks on live text also retain its exact contents, including the final newline, so different unsaved text cannot inherit a checkmark. Marks do not change document text, undo history, or repository state. Without configured SQLite storage, use `:diff.review.save path` to save them as JSON. Older revision-pair-scoped marks remain stored but must be marked again to acquire per-file identities; comments, summaries, and verdicts are unaffected.
**Local review comments** share local storage with the file-reviewed checkmarks above; neither feature publishes to GitHub. Focus either diff text pane and run **`:diff.comment`** to comment on the current line. A characterwise Visual selection (`v`) anchors its exact text, including partial lines and multiline spans; Visual Line (`V`) anchors whole selected lines. Type **`:diff.comment`** and press Enter, or use the palette/a configured binding. Multiple independent carets/ranges must be reviewed separately.
**Local review comments** work in ordinary repository source buffers and diff text panes; they share storage with file-reviewed checkmarks and never publish to GitHub. Run **`:comment`** to comment on the current line. A characterwise Visual selection (`v`) anchors its exact text, including partial lines and multiline spans; Visual Line (`V`) anchors whole selected lines. Multiple independent carets/ranges must be reviewed separately. `:diff.comment` remains available for the same operation.
Comments, summaries, and the review overview open in a focused floating editor over the whole diff text workspace, not a source-side split. The source layout, file explorer, and viewports stay unchanged. Normal editing, selections, and undo/redo work in editable review buffers. Escape in Normal mode or `:pane.close` dismisses the window and returns to the diff, discarding that editor's unsaved text. An unsaved new comment is not attached to the review; an existing comment or summary reopens with its last saved text and no discarded undo history. Insert/Visual/Command Escape first retains its usual modal behavior. Source rows are never replaced with comment text. Magenta **`○`** gutter markers identify saved comment ranges; **`●`** identifies the selected comment. Its exact current text range has a magenta background, including wrapped continuations and selected newline cells; native Visual selections take precedence. Stale comments have neither markers nor text highlighting. Markers preserve addition/deletion signs, appear only on the first wrapped segment, and also work in unified view.
Comments, summaries, and the review overview open in a focused floating editor over the source workspace, not a source-side split. The source layout, file explorer, and viewports stay unchanged. Normal editing, selections, and undo/redo work in editable review buffers. Escape in Normal mode or `:pane.close` dismisses the window and returns to the source, discarding that editor's unsaved text. An unsaved new comment is not attached to the review; an existing comment or summary reopens with its last saved text and no discarded undo history. Insert/Visual/Command Escape first retains its usual modal behavior. Source rows are never replaced with comment text. Magenta **`○`** gutter markers identify saved comment ranges; **`●`** identifies the selected comment. Its exact current text range has a magenta background, including wrapped continuations and selected newline cells; native Visual selections take precedence. Stale comments have neither markers nor text highlighting. Markers preserve addition/deletion signs, appear only on the first wrapped segment, and also work in unified view.
Local review buffers (comments, summaries, overview, and stored-review selector) soft-wrap by default without inserting newlines into saved text. Use `:word_wrap.toggle` to disable wrapping for the focused review buffer; reopening it retains that choice for the session. Source diff wrapping remains independent.
| Command | Effect |
| --- | --- |
| `:diff.comment` | Add a comment on the current line or exact selected text range, on the focused side |
| `:diff.review.edit [id]` | Edit an existing comment; defaults to the overview entry under the caret or selected comment |
| `:diff.review.delete [id]` | Delete that draft comment |
| `:diff.review.resolve [id]` / `:diff.review.unresolve [id]` | Mark a comment resolved/unresolved; uses the same default selection as edit/delete |
| `:diff.review.next` / `:diff.review.previous` | Cycle comments, opening their text and revealing a matching source anchor |
| `:diff.review.overview` | Refresh/open a read-only summary and comment list; Enter edits the entry under the caret |
| `:diff.review.copy` | Copy all saved comments in the selected review to the configured clipboard, using the CLI text format |
| `:diff.review.summary` | Compose the review summary |
| `:diff.review.verdict looks_good` | Record **Looks good** |
| `:diff.review.verdict changes_requested` | Record **Changes requested**; `none` clears the verdict |
| `:diff.review.save [path]` | Commit to SQLite; with a path, also export JSON for sharing |
| `:diff.review.open [id or path]` | Show stored reviews, open a stored review ID, or import a JSON review as a new review |
| `:diff.review.new` | Create a separate stored review for the current repository |
| `:comment` | Add a comment on the current source line or exact selected text range |
| `:review.edit [id]` | Edit an existing comment; defaults to the overview entry under the caret or selected comment |
| `:review.delete [id]` | Delete that draft comment |
| `:review.resolve [id]` / `:review.unresolve [id]` | Mark a comment resolved/unresolved; uses the same default selection as edit/delete |
| `:review.next` / `:review.previous` | Cycle comments, opening their text and revealing a matching source anchor |
| `:review.overview` | Refresh/open a read-only summary and comment list; Enter edits the entry under the caret |
| `:review.copy` | Copy all saved comments in the selected review to the configured clipboard, using the CLI text format |
| `:review.summary` | Compose the review summary |
| `:review.verdict looks_good` | Record **Looks good** |
| `:review.verdict changes_requested` | Record **Changes requested**; `none` clears the verdict |
| `:review.save [path]` | Commit to SQLite; with a path, also export JSON for sharing |
| `:review.open [id or path]` | Show stored reviews, open a stored review ID, or import a JSON review as a new review |
| `:review.new` | Create a separate stored review for the current repository |
The `:diff.review.*` spellings remain available for these same operations. File checkmarks remain diff-specific (`:diff.review.toggle`).
Comment and summary text is committed only by **`:w`** or **`:diff.review.save`**, which saves the entire review, including hidden drafts. Dismissing an editor discards its unsaved text; switching between review editors can retain hidden drafts but does not save them. File checkmarks, verdicts, deletions, and manual resolution changes commit immediately to local SQLite without committing draft text. `:diff.review.save path` or `:w path` additionally exports JSON; later metadata autosaves still go to SQLite and leave that export unchanged. Imports create a new stored review identity rather than overwriting another review.
@@ -520,6 +522,8 @@ Version 4 JSON is the agent-readable sharing format: repository root, summary, o
Reopening requires the same repository root. Unchanged comment ranges follow edits elsewhere in the file, including inserted/deleted lines above and column shifts before a partial-line selection. Working-snapshot refreshes can retain these attachments; unrelated immutable revisions and paths do not match. Mapping requires an unchanged, unambiguous text region containing the selection. Modified/deleted selections or ambiguous repeated regions are **stale**: comments remain available through the overview and next/previous navigation, with original snapshot coordinates and no current-source marker or jump. Saved anchors and exported excerpts remain unchanged. Unloaded files are shown as **not checked** until visited; refresh the overview to update its statuses. There is no server, authentication, or review-publishing operation.
Ordinary source comments use an immutable content snapshot identity and capture the current buffer, including unsaved text; they do not require a commit or a diff. They retain the same SQLite/JSON formats. Opening a source comment navigates to its file and mapped selection; if that text is stale or the file is unavailable, it opens the preserved original text read-only instead of attaching feedback to different code.
Resolution is a manual human decision, independent of anchor freshness: a comment can be both resolved and stale. Overview entries and comment titles label resolved/unresolved status. Resolved comments remain in the overview and next/previous navigation, and can still be edited or reopened. Resolving never changes an anchor or deletes feedback.