feat: streaming 'typing' reveal for the unified inline diff#301
Open
spoloxs wants to merge 3 commits into
Open
feat: streaming 'typing' reveal for the unified inline diff#301spoloxs wants to merge 3 commits into
spoloxs wants to merge 3 commits into
Conversation
Introduce a VS Code-style unified inline diff: a single persistent, read-only buffer (ClaudeCodeBuffer) showing deleted lines in red/ strikethrough and added lines in green, interleaved in one pane. - Pure-Lua interleave via vim.text.diff/vim.diff (zero external deps, coder#169) - Single reused buffer (bufhidden=hide) kept open showing the reviewed file after accept/reject, so it survives window/tab closes - Tears down any prior unified diff in the same buffer before rendering, preventing stale diffs from accumulating (fixes coder#205) - Wires ClaudeCodeDiffOpened/ClaudeCodeClosed User autocmds for the unified layout (noted in coder#294) - Tracks originating client_id so close_diffs_for_client can tear the diff down on disconnect (parity with the native path, coder#261) - Dispatch in diff.lua branches on layout == 'unified' Implements the unified inline diff layout described in coder#294.
There was a problem hiding this comment.
Pull request overview
Adds an opt-in streamed “typing” reveal for the unified diff renderer, while also refactoring unified diff UI to reuse a persistent buffer/window and improving diff error formatting/cleanup behavior.
Changes:
- Introduces a cancellable, incremental streamed renderer for unified diffs (
stream_reveal*options) and shares highlight logic between static/streamed paths. - Reuses a single persistent
ClaudeCodeBufferacross unified diffs and changes cleanup to leave the buffer/tab open showing the reviewed file. - Updates diff tab/window handling and improves error formatting/logging during diff setup failures.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| lua/claudecode/diff.lua | Adds editor-window reuse in a “diff tab”, tweaks new-tab terminal display behavior, improves setup error formatting/logging, and adjusts accept/deny cleanup calls. |
| lua/claudecode/diff_inline.lua | Implements the streamed reveal renderer + cancellation, introduces a persistent unified diff buffer, reworks setup/cleanup flow, and reuses diff windows where possible. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
1629
to
+1631
| if not setup_success then | ||
| local error_msg | ||
| if type(setup_error) == "table" and setup_error.message then | ||
| -- Handle structured error objects | ||
| error_msg = "Failed to setup diff operation: " .. setup_error.message | ||
| if setup_error.data then | ||
| error_msg = error_msg .. " (" .. setup_error.data .. ")" | ||
| end | ||
| error_msg = "Failed to setup diff operation: " .. format_error(setup_error) | ||
|
|
Comment on lines
+528
to
+532
| local function reuse_or_open_diff_window() | ||
| local diff_win = diff.get_or_create_editor_win_in_diff_tab() | ||
| if diff_win then | ||
| vim.api.nvim_set_current_win(diff_win) | ||
| end |
Comment on lines
+795
to
+817
| local content_lines = {} | ||
| local f = io.open(diff_data.old_file_path, "r") | ||
| if f then | ||
| local text = f:read("*a") or "" | ||
| f:close() | ||
| content_lines = vim.split(text, "\n", { plain = true }) | ||
| if #content_lines > 0 and content_lines[#content_lines] == "" then | ||
| table.remove(content_lines, #content_lines) | ||
| end | ||
| end | ||
| vim.api.nvim_buf_set_lines(b, 0, -1, false, content_lines) | ||
|
|
||
| -- Keep it as a persistent review buffer (no auto-wipe) showing the file. | ||
| vim.api.nvim_buf_set_option(b, "buftype", "nofile") | ||
| vim.api.nvim_buf_set_option(b, "bufhidden", "hide") | ||
| vim.api.nvim_buf_set_option(b, "modifiable", false) | ||
| vim.b[b].claudecode_inline_diff = nil | ||
| vim.b[b].claudecode_diff_tab_name = nil | ||
|
|
||
| local ft = diff._detect_filetype(diff_data.old_file_path) | ||
| if ft and ft ~= "" then | ||
| vim.api.nvim_set_option_value("filetype", ft, { buf = b }) | ||
| end |
Comment on lines
+399
to
+403
| local function get_or_create_diff_tab() | ||
| local tabs = vim.api.nvim_list_tabpages() | ||
| if #tabs >= 2 then | ||
| return tabs[2] | ||
| end |
Comment on lines
+1424
to
+1428
| if original_tab_number and vim.api.nvim_tabpage_is_valid(original_tab_number) then | ||
| vim.schedule(function() | ||
| vim.api.nvim_set_current_tabpage(original_tab_number) | ||
| end) | ||
| end |
Comment on lines
+202
to
+216
| --- Progressively reveal `lines` in `buf` with a natural "typing" cadence. | ||
| --- | ||
| --- The reveal is *incremental*: each tick advances by `chars_per_tick` characters, | ||
| --- appends only the new tail to the buffer (never rewriting earlier lines), and | ||
| --- adds extmarks only for lines that just became fully revealed. This keeps the | ||
| --- work O(n) in the number of lines instead of O(n²), and because earlier lines | ||
| --- and their marks are never touched, they stay correctly positioned. | ||
| ---@param buf number Buffer handle | ||
| ---@param lines string[] Full diff lines (source of truth) | ||
| ---@param line_types string[] Parallel type array | ||
| ---@param opts table|nil { delay_ms:number, chars_per_tick:number } | ||
| local function render_diff_buffer_streamed(buf, lines, line_types, opts) | ||
| opts = opts or {} | ||
| local chars_per_tick = math.max(1, opts.chars_per_tick or 1) | ||
| local delay_floor_ms = math.max(0, opts.delay_ms or 0) |
- Hoist format_error to module scope so the open_diff_blocking error path can use it (it was defined inside _setup_blocking_diff and raised a secondary error, obscuring the original failure). - Declare error_msg as local (was implicitly a global). - Stop reusing tabs[2] in get_or_create_diff_tab; always :tabnew so cleanup never closes an unrelated user tab (issue flagged by reviewer). - Rename the internal export get_or_create_editor_win_in_diff_tab -> _get_or_create_editor_win_in_diff_tab for API consistency. - Guard reuse_or_open_diff_window to only reuse a window in the current tabpage, avoiding hijacking/switching to an unrelated tab.
05f31fb to
e79a0b9
Compare
…ffer in current tab - The unified diff no longer auto-opens a window or creates tab 2. - prepares silently. - Added and for user-initiated buffer access (works in any tab, creates a vsplit if needed). - Cleanup closes any window showing and keeps the buffer alive showing the reviewed file. - Removed , , tab2 logic from this path; those remain for native diff. This eliminates the tab-3 confusion: the diff always lives in and the user decides where to view it.
e79a0b9 to
d08e7d0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in animated "typing" reveal for the unified inline diff introduced in #300.
reveal_genstops animation on new diff or cleanup.<leader>dt/<leader>dcand stays open after accept/reject.Enable
Related
Stacks on #300.