Commit graph

64 commits

Author SHA1 Message Date
Barrett Ruth
6040c054cb
fix: carry forward highlighted hunks on reparse to reduce flicker (#138)
## 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
2026-02-25 12:44:57 -05:00
Barrett Ruth
d45ffd279b
add back hard-code override (#136) 2026-02-25 11:47:25 -05:00
Barrett Ruth
2feb8a86ed
feat(neogit): use new neogit apis for highlight and repo root (#133)
## 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
2026-02-25 11:42:59 -05:00
Barrett Ruth
700a9a21ad
fix(conflict)!: change default nav keymaps from ]x/[x to ]c/[c (#132)
## 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.
2026-02-24 12:07:54 -05:00
Barrett Ruth
b1abfe4f4a
feat: remove config deprecation in v0.3.0 (#129) 2026-02-18 13:34:43 -05:00
Barrett Ruth
d68cddb1a4
fix(parser): exclude git diff metadata from neogit filename patterns (#127)
## 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.
2026-02-16 00:14:27 -05:00
Barrett Ruth
cbc93f9eaa
refactor: remove enabled field from fugitive/neogit config (#126)
## 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.
2026-02-15 19:42:38 -05:00
Barrett Ruth
028ba5314e
fix(highlight): revert line backgrounds to hl_group+hl_eol (#124)
## 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.
2026-02-15 18:27:39 -05:00
Barrett Ruth
cb38865b96
fix: warn users when fugitive/neogit/diff integrations are unconfigured (#123)
## 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.
2026-02-15 16:48:19 -05:00
Barrett Ruth
3d640c207b
feat: add neogit support (#117)
## 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.
2026-02-14 17:12:01 -05:00
5d3bbc3631
fix(ci): styling 2026-02-12 18:10:33 -05:00
3990014a93
feat: add support for diff and other filetypes 2026-02-12 18:04:47 -05:00
Barrett Ruth
9a0b812f69
performance improvements (#116)
closes #111
2026-02-12 16:59:13 -05:00
Barrett Ruth
cc5a368838
fix(highlight): support combined diff format for unmerged files (#106)
Some checks are pending
luarocks / quality (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
## 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
2026-02-09 19:30:13 -05:00
Barrett Ruth
35067151e4
fix: pre-release cleanup for v0.2.0 (#102)
## 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
2026-02-09 15:08:36 -05:00
Barrett Ruth
b5d28e9f2b
feat(conflict): add virtual text formatting and action lines (#101)
## 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`.
2026-02-09 13:55:13 -05:00
Barrett Ruth
f5a090baae
perf: cache repo root and harden async paths (#100)
## 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`.
2026-02-09 12:39:13 -05:00
Barrett Ruth
a2053a132b
feat: unified diff conflict resolution for unmerged files (#99)
## 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
2026-02-09 12:21:13 -05:00
Barrett Ruth
49fc446aae
doc: add plug mappings for merge conflict resolution (#98) 2026-02-08 16:29:39 -05:00
a192830d8c fix(conflict): clear stale diagnostics before re-enabling
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.
2026-02-07 19:47:45 -05:00
35cb13419c fix(conflict): keep TextChanged autocmd alive after resolution
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.
2026-02-07 19:42:29 -05:00
bae86c5fd9 feat(conflict): show branch names in virtual text labels
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)".
2026-02-07 17:58:51 -05:00
1108c33526 refactor(conflict): drop unnecessary @as cast in parser 2026-02-07 17:52:35 -05:00
98a1a4028b fix(conflict): resolve LuaLS missing-fields diagnostics
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.
2026-02-07 17:51:47 -05:00
7ae867c413 fix(conflict): resolve LuaLS duplicate-doc-field and inject-field errors
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.
2026-02-07 17:45:23 -05:00
731222d027 feat(conflict): detect and resolve inline merge conflict markers
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.
2026-02-07 17:38:34 -05:00
c72efec77d fix(commands): add diff buffer UX improvements
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.
2026-02-07 16:48:31 -05:00
a0870a7892 feat(highlight): add highlights.overrides config table
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.
2026-02-07 15:49:56 -05:00
b7477e3af2 feat(highlight): add configurable blend alpha
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.
2026-02-07 15:46:47 -05:00
8e0c41bf6b fix(highlight): add default flag to DiffsDiff* groups
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.
2026-02-07 15:45:34 -05:00
5cfa91039b fix(highlight): use correct line number gutter colors
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).
2026-02-07 15:29:19 -05:00
d10eaed6ac fix(highlight): reduce word-level blend alpha and match line number bg
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.
2026-02-07 15:23:14 -05:00
825012daeb fix(ci): remove unused variable 2026-02-07 15:12:44 -05:00
38220ab368 fix(highlight): use hunk body as context for header treesitter parsing
Problem: the header context string (e.g. "function M.setup()") was
parsed in isolation by treesitter, which couldn't recognize "function"
as @keyword.function because the snippet is an incomplete definition
with no body or "end".

Solution: append the already-built new_code lines as trailing context
when parsing the header string, giving treesitter a complete function
definition. Filter captures to row 0 only so body-line captures don't
produce extmarks on the header line.
2026-02-07 15:10:07 -05:00
011db2f8b3 fix(highlight): make hl_eol work on background extmarks
Problem: hl_eol requires a multiline extmark to extend the background
past end-of-line. The extmark used end_col = line_len on the same
row, so it was single-line and hl_eol was effectively a no-op.

Solution: replace end_col with end_row targeting the next line so
the extmark is multiline and hl_eol takes effect. Split
number_hl_group into a separate extmark to prevent it bleeding to
adjacent lines.
2026-02-07 14:40:55 -05:00
0cefa00d27 fix(highlight): include treesitter injections
Problem: highlight_treesitter only iterated captures from the base
language tree (trees[1]:root()). When code contained injected
languages (e.g. VimL inside vim.cmd()), those captures were never
read, so injected code got no syntax highlighting in diffs.

Solution: pass true to parse() to trigger injection discovery, then
use parser_obj:for_each_tree() to iterate all trees including
injected ones. Each tree gets its own highlights query looked up by
ltree:lang(), and @spell/@nospell captures are filtered out.
2026-02-07 14:26:11 -05:00
9e32384f18 refactor: change highlights.context config to table structure
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.
2026-02-07 13:16:34 -05:00
2e1ebdee03 feat(highlight): add treesitter context padding from disk
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.
2026-02-07 13:05:53 -05:00
bbb87b660e fix(highlight): split old/new treesitter parsing
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.
2026-02-07 00:50:21 -05:00
93f6627dd2 fix(diff): strip linematch from char-level diff
Problem: parse_diffopt() passes linematch from diffopt to byte-level
vim.diff() in char_diff_pair, where each "line" is a single byte.
linematch is meaningless at this granularity.

Solution: copy diff_opts without linematch before passing to byte_diff
in char_diff_pair. linematch remains in effect for the line-level diff
in diff_group_native where it belongs.
2026-02-07 00:50:08 -05:00
2d72963f8f fix(debug): resolve sparse array crash in json dump
Problem: vim.json.encode fails with "excessively sparse array" when
extmark row numbers are used as integer table keys, since they create
gaps in the array.

Solution: use tostring(row) as keys instead. Also add hl_eol to dumped
extmark fields for debugging line background extmarks.
2026-02-07 00:50:02 -05:00
f948982848 fix(commands): handle :e on diffs:// buffers via BufReadCmd
Problem: running :e on a :Gdiff buffer cleared all content because
diffs:// buffers had no BufReadCmd handler. Neovim tried to read the
buffer name as a file path, found nothing on disk, and emptied the
buffer. This affected all three buffer creation paths (gdiff,
gdiff_file, gdiff_section).

Solution: register a BufReadCmd autocmd for diffs://* that parses the
URL and regenerates diff content from git. Change buffer options from
nofile/wipe to nowrite/delete (matching fugitive's approach) so
buffer-local autocmds and variables survive across unload/reload
cycles. Store old filepath as buffer variable for rename support.
2026-02-06 22:21:33 -05:00
b79adba5f2 fix(ci): typing 2026-02-06 21:33:55 -05:00
5722ccdbb2 fix(ci): typing 2026-02-06 21:30:06 -05:00
10af59a70d feat(config): replace algorithm 'auto'/'native' with 'default'/'vscode'
'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.
2026-02-06 21:23:40 -05:00
cc947167c3 fix(highlight): use hl_group instead of line_hl_group for diff backgrounds
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.
2026-02-06 18:31:10 -05:00
f1c13966ba fix(highlight): use diffAdded/diffRemoved fg for char-level backgrounds
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.
2026-02-06 14:43:23 -05:00
997bc49f8b feat(highlight): add character-level intra-line diff highlighting
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
2026-02-06 13:53:58 -05:00
33c58c7498 fix(parser): detect filetype from file content (shebang/modeline)
When a file has no extension but contains a shebang (e.g., `#!/bin/bash`),
filetype detection now reads the first 10 lines from disk and uses
`vim.filetype.match({ filename, contents })` for content-based detection.

This enables syntax highlighting for files like `build` scripts that rely
on shebang detection, even when the file isn't open in a buffer.

Detection order:
1. Existing buffer's filetype (already implemented in #69)
2. File content (shebang/modeline) - NEW
3. Filename extension only

Also adds `filetype on` to test helpers to ensure `vim.g.ft_ignore_pat`
is set, which is required for shell detection.
2026-02-05 01:08:43 -05:00
9e857d4b29 feat(fugitive): line position tracking for keymaps
When pressing `du`/`dU` from a hunk line in the fugitive status buffer
(after expanding with `=`), the unified diff now opens at the
corresponding line instead of line 1.

Implementation:
- `fugitive.get_hunk_position()` returns @@ header and offset when on a hunk line
- `commands.find_hunk_line()` finds matching @@ header in diff buffer
- `commands.gdiff_file()` accepts optional `hunk_position` and jumps after opening

Also updates @phanen's README credit for the previous two fixes.

Closes #65
2026-02-05 00:27:35 -05:00