aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorDawidPietrykowski <53954695+DawidPietrykowski@users.noreply.github.com>2026-06-25 18:12:51 +0200
committerGitHub <noreply@github.com>2026-06-25 18:12:51 +0200
commitebd731b0ff12737a6e2160a255280534ef3991bd (patch)
tree4313963a269205391aa519fd090ea1de2b7cc4af /zellij-utils
parenttests: add more integration tests (#5308) (diff)
downloadsl0ppij-ebd731b0ff12737a6e2160a255280534ef3991bd.tar.gz
sl0ppij-ebd731b0ff12737a6e2160a255280534ef3991bd.tar.xz
sl0ppij-ebd731b0ff12737a6e2160a255280534ef3991bd.zip
feat(panes): `FocusLastPane` action (#4241)
* feat(panes): focus last pane (#4142) * add tests * add client id parameter to `add_floating_pane` * add action for floating panes * add tests for floating panes * add keybinds to configs * rebase fixes * simplify focus logic when adding stacked pane * formatting fix * formatting fix 2 * fix: make sure expanding pane always focuses it for all clients in stack * add pr * fix rebase --------- Co-authored-by: Aram Drevekenin <aram@poor.dev>
Diffstat (limited to 'zellij-utils')
-rw-r--r--zellij-utils/assets/prost/api.action.rs3
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs3
-rw-r--r--zellij-utils/assets/prost_ipc/client_server_contract.rs8
-rw-r--r--zellij-utils/src/cli.rs2
-rw-r--r--zellij-utils/src/client_server_contract/common_types.proto2
-rw-r--r--zellij-utils/src/data.rs1
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/actions.rs3
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs5
-rw-r--r--zellij-utils/src/kdl/mod.rs6
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__keybinds_to_string_with_all_actions.snap3
-rw-r--r--zellij-utils/src/plugin_api/action.proto1
-rw-r--r--zellij-utils/src/plugin_api/action.rs8
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto1
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs10
15 files changed, 54 insertions, 3 deletions
diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs
index 8d331b60..31325096 100644
--- a/zellij-utils/assets/prost/api.action.rs
+++ b/zellij-utils/assets/prost/api.action.rs
@@ -1124,6 +1124,7 @@ pub enum ActionName {
SetDarkTheme = 101,
SetLightTheme = 102,
ToggleTheme = 103,
+ FocusLastPane = 104,
}
impl ActionName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -1233,6 +1234,7 @@ impl ActionName {
ActionName::SetDarkTheme => "SetDarkTheme",
ActionName::SetLightTheme => "SetLightTheme",
ActionName::ToggleTheme => "ToggleTheme",
+ ActionName::FocusLastPane => "FocusLastPane",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -1339,6 +1341,7 @@ impl ActionName {
"SetDarkTheme" => Some(Self::SetDarkTheme),
"SetLightTheme" => Some(Self::SetLightTheme),
"ToggleTheme" => Some(Self::ToggleTheme),
+ "FocusLastPane" => Some(Self::FocusLastPane),
_ => None,
}
}
diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs
index b43bd004..4cef814a 100644
--- a/zellij-utils/assets/prost/api.plugin_command.rs
+++ b/zellij-utils/assets/prost/api.plugin_command.rs
@@ -2229,6 +2229,7 @@ pub enum CommandName {
NewTabUnfocused = 220,
NewTiledPaneInTab = 221,
ExitMobileMode = 222,
+ FocusLastPane = 223,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -2437,6 +2438,7 @@ impl CommandName {
CommandName::NewTabUnfocused => "NewTabUnfocused",
CommandName::NewTiledPaneInTab => "NewTiledPaneInTab",
CommandName::ExitMobileMode => "ExitMobileMode",
+ CommandName::FocusLastPane => "FocusLastPane",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -2642,6 +2644,7 @@ impl CommandName {
"NewTabUnfocused" => Some(Self::NewTabUnfocused),
"NewTiledPaneInTab" => Some(Self::NewTiledPaneInTab),
"ExitMobileMode" => Some(Self::ExitMobileMode),
+ "FocusLastPane" => Some(Self::FocusLastPane),
_ => None,
}
}
diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs
index 4ef94968..e08496e7 100644
--- a/zellij-utils/assets/prost_ipc/client_server_contract.rs
+++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs
@@ -119,7 +119,7 @@ pub struct RgbColor {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Action {
- #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140")]
+ #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141")]
pub action_type: ::core::option::Option<action::ActionType>,
}
/// Nested message and enum types in `Action`.
@@ -409,6 +409,8 @@ pub mod action {
ToggleTheme(super::ToggleThemeAction),
#[prost(message, tag="140")]
ToggleMobileMode(super::ToggleMobileModeAction),
+ #[prost(message, tag="141")]
+ FocusLastPane(super::FocusLastPaneAction),
}
}
// Action message definitions (all 92 variants)
@@ -428,6 +430,10 @@ pub struct FocusPreviousPaneAction {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct FocusLastPaneAction {
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SwitchFocusAction {
}
#[allow(clippy::derive_partial_eq_without_eq)]
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 5b169170..a00c4d05 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -765,6 +765,8 @@ pub enum CliAction {
/// The pane_id of the pane, eg. terminal_1, plugin_2 or 3
pane_id: String,
},
+ /// Change focus to the last focused frame
+ FocusLastPane,
/// Move the focused pane in the specified direction. [right|left|up|down]
MoveFocus {
direction: Direction,
diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto
index 1dfc3994..718cc279 100644
--- a/zellij-utils/src/client_server_contract/common_types.proto
+++ b/zellij-utils/src/client_server_contract/common_types.proto
@@ -278,6 +278,7 @@ message Action {
SetLightThemeAction set_light_theme = 138;
ToggleThemeAction toggle_theme = 139;
ToggleMobileModeAction toggle_mobile_mode = 140;
+ FocusLastPaneAction focus_last_pane = 141;
}
}
@@ -287,6 +288,7 @@ message Action {
message QuitAction {}
message FocusNextPaneAction {}
message FocusPreviousPaneAction {}
+message FocusLastPaneAction {}
message SwitchFocusAction {}
message MovePaneBackwardsAction {}
message ClearScreenAction {}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 16ff560e..c11f777f 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -3384,6 +3384,7 @@ pub enum PluginCommand {
ResizeWithDirection(ResizeStrategy),
FocusNextPane,
FocusPreviousPane,
+ FocusLastPane,
MoveFocus(Direction),
MoveFocusOrTab(Direction),
Detach,
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 8e4bc66c..c9e8e25f 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -246,6 +246,7 @@ pub enum ScreenContext {
SwitchFocus,
FocusNextPane,
FocusPreviousPane,
+ FocusLastPane,
FocusPaneAt,
MoveFocusLeft,
MoveFocusLeftOrPreviousTab,
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index cf5fc448..40158b8b 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -158,6 +158,8 @@ pub enum Action {
/// Switch focus to next pane in specified direction.
FocusNextPane,
FocusPreviousPane,
+ /// Switch focus to the last focused pane.
+ FocusLastPane,
/// Move the focus pane in specified direction.
SwitchFocus,
MoveFocus {
@@ -828,6 +830,7 @@ impl Action {
))?;
Ok(vec![Action::FocusPaneByPaneId { pane_id }])
},
+ CliAction::FocusLastPane => Ok(vec![Action::FocusLastPane]),
CliAction::MoveFocus { direction } => Ok(vec![Action::MoveFocus { direction }]),
CliAction::MoveFocusOrTab { direction } => {
Ok(vec![Action::MoveFocusOrTab { direction }])
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index d1bfb7d4..c4941676 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -915,6 +915,7 @@ impl From<crate::input::actions::Action>
EditFileAction,
EditScrollbackAction,
EditScrollbackByPaneIdAction,
+ FocusLastPaneAction,
FocusNextPaneAction,
FocusPaneByPaneIdAction,
FocusPluginPaneWithIdAction,
@@ -1090,6 +1091,9 @@ impl From<crate::input::actions::Action>
crate::input::actions::Action::FocusPreviousPane => {
ActionType::FocusPreviousPane(FocusPreviousPaneAction {})
},
+ crate::input::actions::Action::FocusLastPane => {
+ ActionType::FocusLastPane(FocusLastPaneAction {})
+ },
crate::input::actions::Action::SwitchFocus => {
ActionType::SwitchFocus(SwitchFocusAction {})
},
@@ -1961,6 +1965,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
ActionType::FocusPreviousPane(_) => {
Ok(crate::input::actions::Action::FocusPreviousPane)
},
+ ActionType::FocusLastPane(_) => Ok(crate::input::actions::Action::FocusLastPane),
ActionType::SwitchFocus(_) => Ok(crate::input::actions::Action::SwitchFocus),
ActionType::MoveFocus(move_focus_action) => {
Ok(crate::input::actions::Action::MoveFocus {
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index 0164ef92..14695f68 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -48,6 +48,7 @@ macro_rules! parse_kdl_action_arguments {
"Quit" => Ok(Action::Quit),
"FocusNextPane" => Ok(Action::FocusNextPane),
"FocusPreviousPane" => Ok(Action::FocusPreviousPane),
+ "FocusLastPane" => Ok(Action::FocusLastPane),
"SwitchFocus" => Ok(Action::SwitchFocus),
"EditScrollback" => Ok(Action::EditScrollback { ansi: false }),
"ScrollUp" => Ok(Action::ScrollUp),
@@ -660,6 +661,7 @@ impl Action {
},
Action::FocusNextPane => Some(KdlNode::new("FocusNextPane")),
Action::FocusPreviousPane => Some(KdlNode::new("FocusPreviousPane")),
+ Action::FocusLastPane => Some(KdlNode::new("FocusLastPane")),
Action::SwitchFocus => Some(KdlNode::new("SwitchFocus")),
Action::MoveFocus { direction } => {
let mut node = KdlNode::new("MoveFocus");
@@ -1512,6 +1514,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
"FocusPreviousPane" => {
parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
},
+ "FocusLastPane" => {
+ parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
+ },
"SwitchFocus" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
"EditScrollback" => {
let ansi = crate::kdl_get_bool_property_or_child_value!(kdl_action, "ansi")
@@ -6729,6 +6734,7 @@ fn keybinds_to_string_with_all_actions() {
config_key_2 "config_value_2";
};
}
+ bind "Ctrl Alt k" { FocusLastPane; }
}
}"#;
let document: KdlDocument = fake_config.parse().unwrap();
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__keybinds_to_string_with_all_actions.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__keybinds_to_string_with_all_actions.snap
index 43e85faa..b0119a63 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__keybinds_to_string_with_all_actions.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__keybinds_to_string_with_all_actions.snap
@@ -1,6 +1,5 @@
---
source: zellij-utils/src/kdl/mod.rs
-assertion_line: 2731
expression: serialized.to_string()
---
keybinds clear-defaults=true {
@@ -47,6 +46,7 @@ keybinds clear-defaults=true {
}
bind "Alt j" { GoToPreviousTab; }
bind "Ctrl k" { MovePane "right"; }
+ bind "Ctrl Alt k" { FocusLastPane; }
bind "Alt k" { CloseTab; }
bind "Ctrl l" { MovePaneBackwards; }
bind "Alt l" { GoToTab 1; }
@@ -127,4 +127,3 @@ keybinds clear-defaults=true {
bind "Alt z" { SearchInput 0; }
}
}
-
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto
index 7eedf393..68c859b8 100644
--- a/zellij-utils/src/plugin_api/action.proto
+++ b/zellij-utils/src/plugin_api/action.proto
@@ -493,6 +493,7 @@ enum ActionName {
SetDarkTheme = 101;
SetLightTheme = 102;
ToggleTheme = 103;
+ FocusLastPane = 104;
}
message Position {
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs
index 34b3adce..f4e35d84 100644
--- a/zellij-utils/src/plugin_api/action.rs
+++ b/zellij-utils/src/plugin_api/action.rs
@@ -168,6 +168,10 @@ impl TryFrom<ProtobufAction> for Action {
Some(_) => Err("FocusPreviousPane should not have a payload"),
None => Ok(Action::FocusPreviousPane),
},
+ Some(ProtobufActionName::FocusLastPane) => match protobuf_action.optional_payload {
+ Some(_) => Err("FocusLastPane should not have a payload"),
+ None => Ok(Action::FocusLastPane),
+ },
Some(ProtobufActionName::SwitchFocus) => match protobuf_action.optional_payload {
Some(_) => Err("SwitchFocus should not have a payload"),
None => Ok(Action::SwitchFocus),
@@ -1173,6 +1177,10 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::FocusPreviousPane as i32,
optional_payload: None,
}),
+ Action::FocusLastPane => Ok(ProtobufAction {
+ name: ProtobufActionName::FocusLastPane as i32,
+ optional_payload: None,
+ }),
Action::SwitchFocus => Ok(ProtobufAction {
name: ProtobufActionName::SwitchFocus as i32,
optional_payload: None,
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index c65bcbfd..82f4d835 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -213,6 +213,7 @@ enum CommandName {
NewTabUnfocused = 220;
NewTiledPaneInTab = 221;
ExitMobileMode = 222;
+ FocusLastPane = 223;
}
message PluginCommand {
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index 42b0774f..546c3586 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -980,6 +980,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
}
Ok(PluginCommand::FocusPreviousPane)
},
+ Some(CommandName::FocusLastPane) => {
+ if protobuf_plugin_command.payload.is_some() {
+ return Err("FocusLastPane should not have a payload");
+ }
+ Ok(PluginCommand::FocusLastPane)
+ },
Some(CommandName::MoveFocus) => match protobuf_plugin_command.payload {
Some(Payload::MoveFocusPayload(move_payload)) => match move_payload.direction {
Some(direction) => Ok(PluginCommand::MoveFocus(direction.try_into()?)),
@@ -3005,6 +3011,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::FocusPreviousPane as i32,
payload: None,
}),
+ PluginCommand::FocusLastPane => Ok(ProtobufPluginCommand {
+ name: CommandName::FocusLastPane as i32,
+ payload: None,
+ }),
PluginCommand::MoveFocus(direction) => Ok(ProtobufPluginCommand {
name: CommandName::MoveFocus as i32,
payload: Some(Payload::MoveFocusPayload(MovePayload {