Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions TIPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,29 @@ Common questions, useful patterns, and known compatibility issues.
},
})
```
- Customise the look with `:hi DiffviewDiffDeleteInline gui=...`.
- Customise the strikethrough via `DiffviewDiffDeleteInline` (see the
next entry on overriding inline groups).
- **Customise inline char-level highlights:**
- In the `diff1_inline` layout, changed characters on paired
modification rows use the `DiffviewDiffAddInline` highlight group,
and inline strikethrough deletions in the "overleaf" style use
`DiffviewDiffDeleteInline`. The defaults derive their backgrounds
from `DiffviewDiffAdd` and `DiffviewDiffDelete` respectively. If
your colourscheme defines `DiffAdd.bg` and `DiffChange.bg` with
similar tints, char-level changes can blend into the paired-row
`DiffviewDiffChange` backdrop. Override the inline group to taste,
e.g., to use the colourscheme's `DiffText` as the char-level
background:
- In the `diff1_inline` layout, changed characters use these groups:
the unified style highlights them with `DiffviewDiffTextInline`,
while the "overleaf" style uses `DiffviewDiffAddInline` for added
chars and `DiffviewDiffDeleteInline` for the strikethrough
deletions. Their backgrounds derive by default from `DiffText`,
`DiffviewDiffAdd`, and `DiffviewDiffDelete` respectively. The
unified group tracks `DiffText` (as the built-in side-by-side diff
does), so changes stay visible against the paired-row
`DiffviewDiffChange` backdrop even when your colourscheme gives
`DiffAdd` and `DiffChange` similar tints (e.g. tokyonight); it falls
back to `DiffAdd` for schemes that leave `DiffText` unset.
- These groups are re-derived on every colourscheme change, so set
overrides from a `ColorScheme` autocmd rather than once at startup
(a plain `:hi` is reapplied over on the next change):
```lua
vim.api.nvim_set_hl(0, "DiffviewDiffAddInline", { link = "DiffText" })
-- Or with explicit colours:
vim.api.nvim_set_hl(0, "DiffviewDiffAddInline", { bg = "#3a4a3a" })
vim.api.nvim_create_autocmd("ColorScheme", {
callback = function()
vim.api.nvim_set_hl(0, "DiffviewDiffTextInline", { bg = "#3a4a3a" })
end,
})
```
- **Better diff display (changes shown as add+delete instead of modification):**
- Set Neovim's `diffopt` to use a better algorithm:
Expand Down
38 changes: 26 additions & 12 deletions doc/diffview.txt
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,8 @@ view.x.layout *diffview-config-view.x.layout*

A single-window unified ("inline") diff. The new-side content
is shown in one buffer; deletions are rendered as virtual
lines and intra-line added chars are highlighted with
`DiffviewDiffAddInline`. Not applicable to merge conflicts.
lines and intra-line changed chars are highlighted with
`DiffviewDiffTextInline`. Not applicable to merge conflicts.

┌──────────────┐
│ │
Expand Down Expand Up @@ -1019,8 +1019,8 @@ view.inline *diffview-config-view.inline*
• `"unified"`: proper unified diff (git/GitHub style) —
old lines for each hunk (pure deletion or modification)
shown above as virtual lines; modified new lines get a
|hl-DiffChange| line background with added chars
highlighted via `DiffviewDiffAddInline`.
|hl-DiffChange| line background with changed chars
highlighted via `DiffviewDiffTextInline`.
• `"overleaf"`: Overleaf-editor style. Deleted chars on
modified lines are rendered inline as strikethrough
virtual text where they used to be; added chars get
Expand All @@ -1032,16 +1032,30 @@ view.inline *diffview-config-view.inline*

Highlight groups used: ~
• |hl-DiffAdd|, |hl-DiffChange|, |hl-DiffDelete| via
their `Diffview*` variants.
• `DiffviewDiffAddInline` for the added char ranges
(pure adds or the added side of a modification).
Default: `DiffviewDiffAdd`'s `bg` with no `fg`, so
tree-sitter syntax foreground composes through the
char-level extmark instead of being overridden.
Override with |:hi| to customise.
their `Diffview*` variants, used as line-level
backgrounds. A wholly added line (and the surplus
new lines of an uneven modification) gets a
full-line `DiffviewDiffAdd` with no char-level
overlay; the inline groups below apply only to the
differing characters on a paired/modified row.
• `DiffviewDiffTextInline` for the changed char ranges
on paired ("unified") modifications. Default:
`DiffText`'s `bg` with no `fg`, matching the
side-by-side diff so the change contrasts with the
|hl-DiffChange| backdrop even when a colourscheme
gives `DiffAdd` and `DiffChange` similar backgrounds.
Falls back to `DiffAdd`'s `bg` when the colourscheme
leaves `DiffText` unset.
• `DiffviewDiffAddInline` for the added char ranges in
"overleaf" style. Default: `DiffviewDiffAdd`'s `bg`.
Both inline groups drop `fg` so tree-sitter syntax
foreground composes through the char-level extmark
instead of being overridden. Override either with a
|ColorScheme| autocmd (a plain |:hi| is reapplied on
the next colourscheme change).
• `DiffviewDiffDeleteInline` for overleaf-mode
deletions (default: `DiffDelete` colours +
`strikethrough`; override with |:hi| to customise).
`strikethrough`; override as above).

{deletion_highlight} ("text"|"full_width"|"hanging")
How far the |hl-DiffDelete| background extends on the
Expand Down
122 changes: 84 additions & 38 deletions lua/diffview/hl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,71 @@ M.hl_links = {
DiffText = "DiffText",
}

-- Compute the inline-overlay `bg` and kept style attrs from `group`'s
-- colours. `bg` comes from `group`; when the group uses `reverse`/`standout`
-- the visible bg is actually its `fg` (the swap moves it there), so read that
-- instead -- otherwise reverse-only colourschemes lose the bg entirely once
-- we strip `reverse`. Returns `bg` ("NONE" when the group has no usable
-- background) and the comma-joined style string ("NONE" when empty), with
-- `reverse`/`standout` dropped (see `derive_inline_hl` for why).
---@param group string
---@return string bg
---@return string style
local function inline_bg_and_style(group)
local kept_attrs = {}
local is_reversed = false

for _, attr in ipairs(vim.split(M.get_style(group) or "", ",")) do
if attr == "reverse" or attr == "standout" then
is_reversed = true
elseif attr ~= "" then
kept_attrs[#kept_attrs + 1] = attr
end
end

local bg
if is_reversed then
bg = M.get_fg(group) or M.get_bg(group) or "NONE"
else
bg = M.get_bg(group) or "NONE"
end

return bg, #kept_attrs > 0 and table.concat(kept_attrs, ",") or "NONE"
end

-- Derive an inline char-range highlight `target` from `source`'s colours,
-- used by the `diff1_inline` layout to paint changed/added characters on top
-- of a paired row (priority-200 extmark). `fg` is dropped so tree-sitter
-- foreground composes through the extmark instead of being stomped; `reverse`
-- and `standout` are stripped for the same reason (they swap fg/bg at render
-- time, which would let the `source` bg paint over the syntax `fg`).
--
-- When `source` yields no usable background (e.g. a colourscheme that defines
-- `DiffAdd`/`DiffChange` but leaves `DiffText` unset), derive from `fallback`
-- instead so the overlay never regresses to invisible.
--
-- Set with `explicit` rather than `default`: a `default` highlight is a no-op
-- once the group exists, which would pin whatever value was derived at the
-- first `setup()` (e.g. from a built-in `DiffAdd` active before the user's
-- colourscheme loaded) and never refresh it on later `ColorScheme` events.
-- `explicit` rebuilds the group from scratch each call so it always tracks
-- the active colourscheme.
---@param source string Source highlight group to derive from.
---@param target string Target highlight group to (re)define.
---@param fallback? string Source used when `source` has no usable background.
local function derive_inline_hl(source, target, fallback)
local bg, style = inline_bg_and_style(source)
if bg == "NONE" and fallback then
bg, style = inline_bg_and_style(fallback)
end

M.hi(target, {
bg = bg,
style = style,
explicit = true,
})
end

function M.update_diff_hl()
local fg = M.get_fg("DiffDelete", true) or "NONE"
local bg = M.get_bg("DiffDelete", true) or "NONE"
Expand All @@ -461,51 +526,32 @@ function M.update_diff_hl()
-- is honoured. Runs AFTER the relink above so the final state is read.
local del_fg = M.get_fg("DiffviewDiffDelete") or "NONE"
local del_bg = M.get_bg("DiffviewDiffDelete") or "NONE"
-- Use `explicit` (not `default`): a `default` highlight is a no-op once the
-- group exists, so the colours derived at the first `setup()` would be pinned
-- and never refresh on later `ColorScheme` events. `explicit` rebuilds the
-- group from scratch each call so it always tracks the active colourscheme.
-- `explicit` (not `default`) so the group is rebuilt on every `ColorScheme`
-- rather than pinned to the value derived at the first `setup()`; see
-- `derive_inline_hl` for the full rationale.
M.hi("DiffviewDiffDeleteInline", {
fg = del_fg,
bg = del_bg,
style = "strikethrough",
explicit = true,
})

-- `diff1_inline` highlight for inserted char ranges (pure adds or the
-- added side of a modification). Derives `bg` from `DiffviewDiffAdd`
-- (not `DiffviewDiffText`) so inserted bytes share the addition bg at
-- any granularity, matching `diff2`. `fg` is dropped so tree-sitter
-- foreground composes through the priority-200 extmark instead of
-- being stomped. `reverse` and `standout` are stripped from the
-- inherited style for the same reason: they swap fg/bg at render
-- time, so leaving them in would let the addition `bg` paint over
-- the syntax `fg`. When the source uses one of those attrs, the
-- visible bg is actually the source `fg` (the swap moves it there),
-- so use that instead — otherwise reverse-only colourschemes lose
-- the addition bg entirely after we strip `reverse`.
local add_style_attrs = {}
local add_is_reversed = false
for _, attr in ipairs(vim.split(M.get_style("DiffviewDiffAdd") or "", ",")) do
if attr == "reverse" or attr == "standout" then
add_is_reversed = true
elseif attr ~= "" then
add_style_attrs[#add_style_attrs + 1] = attr
end
end
local add_bg
if add_is_reversed then
add_bg = M.get_fg("DiffviewDiffAdd") or M.get_bg("DiffviewDiffAdd") or "NONE"
else
add_bg = M.get_bg("DiffviewDiffAdd") or "NONE"
end
local add_style = #add_style_attrs > 0 and table.concat(add_style_attrs, ",") or "NONE"
-- `explicit` for the same reason as `DiffviewDiffDeleteInline` above.
M.hi("DiffviewDiffAddInline", {
bg = add_bg,
style = add_style,
explicit = true,
})
-- `diff1_inline` overlays for changed/added char ranges (priority 200,
-- layered on the paired row). The two inline styles need different
-- backdrops, so each derives from a different source group:
-- * "unified" paints the paired row with `DiffviewDiffChange` and
-- overlays `DiffviewDiffTextInline`, derived from `DiffText` -- the
-- same group the built-in side-by-side diff uses for intra-line
-- changes. Deriving from `DiffText` (not `DiffAdd`) keeps the overlay
-- visible against the `DiffChange` backdrop even when a colourscheme
-- gives `DiffAdd` and `DiffChange` near-identical backgrounds (e.g.
-- tokyonight), which would otherwise hide the change. Falls back to
-- `DiffAdd` for the rare colourscheme that leaves `DiffText` unset.
-- * "overleaf" leaves the row unpainted and overlays
-- `DiffviewDiffAddInline`, derived from `DiffAdd` -- the natural
-- "added" colour read against the normal background.
derive_inline_hl("DiffviewDiffText", "DiffviewDiffTextInline", "DiffviewDiffAdd")
derive_inline_hl("DiffviewDiffAdd", "DiffviewDiffAddInline")
end

function M.setup()
Expand Down
Loading
Loading