aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-11 21:30:00 +0200
committeruser <user@clank>2026-08-11 21:30:00 +0200
commit568c45ba47dd27c4fdfcf6bd8303beeb853b93d4 (patch)
tree00e3581fdb0ee1779c2b853940d06e770102e8fd
parentsl0p: keep plugin panes pinned when synthesizing a layout (diff)
downloadsl0ppij-568c45ba47dd27c4fdfcf6bd8303beeb853b93d4.tar.gz
sl0ppij-568c45ba47dd27c4fdfcf6bd8303beeb853b93d4.tar.xz
sl0ppij-568c45ba47dd27c4fdfcf6bd8303beeb853b93d4.zip
sl0p: stop hide_default_pane_names blanking the plugin API
`hide_default_pane_names` is a frame-rendering option, but it was applied inside current_title() -- which is also what fills PaneInfo.title. Every plugin that lists panes therefore received an empty string for any pane whose program had never set a title. For the bundled session manager that is untidy. For a fuzzy pane finder it is broken: the rows are identical AND unsearchable, so the plugin's whole purpose stops working. The blanking moves to the frame, which already had a blank_title concept, and current_title() goes back to reporting a real title to everything that asks. Frames still show nothing for an unnamed pane, named panes still show their name, and PaneInfo reports "Pane #N" again. Changes a value rather than adding a field, so it needs no protobuf regeneration. Only observable without a shell that sets a title: verified by disabling the zsh hook, where the pane finder previously listed three identical blank rows and now lists Pane #1/#2/#3. That masking is also why this went unnoticed -- and why I flagged it twice without confirming it was actually reachable.
-rw-r--r--README.md5
-rw-r--r--zellij-server/src/panes/terminal_pane.rs39
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs68
3 files changed, 91 insertions, 21 deletions
diff --git a/README.md b/README.md
index 3d8d73d6..56d1355b 100644
--- a/README.md
+++ b/README.md
@@ -147,6 +147,11 @@ Two deliberate behaviours:
It protected little in any case: expanding applies `BASE` or a grid, so a
manual arrangement is replaced by a collapse/expand round trip either way.
+Note this affects **frame rendering only**. Panes still report their `Pane #N`
+placeholder to the plugin API, so anything that lists panes — the session
+manager, a pane finder — can still label and search them. Blanking it there too
+left such plugins with rows they could neither identify nor fuzzy-match.
+
### Capping the stacked-pane picker
A stack reserves one row per member plus a padding row, so a deep stack spends
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index 735bdc9d..8085e6be 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -583,7 +583,11 @@ impl Pane for TerminalPane {
} else {
self.current_title()
};
- let pane_title = if frame_params.blank_title {
+ let pane_title = if frame_params.blank_title
+ || (self.has_placeholder_title() && self.style.hide_default_pane_names)
+ {
+ // nothing named this pane: draw no title rather than "Pane #3".
+ // Read from style at render time so the option hot-reloads.
String::new()
} else if let Some(text_color_override) = self
.pane_frame_color_override
@@ -1219,30 +1223,27 @@ impl Pane for TerminalPane {
fn current_title(&self) -> String {
// precedence: user-given name > title set by the running program >
// the "Pane #N" placeholder
+ //
+ // Deliberately does NOT consult `hide_default_pane_names`. That is a
+ // frame-rendering option, and blanking here leaked it into everything
+ // that asks a pane for its title -- including PaneInfo, so every plugin
+ // listing panes (the pane finder, the session manager) got empty rows
+ // it could neither label nor search. The frame does its own blanking;
+ // see `has_placeholder_title`.
if !self.pane_name.is_empty() {
return self.pane_name.to_owned();
}
if let Some(program_title) = self.grid.title.as_deref() {
return program_title.into();
}
- if self.pane_title_is_placeholder && self.style.hide_default_pane_names {
- // nothing has named this pane: show no title at all rather than
- // "Pane #3". Read from style at render time so the option
- // hot-reloads and the placeholder is still there if it is turned
- // back off.
- return String::new();
- }
self.pane_title.clone()
}
fn stack_list_entry_label(&self) -> String {
- // `hide_default_pane_names` deliberately does NOT apply here.
- //
- // In a frame, an empty title is fine: the pane is still visible, sized
- // and positioned, so you can tell what it is by looking at it. In the
- // stack list the label is the ENTIRE affordance -- blanking it leaves a
- // row that cannot be identified or distinguished from its neighbours.
- // So the stack list falls back to the "Pane #N" placeholder even when
- // frames are configured to hide it.
+ // `hide_default_pane_names` does not reach current_title() any more, so
+ // this gets the "Pane #N" placeholder rather than an empty string. Kept
+ // as a floor anyway: in a frame an empty title is merely clean, but in
+ // the stack list the label is the ENTIRE affordance, and a blank row
+ // cannot be identified or told apart from its neighbours.
let title = match self.current_title() {
title if title.is_empty() => self.pane_title.clone(),
title => title,
@@ -1451,6 +1452,12 @@ impl Pane for TerminalPane {
}
impl TerminalPane {
+ /// True while nothing has named this pane, so its title is still the
+ /// auto-generated "Pane #N". Inherent rather than part of the Pane trait:
+ /// only terminal panes have a placeholder title to hide.
+ fn has_placeholder_title(&self) -> bool {
+ self.pane_name.is_empty() && self.grid.title.is_none() && self.pane_title_is_placeholder
+ }
fn title_with_bell_indicator(&self, title: String) -> String {
if self.has_bell_notification {
format!("{} [!]", title)
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index d59e86b6..eb1df250 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -15972,13 +15972,13 @@ fn sl0p_stack_list_entry_keeps_a_label_when_default_pane_names_are_hidden() {
);
for (pane_id, pane) in tab.tiled_panes.panes.iter() {
- // precondition: the option really is blanking the frame title, or this
- // test would pass for the wrong reason
+ // The option no longer reaches current_title(): it is applied by the
+ // frame at render time, so everything that ASKS a pane for its title
+ // still gets a real one. See the PaneInfo test below.
assert_eq!(
pane.current_title(),
- "",
- "precondition: {:?} should have a blank FRAME title while \
- hide_default_pane_names is on",
+ format!("Pane #{}", pane_id_index(pane_id)),
+ "{:?} should still report its placeholder title",
pane_id
);
let label = pane.stack_list_entry_label();
@@ -16746,3 +16746,61 @@ fn sl0p_stack_list_style_changes_the_cell_width_the_layout_reserves() {
minimal.cells.len()
);
}
+
+#[cfg(test)]
+fn pane_id_index(pane_id: &PaneId) -> u32 {
+ match pane_id {
+ PaneId::Terminal(id) => *id,
+ PaneId::Plugin(id) => *id,
+ }
+}
+
+#[test]
+fn sl0p_hiding_default_pane_names_does_not_blank_the_plugin_api() {
+ // REGRESSION: `hide_default_pane_names` is a frame-rendering option, but it
+ // used to be applied inside current_title() -- which is also what fills
+ // PaneInfo.title. Every plugin listing panes (the pane finder, the session
+ // manager) therefore got empty rows it could neither label nor fuzzy-match,
+ // for any pane whose program never set a title.
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 1;
+ let mut tab = create_new_tab(size, ModeInfo::default());
+ tab.new_pane(
+ PaneId::Terminal(2),
+ None,
+ None,
+ false,
+ true,
+ NewPanePlacement::default(),
+ Some(client_id),
+ None,
+ )
+ .unwrap();
+ tab.update_frame_layout(
+ zellij_utils::data::PaneTitleAlignment::Left,
+ 0,
+ 0,
+ zellij_utils::data::PanePadding::default(),
+ 0,
+ true, // hide_default_pane_names
+ );
+
+ let current_pane_group = HashMap::new();
+ for (pane_id, pane) in tab.tiled_panes.panes.iter() {
+ let pane_info = super::pane_info_for_pane(pane_id, pane, &current_pane_group);
+ assert!(
+ !pane_info.title.trim().is_empty(),
+ "{:?} reported an empty title to the plugin API, so a pane list \
+ cannot label or search it",
+ pane_id
+ );
+ assert_eq!(
+ pane_info.title,
+ format!("Pane #{}", pane_id_index(pane_id)),
+ "the API should report the real placeholder title"
+ );
+ }
+}