mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 08:01:06 +00:00
fix: hide the hostname when it would squeeze out the tab strip
This commit is contained in:
committed by
Ogulcan Celik
parent
4d83688df1
commit
762f247f72
+58
-9
@@ -13,6 +13,9 @@ const MIN_TAB_WIDTH: u16 = 8;
|
||||
const NEW_TAB_WIDTH: u16 = 3;
|
||||
const TAB_SCROLL_BUTTON_WIDTH: u16 = 3;
|
||||
const ZOOM_INDICATOR: &str = " ZOOM ";
|
||||
// The narrowest tab strip worth keeping interactive: one minimum-width tab
|
||||
// plus the trailing controls. Decorations yield below this.
|
||||
const MIN_TAB_STRIP_WIDTH: u16 = MIN_TAB_WIDTH + NEW_TAB_WIDTH + TAB_SCROLL_BUTTON_WIDTH;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct TabBarView {
|
||||
@@ -48,7 +51,7 @@ fn tab_chrome_label(ws: &crate::workspace::Workspace, tab_idx: usize) -> String
|
||||
}
|
||||
}
|
||||
|
||||
fn hostname_label(app: &AppState) -> Option<String> {
|
||||
fn hostname_label(app: &AppState, area: Rect) -> Option<String> {
|
||||
// Hostnames can contain arbitrary bytes; drop control characters so they
|
||||
// can't smuggle escape sequences into the row or skew its width math.
|
||||
let hostname: String = app
|
||||
@@ -57,13 +60,22 @@ fn hostname_label(app: &AppState) -> Option<String> {
|
||||
.chars()
|
||||
.filter(|character| !character.is_control())
|
||||
.collect();
|
||||
(!hostname.is_empty()).then(|| format!(" {hostname} "))
|
||||
if hostname.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let label = format!(" {hostname} ");
|
||||
// Tabs win over decoration: hide the hostname entirely rather than let its
|
||||
// reservation squeeze the tab strip below a usable width.
|
||||
let remaining = area
|
||||
.width
|
||||
.saturating_sub(display_width_u16(&label).saturating_add(1));
|
||||
(remaining >= MIN_TAB_STRIP_WIDTH).then_some(label)
|
||||
}
|
||||
|
||||
// The tab strip's usable area once the right edge is reserved for the
|
||||
// hostname display, so tabs and trailing controls never render underneath it.
|
||||
pub(crate) fn tab_bar_content_area(app: &AppState, area: Rect) -> Rect {
|
||||
let reserved = hostname_label(app)
|
||||
let reserved = hostname_label(app, area)
|
||||
.map(|label| display_width_u16(&label).saturating_add(1))
|
||||
.unwrap_or(0);
|
||||
Rect {
|
||||
@@ -437,10 +449,8 @@ pub(super) fn render_tab_bar(app: &AppState, frame: &mut Frame, area: Rect) {
|
||||
}
|
||||
}
|
||||
|
||||
let hostname_width = hostname_label(app)
|
||||
.as_deref()
|
||||
.map(display_width_u16)
|
||||
.unwrap_or(0);
|
||||
let hostname = hostname_label(app, area);
|
||||
let hostname_width = hostname.as_deref().map(display_width_u16).unwrap_or(0);
|
||||
if ws.zoomed {
|
||||
let width = display_width_u16(ZOOM_INDICATOR).min(area.width);
|
||||
let rect = Rect::new(
|
||||
@@ -464,7 +474,7 @@ pub(super) fn render_tab_bar(app: &AppState, frame: &mut Frame, area: Rect) {
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(label) = hostname_label(app) {
|
||||
if let Some(label) = hostname {
|
||||
let width = display_width_u16(&label).min(area.width);
|
||||
let rect = Rect::new(area.x + area.width - width, area.y, width, 1);
|
||||
frame.render_widget(
|
||||
@@ -637,13 +647,52 @@ mod tests {
|
||||
let mut app = AppState::test_new();
|
||||
app.tab_bar_hostname = Some("\x1b\r\n".into());
|
||||
|
||||
assert_eq!(hostname_label(&app), None);
|
||||
assert_eq!(hostname_label(&app, Rect::new(0, 0, 40, 1)), None);
|
||||
assert_eq!(
|
||||
tab_bar_content_area(&app, Rect::new(0, 0, 40, 1)),
|
||||
Rect::new(0, 0, 40, 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oversized_hostname_yields_to_tab_controls() {
|
||||
let mut app = AppState::test_new();
|
||||
app.tab_bar_hostname = Some("a-hostname-wider-than-the-whole-bar".into());
|
||||
let ws = Workspace::test_new("test");
|
||||
|
||||
app.workspaces = vec![ws];
|
||||
app.active = Some(0);
|
||||
app.view.tab_bar_rect = Rect::new(0, 0, 30, 1);
|
||||
|
||||
// The reservation would leave less than a usable tab strip, so the
|
||||
// hostname is dropped and the tabs keep the full row.
|
||||
assert_eq!(
|
||||
tab_bar_content_area(&app, app.view.tab_bar_rect),
|
||||
app.view.tab_bar_rect
|
||||
);
|
||||
|
||||
let view = compute_tab_bar_view(
|
||||
&app.workspaces[0],
|
||||
tab_bar_content_area(&app, app.view.tab_bar_rect),
|
||||
0,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
app.view.tab_hit_areas = view.tab_hit_areas.clone();
|
||||
app.view.new_tab_hit_area = view.new_tab_hit_area;
|
||||
assert!(view.tab_hit_areas[0].width > 0);
|
||||
assert!(view.new_tab_hit_area.width > 0);
|
||||
|
||||
let backend = TestBackend::new(30, 1);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|frame| render_tab_bar(&app, frame, app.view.tab_bar_rect))
|
||||
.unwrap();
|
||||
|
||||
let row = buffer_row_text(terminal.backend().buffer(), app.view.tab_bar_rect, 0);
|
||||
assert!(!row.contains("hostname"), "tab row: {row:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tab_bar_omits_hostname_when_disabled() {
|
||||
let mut app = AppState::test_new();
|
||||
|
||||
Reference in New Issue
Block a user