feat: use two-column diagonal diff hatching (EXPERIMENT; DISCARDED)

This commit is contained in:
2026-10-14 00:01:10 +02:00
parent a8428f9dde
commit 4099435175
4 changed files with 34 additions and 14 deletions
+2 -3
View File
@@ -1,14 +1,13 @@
- [x] add option to highlight trailing whitespace (off by default)
- [x] add option to put characters at certain whitespace characters (off by default)
- whitespace kind -> character map should be configurable.
- [ ] diff view - design question:
- is it possible to angle the hatchings for empty lines more?
- if a character like the current one exists with a 45degree angle instead (and a top and bottom), we could use that across two columns. that would give us a perfectly 45 degree hatching.
- [x] diff view: use two-column diagonal hatching for missing counterparts (45° with 2:1 terminal cells)
- [ ] github/gitlab/gitea integration (probably requries authentication with github)
- github pr picker (floating window)
- three tabs: open, closed, all
- search under the given category
- selecting a pr opens the diff viewer for that pr using the branch merge base (standard github diff view on prs; already implemented as default diff viewer behavior in vamp)
- [ ] cut down on the amount of documentation in the project (readme file is needlessly detailed and filled with irrelevant information).
- [ ] diff view: github integration (code review) - add comments on a pr, add review comments at specific lines of files, request changes, approve pr, etc.
- [ ] diff view: local review comments (same features as for github reviews) that allows review of local changes to deliver specific reviews to ai agents used for coding.
- design question: would this require a server that the agent can interact with, or would local review files be fine? don't want to use server client stuff, if it would be easier and better to just use local files.
+2 -2
View File
@@ -40,7 +40,7 @@ U+1FBD0/U+1FBD1 are **light box-drawing strokes**. The chart separately labels U
- Unicode 16.0 repertoire support is a prerequisite; the existence of these characters does not establish coverage in the selected terminal/font. Missing glyphs or fallback glyphs remain possible, and fallback geometry must be checked in the intended renderer. Unicode explicitly says its chart glyph shapes are **not prescriptive** and actual fonts may vary ([chart, page 1](https://www.unicode.org/Public/17.0.0/charts/PDF/U1FB00.pdf)). Pixel-perfect joins or 45° rendering cannot be promised.
- The pair has East Asian Width **Neutral (`N`)**, from `1FB94..1FBEF` in [EastAsianWidth.txt](https://www.unicode.org/Public/17.0.0/ucd/EastAsianWidth.txt). This is not a terminal-width guarantee: [UAX #11, §2 and §4](https://www.unicode.org/reports/tr11/) explicitly requires terminal-specific tailoring and distinguishes font/layout width from the property. Both the application and terminal must agree on one column per glyph.
- Current rendering writes one `╱` per column with `DarkGray` foreground and reset background in [src/tui.rs:3308–3316](../src/tui.rs#L3308-L3316); the documented behavior is [docs/editing.md:426](editing.md#L426). This note proposes no implementation change. Any later cutover would need a defined pair phase and odd-width clipping behavior, plus terminal/font evidence.
- Rendering now alternates `🯐` and `🯑`, starting at each pane's left content edge, with `DarkGray` foreground and reset background in [src/tui.rs:3308–3316](../src/tui.rs#L3308-L3316). The phase stays fixed during scrolling; odd widths clip the final pair. Actual empty source lines remain unhatched. See [docs/editing.md:426](editing.md#L426).
## Terminal support
@@ -54,4 +54,4 @@ U+1FBD0/U+1FBD1 are **light box-drawing strokes**. The chart separately labels U
An executed disposable `rustc` smoke program used the repository's actual compiled `ratatui` and `unicode-width` dependencies, a fixed viewport, `Terminal<CrosstermBackend<Vec<u8>>>`, and the same per-cell `set_symbol`/style logic as the current renderer. Both U+1FBD0 and U+1FBD1 returned width `Some(1)`. At three rows high, widths 1, 2, 3, and 12 emitted exactly 3, 6, 9, and 36 glyphs respectively: no dropped or double-width cells. This establishes application-side width/emission behavior, **not** actual terminal glyph coverage, pixel joins, or the visible angle; those were not visually verified. The disposable program and binary were removed.
Investigation only: no production code changed and no TODO marked complete.
Implemented with no new dependency or graphics protocol. Verification: `cargo test diff_` (94 passing checks), `cargo build`, and `cargo fmt --package vamp --check`. A live PTY session opened `:diff` against a disposable jj baseline with two removed rows and emitted paired hatching at 100, 101, and 77 terminal columns; no U+2571 was emitted, the editor exited cleanly, and fixture content stayed unchanged. The fixture was removed. Terminal pixel joins remain visually unverified. The TODO item is complete.
+1 -1
View File
@@ -423,7 +423,7 @@ Character-wise highlighting is enabled automatically within paired changed lines
Diff backgrounds blend the terminal's default background with its red/green palette colors: an 18% accent for changed rows and a stronger 38% accent for changed characters, both computed from the original background. Read-only OSC 11/4 queries run after the initial paint, on focus regain, and whenever a subscribed DEC 2031 color-scheme notification arrives. While a diff is visible in any pane, a nonblocking one-second palette probe also catches external theme changes without a notification, including changes between two themes with the same light/dark preference. Ordinary editor views do not poll; cached colors apply across editor panes. If the complete palette is unavailable within 250 ms, change backgrounds remain normal and gutter colors/markers still identify changes.
Missing counterparts use adjacent muted `╱` diagonals (U+2571, box drawings light diagonal) on the normal terminal background, one per column, with the same terminal `DarkGray` foreground as ordinary gutter text. Unlike the text slash `/`, this glyph is designed to reach cell corners so diagonals can join end to end across successive rows. Pixel-perfect joins depend on the terminal's font and line-spacing rendering. Actual empty source lines retain their normal/change background and are not hatched.
Missing counterparts use repeated muted `🯐🯑` diagonals (U+1FBD0/U+1FBD1), one glyph per column, on the normal terminal background with the same terminal `DarkGray` foreground as ordinary gutter text. Each pair joins the bottom-left corner to the top-right corner across two columns, giving a 45° angle when cells are twice as tall as they are wide. The pattern starts at each pane's left content edge, stays fixed during scrolling, and clips the final pair at odd widths. These Unicode 16 characters need a supporting terminal or font; Ghostty 1.3.1 draws them natively. Pixel-perfect joins and the exact angle depend on terminal cell geometry and rendering. Actual empty source lines retain their normal/change background and are not hatched.
In Normal mode, Tab/Shift+Tab cycles focus between Files and the visible text panes, skipping absent panes. Switching Before/After synchronizes the primary cursor by aligned diff row and display column. A missing counterpart uses the nearest visible source row; shorter lines clamp the column while retaining the preferred display column for the return trip. Vertical wheel input scrolls the visible panes without moving the focused text caret; switching sides by vertical wheel also synchronizes the cursor. Over Files it navigates visible tree rows without opening them. Horizontal wheel input in either split synchronizes the Before/After horizontal offsets, bounded by the longer side, without changing focus, cursor, or selections; over Files it is consumed. Added/deleted files retain single-pane scrolling. While a comparison is open, Escape cancels modes, prompts, or filtering, or returns picker focus to the last bound text pane; it leaves the comparison open. Use **`:diff.close`**, find `diff.close` in the palette, or bind **`config.core.diff.close`** to leave the comparison explicitly. The dotted command is intentional; there is no `:diffclose` alias.
+29 -8
View File
@@ -3310,7 +3310,7 @@ fn draw_diff_pane(
let buffer = frame.buffer_mut();
for x in 0..content.width {
buffer[(content.x + x, row_area.y)]
.set_symbol("╱")
.set_symbol(["🯐", "🯑"][usize::from(x % 2)])
.set_style(style);
}
continue;
@@ -5353,7 +5353,7 @@ mod tests {
if scope == WhitespaceScope::All { 8 } else { 4 },
);
if mode == ViewMode::Split {
assert!(buffer.content.iter().any(|cell| cell.symbol() == "╱"));
assert!(buffer.content.iter().any(|cell| cell.symbol() == "🯐"));
}
}
}
@@ -6479,7 +6479,7 @@ mod tests {
pane_screen(&terminal, Rect::new(present.right() - 13, present.y, 12, 1),)
.contains(if added { "editable" } else { "read-only" })
);
assert!(!text.contains('╱'));
assert!(!text.contains(['🯐', '🯑']));
}
let mut empty_added = deletion_file();
empty_added.status = FileStatus::Added;
@@ -6541,7 +6541,7 @@ mod tests {
.contains("old revision → Working checkout")
);
assert!(!text.contains("unchanged rename content"));
assert!(!text.contains('╱'));
assert!(!text.contains(['🯐', '🯑']));
let mut modified = diff_comparison(vec![deletion_file()]);
modified.files_visible = false;
@@ -6802,7 +6802,14 @@ mod tests {
let gap_symbols: Vec<_> = (after.x + 1..after.right() - 1)
.map(|x| {
let cell = &buffer[(x, y)];
assert_eq!(cell.symbol(), "╱");
assert_eq!(
cell.symbol(),
if (x - after.x - 1) % 2 == 0 {
"🯐"
} else {
"🯑"
}
);
assert_eq!(cell.bg, Color::Reset);
assert_eq!(cell.fg, buffer[(after.x + 1, y + 1)].fg);
cell.symbol().to_owned()
@@ -6858,7 +6865,14 @@ mod tests {
let y = before.y + 1 + offset;
for x in before.x + 1..before.right() - 1 {
let cell = &buffer[(x, y)];
assert_eq!(cell.symbol(), "╱");
assert_eq!(
cell.symbol(),
if (x - before.x - 1) % 2 == 0 {
"🯐"
} else {
"🯑"
}
);
assert_eq!(cell.bg, Color::Reset);
}
assert_eq!(buffer[(after.x + 1, y)].symbol(), (offset + 1).to_string());
@@ -6876,7 +6890,14 @@ mod tests {
render_diff(&mut terminal, &diff, &mut panels);
let buffer = terminal.backend().buffer();
for x in before.x + 1..before.right() - 1 {
assert_eq!(buffer[(x, before.y + 1)].symbol(), "╱");
assert_eq!(
buffer[(x, before.y + 1)].symbol(),
if (x - before.x - 1) % 2 == 0 {
"🯐"
} else {
"🯑"
}
);
}
assert_eq!(buffer[(before.x, before.y + 1)].symbol(), "│");
assert_eq!(buffer[(before.right() - 1, before.y + 1)].symbol(), "│");
@@ -10751,7 +10772,7 @@ end
vec!["3", "3", "end"]
]
);
assert!(!lines.iter().any(|line| line.contains('╱')));
assert!(!lines.iter().any(|line| line.contains(['🯐', '🯑'])));
editor.handle(Input::Char('\t'));
render_native_diff(&mut terminal, &mut views, &mut editor);
let geometry = views.panels.diff.as_ref().unwrap();