Commit graph

87 commits

Author SHA1 Message Date
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
c51d625dc6 fix(parser): detect filetype from existing buffer
Files detected via shebang or modeline (e.g., `build` with `#!/bin/bash`)
weren't getting syntax highlighting because `vim.filetype.match()` only
does filename-based detection.

Now `get_ft_from_filename()` first checks if a buffer already exists for
the file and uses its detected filetype. This requires knowing the repo
root to construct the full path, so:

- `parse_buffer()` reads `b:diffs_repo_root` or `b:git_dir` (fugitive)
- `commands.lua` sets `b:diffs_repo_root` on diff buffers it creates

Co-authored-by: phanen <phanen@qq.com>
2026-02-05 00:13:32 -05:00
980bedc8a6 fix(parser): emit hunks for files with unknown filetypes
Previously, hunks were discarded entirely if vim.filetype.match()
returned nil. This meant files with unrecognized extensions got no
highlighting at all - not even the basic green/red backgrounds for
added/deleted lines.

Remove the (current_lang or current_ft) condition from flush_hunk()
so all hunks are collected. highlight_hunk() already handles the
case where ft/lang are nil by skipping syntax highlighting but still
applying background colors.

Co-authored-by: phanen <phanen@qq.com>
2026-02-04 23:51:15 -05:00
9ed0639005 fix(fugitive): handle renamed files correctly
Parse both old and new filenames from rename lines (R old -> new).
When diffing staged renames, use old filename as base to correctly
show content changes rather than treating the file as entirely new.

Also adds comprehensive tests for filename edge cases:
- Double extensions, hyphens, underscores, dotfiles
- Deep nested paths, complex renames
- Documents known limitation with filenames containing ' -> '
2026-02-04 23:12:30 -05:00
6072dd0156 feat(fugitive): add section header and untracked file support
Section headers (Staged/Unstaged) now show all diffs in that section,
matching fugitive's behavior. Untracked files show as all-added diffs.
Deleted files show as all-removed diffs.

Also handles edge cases:
- Empty new/old content for deleted/new files
- Section header detection returns is_header flag
2026-02-04 22:39:07 -05:00
9289f33639 feat(fugitive): add status buffer keymaps for unified diffs
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).
2026-02-04 22:23:21 -05:00
ea60ab8d01 feat(commands): add gdiff_file for diffing arbitrary paths
Adds gdiff_file() which can diff any file path (not just current buffer)
with support for staged vs unstaged changes:
- staged=true diffs index against HEAD
- staged=false diffs working tree against index
- Falls back to HEAD if file not in index (for untracked comparison)
2026-02-04 22:23:13 -05:00
85080514b6 feat(git): add index and working tree content retrieval
Adds functions for accessing git index content and working tree files:
- get_index_content() retrieves file from staging area via :0:path
- get_working_content() reads file directly from disk
- file_exists_in_index() checks if file is staged
- file_exists_at_revision() checks if file exists at given revision
2026-02-04 22:23:09 -05:00
045a9044b5 feat: add :Gdiff, :Gvdiff, :Ghdiff commands for unified diff view
Compares current buffer against any git revision (default HEAD), opens result
with full diffs.nvim syntax highlighting. Follows fugitive convention:
:Gdiff/:Gvdiff open vertical split, :Ghdiff opens horizontal split.
2026-02-04 19:52:17 -05:00
a25f1e9d84 feat: treesitter highlighting for diff headers
Apply treesitter highlighting to diff metadata lines (diff --git, index,
---, +++) using the diff language parser. Header info is attached only
to the first hunk of each file to avoid duplicate highlighting.

Based on PR #52 by @phanen with fixes:
- header_lines now only contains diff metadata, not hunk content
- header info attached only to first hunk per file
- removed arbitrary hunk count restriction
2026-02-04 15:11:35 -05:00
2b38874699 feat(config): use vim.g over .setup() 2026-02-03 16:18:55 -05:00
f71b0f54c5 fix: remove useless enabled flag 2026-02-03 02:50:25 -05:00
dc45dd66ec fix highlights 2026-02-03 01:21:57 -05:00
188de47d77 feat: docs update (vim-fugitive is optional) 2026-02-03 01:07:37 -05:00
67116f38bc feat: rename everything 2026-02-02 22:09:13 -05:00