## Problem
Integration keys (`fugitive`, `neogit`, `gitsigns`, `committia`,
`telescope`)
live at the top level of `vim.g.diffs`, cluttering the config namespace.
## Solution
Move them under `vim.g.diffs.integrations.*`. Old top-level keys still
work but
emit `vim.deprecate` targeting v0.3.2. `compute_filetypes` and
`plugin/diffs.lua`
fall back to legacy keys for pre-`init()` callers.
Also includes `83c17ac` which fixes an invalid hex hash in the combined
diff test
fixture.
Closes#169.
## Problem
Telescope never sets `filetype=diff` on preview buffers — it calls
`vim.treesitter.start(bufnr, "diff")` directly, so diffs.nvim's
`FileType` autocmd never fires.
## Solution
Add a `telescope` config toggle (same pattern as
neogit/gitsigns/committia) and a `User TelescopePreviewerLoaded` autocmd
that calls `attach()` on the preview buffer. Disabled by default; enable
with `telescope = true`.
Also adds a `diffs-telescope` vimdoc section documenting the integration
and the upstream first-line preview bug
(nvim-telescope/telescope.nvim#3626).
Includes committia.vim integration from `feat/committia`.
## Problem
committia.vim's diff pane (`ft=git`, buffer name `__committia_diff__`)
is rejected by the `ft=git` guard in the `FileType` callback, preventing
diffs.nvim from highlighting it.
## Solution
Add a `committia` config toggle following the same pattern as
`neogit`/`gitsigns`. When enabled, the `ft=git` guard also allows
committia's `__committia_diff__` buffer through.
Closes#161
## Problem
gitsigns' `:Gitsigns blame_line` popup shows flat
`GitSignsAddPreview`/`GitSignsDeletePreview` line highlights with basic
word-level inline diffs, but no treesitter syntax or diffs.nvim's
character-level intra-line highlighting.
## Solution
Add `lua/diffs/gitsigns.lua` which patches gitsigns' `Popup.create` and
`Popup.update` to intercept blame popups. Parses `Hunk N of M` sections
from the popup buffer, clears gitsigns' own `gitsigns_popup` namespace
on the diff region, and applies `highlight_hunk` with manual
`@diff.plus`/`@diff.minus` prefix extmarks. Uses a separate
`diffs-gitsigns` namespace to avoid colliding with the main decoration
provider.
Enabled via `vim.g.diffs = { gitsigns = true }`. Wired in
`plugin/diffs.lua` with a `User GitAttach` lazy-load retry for when
gitsigns loads after diffs.nvim. Config plumbing adds
`get_highlight_opts()` as a public getter, replacing the
`debug.getupvalue` hack used by the standalone `blame_hl.nvim` plugin.
Closes#155.
## Problem
Neovim 0.11+ deprecated the table-based `vim.validate({...})` form.
Every
plugin load produces 5 deprecation warnings pointing at `init.lua`
config
validation.
## Solution
Convert all `vim.validate` calls in `init()` to the new positional
`vim.validate(name, value, validator, optional_or_msg)` form. No
behavioral
change — identical validation logic, just the calling convention.
## Problem
Highlight group fallbacks in `compute_highlight_groups` were hardcoded
to
catppuccin mocha colors, producing wrong results for any other
colorscheme
when `Normal.bg` is nil. This happens on transparent terminals or when
the
colorscheme loads after the first diff buffer opens.
## Solution
Replace hardcoded fallbacks with `vim.o.background`-aware neutral
values.
When `Normal.bg` is still absent after initial computation, schedule a
single
deferred retry via `vim.schedule` that recomputes and invalidates all
attached
buffer caches. Document the load-order requirement in the setup section.
## Problem
`highlights.context.enabled` and `highlights.context.lines` were
defined, validated, and range-checked but never read during
highlighting. Hunks inside incomplete constructs (e.g., a table literal
or function body whose opening is beyond the hunk's own context lines)
parsed incorrectly because treesitter had no surrounding code.
## Solution
`compute_hunk_context` in `init.lua` reads the working tree file using
the hunk's `@@ +start,count @@` line numbers to collect up to `lines`
(default 25) surrounding code lines in each direction. Files are read
once via `io.open` and cached across hunks in the same file.
`highlight_treesitter` in `highlight.lua` accepts an optional context
parameter that prepends/appends context lines to the parse string and
offsets capture rows by the prefix count, so extmarks only land on
actual hunk lines. Wired through `highlight_hunk` for the two
code-language treesitter calls (not headers, not `highlight_text`, not
vim syntax).
Closes#148.
## Problem
Languages without a treesitter parser (COBOL, Fortran, etc.) got no
syntax highlighting because \`highlights.vim.enabled\` defaulted to
\`false\`.
## Solution
Flip the default to \`true\`. The vim syntax path is already deferred
via \`vim.schedule\` so it never blocks the first paint. \`max_lines =
200\` stays unchanged — appropriate given the ~30x slower per-hunk cost
vs treesitter.
## Problem
diffs.nvim blanked `NeogitDiffContextHighlight` globally on attach and
on `ColorScheme` to work around Neogit's `ViewContext` decoration
provider overriding `DiffsAdd`/`DiffsDelete` line backgrounds with
`NeogitDiffContextHighlight` at priority 200.
## Solution
Remove the `override_neogit_context_highlights` workaround.
NeogitOrg/neogit#1907 moves the `neogit_disable_hunk_highlight` check
inside ViewContext's per-line loop, so non-cursor lines skip
`add_line_highlight` entirely.
`vim.b[bufnr].neogit_disable_hunk_highlight = true` (set on attach) is
sufficient.
Closes#135
## Problem
Two-pass rendering (Pass 1: backgrounds + intra-line; Pass 2:
treesitter) caused Pass 2 to re-apply all extmarks that Pass 1 already
set, doubling the extmark count on affected lines.
## Solution
Add `syntax_only` mode to `highlight_hunk`. When `syntax_only = true`,
only treesitter syntax and content `DiffsClear` extmarks are applied —
backgrounds, intra-line, prefix clears, and per-char prefix highlights
are skipped. Pass 2 now uses `syntax_only = true` and no longer calls
`nvim_buf_clear_namespace`, so Pass 1's extmarks persist while Pass 2
layers syntax on top.
Closes#143
## Problem
`highlight_hunk` applied DiffsClear extmarks across 5 scattered sites
with
ad-hoc column arithmetic. This fragmentation produced the 1-column
DiffsClear
gap on email-quoted body context lines (#142 issue 1). A redundant
`highlight_hunk_vim_syntax` function duplicated the inline vim syntax
path,
and the deferred pass in init.lua double-called it, creating duplicate
scratch
buffers and extmarks.
## Solution
Reorganize `highlight_hunk` into two clean phases:
- **Phase 1** — multi-line syntax computation (treesitter, vim syntax,
diff
grammar, header context text). Sets syntax extmarks only, no DiffsClear.
- **Phase 2** — per-line chrome (DiffsClear, backgrounds, gutter,
overlays,
intra-line). All non-syntax extmarks consolidated in one pass.
Hoist `new_code` to function scope (needed by `highlight_text` outside
the
`use_ts` block). Hoist `at_raw_line` so Phase 1d and Phase 2b share one
`nvim_buf_get_lines` call.
Delete `highlight_hunk_vim_syntax` (redundant with inline path). Remove
the
double-call from the deferred pass in init.lua.
Extend body prefix DiffsClear `end_col` from `qw` to `pw + qw`, fixing
the
1-column gap where native treesitter background bled through on context
lines
in email-quoted diffs (#142 issue 1).
### Email-quoted diff support
The parser now strips `> ` (and `>> `, etc.) email quote prefixes before
pattern matching, enabling syntax highlighting for diffs embedded in
email
replies and `git-send-email` / sourcehut-style patch review threads.
Each hunk stores `quote_width` so the highlight pipeline can apply
`DiffsClear` at the correct column offsets to suppress native treesitter
on quoted regions.
Closes#141
### #142 status after this PR
| Sub-issue | Status |
|-----------|--------|
| 1. Col gap on context lines | Fixed |
| 2. Bare `>` context lines | Improved, edge case remains |
| 3. Diff prefix marker fg | Not addressed (follow-up) |
## Problem
Repeatedly toggling `=` in fugitive left green gutter
(`number_hl_group`)
extmarks on lines between sections. When fugitive collapses a diff
section,
Neovim compresses extmarks from deleted lines onto the next surviving
line
(the `M ...` file entry). Two issues prevented cleanup:
1. `carry_forward_highlighted` returned `{}` (truthy in Lua) when zero
hunks
matched, so `pending_clear` stayed `false` and the compressed extmarks
were never cleared.
2. The `nvim_buf_clear_namespace` call in `on_buf`'s `pending_clear`
path was
removed in 2feb8a8, so even when `pending_clear` was `true` the extmarks
survived.
## Solution
Return `nil` from `carry_forward_highlighted` when no hunks were carried
forward (`next(highlighted) == nil`), so `pending_clear` is correctly
set to
`true`. Restore `nvim_buf_clear_namespace` in `on_buf`'s `pending_clear`
block. Add `process_pending_clear` test helper and spec coverage.
## Problem
Toggling large diffs via fugitive's `=` caused the top of the buffer to
re-render and glitch. `ensure_cache` always created a new cache entry
with
`pending_clear=true` and `highlighted={}`, forcing `on_win` to clear and
re-highlight every visible hunk — including stable ones above the toggle
point that never changed.
## Solution
On reparse, compare old and new hunk lists using a prefix + suffix
matching
strategy. Hunks that match (same filename, line count, and sampled
content)
carry forward their `highlighted` state so `on_win` skips them.
Comparison
is O(1) per hunk. Only runs when the old entry had
`pending_clear=false`;
`invalidate_cache`/`ColorScheme` paths still force full re-highlight.
Closes#131
## Problem
diffs.nvim was blanking 18 Neogit highlight groups globally on attach to
prevent Neogit's `line_hl_group` fg from stomping treesitter syntax. It
also fell back to `getcwd()` plus a subprocess call for repo root
detection on Neogit buffers, and had no mechanism to refresh the hunk
cache when Neogit lazy-loaded new diff sections.
## Solution
Adopts three APIs introduced in NeogitOrg/neogit#1897:
- Sets `vim.b.neogit_disable_hunk_highlight = true` on the Neogit buffer
at attach time. Neogit's `HunkLine` renderer skips all its own highlight
logic when this is set, replacing the need to blank 18 hl groups
globally and the associated ColorScheme re-application.
- Reads `vim.b.neogit_git_dir` in `get_repo_root()` as a reliable
fallback between the existing `b:git_dir` check and the `getcwd()`
subprocess path.
- Registers a buffer-local `User NeogitDiffLoaded` autocmd on attach
that calls `M.refresh()` when Neogit lazy-loads a new diff section,
keeping the hunk cache in sync.
Closes#128
## Problem
The default conflict navigation keymaps `]x`/`[x` are non-standard. Vim
natively uses `]c`/`[c` for diff navigation, so the same keys are far
more
intuitive for conflict jumping.
## Solution
Change the defaults for `conflict.keymaps.next` and
`conflict.keymaps.prev`
to `]c` and `[c`. This is a breaking change for users relying on the
previous
defaults without explicit configuration.
## Problem
Users had to pass `enabled = true` or `enabled = false` inside
fugitive/neogit config tables, which was redundant — table presence
already implied the integration should be active.
## Solution
Remove the `enabled` field from the public API. Table presence now
implies enabled, `false` disables, `true` expands to sub-defaults.
The `enabled` field is still accepted for backward compatibility.
Added 20 `compute_filetypes` tests covering all config shapes (true,
false, table, nil, backward-compat enabled field). Updated docs
and type annotations.
## Problem
Commit 0f27488 changed fugitive and neogit integrations from enabled by
default
to disabled by default. Users who never explicitly set these keys in
their config
saw no deprecation notice and silently lost integration support. The
existing
deprecation warning only fires for the old `filetypes` key, missing the
far more
common case of users who had no explicit config at all.
## Solution
Add an ephemeral migration check in `init()` that emits a `vim.notify`
warning
at WARN level when `fugitive`, `neogit`, and `diff` (via
`extra_filetypes`) are
all absent from the user's config. This covers the gap between the old
`filetypes`
deprecation and users who relied on implicit defaults. To be removed in
0.3.0.
## TODO
1. docs (vimdoc + readme) - this is a non-trivial feature
2. push luarocks version
## Problem
diffs.nvim only activates on `fugitive`, `git`, and `gitcommit`
filetypes.
Neogit uses its own custom filetypes (`NeogitStatus`,
`NeogitCommitView`,
`NeogitDiffView`) and doesn't set `b:git_dir`, so the plugin never
attaches
and repo root resolution fails for filetype detection within diff hunks.
## Solution
Two changes:
1. **`lua/diffs/init.lua`** — Add the three Neogit filetypes to the
default
`filetypes` list. The `FileType` autocmd in `plugin/diffs.lua` already
handles them correctly since the `is_fugitive_buffer` guard only applies
to the `git` filetype.
2. **`lua/diffs/parser.lua`** — Add a CWD-based fallback in
`get_repo_root()`.
After the existing `b:diffs_repo_root` and `b:git_dir` checks, fall back
to
`vim.fn.getcwd()` via `git.get_repo_root()` (already cached). Without
this,
the parser can't resolve filetypes for files in Neogit buffers.
Neogit's expanded diffs use standard unified diff format, so the parser
handles
them without modification.
Closes#110.
## Problem
Three minor issues remain before the v0.2.0 release:
1. Git quotes filenames containing spaces, unicode, or special
characters
in the fugitive status buffer. `parse_file_line` passed the quotes
through verbatim, causing file-not-found errors on diff operations.
2. Navigation wrap-around in both conflict and merge modules was silent,
giving no indication when jumping past the last/first item back to the
beginning/end.
3. `resolved_hunks` and `(resolved)` virtual text in the merge module
persisted across buffer re-reads, showing stale markers for hunks that
were no longer resolved.
## Solution
1. Add an `unquote()` helper to fugitive.lua that strips surrounding
quotes and unescapes `\\`, `\"`, `\n`, `\t`, and octal `\NNN`
sequences. Applied to both return paths in `parse_file_line`.
2. Add `vim.notify` before the wrap-around jump in all four navigation
functions (`goto_next`/`goto_prev` in conflict.lua and merge.lua).
3. Clear `resolved_hunks[bufnr]` and the merge namespace at the top of
`setup_keymaps` so each buffer init starts fresh.
Closes#66
## Problem
Conflict resolution virtual text only showed plain "current" /
"incoming"
labels with no keymap hints. Users had no way to discover available
resolution keymaps without reading docs.
## Solution
Default virtual text labels now include keymap hints: `(current — doo)`
and
`(incoming — dot)`. A new `format_virtual_text` config option lets users
customize or hide labels entirely. A new `show_actions` option (off by
default) renders a codelens-style action line above each `<<<<<<<`
marker
listing all enabled resolution keymaps. Merge diff views also gain hunk
hints on `@@` header lines showing available keymaps.
New config fields: `conflict.format_virtual_text` (function|nil),
`conflict.show_actions` (boolean). New highlight group:
`DiffsConflictActions`.
## Problem
`get_repo_root()` shells out to `git rev-parse` on every call, causing
4-6
redundant subprocesses per `gdiff_file()` invocation. Three other minor
issues: `highlight_vim_syntax()` leaks a scratch buffer if
`nvim_buf_call`
errors, `lib.ensure()` silently drops callbacks during download so hunks
highlighted mid-download permanently miss intra-line highlights, and the
debounce timer callback can operate on a deleted buffer.
## Solution
Cache `get_repo_root()` results by parent directory — repo roots don't
change within a session. Wrap `nvim_buf_call` and `nvim_buf_delete` in
pcall so the scratch buffer is always cleaned up. Replace the early
`callback(nil)` in `lib.ensure()` with a pending callback queue that
fires
once the download completes. Guard the debounce timer callback with
`nvim_buf_is_valid`.
Problem: when git hits a merge conflict, users stare at raw <<<<<<<
markers with broken treesitter and noisy LSP diagnostics. Existing
solutions (git-conflict.nvim) use their own highlighting rather than
integrating with diffs.nvim's color blending pipeline.
Solution: add conflict.lua module that detects <<<<<<</=======/>>>>>>>
markers (with diff3 ||||||| support), highlights ours/theirs/base
regions with blended DiffsConflict* highlight groups, provides
resolution keymaps (doo/dot/dob/don) and navigation (]x/[x),
suppresses diagnostics while markers are present, and auto-detaches
when all conflicts are resolved. Fires DiffsConflictResolved user
event on last resolution.
Problem: users had no config-level way to override computed highlight
groups and had to call nvim_set_hl externally.
Solution: add highlights.overrides table that maps group names to
highlight definitions. Overrides are applied after all computed groups
without default = true, so they always win over both computed defaults
and colorscheme definitions.
Problem: the character-level blend intensity was hardcoded to 0.6,
giving users no way to tune how strongly changed characters stand out
from the line-level background.
Solution: add highlights.blend_alpha config option (number, 0-1,
default 0.6) with type validation and range check.
Problem: DiffsDiff* highlight groups lacked default = true, making them
impossible for colorschemes to override, inconsistent with the fugitive
unified diff groups which already had it.
Solution: add default = true to all four DiffsDiffAdd, DiffsDiffDelete,
DiffsDiffChange, and DiffsDiffText nvim_set_hl calls.
Problem: DiffsAddNr/DiffsDeleteNr used raw diffAdded/diffRemoved
foreground and the word-level blended background, instead of matching
the line-level and character-level highlight groups.
Solution: set gutter bg to the line-level blend (DiffsAdd/DiffsDelete)
and fg to the character-level blend (DiffsAddText/DiffsDeleteText).
Problem: word-level diff highlights were too intense at 70% alpha, and
line number backgrounds used the line-level blend instead of matching
the word-level highlights.
Solution: reduce DiffsAddText/DiffsDeleteText blend alpha from 0.7 to
0.6 and use the same blended background for DiffsAddNr/DiffsDeleteNr.
Problem: highlights.context was a plain integer, inconsistent with the
table structure used by treesitter, vim, and intra sub-configs.
Solution: change to { enabled = true, lines = 25 } with full
vim.validate() coverage matching the existing pattern.
Problem: treesitter parses each diff hunk in isolation, so incomplete
syntax constructs at hunk boundaries (e.g., a function definition with
no body) produce ERROR nodes and drop captures.
Solution: read N lines from the on-disk file before/after each hunk and
prepend/append them as unmapped padding lines. The line_map guard in
highlight_treesitter skips extmarks for unmapped lines, so padding
provides syntax context without visual output. Controlled by
highlights.context (default 25, 0 to disable). Also applies to the vim
syntax fallback path via a leading_offset filter.
Problem: highlight_treesitter concatenated all hunk lines (context, -,
+) into a single string. Mixed old/new code produced invalid syntax
(e.g. two return statements), causing treesitter error recovery to drop
captures on lines after the syntax error.
Solution: split hunk lines into two versions — new (context + added)
and old (context + deleted) — each parsed independently. Use a line_map
to resolve treesitter row indices to buffer lines, with the old version
only mapping deleted lines to avoid duplicate extmarks on context.
Also fixes three related issues exposed by the improved TS coverage:
- Replace Normal extmark with DiffsClear (explicit fg from Normal.fg).
Normal in extmarks doesn't reliably override vim :syntax foreground.
- Reorder priority stack to DiffsClear(198) < syntax(199) < line
bg(200) < char bg(201). TS capture groups can carry colorscheme
backgrounds that would override diff line backgrounds at higher
priority.
- Gate DiffsClear on per-line coverage tracking. Only clear fugitive
syntax fg on lines where TS/vim actually produced captures, preventing
force-clearing on lines where error recovery drops captures.
'default' inherits algorithm and linematch from diffopt, 'vscode' uses
the FFI library. Removes the need for diffs.nvim to duplicate settings
that users already control globally.
line_hl_group bg occupies a separate rendering channel from hl_group in
Neovim's extmark system, causing character-level bg-only highlights to be
invisible regardless of priority. Switching to hl_group + hl_eol ensures
all backgrounds compete in the same channel.
Also reorders priorities (Normal 198 < line bg 199 < syntax 200 < char
bg 201), bumps char-level blend alpha from 0.4 to 0.7 for visibility,
and adds debug logging throughout the intra pipeline.
The previous 70% alpha blend of DiffAdd bg was nearly identical to the
40% line-level blend, making char-level highlights invisible. Now blends
the bright diffAdded/diffRemoved foreground color (same base as line
number fg) into the char-level bg, matching GitHub/VSCode intensity.
Also bumps intra.max_lines default from 200 to 500.
Line-level backgrounds (DiffsAdd/DiffsDelete) now get a second tier:
changed characters within modified lines receive an intense background
overlay (DiffsAddText/DiffsDeleteText at 70% alpha vs 40% for lines).
Treesitter foreground colors show through since the extmarks only set bg.
diff.lua extracts contiguous -/+ change groups from hunk lines and diffs
each group byte-by-byte using vim.diff(). An optional libvscodediff FFI
backend (lib.lua) auto-downloads the .so from codediff.nvim releases and
falls back to native if unavailable.
New config: highlights.intra.{enabled, algorithm, max_lines}. Gated by
max_lines (default 200) to avoid stalling on huge hunks. Priority 201
sits above treesitter (200) so the character bg always wins.
Closes#60
Adds du/dU keymaps to fugitive's :Git status buffer for opening unified
diffs instead of side-by-side diffs:
- du opens horizontal split (mirrors dd)
- dU opens vertical split (mirrors dv)
Parses status buffer lines to extract filename and detect section
(staged/unstaged/untracked). For staged files, diffs index vs HEAD.
For unstaged files, diffs working tree vs index.
Configurable via vim.g.diffs.fugitive.horizontal/vertical (set to
false to disable).