Repository: zellij-org/zellij (language: rust)

Issue:
List opened tabs in a session?

Unless I'm very much mistaken, there doesn't seem to be any way to list the opened tabs, just like you can list opened sessions. Can we please have this feature? Absence of that, the `action` subcomamnd doesn't give that much power.

Thanks.


Discussion / hints:

Fix patch:
diff --git a/zellij-client/src/cli_client.rs b/zellij-client/src/cli_client.rs
index 95cd9d133d..197efa141e 100644
--- a/zellij-client/src/cli_client.rs
+++ b/zellij-client/src/cli_client.rs
@@ -28,6 +28,10 @@ pub fn start_cli_client(os_input: Box<dyn ClientOsApi>, session_name: &str, acti
                 os_input.send_to_server(ClientToServerMsg::ClientExited);
                 process::exit(0);
             },
+            Some((ServerToClientMsg::Log(log_lines), _)) => {
+                log_lines.iter().for_each(|line| println!("{line}"));
+                process::exit(0);
+            },
             _ => {},
         }
     }
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 67a7fb902e..68bc45e9e9 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -45,6 +45,7 @@ pub(crate) enum ClientInstruction {
     ActiveClients(Vec<ClientId>),
     StartedParsingStdinQuery,
     DoneParsingStdinQuery,
+    Log(Vec<String>),
 }
 
 impl From<ServerToClientMsg> for ClientInstruction {
@@ -58,6 +59,7 @@ impl From<ServerToClientMsg> for ClientInstruction {
             },
             ServerToClientMsg::Connected => ClientInstruction::Connected,
             ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
+            ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
         }
     }
 }
@@ -72,6 +74,7 @@ impl From<&ClientInstruction> for ClientContext {
             ClientInstruction::SwitchToMode(_) => ClientContext::SwitchToMode,
             ClientInstruction::Connected => ClientContext::Connected,
             ClientInstruction::ActiveClients(_) => ClientContext::ActiveClients,
+            ClientInstruction::Log(_) => ClientContext::Log,
             ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
             ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
         }
@@ -406,6 +409,11 @@ pub fn start_client(
                     .send(InputInstruction::SwitchToMode(input_mode))
                     .unwrap();
             },
+            ClientInstruction::Log(lines_to_log) => {
+                for line in lines_to_log {
+                    log::info!("{line}");
+                }
+            },
             _ => {},
         }
     }
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 5dc728fb4e..34c807f16e 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -77,6 +77,7 @@ pub enum ServerInstruction {
     AttachClient(ClientAttributes, Options, ClientId),
     ConnStatus(ClientId),
     ActiveClients(ClientId),
+    Log(Vec<String>, ClientId),
 }
 
 impl From<&ServerInstruction> for ServerContext {
@@ -93,6 +94,7 @@ impl From<&ServerInstruction> for ServerContext {
             ServerInstruction::AttachClient(..) => ServerContext::AttachClient,
             ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
             ServerInstruction::ActiveClients(_) => ServerContext::ActiveClients,
+            ServerInstruction::Log(..) => ServerContext::Log,
         }
     }
 }
@@ -623,6 +625,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
                     session_state
                 );
             },
+            ServerInstruction::Log(lines_to_log, client_id) => {
+                send_to_client!(
+                    client_id,
+                    os_input,
+                    ServerToClientMsg::Log(lines_to_log),
+                    session_state
+                );
+            },
         }
     }
 
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 53ccf964da..a4ab8dcf96 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -659,6 +659,12 @@ pub(crate) fn route_action(
                 .send_to_screen(ScreenInstruction::NextSwapLayout(client_id))
                 .with_context(err_context)?;
         },
+        Action::QueryTabNames => {
+            session
+                .senders
+                .send_to_screen(ScreenInstruction::QueryTabNames(client_id))
+                .with_context(err_context)?;
+        },
     }
     Ok(should_break)
 }
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 97acc0fdf0..a5b8b8cd0f 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -248,6 +248,7 @@ pub enum ScreenInstruction {
     ClearPaneFrameColorOverride(Vec<PaneId>),
     PreviousSwapLayout(ClientId),
     NextSwapLayout(ClientId),
+    QueryTabNames(ClientId),
 }
 
 impl From<&ScreenInstruction> for ScreenContext {
@@ -390,6 +391,7 @@ impl From<&ScreenInstruction> for ScreenContext {
             },
             ScreenInstruction::PreviousSwapLayout(..) => ScreenContext::PreviousSwapLayout,
             ScreenInstruction::NextSwapLayout(..) => ScreenContext::NextSwapLayout,
+            ScreenInstruction::QueryTabNames(..) => ScreenContext::QueryTabNames,
         }
     }
 }
@@ -1171,7 +1173,7 @@ impl Screen {
                             },
                             c => {
                                 // It only allows printable unicode
-                                if buf.iter().all(|u| matches!(u, 0x20..=0x7E | 0x80..=0xFF)) {
+                                if buf.iter().all(|u| matches!(u, 0x20..=0x7E | 0xA0..=0xFF)) {
                                     active_tab.name.push_str(c);
                                 }
                             },
@@ -2360,6 +2362,17 @@ pub(crate) fn screen_thread_main(
                 screen.update_tabs()?;
                 screen.unblock_input()?;
             },
+            ScreenInstruction::QueryTabNames(client_id) => {
+                let tab_names = screen
+                    .get_tabs_mut()
+                    .values()
+                    .map(|tab| tab.name.clone())
+                    .collect::<Vec<String>>();
+                screen
+                    .bus
+                    .senders
+                    .send_to_server(ServerInstruction::Log(tab_names, client_id))?;
+            },
         }
     }
     Ok(())
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 7cdef96191..d009a3714c 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -3082,7 +3082,7 @@ impl Tab {
             // It only allows printable unicode, delete and backspace keys.
             let is_updatable = buf
                 .iter()
-                .all(|u| matches!(u, 0x20..=0x7E | 0x80..=0xFF | 0x08 | 0x7F));
+                .all(|u| matches!(u, 0x20..=0x7E | 0xA0..=0xFF | 0x08 | 0x7F));
             if is_updatable {
                 let s = str::from_utf8(&buf).with_context(err_context)?;
                 active_terminal.update_name(s);
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 2733f1c747..3947dfad39 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -366,4 +366,6 @@ pub enum CliAction {
     },
     PreviousSwapLayout,
     NextSwapLayout,
+    /// Query all tab names
+    QueryTabNames,
 }
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 3493f84b7f..f1522c1de9 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -323,6 +323,7 @@ pub enum ScreenContext {
     ClearPaneFrameColorOverride,
     PreviousSwapLayout,
     NextSwapLayout,
+    QueryTabNames,
 }
 
 /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@@ -366,6 +367,7 @@ pub enum ClientContext {
     SwitchToMode,
     Connected,
     ActiveClients,
+    Log,
     OwnClientId,
     StartedParsingStdinQuery,
     DoneParsingStdinQuery,
@@ -385,6 +387,7 @@ pub enum ServerContext {
     AttachClient,
     ConnStatus,
     ActiveClients,
+    Log,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index 7b7f78c433..b2ec8d8287 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -223,6 +223,8 @@ pub enum Action {
     ToggleMouseMode,
     PreviousSwapLayout,
     NextSwapLayout,
+    /// Query all tab names
+    QueryTabNames,
 }
 
 impl Action {
@@ -456,6 +458,7 @@ impl Action {
             },
             CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]),
             CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
+            CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
         }
     }
 }
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index e76a21bd82..149a157ae4 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -109,6 +109,7 @@ pub enum ServerToClientMsg {
     SwitchToMode(InputMode),
     Connected,
     ActiveClients(Vec<ClientId>),
+    Log(Vec<String>),
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone)]


Test patch:
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 8383a24629..3cde1d8183 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -2674,3 +2674,39 @@ pub fn send_cli_undo_rename_tab() {
         *received_plugin_instructions.lock().unwrap()
     ))
 }
+
+#[test]
+pub fn send_cli_query_tab_names_action() {
+    let size = Size { cols: 80, rows: 10 };
+    let client_id = 10; // fake client id should not appear in the screen's state
+    let mut mock_screen = MockScreen::new(size);
+    mock_screen.new_tab(TiledPaneLayout::default());
+    let session_metadata = mock_screen.clone_session_metadata();
+    let screen_thread = mock_screen.run(Some(TiledPaneLayout::default()));
+    let received_server_instructions = Arc::new(Mutex::new(vec![]));
+    let server_receiver = mock_screen.server_receiver.take().unwrap();
+    let server_thread = log_actions_in_thread!(
+        received_server_instructions,
+        ServerInstruction::KillSession,
+        server_receiver
+    );
+    let query_tab_names = CliAction::QueryTabNames;
+    send_cli_action_to_server(
+        &session_metadata,
+        query_tab_names,
+        &mut mock_screen,
+        client_id,
+    );
+    std::thread::sleep(std::time::Duration::from_millis(100));
+    mock_screen.teardown(vec![server_thread, screen_thread]);
+    let log_tab_names_instruction = received_server_instructions
+        .lock()
+        .unwrap()
+        .iter()
+        .find(|instruction| match instruction {
+            ServerInstruction::Log(..) => true,
+            _ => false,
+        })
+        .cloned();
+    assert_snapshot!(format!("{:#?}", log_tab_names_instruction));
+}
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_query_tab_names_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_query_tab_names_action.snap
new file mode 100644
index 0000000000..bdaf546fe4
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_query_tab_names_action.snap
@@ -0,0 +1,14 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2713
+expression: "format!(\"{:#?}\", log_tab_names_action)"
+---
+Some(
+    Log(
+        [
+            "Tab #1",
+            "Tab #2",
+        ],
+        10,
+    ),
+)
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index c8c6c64dc3..237cf978c6 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -1943,6 +1943,7 @@ fn cannot_define_stacked_panes_with_grandchildren_in_pane_template() {
     assert!(layout.is_err(), "error provided for tab name with space");
 }
 
+#[test]
 fn run_plugin_location_parsing() {
     let kdl_layout = r#"
         layout {