diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-03-09 15:37:42 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-09 15:37:42 +0100 |
| commit | 47ba6331c36f1b55f1a7d7ad3c0ab511629586a3 (patch) | |
| tree | 37faab2182e15a2bc137991d37f282439df9ebc6 | |
| parent | docs(changelog): dump viewport of pane id (diff) | |
| download | sl0ppij-47ba6331c36f1b55f1a7d7ad3c0ab511629586a3.tar.gz sl0ppij-47ba6331c36f1b55f1a7d7ad3c0ab511629586a3.tar.xz sl0ppij-47ba6331c36f1b55f1a7d7ad3c0ab511629586a3.zip | |
allow dumping screen to STDOUT as well (#4823)
| -rw-r--r-- | docs/MANPAGE.md | 3 | ||||
| -rw-r--r-- | zellij-server/src/route.rs | 1 | ||||
| -rw-r--r-- | zellij-server/src/screen.rs | 90 | ||||
| -rw-r--r-- | zellij-server/src/tab/mod.rs | 14 | ||||
| -rw-r--r-- | zellij-server/src/unit/screen_tests.rs | 2 | ||||
| -rw-r--r-- | zellij-utils/assets/prost/api.action.rs | 2 | ||||
| -rw-r--r-- | zellij-utils/assets/prost_ipc/client_server_contract.rs | 2 | ||||
| -rw-r--r-- | zellij-utils/src/cli.rs | 6 | ||||
| -rw-r--r-- | zellij-utils/src/client_server_contract/common_types.proto | 1 | ||||
| -rw-r--r-- | zellij-utils/src/input/actions.rs | 8 | ||||
| -rw-r--r-- | zellij-utils/src/ipc/protobuf_conversion.rs | 21 | ||||
| -rw-r--r-- | zellij-utils/src/ipc/tests/roundtrip_tests.rs | 16 | ||||
| -rw-r--r-- | zellij-utils/src/kdl/mod.rs | 7 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/action.proto | 1 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 28 |
15 files changed, 153 insertions, 49 deletions
diff --git a/docs/MANPAGE.md b/docs/MANPAGE.md index 731929b9..1d490c80 100644 --- a/docs/MANPAGE.md +++ b/docs/MANPAGE.md @@ -156,7 +156,8 @@ ACTIONS * __MoveFocus: <Direction\>__ - moves focus in the specified direction (Left, Right, Up, Down). * __Clear__ - clears current screen. -* __DumpScreen: <File\> [--pane-id <ID\>]__ - dumps the pane content to the specified file. +* __DumpScreen: [File\] [--pane-id <ID\>]__ - dumps the pane content to a file or STDOUT. + If a file path is provided, writes the content to that file. If omitted, prints the content to STDOUT. If --pane-id is provided, dumps the specified pane; otherwise dumps the focused pane. <ID\> can be a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1). A bare integer is equivalent to a terminal pane id with the same number. diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index f6ee7949..3f7250e0 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -435,6 +435,7 @@ pub(crate) fn route_action( include_scrollback, pane_id.map(|p| p.into()), Some(NotificationEnd::new(completion_tx)), + cli_client_id, )) .with_context(err_context)?; }, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 581a98d2..84aecf44 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -301,11 +301,12 @@ pub enum ScreenInstruction { Exit, ClearScreen(ClientId, Option<NotificationEnd>), DumpScreen( - String, + Option<String>, ClientId, bool, Option<PaneId>, Option<NotificationEnd>, + Option<ClientId>, // cli_client_id - used to send output to the CLI client's STDOUT ), DumpLayout(Option<PathBuf>, ClientId, Option<NotificationEnd>), // PathBuf is the default configured // shell @@ -5129,32 +5130,79 @@ pub(crate) fn screen_thread_main( client_id, full, pane_id, - _completion_tx, // the action ends here, dropping this will release anything - // waiting for it + completion_tx, + cli_client_id, ) => { - match pane_id { - Some(pane_id) => { - for tab in screen.get_tabs_mut().values_mut() { - if tab.has_pane_with_pid(&pane_id) { - tab.dump_terminal_screen(Some(file.clone()), pane_id, full)?; - break; - } + match file { + Some(file_path) => { + // Write dump to file (existing behavior) + match pane_id { + Some(pane_id) => { + for tab in screen.get_tabs_mut().values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.dump_terminal_screen( + Some(file_path.clone()), + pane_id, + full, + )?; + break; + } + } + }, + None => { + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, client_id: ClientId| tab.dump_active_terminal_screen( + Some(file_path.to_string()), + client_id, + full + ), + ? + ); + }, } + screen.render(None)?; + drop(completion_tx); }, None => { - active_tab_and_connected_client_id!( - screen, - client_id, - |tab: &mut Tab, client_id: ClientId| tab.dump_active_terminal_screen( - Some(file.to_string()), - client_id, - full - ), - ? - ); + // Dump to STDOUT via Log + let dump = match pane_id { + Some(pane_id) => { + let mut result = String::new(); + for tab in screen.get_tabs_mut().values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let Some(dump) = + tab.get_dump_terminal_screen(pane_id, full) + { + result = dump; + } + break; + } + } + result + }, + None => { + let mut result = String::new(); + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, client_id: ClientId| { + result = tab.get_dump_active_terminal_screen(client_id, full); + Ok::<(), anyhow::Error>(()) + }, + ? + ); + result + }, + }; + screen.bus.senders.send_to_server(ServerInstruction::Log( + vec![dump], + cli_client_id.unwrap_or(client_id), + completion_tx, + ))?; }, } - screen.render(None)?; }, ScreenInstruction::DumpLayout(default_shell, client_id, completion_tx) => { let err_context = || format!("Failed to dump layout"); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index fba48de1..ad6d956b 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -4096,6 +4096,20 @@ impl Tab { } Ok(()) } + pub fn get_dump_active_terminal_screen(&mut self, client_id: ClientId, full: bool) -> String { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.dump_screen(full, Some(client_id)) + } else { + String::new() + } + } + pub fn get_dump_terminal_screen(&mut self, pane_id: PaneId, full: bool) -> Option<String> { + if let Some(pane) = self.get_pane_with_id(pane_id) { + Some(pane.dump_screen(full, None)) + } else { + None + } + } pub fn edit_scrollback( &mut self, client_id: ClientId, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index b7a9bb7e..06c0056d 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -2435,7 +2435,7 @@ pub fn send_cli_dump_screen_action() { server_receiver ); let cli_action = CliAction::DumpScreen { - path: PathBuf::from("/tmp/foo"), + path: Some(PathBuf::from("/tmp/foo")), full: true, pane_id: None, }; diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index 07303884..53e3b898 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -621,6 +621,8 @@ pub struct DumpScreenPayload { pub include_scrollback: bool, #[prost(message, optional, tag="3")] pub pane_id: ::core::option::Option<PaneId>, + #[prost(bool, tag="4")] + pub dump_to_stdout: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs index 2bb51289..9eabb990 100644 --- a/zellij-utils/assets/prost_ipc/client_server_contract.rs +++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs @@ -696,6 +696,8 @@ pub struct DumpScreenAction { pub include_scrollback: bool, #[prost(message, optional, tag="3")] pub pane_id: ::core::option::Option<PaneId>, + #[prost(bool, tag="4")] + pub dump_to_stdout: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 70aa10ac..1d6cd7ac 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -738,9 +738,11 @@ pub enum CliAction { MovePaneBackwards, /// Clear all buffers for a focused pane Clear, - /// Dumps the viewport and optionally scrollback of a pane to STDOUT + /// Dumps the viewport and optionally scrollback of a pane to a file or STDOUT DumpScreen { - path: PathBuf, + /// File path to dump the pane content to. If omitted, prints to STDOUT. + #[clap(long, value_parser)] + path: Option<PathBuf>, /// Dump the pane with full scrollback #[clap(short, long, value_parser, default_value("false"), takes_value(false))] diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto index 4e770a90..430a1369 100644 --- a/zellij-utils/src/client_server_contract/common_types.proto +++ b/zellij-utils/src/client_server_contract/common_types.proto @@ -392,6 +392,7 @@ message DumpScreenAction { string file_path = 1; bool include_scrollback = 2; optional PaneId pane_id = 3; + bool dump_to_stdout = 4; } message ScrollUpAtAction { diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index ac311480..d56389c8 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -174,9 +174,9 @@ pub enum Action { MovePaneBackwards, /// Clear all buffers of a current screen ClearScreen, - /// Dumps the screen to a file + /// Dumps the screen to a file or STDOUT DumpScreen { - file_path: String, + file_path: Option<String>, include_scrollback: bool, pane_id: Option<PaneId>, }, @@ -714,7 +714,7 @@ impl Action { match parsed_pane_id { Ok(parsed_pane_id) => { Ok(vec![Action::DumpScreen { - file_path: path.as_os_str().to_string_lossy().into(), + file_path: path.map(|p| p.as_os_str().to_string_lossy().into()), include_scrollback: full, pane_id: Some(parsed_pane_id), }]) @@ -728,7 +728,7 @@ impl Action { } }, None => Ok(vec![Action::DumpScreen { - file_path: path.as_os_str().to_string_lossy().into(), + file_path: path.map(|p| p.as_os_str().to_string_lossy().into()), include_scrollback: full, pane_id: None, }]), diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs index 3551895d..cc2a3555 100644 --- a/zellij-utils/src/ipc/protobuf_conversion.rs +++ b/zellij-utils/src/ipc/protobuf_conversion.rs @@ -891,11 +891,15 @@ impl From<crate::input::actions::Action> file_path, include_scrollback, pane_id, - } => ActionType::DumpScreen(DumpScreenAction { - file_path, - include_scrollback, - pane_id: pane_id.map(|p| p.into()), - }), + } => { + let dump_to_stdout = file_path.is_none(); + ActionType::DumpScreen(DumpScreenAction { + file_path: file_path.unwrap_or_default(), + include_scrollback, + pane_id: pane_id.map(|p| p.into()), + dump_to_stdout, + }) + }, crate::input::actions::Action::DumpLayout => { ActionType::DumpLayout(DumpLayoutAction {}) }, @@ -1569,8 +1573,13 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> }, ActionType::ClearScreen(_) => Ok(crate::input::actions::Action::ClearScreen), ActionType::DumpScreen(dump_screen_action) => { + let file_path = if dump_screen_action.dump_to_stdout { + None + } else { + Some(dump_screen_action.file_path) + }; Ok(crate::input::actions::Action::DumpScreen { - file_path: dump_screen_action.file_path, + file_path, include_scrollback: dump_screen_action.include_scrollback, pane_id: dump_screen_action.pane_id.and_then(|p| p.try_into().ok()), }) diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs index 8b018c16..8abd3040 100644 --- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs +++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs @@ -873,7 +873,7 @@ fn test_client_messages() { }); test_client_roundtrip!(ClientToServerMsg::Action { action: Action::DumpScreen { - file_path: "/path/to/file".to_owned(), + file_path: Some("/path/to/file".to_owned()), include_scrollback: false, pane_id: None, }, @@ -883,7 +883,7 @@ fn test_client_messages() { }); test_client_roundtrip!(ClientToServerMsg::Action { action: Action::DumpScreen { - file_path: "/path/to/file".to_owned(), + file_path: Some("/path/to/file".to_owned()), include_scrollback: true, pane_id: None, }, @@ -893,7 +893,7 @@ fn test_client_messages() { }); test_client_roundtrip!(ClientToServerMsg::Action { action: Action::DumpScreen { - file_path: "/path/to/file".to_owned(), + file_path: Some("/path/to/file".to_owned()), include_scrollback: true, pane_id: Some(PaneId::Terminal(5)), }, @@ -902,6 +902,16 @@ fn test_client_messages() { is_cli_client: true, }); test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::DumpScreen { + file_path: None, + include_scrollback: false, + pane_id: None, + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { action: Action::DumpLayout, terminal_id: Some(1), client_id: Some(100), diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 0a9a1d75..7408c3a4 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -541,7 +541,7 @@ impl Action { }, "MovePaneBackwards" => Ok(Action::MovePaneBackwards), "DumpScreen" => Ok(Action::DumpScreen { - file_path: string, + file_path: Some(string), include_scrollback: false, pane_id: None, }), @@ -693,7 +693,7 @@ impl Action { }, Action::MovePaneBackwards => Some(KdlNode::new("MovePaneBackwards")), Action::DumpScreen { - file_path: file, + file_path: Some(file), include_scrollback: _, pane_id: _, } => { @@ -701,6 +701,9 @@ impl Action { node.push(file.clone()); Some(node) }, + Action::DumpScreen { + file_path: None, .. + } => None, Action::DumpLayout => Some(KdlNode::new("DumpLayout")), Action::EditScrollback => Some(KdlNode::new("EditScrollback")), Action::EditScrollbackRaw => Some(KdlNode::new("EditScrollbackRaw")), diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index 169bc24f..57442e6c 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -382,6 +382,7 @@ message DumpScreenPayload { string file_path = 1; bool include_scrollback = 2; optional PaneId pane_id = 3; + bool dump_to_stdout = 4; } enum ActionName { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 5ccd349c..aef9d881 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -211,7 +211,11 @@ impl TryFrom<ProtobufAction> for Action { }, Some(ProtobufActionName::DumpScreen) => match protobuf_action.optional_payload { Some(OptionalPayload::DumpScreenPayload(payload)) => { - let file_path = payload.file_path; + let file_path = if payload.dump_to_stdout { + None + } else { + Some(payload.file_path) + }; let include_scrollback = payload.include_scrollback; let pane_id = payload.pane_id.and_then(|p| p.try_into().ok()); Ok(Action::DumpScreen { @@ -1149,14 +1153,20 @@ impl TryFrom<Action> for ProtobufAction { file_path, include_scrollback, pane_id, - } => Ok(ProtobufAction { - name: ProtobufActionName::DumpScreen as i32, - optional_payload: Some(OptionalPayload::DumpScreenPayload(DumpScreenPayload { - file_path, - include_scrollback, - pane_id: pane_id.and_then(|p| p.try_into().ok()), - })), - }), + } => { + let dump_to_stdout = file_path.is_none(); + Ok(ProtobufAction { + name: ProtobufActionName::DumpScreen as i32, + optional_payload: Some(OptionalPayload::DumpScreenPayload( + DumpScreenPayload { + file_path: file_path.unwrap_or_default(), + include_scrollback, + pane_id: pane_id.and_then(|p| p.try_into().ok()), + dump_to_stdout, + }, + )), + }) + }, Action::EditScrollback => Ok(ProtobufAction { name: ProtobufActionName::EditScrollback as i32, optional_payload: None, |
