Problem: highlight_hunk applied DiffsClear extmarks across 5 scattered
sites with ad-hoc column arithmetic. highlight_hunk_vim_syntax 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
- Phase 2: per-line chrome (DiffsClear, backgrounds, gutter, overlays,
intra-line) — all non-syntax extmarks in one unified 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 it.
Delete highlight_hunk_vim_syntax (redundant with inline path). Remove
the double-call from the deferred pass in init.lua.
## 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
Regressions #119 and #120 showed the test suite had no coverage of the
decoration provider cache pipeline, no end-to-end pipeline tests from
buffer content to extmarks, and no Neogit-specific integration tests.
## Solution
Adds three new spec files (28 new tests, 316 total):
- `spec/decoration_provider_spec.lua` — indirect cache pipeline tests
via `_test` table: `ensure_cache` population, content fingerprint guard,
`pending_clear` semantics, BufWipeout cleanup
- `spec/integration_spec.lua` — full pipeline: diff buffer → `attach` →
extmarks; verifies `DiffsAdd`/`DiffsDelete` on correct lines, treesitter
captures, multi-hunk coverage
- `spec/neogit_integration_spec.lua` — `neogit_disable_hunk_highlight`
behavior, NeogitStatus/NeogitDiffView attach and cache population,
parser neogit filename patterns
Depends on #133.
Closes#122
## 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: lua-language-server is not available in the dev shell, making
it impossible to run local type checks.
Solution: add lua-language-server to the devShell packages.
## Problem
Git diff metadata lines like "new file mode 100644" and "deleted file
mode 100644" matched the neogit "new file" and "deleted" filename
patterns in the parser, corrupting the current filename and breaking
syntax highlighting for subsequent hunks.
Closes#120
## Solution
Add negative guards so "new file mode" and "deleted file mode" lines
are skipped before the neogit filename capture runs. The guard must
evaluate before the capture due to Lua's and/or short-circuit semantics
— otherwise the and-operator returns true instead of the captured
string.
Added 16 parser tests covering all neogit filename patterns, all git
diff extended header lines that could collide, and integration scenarios
with mixed neogit status + diff metadata buffers.
## 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
Users attempt to lazy-load diffs.nvim with `event`, `ft`, `lazy`, or
`keys` options, which interferes with the plugin's own `FileType`
autocmd registered at startup.
## Solution
Add a FAQ entry with a correct lazy.nvim snippet using `init` and a
note explaining that the plugin lazy-loads itself.
## Problem
The neogit commit (3d640c2) switched line background extmarks from
`hl_group`+`hl_eol` to `line_hl_group`. Due to [neovim#31151][1],
`line_hl_group` bg overrides `hl_group` bg regardless of extmark
priority. This made `DiffsAddText`/`DiffsDeleteText` intra-line
highlights invisible beneath line backgrounds — the extmarks were
placed correctly but Neovim rendered the line bg on top.
[1]: https://github.com/neovim/neovim/issues/31151
## Solution
Revert line backgrounds to `hl_group`+`hl_eol` where priority stacking
works correctly. Keep `number_hl_group` in a separate point extmark to
prevent gutter color bleeding to adjacent lines. The Neogit highlight
override (clearing their groups to `{}`) is independent and unaffected.
## 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
Fugitive shows combined diffs (`@@@` headers, 2-character prefixes like
`++`, ` +`, `+ `) for unmerged (`UU`) files. The parser and highlight
pipeline assumed unified diff format (`@@`, 1-char prefix), causing:
- Prefix concealment only hiding 1 of 2 prefix chars
- Missing background colors on ` +` and `+ ` lines (first char is space
→ misclassified as context)
- No treesitter highlights (extra prefix char poisoned code arrays)
- `U` file header not recognized by parser (missing from filename
pattern)
## Solution
Detect prefix width from leading `@` count in hunk headers (`@@` → 1,
`@@@` → 2). Propagate `prefix_width` through the pipeline:
- **Parser**: new `prefix_width` field on `diffs.Hunk`, `U` added to
filename pattern, combined diff range extraction
- **Highlight**: prefix stripping, `col_offset`, concealment width, and
line classification all use `prefix_width`
- **Intra-line**: skipped for combined diffs (`prefix_width > 1`) since
2-char prefix semantics don't produce meaningful change groups
## Problem
The README doesn't mention the optional vscode-diff FFI backend for
word-level intra-line accuracy, and the codediff.nvim acknowledgement
doesn't credit the author by name.
## Solution
Expand the intra-line feature bullet to mention vscode-diff with a link
to codediff.nvim. Credit @esmuellert by name in the acknowledgements
section. Also update the stale context padding reference in known
limitations to match the current behavior.
## 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
Pressing `du` on a `UU` (unmerged) file in the fugitive status buffer
had no
effect. There was no way to see a proper ours-vs-theirs diff with syntax
highlighting and intra-line changes, or to resolve conflicts from within
a
unified diff view.
Additionally, pressing `du` on a section header containing only unmerged
files
showed "no changes in section" because `git diff` produces combined
(`diff --cc`)
output for unmerged files, which was stripped entirely.
## Solution
Fetch `:2:` (ours) and `:3:` (theirs) from the git index and generate a
standard
unified diff. The existing highlight pipeline (treesitter + intra-line)
applies
automatically. Resolution keymaps (`doo`/`dot`/`dob`/`don`) on hunks in
the diff
view write changes back to the working file's conflict markers.
Navigation
(`]x`/`[x`) jumps between unresolved conflict hunks.
For section diffs, combined diff entries are now replaced with generated
ours-vs-theirs unified diffs instead of being stripped.
Works for merge, cherry-pick, and rebase conflicts — git populates
`:2:`/`:3:`
the same way for all three.
Closes#61
Problem: after resolving all conflicts, vim.diagnostic.enable(true)
restored diagnostics that were cached while markers were present,
showing errors like "unexpected token end" on clean code.
Solution: call vim.diagnostic.reset() before re-enabling to flush
stale results and let the LSP re-analyze the resolved buffer.
Problem: resolving the last conflict called M.detach(), which cleared
attached_buffers[bufnr]. The TextChanged callback then returned true,
permanently deleting the autocmd. Undo restored conflict markers but
nothing re-highlighted or re-suppressed diagnostics.
Solution: inline the cleanup in refresh() instead of calling detach().
Keep attached_buffers set so the autocmd survives. Re-suppress
diagnostics when conflicts reappear after undo.
Problem: virtual text showed generic "current"/"incoming" labels with
no indication of which branch each side came from.
Solution: extract the branch name from the marker line itself
(e.g. <<<<<<< HEAD, >>>>>>> feature) and display as
"HEAD (current)" / "feature (incoming)".
Problem: LuaLS reports missing-fields errors because the parser builds
ConflictRegion tables incrementally, but the variable is typed as
diffs.ConflictRegion? which expects all required fields at construction.
Solution: type the work-in-progress variable as table? and cast to
diffs.ConflictRegion on insertion into the results array.
Problem: lua-language-server reports duplicate @class definitions for
ConflictKeymaps and ConflictConfig (defined in both init.lua and
conflict.lua), and inject-field errors for the untyped parser table.
Solution: remove duplicate @class annotations from conflict.lua
(init.lua is the canonical source), and annotate the parser's current
variable as diffs.ConflictRegion? so LuaLS knows its shape.
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: diffs:// buffers could trigger spurious LSP diagnostics,
opening multiple diffs from fugitive created redundant splits, and
there was no quick way to close diff windows.
Solution: disable diagnostics on diff buffers, reuse existing
diffs:// windows in the tabpage instead of creating new splits,
and add a buffer-local q keymap to close diff windows.
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.