Diff mode: highlight unsaved mutations in oil buffers #37

Closed
opened 2026-09-21 18:54:22 +00:00 by barrettruth · 1 comment
Owner

Original issue: barrettruth/canola.nvim#193
Original author: barrettruth
Original date: 2026-03-19T03:03:54Z

Problem

Oil buffers give no visual feedback about pending mutations before :w. After editing lines — renaming a file, adding new entries, or deleting lines — there is no indication of what will happen when the buffer is saved. Users must mentally track their edits or save to find out.

Consolidates

Feature: pending mutation highlights

Run parser.parse() on CursorHold and TextChanged (debounced or gated to CursorHold only to avoid per-keystroke overhead), cache the resulting diff, then apply decorations via nvim_set_decoration_provider so highlights update on every redraw without touching buffer content.

Three states to indicate:

  • Renamed / moved — the line's name differs from the cached entry. Highlight the name column with CanolaPendingMove.
  • Created — the line has no matching cache entry (new entry). Highlight with CanolaPendingCreate.
  • Deleted — a cached entry has no corresponding line. Render a virtual ghost line at the position where the entry was, styled with CanolaPendingDelete, showing the entry name struck through or dimmed.

Highlight groups (all off by default until the feature is enabled):

Group Default link Purpose
CanolaPendingMove DiffChange Line whose name/path differs from cache
CanolaPendingCreate DiffAdd New line with no cache entry
CanolaPendingDelete DiffDelete Virtual ghost line for a deleted cache entry

Performance

parser.parse() walks the buffer line-by-line and is already fast, but running it on every TextChanged event (every inserted character) is likely too much. The plan:

  1. Gate the diff behind CursorHold (fires after updatetime ms of inactivity). This covers the common case with no latency visible to the user.
  2. Optionally add a TextChanged path that only re-parses the current line (not the full buffer) and updates just that line's extmark. Full re-diff deferred to CursorHold.
  3. The decoration provider applies cached results on redraw — no parsing happens at paint time.

Config

Off by default. Opt in via:

require('oil').setup({
  diff_mode = true,
})

This is a new UX paradigm (the buffer is no longer just a text file) so it should not be on by default.

Follow-on: mark and diff (stevearc/oil.nvim#659)

The upstream issue asks for a netrw-style workflow: mark several files in the oil buffer, then open them in a vimdiff split. This is a separate feature from pending mutation highlights, but fits naturally alongside it (both involve annotating entries in the buffer). Scope:

  • A mark_entry action that toggles a mark on the entry under the cursor, shown via a sign or virtual text glyph.
  • A diff_marked action that opens all marked entries in vimdiff splits.
  • Marks are buffer-local and cleared on refresh.

This can be designed and implemented independently once the highlight infrastructure from the main feature is in place.

Complexity

Non-trivial. The decoration provider approach must co-exist with the existing semantic highlight provider planned in #129. Parser changes needed to expose per-line diff state for incremental updates. Ghost lines for deleted entries require careful extmark placement to stay stable as surrounding lines shift.

> Original issue: barrettruth/canola.nvim#193 > Original author: `barrettruth` > Original date: 2026-03-19T03:03:54Z ## Problem Oil buffers give no visual feedback about pending mutations before `:w`. After editing lines — renaming a file, adding new entries, or deleting lines — there is no indication of what will happen when the buffer is saved. Users must mentally track their edits or save to find out. ## Consolidates - stevearc/oil.nvim#263 (diff mode: highlight changes in oil buffers) - stevearc/oil.nvim#659 (mark and diff files — lower-priority subset, see below) ## Feature: pending mutation highlights Run `parser.parse()` on `CursorHold` and `TextChanged` (debounced or gated to `CursorHold` only to avoid per-keystroke overhead), cache the resulting diff, then apply decorations via `nvim_set_decoration_provider` so highlights update on every redraw without touching buffer content. Three states to indicate: - **Renamed / moved** — the line's name differs from the cached entry. Highlight the name column with `CanolaPendingMove`. - **Created** — the line has no matching cache entry (new entry). Highlight with `CanolaPendingCreate`. - **Deleted** — a cached entry has no corresponding line. Render a virtual ghost line at the position where the entry was, styled with `CanolaPendingDelete`, showing the entry name struck through or dimmed. Highlight groups (all off by default until the feature is enabled): | Group | Default link | Purpose | |---|---|---| | `CanolaPendingMove` | `DiffChange` | Line whose name/path differs from cache | | `CanolaPendingCreate` | `DiffAdd` | New line with no cache entry | | `CanolaPendingDelete` | `DiffDelete` | Virtual ghost line for a deleted cache entry | ## Performance `parser.parse()` walks the buffer line-by-line and is already fast, but running it on every `TextChanged` event (every inserted character) is likely too much. The plan: 1. Gate the diff behind `CursorHold` (fires after `updatetime` ms of inactivity). This covers the common case with no latency visible to the user. 2. Optionally add a `TextChanged` path that only re-parses the current line (not the full buffer) and updates just that line's extmark. Full re-diff deferred to `CursorHold`. 3. The decoration provider applies cached results on redraw — no parsing happens at paint time. ## Config Off by default. Opt in via: ```lua require('oil').setup({ diff_mode = true, }) ``` This is a new UX paradigm (the buffer is no longer just a text file) so it should not be on by default. ## Follow-on: mark and diff (stevearc/oil.nvim#659) The upstream issue asks for a netrw-style workflow: mark several files in the oil buffer, then open them in a vimdiff split. This is a separate feature from pending mutation highlights, but fits naturally alongside it (both involve annotating entries in the buffer). Scope: - A `mark_entry` action that toggles a mark on the entry under the cursor, shown via a sign or virtual text glyph. - A `diff_marked` action that opens all marked entries in vimdiff splits. - Marks are buffer-local and cleared on refresh. This can be designed and implemented independently once the highlight infrastructure from the main feature is in place. ## Complexity Non-trivial. The decoration provider approach must co-exist with the existing semantic highlight provider planned in #129. Parser changes needed to expose per-line diff state for incremental updates. Ghost lines for deleted entries require careful extmark placement to stay stable as surrounding lines shift.
barrettruth added this to the v1.1 milestone 2026-09-21 18:54:22 +00:00
Author
Owner

Original comment: barrettruth/canola.nvim#193, comment 4101795972
Original author: barrettruth
Original date: 2026-03-21T01:46:26Z

Closing — the confirmation dialog already shows every pending mutation before execution. Diff mode would duplicate this with significant complexity (ghost line extmarks, decoration provider interaction) for marginal practical value. Navigation is 90%+ of file manager usage; the mutation editing path already has adequate feedback.

> Original comment: barrettruth/canola.nvim#193, comment 4101795972 > Original author: `barrettruth` > Original date: 2026-03-21T01:46:26Z Closing — the confirmation dialog already shows every pending mutation before execution. Diff mode would duplicate this with significant complexity (ghost line extmarks, decoration provider interaction) for marginal practical value. Navigation is 90%+ of file manager usage; the mutation editing path already has adequate feedback.
Sign in to join this conversation.
No description provided.