aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorYacine Hmito <6893840+yacinehmito@users.noreply.github.com>2026-03-09 14:55:14 +0100
committerGitHub <noreply@github.com>2026-03-09 14:55:14 +0100
commit0172836fda1eb97005ec536571714998f48ad2e2 (patch)
tree76aa93a7f7d2bc9e7453a80e2a95af63a63ea68a /zellij-utils
parentRemove unnecessary winapi features (#4816) (diff)
downloadsl0ppij-0172836fda1eb97005ec536571714998f48ad2e2.tar.gz
sl0ppij-0172836fda1eb97005ec536571714998f48ad2e2.tar.xz
sl0ppij-0172836fda1eb97005ec536571714998f48ad2e2.zip
feat(cli): add --pane-id option to dump-screen action (#4598)
* feat(cli): add --pane-id option to dump-screen action Adds the ability to dump a specific terminal pane's content by passing `--pane-id <ID>` to the `dump-screen` CLI action. When omitted, the existing behavior of dumping the focused pane is preserved. This enables coding agents running inside Zellij to inspect the content of other panes programmatically, without needing complex workarounds. Prior art: https://github.com/theslyprofessor/zellij-pane-tracker, whose design complexity is due to current limitations that this PR addresses. * address review * improve man page entry
Diffstat (limited to 'zellij-utils')
-rw-r--r--zellij-utils/assets/prost/api.action.rs2
-rw-r--r--zellij-utils/assets/prost_ipc/client_server_contract.rs2
-rw-r--r--zellij-utils/src/cli.rs6
-rw-r--r--zellij-utils/src/client_server_contract/common_types.proto1
-rw-r--r--zellij-utils/src/input/actions.rs34
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs3
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs12
-rw-r--r--zellij-utils/src/kdl/mod.rs2
-rw-r--r--zellij-utils/src/plugin_api/action.proto1
-rw-r--r--zellij-utils/src/plugin_api/action.rs4
10 files changed, 62 insertions, 5 deletions
diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs
index 1d2908a1..07303884 100644
--- a/zellij-utils/assets/prost/api.action.rs
+++ b/zellij-utils/assets/prost/api.action.rs
@@ -619,6 +619,8 @@ pub struct DumpScreenPayload {
pub file_path: ::prost::alloc::string::String,
#[prost(bool, tag="2")]
pub include_scrollback: bool,
+ #[prost(message, optional, tag="3")]
+ pub pane_id: ::core::option::Option<PaneId>,
}
#[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 cb420225..2bb51289 100644
--- a/zellij-utils/assets/prost_ipc/client_server_contract.rs
+++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs
@@ -694,6 +694,8 @@ pub struct DumpScreenAction {
pub file_path: ::prost::alloc::string::String,
#[prost(bool, tag="2")]
pub include_scrollback: bool,
+ #[prost(message, optional, tag="3")]
+ pub pane_id: ::core::option::Option<PaneId>,
}
#[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 4d382477..70aa10ac 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -738,13 +738,17 @@ pub enum CliAction {
MovePaneBackwards,
/// Clear all buffers for a focused pane
Clear,
- /// Dump the focused pane to a file
+ /// Dumps the viewport and optionally scrollback of a pane to STDOUT
DumpScreen {
path: PathBuf,
/// Dump the pane with full scrollback
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
full: bool,
+
+ /// The pane_id of the pane, eg. terminal_1, plugin_2 or 3 (equivalent to terminal_3). If not specified, dumps the focused pane.
+ #[clap(short, long, value_parser)]
+ pane_id: Option<String>,
},
/// Dump current layout to stdout
DumpLayout,
diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto
index d872d8a6..4e770a90 100644
--- a/zellij-utils/src/client_server_contract/common_types.proto
+++ b/zellij-utils/src/client_server_contract/common_types.proto
@@ -391,6 +391,7 @@ message MovePaneAction {
message DumpScreenAction {
string file_path = 1;
bool include_scrollback = 2;
+ optional PaneId pane_id = 3;
}
message ScrollUpAtAction {
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index c722e5ff..ac311480 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -178,6 +178,7 @@ pub enum Action {
DumpScreen {
file_path: String,
include_scrollback: bool,
+ pane_id: Option<PaneId>,
},
/// Dumps
DumpLayout,
@@ -703,10 +704,35 @@ impl Action {
CliAction::MovePaneBackwards => Ok(vec![Action::MovePaneBackwards]),
CliAction::MoveTab { direction } => Ok(vec![Action::MoveTab { direction }]),
CliAction::Clear => Ok(vec![Action::ClearScreen]),
- CliAction::DumpScreen { path, full } => Ok(vec![Action::DumpScreen {
- file_path: path.as_os_str().to_string_lossy().into(),
- include_scrollback: full,
- }]),
+ CliAction::DumpScreen {
+ path,
+ full,
+ pane_id,
+ } => match pane_id {
+ Some(pane_id_str) => {
+ let parsed_pane_id = PaneId::from_str(&pane_id_str);
+ match parsed_pane_id {
+ Ok(parsed_pane_id) => {
+ Ok(vec![Action::DumpScreen {
+ file_path: path.as_os_str().to_string_lossy().into(),
+ include_scrollback: full,
+ pane_id: Some(parsed_pane_id),
+ }])
+ },
+ Err(_e) => {
+ Err(format!(
+ "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
+ pane_id_str
+ ))
+ }
+ }
+ },
+ None => Ok(vec![Action::DumpScreen {
+ file_path: path.as_os_str().to_string_lossy().into(),
+ include_scrollback: full,
+ pane_id: None,
+ }]),
+ },
CliAction::DumpLayout => Ok(vec![Action::DumpLayout]),
CliAction::SaveSession => Ok(vec![Action::SaveSession]),
CliAction::EditScrollback => Ok(vec![Action::EditScrollback]),
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index 4c8316d2..3551895d 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -890,9 +890,11 @@ impl From<crate::input::actions::Action>
crate::input::actions::Action::DumpScreen {
file_path,
include_scrollback,
+ pane_id,
} => ActionType::DumpScreen(DumpScreenAction {
file_path,
include_scrollback,
+ pane_id: pane_id.map(|p| p.into()),
}),
crate::input::actions::Action::DumpLayout => {
ActionType::DumpLayout(DumpLayoutAction {})
@@ -1570,6 +1572,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
Ok(crate::input::actions::Action::DumpScreen {
file_path: dump_screen_action.file_path,
include_scrollback: dump_screen_action.include_scrollback,
+ pane_id: dump_screen_action.pane_id.and_then(|p| p.try_into().ok()),
})
},
ActionType::DumpLayout(_) => Ok(crate::input::actions::Action::DumpLayout),
diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
index aee81757..8b018c16 100644
--- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs
+++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
@@ -875,6 +875,7 @@ fn test_client_messages() {
action: Action::DumpScreen {
file_path: "/path/to/file".to_owned(),
include_scrollback: false,
+ pane_id: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -884,6 +885,17 @@ fn test_client_messages() {
action: Action::DumpScreen {
file_path: "/path/to/file".to_owned(),
include_scrollback: true,
+ pane_id: None,
+ },
+ terminal_id: Some(1),
+ client_id: Some(100),
+ is_cli_client: true,
+ });
+ test_client_roundtrip!(ClientToServerMsg::Action {
+ action: Action::DumpScreen {
+ file_path: "/path/to/file".to_owned(),
+ include_scrollback: true,
+ pane_id: Some(PaneId::Terminal(5)),
},
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 1bc31117..0a9a1d75 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -543,6 +543,7 @@ impl Action {
"DumpScreen" => Ok(Action::DumpScreen {
file_path: string,
include_scrollback: false,
+ pane_id: None,
}),
"DumpLayout" => Ok(Action::DumpLayout),
"NewPane" => {
@@ -694,6 +695,7 @@ impl Action {
Action::DumpScreen {
file_path: file,
include_scrollback: _,
+ pane_id: _,
} => {
let mut node = KdlNode::new("DumpScreen");
node.push(file.clone());
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto
index e4a42668..169bc24f 100644
--- a/zellij-utils/src/plugin_api/action.proto
+++ b/zellij-utils/src/plugin_api/action.proto
@@ -381,6 +381,7 @@ message WriteCharsPayload {
message DumpScreenPayload {
string file_path = 1;
bool include_scrollback = 2;
+ optional PaneId pane_id = 3;
}
enum ActionName {
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs
index b115b86d..5ccd349c 100644
--- a/zellij-utils/src/plugin_api/action.rs
+++ b/zellij-utils/src/plugin_api/action.rs
@@ -213,9 +213,11 @@ impl TryFrom<ProtobufAction> for Action {
Some(OptionalPayload::DumpScreenPayload(payload)) => {
let file_path = 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 {
file_path,
include_scrollback,
+ pane_id,
})
},
_ => Err("Wrong payload for Action::DumpScreen"),
@@ -1146,11 +1148,13 @@ impl TryFrom<Action> for ProtobufAction {
Action::DumpScreen {
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()),
})),
}),
Action::EditScrollback => Ok(ProtobufAction {