fix(diff-overlay): let the scrollbar reach the end of the patch

A list counts an item it has not laid out yet as zero tall, so the flattened
row list reported a patch of any length as about one viewport of content. The
scrollbar reads that height: its thumb filled the track, and dragging it from
the top to the bottom travelled 248px into an 800-line diff and stopped —
`scrollbar_drag_start_height` freezes the height for the duration of a drag,
so the gesture could not even grow into the rows it uncovered. The overlay
had a bar that could reach anywhere before the rows were virtualised.

`measure_all` would settle it by laying out every row on the first frame,
which is the cost the row list exists to avoid. Count the rows below the fold
at `DIFF_LINE_H` instead — the 19px both views already give a line of a patch
— through `ListState::with_size_hint`, a new gpui fork patch. File and hunk
rows are a few pixels taller, so the estimate runs short by a few percent
until they are measured; a diff is overwhelmingly its lines.

`a_long_patch_builds_only_the_rows_on_screen` now also asserts the bar's
reach. Without the hint it measures 248.5px against 802 rows.
This commit is contained in:
l0ng-ai
2026-09-07 21:58:13 +08:00
parent b5c20932cb
commit abbe69374e
3 changed files with 95 additions and 58 deletions
Generated
+45 -45
View File
@@ -298,7 +298,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -309,7 +309,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -891,7 +891,7 @@ dependencies = [
"bitflags 2.13.1",
"cexpr",
"clang-sys",
"itertools 0.13.0",
"itertools 0.11.0",
"log",
"prettyplease",
"proc-macro2",
@@ -1483,7 +1483,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"gpui_util",
"indexmap",
@@ -2047,7 +2047,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"proc-macro2",
"quote",
@@ -2104,7 +2104,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -2142,7 +2142,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
dependencies = [
"libloading 0.8.9",
"libloading 0.7.4",
]
[[package]]
@@ -2394,7 +2394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -3199,7 +3199,7 @@ dependencies = [
"log",
"presser",
"thiserror 2.0.20",
"windows 0.62.2",
"windows 0.58.0",
]
[[package]]
@@ -3225,7 +3225,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.2.2"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"accesskit",
"anyhow",
@@ -3416,7 +3416,7 @@ dependencies = [
[[package]]
name = "gpui_linux"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"accesskit",
"accesskit_unix",
@@ -3467,7 +3467,7 @@ dependencies = [
[[package]]
name = "gpui_macos"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"accesskit",
"accesskit_macos",
@@ -3514,7 +3514,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -3525,7 +3525,7 @@ dependencies = [
[[package]]
name = "gpui_platform"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"console_error_panic_hook",
"gpui",
@@ -3538,7 +3538,7 @@ dependencies = [
[[package]]
name = "gpui_shared_string"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"schemars",
"serde",
@@ -3548,7 +3548,7 @@ dependencies = [
[[package]]
name = "gpui_util"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"log",
@@ -3557,7 +3557,7 @@ dependencies = [
[[package]]
name = "gpui_web"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"console_error_panic_hook",
@@ -3581,7 +3581,7 @@ dependencies = [
[[package]]
name = "gpui_wgpu"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"bytemuck",
@@ -3610,7 +3610,7 @@ dependencies = [
[[package]]
name = "gpui_windows"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"accesskit",
"accesskit_windows",
@@ -3939,7 +3939,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"async-compression",
@@ -3964,7 +3964,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"rustls",
"rustls-platform-verifier",
@@ -4057,7 +4057,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core 0.58.0",
]
[[package]]
@@ -5092,7 +5092,7 @@ checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
[[package]]
name = "media"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"bindgen",
@@ -5202,7 +5202,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -5258,7 +5258,7 @@ dependencies = [
"once_cell",
"png",
"thiserror 2.0.20",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -5481,7 +5481,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -6204,7 +6204,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "perf"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"collections",
"serde",
@@ -6900,7 +6900,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7207,7 +7207,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"derive_refineable",
]
@@ -7250,7 +7250,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "reqwest_client"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"bytes",
@@ -7611,7 +7611,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7784,7 +7784,7 @@ dependencies = [
[[package]]
name = "scheduler"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"async-task",
"backtrace",
@@ -8389,7 +8389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8515,7 +8515,7 @@ dependencies = [
"cfg-if",
"libc",
"psm",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8627,7 +8627,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"heapless",
"log",
@@ -8917,7 +8917,7 @@ dependencies = [
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -9384,7 +9384,7 @@ dependencies = [
"once_cell",
"png",
"thiserror 2.0.20",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -9896,7 +9896,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset 0.9.1",
"tempfile",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -10108,7 +10108,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"async-fs",
@@ -10147,7 +10147,7 @@ dependencies = [
[[package]]
name = "util_macros"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"perf",
"quote",
@@ -10740,7 +10740,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -11491,7 +11491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
dependencies = [
"cfg-if",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -12048,7 +12048,7 @@ dependencies = [
[[package]]
name = "zlog"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"anyhow",
"chrono",
@@ -12093,7 +12093,7 @@ dependencies = [
[[package]]
name = "ztracing"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
dependencies = [
"tracing",
"tracing-subscriber",
@@ -12104,7 +12104,7 @@ dependencies = [
[[package]]
name = "ztracing_macro"
version = "0.1.0"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#d99a40a5f79a173465feec00363d480f75881a81"
source = "git+https://github.com/l0ng-ai/zed?branch=tty7#ece710e365575bc08656c4a8d20488769b2de9a6"
[[package]]
name = "zune-core"
+9 -1
View File
@@ -367,7 +367,7 @@ lto = "thin"
codegen-units = 1
# ---- gpui fork ------------------------------------------------------------
# Our `tty7` branch (cut from the pinned upstream rev, three commits on top) carries:
# Our `tty7` branch (cut from the pinned upstream rev) carries:
#
# 1. `prefers_ime_for_printable_keys` takes the keystroke, so an input handler can
# answer per key instead of per view. tty7 needs it for Option-as-Meta — macOS
@@ -386,6 +386,14 @@ codegen-units = 1
# 3. resvg/usvg bumped 0.45 → 0.47 so gpui's SVG stack unifies with the resvg
# tty7 pins directly above (follow-up to the #227 dependabot bump).
#
# 4. `ListState::with_size_hint`. A list counts an item it has not laid out yet
# as zero tall, so a long document reports itself as about one viewport and
# a scrollbar reading that height drags one viewport and stops — the diff
# overlay scrolls a whole working tree through one of these. The hint counts
# the unmeasured items at an estimate instead, which `measure_all` would
# settle exactly at the price of laying out the whole document on the first
# frame (see `ui::diff_overlay::DIFF_LINE_H`).
#
# Patching by source rather than editing the `gpui`/`gpui_platform` pins above is
# deliberate: `gpui-component` declares its own `gpui` from the upstream URL, and
# a plain pin swap would put two incompatible copies of gpui in the tree. `[patch]`
+41 -12
View File
@@ -168,7 +168,8 @@ impl Tty7App {
focus,
preview: None,
preview_loading: None,
list: gpui::ListState::new(0, gpui::ListAlignment::Top, px(256.)),
list: gpui::ListState::new(0, gpui::ListAlignment::Top, px(256.))
.with_size_hint(DIFF_LINE_H),
rows: Rc::new(Vec::new()),
rows_key: None,
epoch: None,
@@ -897,15 +898,14 @@ impl Tty7App {
// padding here would be silently ignored. The rows carry their own —
// see `diff_row_element`.
.py_4();
// A whole working tree can scroll past here with nothing to say how
// far it runs or where in it you are — the one long document in the
// app without the bar every other scroll area has.
//
// The bar reads the list's own height, and a list only knows the rows
// it has measured: until the reader has been to the bottom once, the
// thumb is sized against a document that is still being discovered, so
// it shrinks as they scroll. `ListState::measure_all` would settle it
// by laying out every row on the first frame, which is the cost this
// The bar reads the list's own height, and a list only counts the
// rows it has measured. Left at that, a patch of any length would
// report itself as one screen long and the thumb would fill the
// track: a drag from top to bottom would travel one screen and stop,
// on the one document in the app long enough to need the bar. The
// rows below the fold are counted at `DIFF_LINE_H` until they are
// laid out — `ListState::measure_all` would settle it exactly, by
// laying out every row on the first frame, which is the cost this
// whole list exists to avoid.
crate::ui::scrollbar::with_vertical_scrollbar("diff-overlay-scrollbar", body, &list)
}
@@ -1055,6 +1055,16 @@ const FILE_ROW_H: Pixels = px(26.);
/// The radius on a row that lights up under the pointer. Matches the panel's.
const ROW_RADIUS: Pixels = px(5.);
/// The height of one line of a patch, in either view.
///
/// Also what the list counts a row it has not laid out yet at. A list knows
/// only the rows it has measured, so without an estimate for the rest a patch
/// of any length reports itself as one screen long — and the scrollbar, which
/// reads that height, drags the reader one screen and stops. The file and hunk
/// rows are a few pixels taller, so the estimate runs a little short until
/// they have been measured; a diff is overwhelmingly its lines.
const DIFF_LINE_H: Pixels = px(19.);
/// The rule between one hunk and the last line of the one before it.
///
/// Barely there on purpose: with the cards gone it is the only line left in
@@ -1322,7 +1332,7 @@ fn diff_untracked_row(
fn diff_split_row(row: &SplitRow, font: &SharedString, cx: &gpui::App) -> impl IntoElement {
h_flex()
.w_full()
.h(px(19.))
.h(DIFF_LINE_H)
.items_stretch()
.text_xs()
.font_family(font.clone())
@@ -1391,7 +1401,7 @@ fn diff_unified_row(row: &UnifiedRow, font: &SharedString, cx: &gpui::App) -> im
};
h_flex()
.w_full()
.h(px(19.))
.h(DIFF_LINE_H)
.items_center()
.text_xs()
.font_family(font.clone())
@@ -2778,6 +2788,25 @@ mod render_idle_gpui_tests {
"a frame built {built} of {rows} rows: the list is not virtualised"
);
// The other half of virtualising: a list counts the rows it has not
// laid out at zero unless it is given an estimate, and the scrollbar
// reads that count as the length of the document. Without the size
// hint the bar reaches 248px into this patch — its thumb fills the
// track, and dragging it to the bottom lands a screen down.
let reach = app.update_in(&mut vcx, |app, _, _| {
app.tabs[app.active]
.diff_overlay
.as_ref()
.expect("the overlay is open")
.list
.max_offset_for_scrollbar()
.y
});
assert!(
reach > DIFF_LINE_H * (rows as f32 * 0.75),
"the scrollbar reaches {reach} into a patch of {rows} rows"
);
let _ = std::fs::remove_dir_all(&root);
}