mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-11-06 00:15:06 +00:00
fix(sidebar): elide branch names from the end only
The sidebar branch (group header and row git line) was front-elided, but its width budget was a few pixels short: the line paints with tabular figures while it was measured with proportional ones, and the separator was measured as " · " though it is drawn as a bare "·" between two gaps. The front-elided branch then overflowed and CSS truncation cut its tail too, leaving "…nd-error-classificati…". - Measure the git line and header with the same tabular font they paint with, and measure the separator as drawn. - Elide the branch from the end, the plain cut a reader expects; the CSS backstop now cuts in the same direction.
This commit is contained in:
+28
-24
@@ -24,8 +24,8 @@ use crate::ui::i18n::{L10nKey, t, t_fmt};
|
||||
use crate::ui::reorder::{self, Reorder, Surface};
|
||||
use crate::ui::right_panel::RESIZE_HANDLE_WIDTH;
|
||||
use crate::ui::tab_strip::{
|
||||
DragTab, REORDER_SLIDE_MS, abbreviate_home, elide_label, elide_path_keep_tail,
|
||||
elide_tail_clusters, measure_text,
|
||||
DragTab, REORDER_SLIDE_MS, abbreviate_home, elide_end_clusters, elide_label,
|
||||
elide_path_keep_tail, measure_text,
|
||||
};
|
||||
|
||||
pub(crate) const MIN_SIDEBAR_WIDTH: f32 = 180.;
|
||||
@@ -68,7 +68,6 @@ const HEAD_CONTROL_HEIGHT: f32 = 28.;
|
||||
const HEAD_CONTROL_RADIUS: f32 = 7.;
|
||||
|
||||
/// What sits between a group's branch and its diff counts.
|
||||
const META_SEP: &str = " · ";
|
||||
const META_SEP_TRIMMED: &str = "·";
|
||||
|
||||
/// Tabular numerals, so a column of diff counts lines up digit for digit.
|
||||
@@ -375,9 +374,17 @@ impl Tty7App {
|
||||
let rem = window.rem_size().as_f32();
|
||||
// Measure with the same interface scale used to paint the header.
|
||||
let header_size = rem * META_REM;
|
||||
// The git lines and the group header paint with `tabular()` figures,
|
||||
// which set a `1` as wide as an `8`; measuring them proportionally
|
||||
// came out a few pixels short on counts like `+1565 −124`, and the
|
||||
// front-elided branch then lost its tail to CSS truncation as well.
|
||||
let meta_font = gpui::Font {
|
||||
features: tabular(),
|
||||
..font.clone()
|
||||
};
|
||||
let header_font = gpui::Font {
|
||||
weight: FontWeight::MEDIUM,
|
||||
..font.clone()
|
||||
..meta_font.clone()
|
||||
};
|
||||
// On the rail the diff counts are metadata like the branch beside
|
||||
// them, so they take its caption ink; the hover card, which is where
|
||||
@@ -678,13 +685,12 @@ impl Tty7App {
|
||||
.text_size(px(meta_size))
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.font_features(tabular());
|
||||
let counts_w = counts_width(&window.text_system(), &font, meta_size, &g);
|
||||
// Branch: cut from the front, so `…session-auth` keeps
|
||||
// the part that tells two branches off one prefix apart.
|
||||
let counts_w = counts_width(&window.text_system(), &meta_font, meta_size, &g);
|
||||
// Branch: cut from the end, the way any other label is.
|
||||
let branch_avail = (label_avail - counts_w).max(0.);
|
||||
let shown = elide_tail_clusters(
|
||||
let shown = elide_end_clusters(
|
||||
&window.text_system(),
|
||||
&font,
|
||||
&meta_font,
|
||||
meta_size,
|
||||
&g.branch,
|
||||
branch_avail,
|
||||
@@ -1236,24 +1242,23 @@ impl Tty7App {
|
||||
}
|
||||
let count_label = row_count.to_string();
|
||||
if folded {
|
||||
avail -=
|
||||
measure_text(&ts, &font, header_size, &count_label) + row_metrics::META_GAP;
|
||||
avail -= measure_text(&ts, &meta_font, header_size, &count_label)
|
||||
+ row_metrics::META_GAP;
|
||||
}
|
||||
let avail = avail.max(HEADER_NAME_FLOOR);
|
||||
// What the shared branch would take if nothing were in its
|
||||
// way: the icon, the gap after it, the branch itself, the
|
||||
// counts, and the two gaps the spacer between the name and
|
||||
// the branch sits in.
|
||||
// What the shared branch would take if nothing were in its
|
||||
// way: the branch itself, the ` · ` before its counts, the
|
||||
// way: the branch itself, the `·` before its counts, the
|
||||
// counts, and the gaps the spacer between the name and the
|
||||
// branch sits in.
|
||||
let sep_w = measure_text(&ts, &font, header_size, META_SEP);
|
||||
// Drawn as a bare `·` with a gap on either side; the gap before
|
||||
// it is the one `counts_width` already reserves.
|
||||
let sep_w = measure_text(&ts, &meta_font, header_size, META_SEP_TRIMMED)
|
||||
+ row_metrics::META_GAP;
|
||||
let git_want = shared_git.as_ref().map(|shared| {
|
||||
let counts = counts_width(&ts, &font, header_size, &shared.status);
|
||||
let counts = counts_width(&ts, &meta_font, header_size, &shared.status);
|
||||
let sep = if counts > 0. { sep_w } else { 0. };
|
||||
2. * row_metrics::META_GAP
|
||||
+ measure_text(&ts, &font, header_size, &shared.status.branch)
|
||||
+ measure_text(&ts, &meta_font, header_size, &shared.status.branch)
|
||||
+ sep
|
||||
+ counts
|
||||
});
|
||||
@@ -1398,15 +1403,14 @@ impl Tty7App {
|
||||
click,
|
||||
rows,
|
||||
} = shared;
|
||||
let counts_w = counts_width(&ts, &font, header_size, &status);
|
||||
let counts_w = counts_width(&ts, &meta_font, header_size, &status);
|
||||
let sep = if counts_w > 0. { sep_w } else { 0. };
|
||||
let branch_avail =
|
||||
(avail - name_w - 2. * row_metrics::META_GAP - sep - counts_w).max(0.);
|
||||
// Cut from the front, like a row's: the tail is what
|
||||
// tells two branches off the same prefix apart.
|
||||
let branch = elide_tail_clusters(
|
||||
// Cut from the end, like a row's.
|
||||
let branch = elide_end_clusters(
|
||||
&ts,
|
||||
&font,
|
||||
&meta_font,
|
||||
header_size,
|
||||
&status.branch,
|
||||
branch_avail,
|
||||
|
||||
@@ -471,6 +471,38 @@ pub(crate) fn elide_label(
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps the longest head of `text` that fits before a trailing ellipsis —
|
||||
/// the plain cut a reader expects, for a string read left to right like a
|
||||
/// branch name.
|
||||
pub(crate) fn elide_end_clusters(
|
||||
text_system: &gpui::WindowTextSystem,
|
||||
font: &gpui::Font,
|
||||
size: f32,
|
||||
text: &str,
|
||||
max_width: f32,
|
||||
) -> SharedString {
|
||||
if measure_text(text_system, font, size, text) <= max_width {
|
||||
return SharedString::from(text.to_string());
|
||||
}
|
||||
let budget = max_width - measure_text(text_system, font, size, "…");
|
||||
if budget <= 0. {
|
||||
return SharedString::from("…");
|
||||
}
|
||||
let cells = clusters(text);
|
||||
let (mut lo, mut hi) = (0usize, cells.len());
|
||||
while lo < hi {
|
||||
let mid = (lo + hi + 1) / 2;
|
||||
if measure_text(text_system, font, size, &cells[..mid].concat()) <= budget {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
let mut out = cells[..lo].concat();
|
||||
out.push('…');
|
||||
SharedString::from(out)
|
||||
}
|
||||
|
||||
/// Keeps the longest tail of `text` that fits after a bare ellipsis. Shared
|
||||
/// by the path and token elisions as their last resort.
|
||||
pub(crate) fn elide_tail_clusters(
|
||||
@@ -3223,6 +3255,19 @@ mod tests {
|
||||
assert!(out.chars().count() < branch.chars().count());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn elide_end_keeps_the_head_of_a_branch(cx: &mut TestAppContext) {
|
||||
let (ts, font, size) = elide_setup(cx);
|
||||
let branch = "fix/rpc-proxy-and-error-classification";
|
||||
let max = 140.;
|
||||
let out = elide_end_clusters(&ts, &font, size, branch, max);
|
||||
assert!(out.starts_with("fix/rpc-"), "head survives: {out}");
|
||||
assert!(out.ends_with('…') && out.matches('…').count() == 1, "{out}");
|
||||
assert!(measure_text(&ts, &font, size, &out) <= max);
|
||||
let fits = measure_text(&ts, &font, size, branch);
|
||||
assert_eq!(elide_end_clusters(&ts, &font, size, branch, fits), branch);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn elide_edges_leaves_short_branches_alone(cx: &mut TestAppContext) {
|
||||
let (ts, font, size) = elide_setup(cx);
|
||||
|
||||
Reference in New Issue
Block a user