feat(ui): spell tab titles out in full in the rail and title bar

The rail's rows and the centred title have room to spare, so they take
the whole label from a new full_tab_label rather than tab_label's
three-segment cut; only the width they have decides what gets elided.
This commit is contained in:
l0ng-ai
2026-09-24 20:52:58 +08:00
parent db53b1e424
commit 943efdf6ce
2 changed files with 59 additions and 42 deletions
+22 -38
View File
@@ -22,7 +22,7 @@ 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, strip_host_prefix,
elide_tail_clusters, measure_text,
};
pub(crate) const MIN_SIDEBAR_WIDTH: f32 = 180.;
@@ -537,44 +537,28 @@ impl Tty7App {
// `full_title` is the unelided string the card can expand
// back to; `None` means the row is showing a placeholder that
// no card can improve on.
let (title_text, full_title) =
if let Some(name) = tab.name.as_ref().filter(|n| !n.trim().is_empty()) {
// A renamed tab is elided like anything else — and so
// the card has to be able to spell the name back out.
let full = SharedString::from(name.trim().to_string());
//
// The ladder the strip and the switcher climb, read here for
// the name and not for the shortening: this column measures in
// pixels and lets a card expand the row back to the whole
// string, so it wants what `label_of` would have cut down
// rather than the cut. A renamed tab is elided like anything
// else — and so the card has to be able to spell it back out.
let (title_text, full_title) = match self.full_tab_label(tab, Some(window), cx) {
Some(raw) => {
let full = SharedString::from(raw);
(full.clone(), Some(full))
} else {
// The ladder the strip and the switcher climb, read
// here for the name and not for the shortening: this
// column measures in pixels and lets a card expand the
// row back to the whole string, so it wants what
// `label_of` would have cut down rather than the cut.
use crate::ui::machine_mirror::TabLabel;
let (view, home) = tab.label_view(Some(window), cx);
let raw = match view.label() {
TabLabel::Osc(title) | TabLabel::Cwd(title) => {
abbreviate_home(strip_host_prefix(title.trim()), home.as_deref())
.into_owned()
}
TabLabel::Agent(agent) => agent.display_name().to_string(),
// A tab holding a name got one above.
TabLabel::Named(name) => name.to_string(),
TabLabel::Process(title) => title.to_string(),
TabLabel::Unknown => String::new(),
};
if raw.trim().is_empty() {
// Nothing to expand: the row is naming an unnamed
// shell, not hiding a title behind an ellipsis.
let placeholder = SharedString::from(t_fmt(
L10nKey::TabUnnamedShell,
&[("n", &((i + 1).to_string()))],
));
(placeholder, None)
} else {
let full = SharedString::from(raw);
(full.clone(), Some(full))
}
};
}
// Nothing to expand: the row is naming an unnamed shell,
// not hiding a title behind an ellipsis.
None => {
let placeholder = SharedString::from(t_fmt(
L10nKey::TabUnnamedShell,
&[("n", &((i + 1).to_string()))],
));
(placeholder, None)
}
};
let mut branch_shown: Option<(SharedString, SharedString, u32, u32)> = None;
let mut cwd_shown: Option<(SharedString, SharedString)> = None;
let git_line = match shared_git.is_some() {
+37 -4
View File
@@ -1656,6 +1656,37 @@ impl Tty7App {
label_of(&view, index, home.as_deref())
}
/// The same ladder as [`tab_label`](Self::tab_label), left whole: a path
/// is only abbreviated under the home and stripped of its `user@host:`,
/// never cut down to its last segments. For the surfaces with room to
/// spare — the rail's rows and the centred title — where only the width
/// they have may decide what gets elided, not a segment count picked for
/// the chips. `None` when there is nothing to say and the caller should
/// fall back to its own placeholder.
pub(crate) fn full_tab_label(
&self,
tab: &Tab,
window: Option<&Window>,
cx: &App,
) -> Option<String> {
use crate::ui::machine_mirror::TabLabel;
if let Some(name) = tab.name.as_ref().filter(|n| !n.trim().is_empty()) {
return Some(name.trim().to_string());
}
let (view, home) = tab.label_view(window, cx);
let raw = match view.label() {
TabLabel::Osc(title) | TabLabel::Cwd(title) => {
abbreviate_home(strip_host_prefix(title.trim()), home.as_deref()).into_owned()
}
TabLabel::Agent(agent) => agent.display_name().to_string(),
TabLabel::Named(name) => name.to_string(),
TabLabel::Process(title) => title.to_string(),
TabLabel::Unknown => String::new(),
};
(!raw.trim().is_empty()).then_some(raw)
}
/// The New Tab control: one `+` that drops the list of everything it could
/// open — the installed shells, and the saved SSH hosts.
///
@@ -2384,10 +2415,12 @@ impl Tty7App {
.then(|| self.tabs.get(active))
.flatten()
.map(|tab| {
let title = match tab.name.as_ref().filter(|n| !n.trim().is_empty()) {
Some(name) => name.trim().to_string(),
None => self.tab_label(tab, active, Some(window), cx),
};
// Whole, not `tab_label`'s three segments: the bar has half the
// window to spend, and the rail beside it spells the same tab
// out in full.
let title = self
.full_tab_label(tab, Some(window), cx)
.unwrap_or_else(|| self.tab_label(tab, active, Some(window), cx));
div()
.absolute()
.inset_0()