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
This commit is contained in:
Barrett Ruth 2026-02-06 13:53:58 -05:00
parent 294cbad749
commit 997bc49f8b
7 changed files with 842 additions and 0 deletions

View file

@ -64,6 +64,11 @@ Configuration is done via `vim.g.diffs`. Set this before the plugin loads:
enabled = false,
max_lines = 200,
},
intra = {
enabled = true,
algorithm = 'auto',
max_lines = 200,
},
},
fugitive = {
horizontal = 'du',
@ -116,6 +121,10 @@ Configuration is done via `vim.g.diffs`. Set this before the plugin loads:
Vim syntax highlighting options (experimental).
See |diffs.VimConfig| for fields.
{intra} (table, default: see below)
Character-level (intra-line) diff highlighting.
See |diffs.IntraConfig| for fields.
*diffs.TreesitterConfig*
Treesitter config fields: ~
{enabled} (boolean, default: true)
@ -140,6 +149,26 @@ Configuration is done via `vim.g.diffs`. Set this before the plugin loads:
this many lines. Lower than the treesitter default
due to the per-character cost of |synID()|.
*diffs.IntraConfig*
Intra config fields: ~
{enabled} (boolean, default: true)
Enable character-level diff highlighting within
changed lines. When a line changes from `local x = 1`
to `local x = 2`, only the `1`/`2` characters get
an intense background overlay while the rest of the
line keeps the softer line-level background.
{algorithm} (string, default: 'auto')
Diff algorithm for character-level analysis.
`'auto'`: use libvscodediff if available, else
native `vim.diff()`. `'native'`: always use
`vim.diff()`. `'vscode'`: require libvscodediff
(falls back to native if not available).
{max_lines} (integer, default: 200)
Skip character-level highlighting for hunks larger
than this many lines.
Note: Header context (e.g., `@@ -10,3 +10,4 @@ func()`) is always
highlighted with treesitter when a parser is available.
@ -259,6 +288,8 @@ Summary / commit detail views: ~
- Background extmarks (`DiffsAdd`/`DiffsDelete`) at priority 198
- `Normal` extmarks at priority 199 clear underlying diff foreground
- Syntax highlights are applied as extmarks at priority 200
- Character-level diff extmarks (`DiffsAddText`/`DiffsDeleteText`) at
priority 201 overlay changed characters with an intense background
- Conceal extmarks hide diff prefixes when `hide_prefix` is enabled
4. Re-highlighting occurs on `TextChanged` (debounced) and `Syntax` events
@ -349,6 +380,18 @@ Fugitive unified diff highlights: ~
DiffsDeleteNr Line number for `-` lines. Foreground from
`diffRemoved`, background from `DiffsDelete`.
*DiffsAddText*
DiffsAddText Character-level background for changed characters
within `+` lines. Derived by blending `DiffAdd`
background with `Normal` at 70% alpha (brighter
than line-level `DiffsAdd`). Only sets `bg`, so
treesitter foreground colors show through.
*DiffsDeleteText*
DiffsDeleteText Character-level background for changed characters
within `-` lines. Derived by blending `DiffDelete`
background with `Normal` at 70% alpha.
Diff mode window highlights: ~
These are used for |winhighlight| remapping in `&diff` windows.
@ -382,6 +425,7 @@ Run |:checkhealth| diffs to verify your setup.
Checks performed:
- Neovim version >= 0.9.0
- vim-fugitive is installed (optional)
- libvscode_diff shared library is available (optional)
==============================================================================
ACKNOWLEDGEMENTS *diffs-acknowledgements*