Skip to content

feat(diff): add layout = "inline" for VS Code-style unified diff#195

Merged
ThomasK33 merged 3 commits into
coder:mainfrom
zhao-tong:main
Jun 22, 2026
Merged

feat(diff): add layout = "inline" for VS Code-style unified diff#195
ThomasK33 merged 3 commits into
coder:mainfrom
zhao-tong:main

Conversation

@zhao-tong

@zhao-tong zhao-tong commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a new layout = "inline" option for diff_opts that renders a VS Code-style unified inline diff - a single read-only buffer with deleted (red/strikethrough) and added (green) lines interleaved. This complements the existing "vertical" and "horizontal" two-panel diff layouts.

Closes #82

Motivation

The existing diff layouts use Neovim's built-in diffthis to show old and new content side by side. While effective, this uses significant screen real estate with two panels. An inline unified diff provides a more compact view that's familiar to VS
Code users, showing changes in context within a single buffer.

Changes

  • New module lua/claudecode/diff_inline.lua — self-contained inline diff implementation with:
    • Pure diff computation via vim.diff() (indices mode)
    • Extmark-based rendering with sign column markers (+/-)
    • Resolution (accept/reject) and cleanup functions
  • Dispatch in diff.lua — routes to inline module when layout = "inline" is configured; exposes shared utilities as M._ members following existing patterns
  • Config/types — validates "inline" as a valid layout value
  • close_all_diff_tabs — detects inline diff buffers via claudecode_inline_diff buffer variable
  • Test mock — adds vim.diff mock with LCS-based hunk computation to tests/mocks/vim.lua
  • 23 new tests covering diff computation, content extraction, rendering, MCP response format, config validation, and cleanup

Configuration

require("claudecode").setup({
  diff_opts = {
    layout = "inline", -- "vertical" (default), "horizontal", or "inline"
  },
})

Highlight groups are customizable:

  • ClaudeCodeInlineDiffAdd — green background for added lines
  • ClaudeCodeInlineDiffDelete — red background + strikethrough for deleted lines
  • ClaudeCodeInlineDiffAddSign / ClaudeCodeInlineDiffDeleteSign — sign column colors

Requirements

  • layout = "inline" requires Neovim >= 0.9.0 (for vim.diff())
  • The plugin's base requirement remains Neovim >= 0.8.0; only inline layout needs 0.9+

Design Decisions

  1. Separate module rather than inlining into diff.lua — keeps the already-large file manageable; inline diff uses fundamentally different rendering (extmarks vs diffthis)
  2. layout field in diff state — enables dispatch in resolution/cleanup without re-reading config
  3. Read-only buffer — inline diff is review-only; content extraction uses saved line_types array rather than parsing buffer text
  4. Always vsplit — single buffer doesn't need horizontal/vertical choice

Test Plan

  • luacheck lua/ tests/ passes with 0 warnings / 0 errors
  • All 406 tests pass (23 new, 5 pre-existing failures unrelated to this change)
  • Manual: layout = "inline" → single-window diff with colored lines
  • Manual: Accept (aa) → file saved correctly, diff closes
  • Manual: Deny (ad) → file unchanged, diff closes
  • Manual: New file creation → all lines shown as green/added
  • Manual: layout = "vertical" still works unchanged (regression check)

@CortlandMorse

Copy link
Copy Markdown

Bumping ➕ , this is a feature I'd love as well.

@joshualawson

Copy link
Copy Markdown

+1 need this!

@RitvarsZvejnieksApplyIT

Copy link
Copy Markdown

+1

@ThomasK33 ThomasK33 mentioned this pull request Jun 22, 2026
zhao-tong and others added 3 commits June 22, 2026 17:22
Add a new inline diff layout that shows deleted (red/strikethrough) and
added (green) lines interleaved in a single read-only buffer, similar to
VS Code's inline diff view. This complements the existing "vertical" and
"horizontal" two-panel diff layouts.

Key changes:
- New `diff_inline.lua` module with pure diff computation, rendering via
  extmarks, and resolution/cleanup functions
- Dispatch logic in `diff.lua` routes to inline module when configured
- Config/types updated to accept `layout = "inline"`
- `close_all_diff_tabs` detects inline diff buffers
- Customizable highlight groups: ClaudeCodeInlineDiff{Add,Delete,*Sign}
- Requires Neovim >= 0.9.0 (for vim.diff); base plugin remains 0.8.0+
- 23 new tests covering computation, rendering, MCP responses, cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
- closeAllDiffTabs: call _resolve_diff_as_rejected before cleanup so
  pending coroutines are properly resumed instead of hanging on yield
- closeAllDiffTabs: include resolved diffs in CLOSED_N_DIFF_TABS count
- diff_inline: in open_in_new_tab mode, pick editor window from current
  tab via nvim_tabpage_list_wins instead of global search which could
  return a window from the original tab
- Add tests for new-tab window selection and cleanup
- Add nvim_tabpage_list_wins to vim mock

Issues found by Codex 5.3 code review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Two small refinements to the inline diff layout while rebasing onto main:

- Resolve the diff function at call time as `vim.text.diff or vim.diff`.
  Neovim 0.12 renamed `vim.diff` to `vim.text.diff` (old name kept as a
  deprecated alias), so prefer the new name and have the version guard
  check the function actually used. Keeps `layout = "inline"` working
  across the supported range and forward-compatible.

- Store `client_id` in the inline diff state so `close_diffs_for_client`
  tears the diff down if its MCP client disconnects, matching the native
  path (coder#261). Without it, orphaned inline diffs would linger.

Change-Id: I59e3d29729e9b7d7838da25c53c23dc335f784c5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33 ThomasK33 added this pull request to the merge queue Jun 22, 2026
Merged via the queue into coder:main with commit f64c307 Jun 22, 2026
4 checks passed
@wookayin

wookayin commented Jun 22, 2026

Copy link
Copy Markdown

Nice feature! However it still opens a new ...(inline diff). VS Code like inline unified diff would mean showing inline diff in virtual text using extmarks, in the same window rather than vsplit.

Also let's beware of potential breaking changes in the layout options, as this sounds more like "inline_vertical" (or "vertical_inline") whereas the true overlay+extmarks mode (once we implement that) should deserve "inline".

@ThomasK33

Copy link
Copy Markdown
Member

Thanks @wookayin — you're right on both counts. The shipped layout = "inline" is a unified diff in a separate rightbelow vsplit, not an in-place virtual-text overlay, and shipping it under the name "inline" would have boxed us into a breaking rename once the real overlay lands.

Since it hasn't gone out in a tagged release yet, we're renaming it now: layout = "inline""unified" in #295, which reserves "inline" for the true in-place overlay. The naming rationale is in #293.

I opened #294 to track that real overlay — the prototype direction is proposed lines as real, editable text + deleted lines as virt_lines extmarks, so the proposal stays editable before accept (the codecompanion/sidekick model). You said "once we implement that" — would genuinely welcome your input or a hand on it if you're up for it. 🙏

gogongxt pushed a commit to gogongxt/claudecode.nvim that referenced this pull request Jun 24, 2026
)

## Summary

Renames the `diff_opts.layout = "inline"` option (added in coder#195, queued
for v0.4.0 via release-please coder#279) to **`"unified"`**, before it ships
in a tagged release. Tracked in coder#293.

[@wookayin pointed
out](coder#195 (comment))
that the shipped `"inline"` mode isn't a true "inline" diff in the VS
Code sense — it renders a unified diff in a separate `rightbelow vsplit`
pane, not an in-place virtual-text overlay in the same window. Naming it
`"inline"` now would force a breaking rename later, once we add the real
in-place overlay (the mode that most deserves the name `"inline"`).
Renaming is free now (not yet released) and a breaking change after coder#279
merges.

## What changed

- **Config value** `"inline"` → `"unified"`: validation + error message
(`config.lua`), the `ClaudeCodeDiffLayout` type alias (`types.lua`), all
four dispatch sites (`diff.lua`), and the stored `layout` value
(`diff_inline.lua`).
- **User-facing buffer name**: `<file> (inline diff)` → `<file> (unified
diff)`.
- **Docs**: README layout enum + label; a `CHANGELOG.md` `[Unreleased]`
entry for `layout = "unified"`.
- **Tests**: the accept test now expects `"unified"`; a new test asserts
the former `"inline"` is rejected.

## Decisions

- **No deprecated alias.** `"inline"` was never in a tagged release, so
there's no backward-compat obligation, and rejecting it cleanly reserves
the name for the future in-place overlay (coder#294). The validation error
names `'unified'`, so anyone tracking `main` is pointed at the new
value.
- **Internal names kept.** The `diff_inline.lua` module, its functions,
the `claudecode_inline_diff` buffer variable, and the
`ClaudeCodeInlineDiff*` highlight groups are unchanged — they describe
the inline-*style* rendering, not the layout value (coder#293 noted the
buffer var can stay). This keeps the diff small and low-risk.
- **coder#279 changelog** regenerates from commits via Communique on the next
push to `main`, so the v0.4.0 entry reflects "unified" automatically; I
did not hand-edit the bot branch.

## Validation

- `mise run all`: treefmt clean, luacheck **0/0**, **703/703** tests
pass (baseline 702 + the new rejection test).
- Headless Neovim smoke test: `layout = "unified"` accepted, `"inline"`
rejected (error names `'unified'`), `"vertical"` still works.

The true in-place overlay (the future `"inline"`) is tracked in coder#294.

Refs coder#293, coder#195, coder#82

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

[FEATURE] Inline diff

6 participants