First pass at a very dumb two file diff viewer#15631
First pass at a very dumb two file diff viewer#15631jcmuller wants to merge 18 commits intohelix-editor:masterfrom
Conversation
9ece1d0 to
7dbb344
Compare
a1e2218 to
38b6910
Compare
|
Great work, this is awesome to see! I'll test drive the branch over the week |
|
Here's the default theme config I'd like to see: "diff.plus" = { bg = "#1f6f4e" } # shifted down by 20% on hsl
"diff.minus" = { bg = "#ad0b55" } # shifted down by 20% on hsl
"diff.delta" = { bg = "#41288c" }
"diff.delta.text" = { bg = "#5e3bc6" }
"diff.delta.conflict" = { fg = "#f22c86", bg = "#6a2038" }
"diff.plus.gutter" = { fg = "#35bf86" }
"diff.minus.gutter" = { fg = "#f22c86" }
"diff.delta.gutter" = { fg = "#6f44f0" }
I haven't adjusted
Bugs so far:
|
|
Also, only scroll position is updated but it would be good if we kept the cursor line position in sync as well. If I scroll halfway through a file then |
Add diff-mode.md covering hx --diff/-d, :diff-open, :diff-this, :diff-put, :reset-diff-change, and :diff-off. Register the page in SUMMARY.md under Usage. Regenerate typable-cmd.md via cargo xtask docgen; the four new diff commands (diff-open, diff-put, diff-off, diff-this) and the updated reset-diff-change description now appear in the table. Add diff.delta.text to the theme scope list in themes.md. Also fix two pre-existing lint errors: MD041 (## to # on first line) and MD059 ([here] to descriptive link text). docs(diff-mode): fix theme key table; separate vimdiff vs VCS gutter scopes The gutter scopes (diff.*.gutter) apply to the VCS diff gutter, not to the vimdiff session. Split the table to make that clear, and fix the icon descriptions: the gutter uses `▍` (bar) for added/modified lines and `▔` (top tick) for deleted lines, not +/-/~. docs(diff-mode): remove vimdiff reference
Pull out side_for_view(), hunks_arc(), and build_get_transaction() as dedicated helpers to cut down on duplication. Use and_then() for cleaner Option handling. Remove comments that just restate the code. 54 fewer lines overall.
Editor::close and close_document didn't clean up diff_sessions after a view or document was closed. The render loop then hit documents[&session.doc_a()] -- a BTreeMap index that panics on a missing key -- and crashed. Fix: Editor::close retains sessions not referencing the closed view, and clears pending_diff_this if it pointed at that view. close_document prunes sessions referencing the closed document. Integration tests cover the three scenarios: closing a session view, closing a session document, closing the pending diff-this view. Each has a negative counterpart (unrelated view/doc leaves the session alone). Unit tests verify DiffSession::contains_doc.
`intra_line_changes()` runs Myers diff over character tokens. The render loop called it for every modified hunk every frame, even with no document changes between frames. `DiffSession` now stores `intra_line_cache: Arc<Vec<...>>` built in `compute_hunks` alongside `hunks`. The render site snapshots it via `intra_line_cache_arc()` in `diff_view_info` and reads the cache instead. `InlineChange` now derives `PartialEq` for the unit tests.
The `line_decoration` closure in `render_view` scanned hunks from index 0 on every rendered line: O(lines * hunks) per frame. The closure now keeps a `cursor` that advances past hunks whose range has ended. `decorate_line` is called in ascending `doc_line` order within a render pass, so no backtracking is needed. Rebuilding the closure each `render_view` call resets `cursor` to 0 automatically. Same pattern as `DiffAlignment::filler_lines_at`.
Lines that exist only in file A (pure-deletion hunks) were colored with diff.plus (green) instead of diff.minus (red). Extract the style dispatch into diff_line_style and dispatch based on DiffSide so each category maps to the correct theme scope: - DiffSide::A, other empty -> diff.minus (deleted from B) - DiffSide::B, other empty -> diff.plus (added in B) - either side, other non-empty -> diff.delta (modified) Covered by a unit test in diff_coloring_tests.
Replace the character-level Myers diff tokenizer in `intra_line_changes` with a word-level one. Alphanumeric+underscore runs are one token; whitespace and punctuation stay as single-char tokens. With character-level diff, substituting one word marks several small fragments as changed. When the theme defines `diff.delta.text` with an explicit background, that overlay covers most of the line and hides the line-level diff color. Word granularity limits the overlay to the changed word(s) only. `word_token_char_starts` precomputes char offsets per token so token-index hunks from imara-diff can be mapped back to char-column ranges.
… scopes theme.get() returns Style::default() when a scope is absent, which has no fg and no bg. fg_to_bg then returns Style::default() unchanged, and renderer.set_style with an all-None style is a no-op on cell backgrounds. Switch to theme.try_get() so themes that define no diff scopes at all receive hardcoded fallback colors instead of invisible highlights.
When a theme sets both fg and bg on diff.minus, diff.plus, or diff.delta,
push an OverlayHighlights::Homogeneous for every affected line. Overlays
patch over syntax fg in draw_grapheme, so the theme's fg overrides syntax
color on the diff line (the vimdiff "whole line in one color" look).
Themes that set only fg or only bg don't trigger this. The overlay is
gated on both being present, so the upstream nord convention
("diff.minus" = "nord11", fg-only) continues to behave as a line bg via
the existing fg-as-bg fallback.
Refactors the in-closure fg_to_bg helper into a module-level diff_bg_style
fn with a four-branch match, preserving fg when both fg and bg are set.
Adds diff_line_char_ranges for bucketing hunks into minus/plus/delta char
ranges on the current side. Tests cover all four diff_bg_style branches
and the four hunk-bucketing cases (pure deletion, pure addition, modified
pair on either side, mixed hunks, empty input).
Rebase onto trunk introduced an unconditional `use termina::event::{Event, KeyEvent};` alongside the platform-gated imports below. The cfg-gated block already covers both Windows and non-Windows targets. Remove the duplicate so the integration test target compiles.
Both entry points for the two-file diff view (CLI `--diff` and the `:diff-open` command) already parsed the optional `file:line` or `file:line:col` suffix via `parse_file`, but discarded the position. Opening `hx --diff a.rs:42 b.rs:58` landed both panes at line 1. Keep the position and apply it after each `editor.open`, using the same pos_at_coords + set_selection + align_view pattern that the non-diff file-open loop uses. In application.rs the work goes through a small `apply_diff_positions` helper so both sides share one code path. Integration tests cover the four cases that matter: - `:diff-open a:3 b:5` positions both panes - `:diff-open a:3 b` positions one pane, leaves the other at the top - `:diff-open a b` with no suffixes keeps both at line 1 (regression guard) - `hx --diff a:3 b:5` via `AppBuilder::with_diff` proves the CLI branch lands each cursor in its own pane The CLI test was mutation-checked by commenting out both `apply_diff_positions` calls and confirming the assertion fails.
38b6910 to
9c2608f
Compare
|
would it be possible to have more dedicated theme keys for this? something like |
|
thinking it over, i think my preference would be:
doing it like that and applying the styles directly also makes it, so i can theoretically highlight the diffs in the diff view via text color instead of being forced to use them as background color |


A vimdiff-style side-by-side diff viewer.
Usage
:diff-open file1 file2opens both files in a vertical split and starts a diff session. Alias::diffs.hx --diff file1 file2(or-d) does the same from the command line.:diff-thismarks a view; call it in a second view to pair them.Inside a session
:diffget/reset-diff-changepulls the hunk at the cursor from the partner file.:diffputpushes the current selection's hunk the other way.:diff-offtears the session down, leaving both views open.]gand[gjump between hunks, using session hunks and falling back to VCS hunks when outside a session.How it works
The renderer recomputes hunks when
Document::version()changes. ALineAnnotationinserts virtual filler lines so both sides line up by logical row. Line backgrounds come fromdiff.plus,diff.minus, anddiff.delta. Intra-line highlights use anOverlayHighlightslayer keyed ondiff.delta.text, falling back todiff.deltawhen unset. Changed regions are diffed at the word level (alphanumeric runs), sofoo_barvsbaz_barhighlightsfoorather than a run of character edits.Theme
diff.delta.textis a new scope for intra-line changed characters; it falls back todiff.deltawhen unset. The defaulttheme.tomldefines it with an explicitbgso whitespace changes are visible against thediff.deltaline background.Tests
23 unit tests in
helix-view/src/diff_session.rscover alignment, hunk intersection, and the diffget/diffput transaction builders. Integration tests inhelix-term/tests/test/diff_mode.rsexercise:diff-open,:diff-this,:diff-off,:diffget, and:diffputagainst tempfile-backed documents.Known rough edges
diff.delta's fg and bg.