From d22b6e4b4abc453bc70dd888ea1ace9dc2a77fd3 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:45:51 +0800 Subject: [PATCH] fix(cli): let the OWNER column speak only when it disagrees with WS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that a pane's owner is the id of the workspace holding it, printing both spells the same id twice on every row of `pane ls --all` — and buries the rows that matter. The column now shows a dash when the two agree, so what is left is exactly what is worth reading: a pane its holder may not attach to, and an orphan still naming where it belongs. --- crates/tty7-cli/src/output.rs | 68 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/crates/tty7-cli/src/output.rs b/crates/tty7-cli/src/output.rs index 3de4fc97..069021fb 100644 --- a/crates/tty7-cli/src/output.rs +++ b/crates/tty7-cli/src/output.rs @@ -136,19 +136,32 @@ pub fn pane_table(machine: &Machine, only: Option) -> String { /// The server's registry rather than the machine tree, so orphans appear. `held` /// answers which workspace holds a pane, if any; a pane with no holder is shown /// as `-` under WS, which is the whole point of the listing. +/// +/// OWNER names the workspace allowed to attach to the pane, and for a pane in +/// its own workspace's tree that is the WS beside it — so the column only +/// speaks when the two disagree, which is the case worth reading: an orphan +/// still remembering where it belongs, or a pane the holder cannot attach to. pub fn registry_table(panes: &[PaneInfo], held: &dyn Fn(u64) -> Option) -> String { if panes.is_empty() { return "no panes\n".to_string(); } + let short = |id: &str| -> String { id.chars().take(8).collect() }; let rows: Vec> = panes .iter() .map(|info| { + let holder = held(info.pane_id); + let owner = match (&info.owner, &holder) { + (None, _) => "-".to_string(), + (Some(owner), Some(holder)) if owner == holder => "-".to_string(), + (Some(owner), _) => short(owner), + }; vec![ format!("%{}", info.pane_id), - held(info.pane_id) - .map(|ws| ws.chars().take(8).collect()) + holder + .as_deref() + .map(short) .unwrap_or_else(|| "-".to_string()), - info.owner.clone().unwrap_or_else(|| "-".to_string()), + owner, info.cwd .as_ref() .map(|p| p.display().to_string()) @@ -307,6 +320,55 @@ mod tests { ); } + #[test] + fn owner_speaks_only_when_it_disagrees_with_the_workspace_holding_the_pane() { + let held = "9fd8072f-465c-4016-9a81-8143bff1240c"; + let elsewhere = "76698a44-3f13-4961-8fed-90d0b3defff1"; + let pane = |id: u64, owner: Option<&str>| PaneInfo { + pane_id: id, + cwd: None, + title: "zsh".into(), + alive: true, + owner: owner.map(str::to_string), + }; + let rendered = registry_table( + &[ + pane(1, Some(held)), + pane(2, Some(elsewhere)), + pane(3, None), + pane(4, Some(elsewhere)), + ], + &|id| (id != 4).then(|| held.to_string()), + ); + + let owner_of = |pane: &str| -> String { + rendered + .lines() + .find(|line| line.starts_with(pane)) + .unwrap_or_else(|| panic!("{pane} is listed: {rendered}")) + .split_whitespace() + .nth(2) + .expect("PANE WS OWNER") + .to_string() + }; + assert_eq!( + owner_of("%1"), + "-", + "repeating the WS beside it says nothing" + ); + assert_eq!( + owner_of("%2"), + "76698a44", + "a holder that may not attach is the whole reason to look" + ); + assert_eq!(owner_of("%3"), "-", "nobody claims it"); + assert_eq!( + owner_of("%4"), + "76698a44", + "an orphan still remembers where it belongs" + ); + } + #[test] fn wide_characters_are_padded_by_display_width_not_byte_length() { // "项目" is 6 bytes but occupies 4 columns. Padding by len() would add