aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-12-11 14:25:42 +0100
committerGitHub <noreply@github.com>2025-12-11 14:25:42 +0100
commit693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch)
treeb3f651462bbc1cd2fa5b2d79207343413ef6b83c /zellij-utils/src
parentfix(zellij-server): unblock building on 32bit targets (#4484) (diff)
downloadsl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.gz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.xz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.zip
Command Sequences, Blocking CLI Commands, New Plugin APIs (#4546)
* fix deadlocks * initial logging to plugin implementation * only log actions, include keys as Action::Write * send actions to plugins as a new Event::UserAction * add Action::Write metadata to plugin API * run_action plugin command and permission * block-until-exit/success/failure in the cli * fix tests * add NewBlockingPane to the plugin API * run actions in a different thread so as not to block plugin execution * add custom context to run_action flow * fixes for floating pane z_index sorting and unknown protobuf events not crashing plugins * always render the frame of pinned panes * macros module * add macros to config * KDL macro parsing * thread macros to the plugin thread * get/update/delete macros from plugins * add macros updated event * add ability to rename macros * fix deadlock * fix z_indices with pinned unselectable panes bug * allow plugins to opt-in to show_cursor * change show_cursor(bool) to show_cursor(Option<coordinates>) * test * upgrade strum to allow proper enum iteration and case insensitive fromstr implementation for Actions * better cwd tracking for exited command panes * add initial panes to NewTab Action (no-op for now) * add initial panes to cli newtab action * make initial panes work * add close_on_exit and start_suspended to the cli * properly convert newtab action (potentially breaking for plugins) * allow first pane in tab to block * make distinction between opening panes with the last active client and the current pane in the cli * improve some apis * fix fixed size closing pane bug * fix plugin cursor coordinates * fix issues with launch-or-focus-plugin having to do with in-place and suppressed panes * fix: set affected pane in replace pane * allow setting a pane in place when it is suppressed and being focused (tests failing) * fix tests * some fixes for unsuppression of panes * some fixes for completion_tx in new tab action * allow replace/suppressed rather than replace/close only * fixes for inserting suppressed panes without conflicts * pane focus history and explicit unsuppress plugin api * fix: prefer explicitly provided pane id in placement in actions that provide it * fix floating pane coordinate change and z_index * fix plugin cache dir to persist between sessions * fix some stack focusing issues when adding panes without a client id * make sure pinned panes are always above non-pinned floating panes * allow plugins to copy to clipboard * initial text styling additions implementations - buggy * fix dimming issues * remove extras, stay with just dim and unbold * unbold and dim as indices * allow ui components to render success/error colors * allow plugins to send sigint/sigkill to processes, as well as receive their pid * ui component fixes and send originating plugin context with run_action * originating plugin for new tab too * make plugn show pane command also expand stacked panes * add sequence plugin * remove macros * remove snapshots * add shortcut for sequence plugin * fix plugin tests * plugin tests for new apis * update snapshots for sequence shortcut * cleanups and formatting * Event::CwdChanged * moar cleanups * remove some warnings * remove mroe warnings * remove dead code * remove more dead code * fix minor bugs in sequence plugin * update sequence help and format codebase * fix conditional blocking cli commands * fix initial-command new-tab cli flag * add client_ids to CwdChanged Event * last fixes * add pr
Diffstat (limited to 'zellij-utils/src')
-rw-r--r--zellij-utils/src/cli.rs128
-rw-r--r--zellij-utils/src/client_server_contract/common_types.proto29
-rw-r--r--zellij-utils/src/consts.rs1
-rw-r--r--zellij-utils/src/data.rs115
-rw-r--r--zellij-utils/src/errors.rs13
-rw-r--r--zellij-utils/src/input/actions.rs169
-rw-r--r--zellij-utils/src/input/command.rs28
-rw-r--r--zellij-utils/src/input/layout.rs38
-rw-r--r--zellij-utils/src/input/mouse.rs6
-rw-r--r--zellij-utils/src/input/plugins.rs1
-rw-r--r--zellij-utils/src/ipc.rs16
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs165
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs119
-rw-r--r--zellij-utils/src/kdl/mod.rs101
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap7
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap7
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap2
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap2
-rw-r--r--zellij-utils/src/plugin_api/action.proto295
-rw-r--r--zellij-utils/src/plugin_api/action.rs1340
-rw-r--r--zellij-utils/src/plugin_api/event.proto31
-rw-r--r--zellij-utils/src/plugin_api/event.rs173
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto43
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs259
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.proto2
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.rs4
-rw-r--r--zellij-utils/src/position.rs6
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap47
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap47
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap12
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap47
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap47
32 files changed, 3131 insertions, 169 deletions
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 0fd52473..e59ba71f 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -1,4 +1,4 @@
-use crate::data::{Direction, InputMode, Resize};
+use crate::data::{Direction, InputMode, Resize, UnblockCondition};
use crate::setup::Setup;
use crate::{
consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV},
@@ -388,9 +388,49 @@ pub enum Sessions {
takes_value(false)
)]
stacked: bool,
- /// Whether to block until this command has finished
+ /// Block until the command has finished and its pane has been closed
#[clap(long, value_parser, default_value("false"), takes_value(false))]
blocking: bool,
+
+ /// Block until the command exits successfully (exit status 0) OR its pane has been closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ conflicts_with("blocking"),
+ conflicts_with("block-until-exit-failure"),
+ conflicts_with("block-until-exit")
+ )]
+ block_until_exit_success: bool,
+
+ /// Block until the command exits with failure (non-zero exit status) OR its pane has been
+ /// closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ conflicts_with("blocking"),
+ conflicts_with("block-until-exit-success"),
+ conflicts_with("block-until-exit")
+ )]
+ block_until_exit_failure: bool,
+
+ /// Block until the command exits (regardless of exit status) OR its pane has been closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ conflicts_with("blocking"),
+ conflicts_with("block-until-exit-success"),
+ conflicts_with("block-until-exit-failure")
+ )]
+ block_until_exit: bool,
+ /// if set, will open the pane near the current one rather than following the user's focus
+ #[clap(long)]
+ near_current_pane: bool,
},
/// Load a plugin
#[clap(visible_alias = "p")]
@@ -484,6 +524,9 @@ pub enum Sessions {
/// Whether to pin a floating pane so that it is always on top
#[clap(long, requires("floating"))]
pinned: Option<bool>,
+ /// if set, will open the pane near the current one rather than following the user's focus
+ #[clap(long)]
+ near_current_pane: bool,
},
ConvertConfig {
old_config_file: PathBuf,
@@ -691,6 +734,14 @@ pub enum CliAction {
stacked: bool,
#[clap(short, long)]
blocking: bool,
+
+ // TODO: clean this up
+ #[clap(skip)]
+ unblock_condition: Option<UnblockCondition>,
+
+ /// if set, will open the pane near the current one rather than following the user's focus
+ #[clap(long)]
+ near_current_pane: bool,
},
/// Open the specified file in a new zellij pane with your default EDITOR
Edit {
@@ -738,6 +789,9 @@ pub enum CliAction {
/// Whether to pin a floating pane so that it is always on top
#[clap(long, requires("floating"))]
pinned: Option<bool>,
+ /// if set, will open the pane near the current one rather than following the user's focus
+ #[clap(long)]
+ near_current_pane: bool,
},
/// Switch input mode of all connected clients [locked|pane|tab|resize|move|search|session]
SwitchMode {
@@ -795,6 +849,76 @@ pub enum CliAction {
/// Change the working directory of the new tab
#[clap(short, long, value_parser)]
cwd: Option<PathBuf>,
+
+ /// Optional initial command to run in the new tab
+ #[clap(
+ value_parser,
+ conflicts_with("initial-plugin"),
+ multiple_values(true),
+ takes_value(true),
+ last(true)
+ )]
+ initial_command: Vec<String>,
+
+ /// Initial plugin to load in the new tab
+ #[clap(long, value_parser, conflicts_with("initial-command"))]
+ initial_plugin: Option<String>,
+
+ /// Close the pane immediately when its command exits
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("initial-command")
+ )]
+ close_on_exit: bool,
+
+ /// Start the command suspended, only running it after you first press ENTER
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("initial-command")
+ )]
+ start_suspended: bool,
+
+ /// Block until the command exits successfully (exit status 0) OR its pane has been closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("initial-command"),
+ conflicts_with("block-until-exit-failure"),
+ conflicts_with("block-until-exit")
+ )]
+ block_until_exit_success: bool,
+
+ /// Block until the command exits with failure (non-zero exit status) OR its pane has been closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("initial-command"),
+ conflicts_with("block-until-exit-success"),
+ conflicts_with("block-until-exit")
+ )]
+ block_until_exit_failure: bool,
+
+ /// Block until the command exits (regardless of exit status) OR its pane has been closed
+ #[clap(
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("initial-command"),
+ conflicts_with("block-until-exit-success"),
+ conflicts_with("block-until-exit-failure")
+ )]
+ block_until_exit: bool,
},
/// Move the focused tab in the specified direction. [right|left]
MoveTab {
diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto
index 254a2a75..e4561989 100644
--- a/zellij-utils/src/client_server_contract/common_types.proto
+++ b/zellij-utils/src/client_server_contract/common_types.proto
@@ -333,6 +333,7 @@ message NewPaneAction {
optional Direction direction = 1;
optional string pane_name = 2;
bool start_suppressed = 3;
+ bool near_current_pane = 4;
}
message EditFileAction {
@@ -342,34 +343,43 @@ message EditFileAction {
bool in_place = 4;
bool start_suppressed = 5;
optional FloatingPaneCoordinates coordinates = 6;
+ bool near_current_pane = 7;
}
message NewFloatingPaneAction {
optional RunCommandAction command = 1;
optional string pane_name = 2;
optional FloatingPaneCoordinates coordinates = 3;
+ bool near_current_pane = 7;
}
message NewTiledPaneAction {
optional Direction direction = 1;
optional RunCommandAction command = 2;
optional string pane_name = 3;
+ bool near_current_pane = 7;
}
message NewInPlacePaneAction {
optional RunCommandAction command = 1;
optional string pane_name = 2;
+ bool near_current_pane = 3;
+ optional PaneId pane_id_to_replace = 4;
+ bool close_replace_pane = 5;
}
message NewStackedPaneAction {
optional RunCommandAction command = 1;
optional string pane_name = 2;
+ bool near_current_pane = 3;
}
message NewBlockingPaneAction {
NewPanePlacement placement = 1;
optional string pane_name = 2;
optional RunCommandAction command = 3;
+ optional UnblockCondition unblock_condition = 4;
+ bool near_current_pane = 5;
}
message PaneNameInputAction {
@@ -384,6 +394,8 @@ message NewTabAction {
optional string tab_name = 5;
bool should_change_focus_to_new_tab = 6;
optional string cwd = 7;
+ repeated CommandOrPlugin initial_panes = 8;
+ optional UnblockCondition first_pane_unblock_condition = 9;
}
message GoToTabAction {
@@ -405,6 +417,7 @@ message MoveTabAction {
message RunAction {
RunCommandAction command = 1;
+ bool near_current_pane = 2;
}
message LaunchOrFocusPluginAction {
@@ -479,11 +492,13 @@ message ClosePluginPaneAction {
message FocusTerminalPaneWithIdAction {
uint32 pane_id = 1;
bool should_float_if_hidden = 2;
+ bool should_be_in_place_if_hidden = 3;
}
message FocusPluginPaneWithIdAction {
uint32 pane_id = 1;
bool should_float_if_hidden = 2;
+ bool should_be_in_place_if_hidden = 3;
}
message RenameTerminalPaneAction {
@@ -571,6 +586,13 @@ enum Direction {
DIRECTION_DOWN = 4;
}
+enum UnblockCondition {
+ UNBLOCK_CONDITION_UNSPECIFIED = 0;
+ UNBLOCK_CONDITION_ON_EXIT_SUCCESS = 1;
+ UNBLOCK_CONDITION_ON_EXIT_FAILURE = 2;
+ UNBLOCK_CONDITION_ON_ANY_EXIT = 3;
+}
+
enum ResizeType {
RESIZE_TYPE_UNSPECIFIED = 0;
RESIZE_TYPE_INCREASE = 1;
@@ -761,6 +783,13 @@ message PluginAlias {
optional RunPlugin run_plugin = 4;
}
+message CommandOrPlugin {
+ oneof command_or_plugin_type {
+ RunCommandAction command = 1;
+ RunPluginOrAlias plugin = 2;
+ }
+}
+
message MouseEvent {
MouseEventType event_type = 1;
bool left = 2;
diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs
index 4f5b729d..e5b93e1a 100644
--- a/zellij-utils/src/consts.rs
+++ b/zellij-utils/src/consts.rs
@@ -138,6 +138,7 @@ mod not_wasm {
add_plugin!(assets, "about.wasm");
add_plugin!(assets, "share.wasm");
add_plugin!(assets, "multiple-select.wasm");
+ add_plugin!(assets, "sequence.wasm");
assets
};
}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 7f700d4d..62571fd4 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -1,8 +1,8 @@
use crate::home::default_layout_dir;
-use crate::input::actions::Action;
+use crate::input::actions::{Action, RunCommandAction};
use crate::input::config::ConversionError;
use crate::input::keybinds::Keybinds;
-use crate::input::layout::{RunPlugin, SplitSize};
+use crate::input::layout::{RunPlugin, RunPluginOrAlias, SplitSize};
use crate::pane_size::PaneGeom;
use crate::position::Position;
use crate::shared::{colors as default_colors, eightbit_to_rgb};
@@ -15,7 +15,7 @@ use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::str::{self, FromStr};
use std::time::Duration;
-use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString};
+use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString};
use unicode_width::UnicodeWidthChar;
#[cfg(not(target_family = "wasm"))]
@@ -26,6 +26,39 @@ use termwiz::{
pub type ClientId = u16; // TODO: merge with crate type?
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum UnblockCondition {
+ /// Unblock only when exit status is 0 (success)
+ OnExitSuccess,
+ /// Unblock only when exit status is non-zero (failure)
+ OnExitFailure,
+ /// Unblock on any exit (success or failure)
+ OnAnyExit,
+}
+
+impl UnblockCondition {
+ /// Check if the condition is met for the given exit status
+ pub fn is_met(&self, exit_status: i32) -> bool {
+ match self {
+ UnblockCondition::OnExitSuccess => exit_status == 0,
+ UnblockCondition::OnExitFailure => exit_status != 0,
+ UnblockCondition::OnAnyExit => true,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum CommandOrPlugin {
+ Command(RunCommandAction),
+ Plugin(RunPluginOrAlias),
+}
+
+impl CommandOrPlugin {
+ pub fn new_command(command: Vec<String>) -> Self {
+ CommandOrPlugin::Command(RunCommandAction::new(command))
+ }
+}
+
pub fn client_id_to_colors(
client_id: ClientId,
colors: MultiplayerColors,
@@ -252,7 +285,7 @@ impl FromStr for BareKey {
}
#[derive(
- Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, ToString,
+ Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, Display,
)]
pub enum KeyModifier {
Ctrl,
@@ -589,6 +622,12 @@ pub enum Direction {
Down,
}
+impl Default for Direction {
+ fn default() -> Self {
+ Direction::Left
+ }
+}
+
impl Direction {
pub fn invert(&self) -> Direction {
match *self {
@@ -642,6 +681,12 @@ pub enum Resize {
Decrease,
}
+impl Default for Resize {
+ fn default() -> Self {
+ Resize::Increase
+ }
+}
+
impl Resize {
pub fn invert(&self) -> Self {
match self {
@@ -877,7 +922,7 @@ impl From<Metadata> for FileMetadata {
/// These events can be subscribed to with subscribe method exported by `zellij-tile`.
/// Once subscribed to, they will trigger the `update` method of the `ZellijPlugin` trait.
-#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, EnumDiscriminants, Display, Serialize, Deserialize)]
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
#[strum_discriminants(name(EventType))]
#[non_exhaustive]
@@ -946,10 +991,14 @@ pub enum Event {
FailedToStartWebServer(String),
BeforeClose,
InterceptedKeyPress(KeyWithModifier),
+ /// An action was performed by the user (requires InterceptInput permission)
+ UserAction(Action, ClientId, Option<u32>, Option<ClientId>), // Action, client_id, terminal_id, cli_client_id
PaneRenderReport(HashMap<PaneId, PaneContents>),
+ ActionComplete(Action, Option<PaneId>, BTreeMap<String, String>), // Action, pane_id, context
+ CwdChanged(PaneId, PathBuf, Vec<ClientId>), // pane_id, cwd, focused_client_ids
}
-#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants, ToString, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants, Display, Serialize, Deserialize)]
pub enum WebServerStatus {
Online(String), // String -> base url
Offline,
@@ -964,7 +1013,7 @@ pub enum WebServerStatus {
Copy,
Clone,
EnumDiscriminants,
- ToString,
+ Display,
Serialize,
Deserialize,
PartialOrd,
@@ -988,6 +1037,8 @@ pub enum Permission {
StartWebServer,
InterceptInput,
ReadPaneContents,
+ RunActionsAsUser,
+ WriteToClipboard,
}
impl PermissionType {
@@ -1017,6 +1068,8 @@ impl PermissionType {
PermissionType::ReadPaneContents => {
"Read pane contents (viewport and selection)".to_owned()
},
+ PermissionType::RunActionsAsUser => "Execute actions as the user".to_owned(),
+ PermissionType::WriteToClipboard => "Write to clipboard".to_owned(),
}
}
}
@@ -1643,6 +1696,7 @@ pub struct SessionInfo {
pub web_clients_allowed: bool,
pub web_client_count: usize,
pub tab_history: BTreeMap<ClientId, Vec<usize>>,
+ pub pane_history: BTreeMap<ClientId, Vec<PaneId>>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -2053,6 +2107,12 @@ pub enum PaneScrollbackResponse {
Err(String),
}
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub enum GetPanePidResponse {
+ Ok(i32),
+ Err(String),
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SelectedText {
pub start: Position,
@@ -2226,6 +2286,12 @@ pub enum PaneId {
Plugin(u32),
}
+impl Default for PaneId {
+ fn default() -> Self {
+ PaneId::Terminal(0)
+ }
+}
+
impl FromStr for PaneId {
type Err = Box<dyn std::error::Error>;
fn from_str(stringified_pane_id: &str) -> Result<Self, Self::Err> {
@@ -2660,13 +2726,14 @@ impl NewPanePlacement {
type Context = BTreeMap<String, String>;
-#[derive(Debug, Clone, EnumDiscriminants, ToString)]
+#[derive(Debug, Clone, EnumDiscriminants, Display)]
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
#[strum_discriminants(name(CommandType))]
pub enum PluginCommand {
Subscribe(HashSet<EventType>),
Unsubscribe(HashSet<EventType>),
SetSelectable(bool),
+ ShowCursor(Option<(usize, usize)>),
GetPluginIds,
GetZellijVersion,
OpenFile(FileToOpen, Context),
@@ -2723,16 +2790,16 @@ pub enum PluginCommand {
NextSwapLayout,
GoToTabName(String),
FocusOrCreateTab(String),
- GoToTab(u32), // tab index
- StartOrReloadPlugin(String), // plugin url (eg. file:/path/to/plugin.wasm)
- CloseTerminalPane(u32), // terminal pane id
- ClosePluginPane(u32), // plugin pane id
- FocusTerminalPane(u32, bool), // terminal pane id, should_float_if_hidden
- FocusPluginPane(u32, bool), // plugin pane id, should_float_if_hidden
- RenameTerminalPane(u32, String), // terminal pane id, new name
- RenamePluginPane(u32, String), // plugin pane id, new name
- RenameTab(u32, String), // tab index, new name
- ReportPanic(String), // stringified panic
+ GoToTab(u32), // tab index
+ StartOrReloadPlugin(String), // plugin url (eg. file:/path/to/plugin.wasm)
+ CloseTerminalPane(u32), // terminal pane id
+ ClosePluginPane(u32), // plugin pane id
+ FocusTerminalPane(u32, bool, bool), // terminal pane id, should_float_if_hidden, should_be_in_place_if_hidden
+ FocusPluginPane(u32, bool, bool), // plugin pane id, should_float_if_hidden, should_be_in_place_if_hidden
+ RenameTerminalPane(u32, String), // terminal pane id, new name
+ RenamePluginPane(u32, String), // plugin pane id, new name
+ RenameTab(u32, String), // tab index, new name
+ ReportPanic(String), // stringified panic
RequestPluginPermissions(Vec<PermissionType>),
SwitchSession(ConnectToSession),
DeleteDeadSession(String), // String -> session name
@@ -2768,7 +2835,7 @@ pub enum PluginCommand {
Reconfigure(String, bool), // String -> stringified configuration, bool -> save configuration
// file to disk
HidePaneWithId(PaneId),
- ShowPaneWithId(PaneId, bool), // bool -> should_float_if_hidden
+ ShowPaneWithId(PaneId, bool, bool), // bools -> should_float_if_hidden, should_focus_pane
OpenCommandPaneBackground(CommandToRun, Context),
RerunCommandPane(u32), // u32 - terminal pane id
ResizePaneIdWithDirection(ResizeStrategy, PaneId),
@@ -2779,6 +2846,11 @@ pub enum PluginCommand {
},
WriteToPaneId(Vec<u8>, PaneId),
WriteCharsToPaneId(String, PaneId),
+ SendSigintToPaneId(PaneId),
+ SendSigkillToPaneId(PaneId),
+ GetPanePid {
+ pane_id: PaneId,
+ },
MovePaneWithPaneId(PaneId),
MovePaneWithPaneIdInDirection(PaneId, Direction),
ClearScreenForPaneId(PaneId),
@@ -2844,5 +2916,8 @@ pub enum PluginCommand {
RenameWebLoginToken(String, String), // (original_name, new_name)
InterceptKeyPresses,
ClearKeyPressesIntercepts,
- ReplacePaneWithExistingPane(PaneId, PaneId), // (pane id to replace, pane id of existing)
+ ReplacePaneWithExistingPane(PaneId, PaneId, bool), // (pane id to replace, pane id of existing,
+ // suppress_replaced_pane)
+ RunAction(Action, BTreeMap<String, String>),
+ CopyToClipboard(String), // text to copy
}
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index bcd05987..74c40534 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -277,6 +277,7 @@ pub enum ScreenContext {
ToggleActiveTerminalFullscreen,
TogglePaneFrames,
SetSelectable,
+ ShowPluginCursor,
SetInvisibleBorders,
SetFixedHeight,
SetFixedWidth,
@@ -313,10 +314,6 @@ pub enum ScreenContext {
ToggleTab,
AddClient,
RemoveClient,
- AddOverlay,
- RemoveOverlay,
- ConfirmPrompt,
- DenyPrompt,
UpdateSearch,
SearchDown,
SearchUp,
@@ -339,6 +336,8 @@ pub enum ScreenContext {
LaunchOrFocusPlugin,
LaunchPlugin,
SuppressPane,
+ UnsuppressPane,
+ UnsuppressOrExpandPane,
FocusPaneWithId,
RenamePane,
RenameTab,
@@ -358,6 +357,7 @@ pub enum ScreenContext {
ResizePaneWithId,
EditScrollbackForPaneWithId,
WriteToPaneId,
+ CopyTextToClipboard,
MovePaneWithPaneId,
MovePaneWithPaneIdInDirection,
ClearScreenForPaneId,
@@ -418,6 +418,10 @@ pub enum PtyContext {
Reconfigure,
ListClientsToPlugin,
ReportPluginCwd,
+ SendSigintToPaneId,
+ SendSigkillToPaneId,
+ GetPanePid,
+ UpdateAndReportCwds,
Exit,
}
@@ -460,6 +464,7 @@ pub enum PluginContext {
WebServerStarted,
FailedToStartWebServer,
PaneRenderReport,
+ UserInput,
}
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index d623197b..92d697a9 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -1,12 +1,15 @@
//! Definition of the actions that can be bound to keys.
-use super::command::{OpenFilePayload, RunCommandAction};
+pub use super::command::{OpenFilePayload, RunCommandAction};
use super::layout::{
FloatingPaneLayout, Layout, PluginAlias, RunPlugin, RunPluginLocation, RunPluginOrAlias,
SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
};
use crate::cli::CliAction;
-use crate::data::{Direction, KeyWithModifier, LayoutInfo, NewPanePlacement, PaneId, Resize};
+use crate::data::{
+ CommandOrPlugin, Direction, KeyWithModifier, LayoutInfo, NewPanePlacement, OriginatingPlugin,
+ PaneId, Resize, UnblockCondition,
+};
use crate::data::{FloatingPaneCoordinates, InputMode};
use crate::home::{find_default_config_dir, get_layout_dir};
use crate::input::config::{Config, ConfigError, KdlError};
@@ -99,7 +102,18 @@ impl FromStr for SearchOption {
// They might need to be adjusted in the default config
// as well `../../assets/config/default.yaml`
/// Actions that can be bound to keys.
-#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, strum_macros::Display)]
+#[derive(
+ Clone,
+ Debug,
+ PartialEq,
+ Eq,
+ Deserialize,
+ Serialize,
+ strum_macros::Display,
+ strum_macros::EnumString,
+ strum_macros::EnumIter,
+)]
+#[strum(ascii_case_insensitive)]
pub enum Action {
/// Quit Zellij.
Quit,
@@ -194,6 +208,8 @@ pub enum Action {
placement: NewPanePlacement,
pane_name: Option<String>,
command: Option<RunCommandAction>,
+ unblock_condition: Option<UnblockCondition>,
+ near_current_pane: bool,
},
/// Open the file in a new pane using the default editor
EditFile {
@@ -203,27 +219,34 @@ pub enum Action {
in_place: bool,
start_suppressed: bool,
coordinates: Option<FloatingPaneCoordinates>,
+ near_current_pane: bool,
},
/// Open a new floating pane
NewFloatingPane {
command: Option<RunCommandAction>,
pane_name: Option<String>,
coordinates: Option<FloatingPaneCoordinates>,
+ near_current_pane: bool,
},
/// Open a new tiled (embedded, non-floating) pane
NewTiledPane {
direction: Option<Direction>,
command: Option<RunCommandAction>,
pane_name: Option<String>,
+ near_current_pane: bool,
},
/// Open a new pane in place of the focused one, suppressing it instead
NewInPlacePane {
command: Option<RunCommandAction>,
pane_name: Option<String>,
+ near_current_pane: bool,
+ pane_id_to_replace: Option<PaneId>,
+ close_replace_pane: bool,
},
NewStackedPane {
command: Option<RunCommandAction>,
pane_name: Option<String>,
+ near_current_pane: bool,
},
/// Embed focused pane in tab if floating or float focused pane if embedded
TogglePaneEmbedOrFloating,
@@ -244,6 +267,8 @@ pub enum Action {
tab_name: Option<String>,
should_change_focus_to_new_tab: bool,
cwd: Option<PathBuf>,
+ initial_panes: Option<Vec<CommandOrPlugin>>,
+ first_pane_unblock_condition: Option<UnblockCondition>,
},
/// Do nothing.
NoOp,
@@ -271,6 +296,7 @@ pub enum Action {
/// Run specified command in new pane.
Run {
command: RunCommandAction,
+ near_current_pane: bool,
},
/// Detach session and exit
Detach,
@@ -356,10 +382,12 @@ pub enum Action {
FocusTerminalPaneWithId {
pane_id: u32,
should_float_if_hidden: bool,
+ should_be_in_place_if_hidden: bool,
},
FocusPluginPaneWithId {
pane_id: u32,
should_float_if_hidden: bool,
+ should_be_in_place_if_hidden: bool,
},
RenameTerminalPane {
pane_id: u32,
@@ -420,6 +448,24 @@ pub enum Action {
ToggleGroupMarking,
}
+impl Default for Action {
+ fn default() -> Self {
+ Action::NoOp
+ }
+}
+
+impl Default for SearchDirection {
+ fn default() -> Self {
+ SearchDirection::Down
+ }
+}
+
+impl Default for SearchOption {
+ fn default() -> Self {
+ SearchOption::CaseSensitivity
+ }
+}
+
impl Action {
/// Checks that two Action are match except their mutable attributes.
pub fn shallow_eq(&self, other_action: &Action) -> bool {
@@ -492,6 +538,8 @@ impl Action {
pinned,
stacked,
blocking,
+ unblock_condition,
+ near_current_pane,
} => {
let current_dir = get_current_dir();
// cwd should only be specified in a plugin alias if it was explicitly given to us,
@@ -500,7 +548,7 @@ impl Action {
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir.clone()));
- if blocking {
+ if blocking || unblock_condition.is_some() {
// For blocking panes, we don't support plugins
if plugin.is_some() {
return Err("Blocking panes do not support plugin variants".to_string());
@@ -543,6 +591,8 @@ impl Action {
placement,
pane_name: name,
command,
+ unblock_condition,
+ near_current_pane,
}])
} else if let Some(plugin) = plugin {
let plugin = match RunPluginLocation::parse(&plugin, cwd.clone()) {
@@ -614,22 +664,28 @@ impl Action {
command: Some(run_command_action),
pane_name: name,
coordinates: FloatingPaneCoordinates::new(x, y, width, height, pinned),
+ near_current_pane,
}])
} else if in_place {
Ok(vec![Action::NewInPlacePane {
command: Some(run_command_action),
pane_name: name,
+ near_current_pane,
+ pane_id_to_replace: None, // TODO: support this
+ close_replace_pane: false,
}])
} else if stacked {
Ok(vec![Action::NewStackedPane {
command: Some(run_command_action),
pane_name: name,
+ near_current_pane,
}])
} else {
Ok(vec![Action::NewTiledPane {
direction,
command: Some(run_command_action),
pane_name: name,
+ near_current_pane,
}])
}
} else {
@@ -638,22 +694,28 @@ impl Action {
command: None,
pane_name: name,
coordinates: FloatingPaneCoordinates::new(x, y, width, height, pinned),
+ near_current_pane,
}])
} else if in_place {
Ok(vec![Action::NewInPlacePane {
command: None,
pane_name: name,
+ near_current_pane,
+ pane_id_to_replace: None, // TODO: support this
+ close_replace_pane: false,
}])
} else if stacked {
Ok(vec![Action::NewStackedPane {
command: None,
pane_name: name,
+ near_current_pane,
}])
} else {
Ok(vec![Action::NewTiledPane {
direction,
command: None,
pane_name: name,
+ near_current_pane,
}])
}
}
@@ -670,6 +732,7 @@ impl Action {
width,
height,
pinned,
+ near_current_pane,
} => {
let mut file = file;
let current_dir = get_current_dir();
@@ -689,6 +752,7 @@ impl Action {
in_place,
start_suppressed,
coordinates: FloatingPaneCoordinates::new(x, y, width, height, pinned),
+ near_current_pane,
}])
},
CliAction::SwitchMode { input_mode } => Ok(vec![Action::SwitchToMode { input_mode }]),
@@ -721,11 +785,68 @@ impl Action {
layout,
layout_dir,
cwd,
+ initial_command,
+ initial_plugin,
+ close_on_exit,
+ start_suspended,
+ block_until_exit_success,
+ block_until_exit_failure,
+ block_until_exit,
} => {
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
- .or_else(|| Some(current_dir));
+ .or_else(|| Some(current_dir.clone()));
+
+ // Map CLI flags to UnblockCondition
+ let first_pane_unblock_condition = if block_until_exit_success {
+ Some(UnblockCondition::OnExitSuccess)
+ } else if block_until_exit_failure {
+ Some(UnblockCondition::OnExitFailure)
+ } else if block_until_exit {
+ Some(UnblockCondition::OnAnyExit)
+ } else {
+ None
+ };
+
+ // Parse initial_panes from initial_command or initial_plugin
+ let initial_panes = if let Some(plugin_url) = initial_plugin {
+ let plugin = match RunPluginLocation::parse(&plugin_url, cwd.clone()) {
+ Ok(location) => RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location,
+ configuration: Default::default(),
+ initial_cwd: cwd.clone(),
+ }),
+ Err(_) => {
+ let mut plugin_alias =
+ PluginAlias::new(&plugin_url, &None, cwd.clone());
+ plugin_alias.set_caller_cwd_if_not_set(Some(current_dir.clone()));
+ RunPluginOrAlias::Alias(plugin_alias)
+ },
+ };
+ Some(vec![CommandOrPlugin::Plugin(plugin)])
+ } else if !initial_command.is_empty() {
+ let mut command: Vec<String> = initial_command.clone();
+ let (command, args) = (
+ PathBuf::from(command.remove(0)),
+ command.into_iter().collect(),
+ );
+ let hold_on_close = !close_on_exit;
+ let hold_on_start = start_suspended;
+ let run_command_action = RunCommandAction {
+ command,
+ args,
+ cwd: cwd.clone(),
+ direction: None,
+ hold_on_close,
+ hold_on_start,
+ ..Default::default()
+ };
+ Some(vec![CommandOrPlugin::Command(run_command_action)])
+ } else {
+ None
+ };
if let Some(layout_path) = layout {
let layout_dir = layout_dir
.or_else(|| config.and_then(|c| c.options.layout_dir))
@@ -813,6 +934,8 @@ impl Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd: None, // the cwd is done through the layout
+ initial_panes: initial_panes.clone(),
+ first_pane_unblock_condition,
});
}
Ok(new_tab_actions)
@@ -829,6 +952,8 @@ impl Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd: None, // the cwd is done through the layout
+ initial_panes,
+ first_pane_unblock_condition,
}])
}
} else {
@@ -841,6 +966,8 @@ impl Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd,
+ initial_panes,
+ first_pane_unblock_condition,
}])
}
},
@@ -1047,6 +1174,38 @@ impl Action {
},
}
}
+ pub fn populate_originating_plugin(&mut self, originating_plugin: OriginatingPlugin) {
+ match self {
+ Action::NewBlockingPane { command, .. }
+ | Action::NewFloatingPane { command, .. }
+ | Action::NewTiledPane { command, .. }
+ | Action::NewInPlacePane { command, .. }
+ | Action::NewStackedPane { command, .. } => {
+ command
+ .as_mut()
+ .map(|c| c.populate_originating_plugin(originating_plugin));
+ },
+ Action::Run { command, .. } => {
+ command.populate_originating_plugin(originating_plugin);
+ },
+ Action::EditFile { payload, .. } => {
+ payload.originating_plugin = Some(originating_plugin);
+ },
+ Action::NewTab { initial_panes, .. } => {
+ if let Some(initial_panes) = initial_panes.as_mut() {
+ for pane in initial_panes.iter_mut() {
+ match pane {
+ CommandOrPlugin::Command(run_command) => {
+ run_command.populate_originating_plugin(originating_plugin.clone());
+ },
+ _ => {},
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
pub fn launches_plugin(&self, plugin_url: &str) -> bool {
match self {
Action::LaunchPlugin { plugin, .. } => &plugin.location_string() == plugin_url,
diff --git a/zellij-utils/src/input/command.rs b/zellij-utils/src/input/command.rs
index 0169f799..11a34f59 100644
--- a/zellij-utils/src/input/command.rs
+++ b/zellij-utils/src/input/command.rs
@@ -30,6 +30,17 @@ pub struct OpenFilePayload {
pub originating_plugin: Option<OriginatingPlugin>,
}
+impl Default for OpenFilePayload {
+ fn default() -> Self {
+ OpenFilePayload {
+ path: PathBuf::new(),
+ line_number: None,
+ cwd: None,
+ originating_plugin: None,
+ }
+ }
+}
+
impl OpenFilePayload {
pub fn new(path: PathBuf, line_number: Option<usize>, cwd: Option<PathBuf>) -> Self {
OpenFilePayload {
@@ -129,6 +140,23 @@ impl From<RunCommand> for RunCommandAction {
}
}
+impl RunCommandAction {
+ pub fn new(mut command: Vec<String>) -> Self {
+ if command.is_empty() {
+ Default::default()
+ } else {
+ RunCommandAction {
+ command: PathBuf::from(command.remove(0)),
+ args: command,
+ ..Default::default()
+ }
+ }
+ }
+ pub fn populate_originating_plugin(&mut self, originating_plugin: OriginatingPlugin) {
+ self.originating_plugin = Some(originating_plugin);
+ }
+}
+
impl RunCommand {
pub fn new(command: PathBuf) -> Self {
RunCommand {
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index de555a00..d925f7c5 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -995,6 +995,44 @@ impl TiledPaneLayout {
}
run_instructions
}
+ pub fn replace_next_empty_slot_with_run(&mut self, run_to_insert: Run) -> bool {
+ // Replaces the first empty slot (None or Run::Cwd) with the given Run instruction.
+ // Returns true if a replacement was made, false if no empty slot was found.
+ // Traversal order matches extract_run_instructions (breadth-first).
+
+ if self.children.is_empty() {
+ // This is a leaf node - check if it's an empty slot
+ match &self.run {
+ None | Some(Run::Cwd(_)) => {
+ self.run = Some(run_to_insert);
+ return true;
+ },
+ _ => return false,
+ }
+ }
+
+ // Check first child of each child (breadth-first first level)
+ for child in self.children.iter_mut() {
+ if child.children.is_empty() {
+ match &child.run {
+ None | Some(Run::Cwd(_)) => {
+ child.run = Some(run_to_insert);
+ return true;
+ },
+ _ => {},
+ }
+ }
+ }
+
+ // Recursively check deeper levels (breadth-first continuation)
+ for child in self.children.iter_mut() {
+ if child.replace_next_empty_slot_with_run(run_to_insert.clone()) {
+ return true;
+ }
+ }
+
+ false
+ }
pub fn ignore_run_instruction(&mut self, run_instruction: Option<Run>) {
self.run_instructions_to_ignore.push(run_instruction);
}
diff --git a/zellij-utils/src/input/mouse.rs b/zellij-utils/src/input/mouse.rs
index d920495d..0977d5e7 100644
--- a/zellij-utils/src/input/mouse.rs
+++ b/zellij-utils/src/input/mouse.rs
@@ -239,3 +239,9 @@ impl MouseEvent {
event
}
}
+
+impl Default for MouseEvent {
+ fn default() -> Self {
+ MouseEvent::new()
+ }
+}
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index c4755205..892c7a66 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -67,6 +67,7 @@ impl PluginConfig {
|| tag == "about"
|| tag == "share"
|| tag == "multiple-select"
+ || tag == "sequence"
{
Some(PluginConfig {
path: PathBuf::from(&tag),
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index 53dd4d9f..96e2c747 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -251,18 +251,14 @@ impl<T: Serialize> IpcSenderWithContext<T> {
pub fn send_client_msg(&mut self, msg: ClientToServerMsg) -> Result<()> {
let proto_msg: ProtoClientToServerMsg = msg.into();
write_protobuf_message(&mut self.sender, &proto_msg)?;
- if let Err(e) = self.sender.flush() {
- log::error!("Failed to flush ipc sender: {}", e);
- }
+ let _ = self.sender.flush();
Ok(())
}
pub fn send_server_msg(&mut self, msg: ServerToClientMsg) -> Result<()> {
let proto_msg: ProtoServerToClientMsg = msg.into();
write_protobuf_message(&mut self.sender, &proto_msg)?;
- if let Err(e) = self.sender.flush() {
- log::error!("Failed to flush ipc sender: {}", e);
- }
+ let _ = self.sender.flush();
Ok(())
}
@@ -370,9 +366,7 @@ pub fn send_protobuf_client_to_server(
) -> Result<()> {
let proto_msg: ProtoClientToServerMsg = msg.into();
write_protobuf_message(&mut sender.sender, &proto_msg)?;
- if let Err(e) = sender.sender.flush() {
- log::error!("Failed to flush ipc sender: {}", e);
- }
+ let _ = sender.sender.flush();
Ok(())
}
@@ -382,9 +376,7 @@ pub fn send_protobuf_server_to_client(
) -> Result<()> {
let proto_msg: ProtoServerToClientMsg = msg.into();
write_protobuf_message(&mut sender.sender, &proto_msg)?;
- if let Err(e) = sender.sender.flush() {
- log::error!("Failed to flush ipc sender: {}", e);
- }
+ let _ = sender.sender.flush();
Ok(())
}
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index ab06faae..f7ab632f 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -858,6 +858,7 @@ impl From<crate::input::actions::Action>
direction: direction.map(|d| direction_to_proto_i32(d)),
pane_name,
start_suppressed,
+ near_current_pane: false,
}),
crate::input::actions::Action::EditFile {
payload,
@@ -866,6 +867,7 @@ impl From<crate::input::actions::Action>
in_place,
start_suppressed,
coordinates,
+ near_current_pane,
} => ActionType::EditFile(EditFileAction {
payload: Some(payload.into()),
direction: direction.map(|d| direction_to_proto_i32(d)),
@@ -873,45 +875,64 @@ impl From<crate::input::actions::Action>
in_place,
start_suppressed,
coordinates: coordinates.map(|c| c.into()),
+ near_current_pane,
}),
crate::input::actions::Action::NewFloatingPane {
command,
pane_name,
coordinates,
+ near_current_pane,
} => ActionType::NewFloatingPane(NewFloatingPaneAction {
command: command.map(|c| c.into()),
pane_name,
coordinates: coordinates.map(|c| c.into()),
+ near_current_pane,
}),
crate::input::actions::Action::NewTiledPane {
direction,
command,
pane_name,
+ near_current_pane,
} => ActionType::NewTiledPane(NewTiledPaneAction {
direction: direction.map(|d| direction_to_proto_i32(d)),
command: command.map(|c| c.into()),
pane_name,
+ near_current_pane,
+ }),
+ crate::input::actions::Action::NewInPlacePane {
+ command,
+ pane_name,
+ near_current_pane,
+ pane_id_to_replace,
+ close_replace_pane,
+ } => ActionType::NewInPlacePane(NewInPlacePaneAction {
+ command: command.map(|c| c.into()),
+ pane_name,
+ near_current_pane,
+ pane_id_to_replace: pane_id_to_replace.and_then(|p| p.try_into().ok()),
+ close_replace_pane,
+ }),
+ crate::input::actions::Action::NewStackedPane {
+ command,
+ pane_name,
+ near_current_pane,
+ } => ActionType::NewStackedPane(NewStackedPaneAction {
+ command: command.map(|c| c.into()),
+ pane_name,
+ near_current_pane,
}),
- crate::input::actions::Action::NewInPlacePane { command, pane_name } => {
- ActionType::NewInPlacePane(NewInPlacePaneAction {
- command: command.map(|c| c.into()),
- pane_name,
- })
- },
- crate::input::actions::Action::NewStackedPane { command, pane_name } => {
- ActionType::NewStackedPane(NewStackedPaneAction {
- command: command.map(|c| c.into()),
- pane_name,
- })
- },
crate::input::actions::Action::NewBlockingPane {
placement,
pane_name,
command,
+ unblock_condition,
+ near_current_pane,
} => ActionType::NewBlockingPane(NewBlockingPaneAction {
placement: Some(placement.into()),
pane_name,
command: command.map(|c| c.into()),
+ unblock_condition: unblock_condition.map(|c| unblock_condition_to_proto_i32(c)),
+ near_current_pane,
}),
crate::input::actions::Action::TogglePaneEmbedOrFloating => {
ActionType::TogglePaneEmbedOrFloating(TogglePaneEmbedOrFloatingAction {})
@@ -938,6 +959,8 @@ impl From<crate::input::actions::Action>
tab_name,
should_change_focus_to_new_tab,
cwd,
+ initial_panes,
+ first_pane_unblock_condition,
} => ActionType::NewTab(NewTabAction {
tiled_layout: tiled_layout.map(|l| l.into()),
floating_layouts: floating_layouts.into_iter().map(|l| l.into()).collect(),
@@ -950,6 +973,11 @@ impl From<crate::input::actions::Action>
tab_name,
should_change_focus_to_new_tab,
cwd: cwd.map(|p| p.to_string_lossy().to_string()),
+ initial_panes: initial_panes
+ .map(|panes| panes.into_iter().map(|p| p.into()).collect())
+ .unwrap_or_default(),
+ first_pane_unblock_condition: first_pane_unblock_condition
+ .map(|c| unblock_condition_to_proto_i32(c)),
}),
crate::input::actions::Action::NoOp => ActionType::NoOp(NoOpAction {}),
crate::input::actions::Action::GoToNextTab => {
@@ -979,8 +1007,12 @@ impl From<crate::input::actions::Action>
direction: direction_to_proto_i32(direction),
})
},
- crate::input::actions::Action::Run { command } => ActionType::Run(RunAction {
+ crate::input::actions::Action::Run {
+ command,
+ near_current_pane,
+ } => ActionType::Run(RunAction {
command: Some(command.into()),
+ near_current_pane,
}),
crate::input::actions::Action::Detach => ActionType::Detach(DetachAction {}),
crate::input::actions::Action::SwitchSession {
@@ -1112,16 +1144,20 @@ impl From<crate::input::actions::Action>
crate::input::actions::Action::FocusTerminalPaneWithId {
pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
} => ActionType::FocusTerminalPaneWithId(FocusTerminalPaneWithIdAction {
pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
}),
crate::input::actions::Action::FocusPluginPaneWithId {
pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
} => ActionType::FocusPluginPaneWithId(FocusPluginPaneWithIdAction {
pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
}),
crate::input::actions::Action::RenameTerminalPane { pane_id, name } => {
ActionType::RenameTerminalPane(RenameTerminalPaneAction {
@@ -1380,6 +1416,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.coordinates
.map(|c| c.try_into())
.transpose()?,
+ near_current_pane: edit_file_action.near_current_pane,
}),
ActionType::NewFloatingPane(new_floating_action) => {
Ok(crate::input::actions::Action::NewFloatingPane {
@@ -1392,6 +1429,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.coordinates
.map(|c| c.try_into())
.transpose()?,
+ near_current_pane: new_floating_action.near_current_pane,
})
},
ActionType::NewTiledPane(new_tiled_action) => {
@@ -1402,6 +1440,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.transpose()?,
command: new_tiled_action.command.map(|c| c.try_into()).transpose()?,
pane_name: new_tiled_action.pane_name,
+ near_current_pane: new_tiled_action.near_current_pane,
})
},
ActionType::NewInPlacePane(new_in_place_action) => {
@@ -1411,6 +1450,11 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.map(|c| c.try_into())
.transpose()?,
pane_name: new_in_place_action.pane_name,
+ near_current_pane: new_in_place_action.near_current_pane,
+ pane_id_to_replace: new_in_place_action
+ .pane_id_to_replace
+ .and_then(|p| p.try_into().ok()),
+ close_replace_pane: new_in_place_action.close_replace_pane,
})
},
ActionType::NewStackedPane(new_stacked_action) => {
@@ -1420,6 +1464,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.map(|c| c.try_into())
.transpose()?,
pane_name: new_stacked_action.pane_name,
+ near_current_pane: new_stacked_action.near_current_pane,
})
},
ActionType::NewBlockingPane(new_blocking_action) => {
@@ -1433,6 +1478,11 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.command
.map(|c| c.try_into())
.transpose()?,
+ unblock_condition: new_blocking_action
+ .unblock_condition
+ .map(|c| proto_i32_to_unblock_condition(c))
+ .transpose()?,
+ near_current_pane: new_blocking_action.near_current_pane,
})
},
ActionType::TogglePaneEmbedOrFloating(_) => {
@@ -1487,6 +1537,22 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
tab_name: new_tab_action.tab_name,
should_change_focus_to_new_tab: new_tab_action.should_change_focus_to_new_tab,
cwd: new_tab_action.cwd.map(PathBuf::from),
+ initial_panes: if new_tab_action.initial_panes.is_empty() {
+ None
+ } else {
+ Some(
+ new_tab_action
+ .initial_panes
+ .into_iter()
+ .map(|p| p.try_into())
+ .collect::<Result<Vec<_>>>()?,
+ )
+ },
+
+ first_pane_unblock_condition: new_tab_action
+ .first_pane_unblock_condition
+ .map(|c| proto_i32_to_unblock_condition(c))
+ .transpose()?,
}),
ActionType::NoOp(_) => Ok(crate::input::actions::Action::NoOp),
ActionType::GoToNextTab(_) => Ok(crate::input::actions::Action::GoToNextTab),
@@ -1516,6 +1582,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
.command
.ok_or_else(|| anyhow!("Run missing command"))?
.try_into()?,
+ near_current_pane: run_action.near_current_pane,
}),
ActionType::Detach(_) => Ok(crate::input::actions::Action::Detach),
ActionType::SwitchSession(switch_session_action) => {
@@ -1661,12 +1728,14 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
Ok(crate::input::actions::Action::FocusTerminalPaneWithId {
pane_id: focus_pane_action.pane_id,
should_float_if_hidden: focus_pane_action.should_float_if_hidden,
+ should_be_in_place_if_hidden: focus_pane_action.should_be_in_place_if_hidden,
})
},
ActionType::FocusPluginPaneWithId(focus_pane_action) => {
Ok(crate::input::actions::Action::FocusPluginPaneWithId {
pane_id: focus_pane_action.pane_id,
should_float_if_hidden: focus_pane_action.should_float_if_hidden,
+ should_be_in_place_if_hidden: focus_pane_action.should_be_in_place_if_hidden,
})
},
ActionType::RenameTerminalPane(rename_pane_action) => {
@@ -2037,6 +2106,15 @@ fn search_option_to_proto_i32(option: crate::input::actions::SearchOption) -> i3
}
}
+fn unblock_condition_to_proto_i32(condition: crate::data::UnblockCondition) -> i32 {
+ use crate::client_server_contract::client_server_contract::UnblockCondition as ProtoUnblockCondition;
+ match condition {
+ crate::data::UnblockCondition::OnExitSuccess => ProtoUnblockCondition::OnExitSuccess as i32,
+ crate::data::UnblockCondition::OnExitFailure => ProtoUnblockCondition::OnExitFailure as i32,
+ crate::data::UnblockCondition::OnAnyExit => ProtoUnblockCondition::OnAnyExit as i32,
+ }
+}
+
// Reverse helper functions for Action conversion
fn proto_i32_to_resize(resize: i32) -> Result<crate::data::Resize> {
@@ -2103,6 +2181,26 @@ fn proto_i32_to_search_option(option: i32) -> Result<crate::input::actions::Sear
}
}
+fn proto_i32_to_unblock_condition(condition: i32) -> Result<crate::data::UnblockCondition> {
+ use crate::client_server_contract::client_server_contract::UnblockCondition as ProtoUnblockCondition;
+ let proto_condition = match condition {
+ x if x == ProtoUnblockCondition::OnExitSuccess as i32 => {
+ ProtoUnblockCondition::OnExitSuccess
+ },
+ x if x == ProtoUnblockCondition::OnExitFailure as i32 => {
+ ProtoUnblockCondition::OnExitFailure
+ },
+ x if x == ProtoUnblockCondition::OnAnyExit as i32 => ProtoUnblockCondition::OnAnyExit,
+ _ => return Err(anyhow!("Invalid UnblockCondition: {}", condition)),
+ };
+ match proto_condition {
+ ProtoUnblockCondition::OnExitSuccess => Ok(crate::data::UnblockCondition::OnExitSuccess),
+ ProtoUnblockCondition::OnExitFailure => Ok(crate::data::UnblockCondition::OnExitFailure),
+ ProtoUnblockCondition::OnAnyExit => Ok(crate::data::UnblockCondition::OnAnyExit),
+ ProtoUnblockCondition::Unspecified => Err(anyhow!("Unspecified unblock condition")),
+ }
+}
+
// Position conversion
impl From<crate::position::Position>
for crate::client_server_contract::client_server_contract::Position
@@ -2722,6 +2820,47 @@ impl From<crate::input::layout::RunPluginOrAlias>
}
}
+// CommandOrPlugin conversion
+impl From<crate::data::CommandOrPlugin>
+ for crate::client_server_contract::client_server_contract::CommandOrPlugin
+{
+ fn from(cmd_or_plugin: crate::data::CommandOrPlugin) -> Self {
+ use crate::client_server_contract::client_server_contract::command_or_plugin::CommandOrPluginType;
+ match cmd_or_plugin {
+ crate::data::CommandOrPlugin::Command(cmd) => Self {
+ command_or_plugin_type: Some(CommandOrPluginType::Command(cmd.into())),
+ },
+ crate::data::CommandOrPlugin::Plugin(plugin) => Self {
+ command_or_plugin_type: Some(CommandOrPluginType::Plugin(plugin.into())),
+ },
+ }
+ }
+}
+
+impl TryFrom<crate::client_server_contract::client_server_contract::CommandOrPlugin>
+ for crate::data::CommandOrPlugin
+{
+ type Error = anyhow::Error;
+
+ fn try_from(
+ proto: crate::client_server_contract::client_server_contract::CommandOrPlugin,
+ ) -> Result<Self> {
+ use crate::client_server_contract::client_server_contract::command_or_plugin::CommandOrPluginType;
+
+ let cmd_or_plugin_type = proto
+ .command_or_plugin_type
+ .ok_or_else(|| anyhow!("CommandOrPlugin missing command_or_plugin_type"))?;
+ match cmd_or_plugin_type {
+ CommandOrPluginType::Command(cmd) => {
+ Ok(crate::data::CommandOrPlugin::Command(cmd.try_into()?))
+ },
+ CommandOrPluginType::Plugin(plugin) => {
+ Ok(crate::data::CommandOrPlugin::Plugin(plugin.try_into()?))
+ },
+ }
+ }
+}
+
// Run reverse conversion
impl TryFrom<crate::client_server_contract::client_server_contract::Run>
for crate::input::layout::Run
diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
index 2ec513da..61981f38 100644
--- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs
+++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
@@ -1,7 +1,8 @@
use super::test_framework::*;
use crate::data::{
- BareKey, ConnectToSession, Direction, FloatingPaneCoordinates, InputMode, KeyModifier,
- KeyWithModifier, LayoutInfo, OriginatingPlugin, PaneId, PluginTag, Resize, WebSharing,
+ BareKey, CommandOrPlugin, ConnectToSession, Direction, FloatingPaneCoordinates, InputMode,
+ KeyModifier, KeyWithModifier, LayoutInfo, OriginatingPlugin, PaneId, PluginTag, Resize,
+ WebSharing,
};
use crate::input::actions::{Action, SearchDirection, SearchOption};
use crate::input::cli_assets::CliAssets;
@@ -1019,6 +1020,7 @@ fn test_client_messages() {
in_place: false,
start_suppressed: false,
coordinates: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1041,6 +1043,7 @@ fn test_client_messages() {
in_place: false,
start_suppressed: false,
coordinates: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1063,6 +1066,7 @@ fn test_client_messages() {
in_place: false,
start_suppressed: false,
coordinates: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1085,6 +1089,7 @@ fn test_client_messages() {
in_place: true,
start_suppressed: true,
coordinates: FloatingPaneCoordinates::new(None, None, None, None, None),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1113,6 +1118,7 @@ fn test_client_messages() {
None,
None
),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1141,6 +1147,7 @@ fn test_client_messages() {
None,
None
),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1169,6 +1176,7 @@ fn test_client_messages() {
Some("20".to_owned()),
Some(true)
),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1191,6 +1199,7 @@ fn test_client_messages() {
in_place: true,
start_suppressed: true,
coordinates: FloatingPaneCoordinates::new(None, None, None, None, Some(false)),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1201,6 +1210,7 @@ fn test_client_messages() {
command: None,
pane_name: None,
coordinates: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1225,7 +1235,8 @@ fn test_client_messages() {
None,
None,
None
- )
+ ),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1254,7 +1265,8 @@ fn test_client_messages() {
None,
None,
None
- )
+ ),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1265,6 +1277,7 @@ fn test_client_messages() {
command: None,
direction: None,
pane_name: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1288,6 +1301,7 @@ fn test_client_messages() {
}),
direction: Some(Direction::Right),
pane_name: Some("my_pane_name".to_owned()),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1310,6 +1324,9 @@ fn test_client_messages() {
use_terminal_title: false,
}),
pane_name: Some("my_pane_name".to_owned()),
+ near_current_pane: false,
+ pane_id_to_replace: None,
+ close_replace_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1319,6 +1336,9 @@ fn test_client_messages() {
action: Action::NewInPlacePane {
command: None,
pane_name: None,
+ near_current_pane: false,
+ pane_id_to_replace: None,
+ close_replace_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1341,6 +1361,7 @@ fn test_client_messages() {
use_terminal_title: false,
}),
pane_name: Some("my_pane_name".to_owned()),
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1350,6 +1371,7 @@ fn test_client_messages() {
action: Action::NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1396,6 +1418,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1410,6 +1434,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1427,6 +1453,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1444,6 +1472,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1462,6 +1492,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1492,6 +1524,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1509,6 +1543,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1526,6 +1562,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1545,6 +1583,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1565,6 +1605,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1585,6 +1627,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1605,6 +1649,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1625,6 +1671,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1645,6 +1693,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1665,6 +1715,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1685,6 +1737,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1705,6 +1759,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1722,6 +1778,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1745,6 +1803,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1770,6 +1830,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1799,6 +1861,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1816,6 +1880,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1837,6 +1903,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1854,6 +1922,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1891,6 +1961,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1913,6 +1985,8 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1927,6 +2001,8 @@ fn test_client_messages() {
tab_name: Some("tab_name".to_owned()),
should_change_focus_to_new_tab: false,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -1941,6 +2017,36 @@ fn test_client_messages() {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: Some(PathBuf::from("relative/path/to/cwd")),
+ initial_panes: None,
+ first_pane_unblock_condition: None,
+ },
+ terminal_id: Some(1),
+ client_id: Some(100),
+ is_cli_client: true,
+ });
+ test_client_roundtrip!(ClientToServerMsg::Action {
+ action: Action::NewTab {
+ tiled_layout: None,
+ floating_layouts: vec![],
+ swap_tiled_layouts: None,
+ swap_floating_layouts: None,
+ tab_name: None,
+ should_change_focus_to_new_tab: true,
+ cwd: None,
+ initial_panes: Some(vec![
+ CommandOrPlugin::Command(RunCommandAction {
+ command: PathBuf::from("/path/to/command"),
+ args: vec![],
+ cwd: None,
+ direction: None,
+ hold_on_close: false,
+ hold_on_start: false,
+ originating_plugin: None,
+ use_terminal_title: false,
+ }),
+ CommandOrPlugin::Plugin(RunPluginOrAlias::RunPlugin(RunPlugin::default())),
+ ]),
+ first_pane_unblock_condition: None,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -2035,6 +2141,7 @@ fn test_client_messages() {
originating_plugin: None,
use_terminal_title: false,
},
+ near_current_pane: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -2328,6 +2435,7 @@ fn test_client_messages() {
action: Action::FocusTerminalPaneWithId {
pane_id: 12,
should_float_if_hidden: false,
+ should_be_in_place_if_hidden: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -2337,6 +2445,7 @@ fn test_client_messages() {
action: Action::FocusTerminalPaneWithId {
pane_id: 12,
should_float_if_hidden: true,
+ should_be_in_place_if_hidden: false,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -2346,6 +2455,7 @@ fn test_client_messages() {
action: Action::FocusPluginPaneWithId {
pane_id: 12,
should_float_if_hidden: false,
+ should_be_in_place_if_hidden: true,
},
terminal_id: Some(1),
client_id: Some(100),
@@ -2355,6 +2465,7 @@ fn test_client_messages() {
action: Action::FocusPluginPaneWithId {
pane_id: 12,
should_float_if_hidden: true,
+ should_be_in_place_if_hidden: false,
},
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 789c3d96..5ca02d86 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -1,8 +1,8 @@
mod kdl_layout_parser;
use crate::data::{
BareKey, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo,
- MultiplayerColors, Palette, PaletteColor, PaneInfo, PaneManifest, PermissionType, Resize,
- SessionInfo, StyleDeclaration, Styling, TabInfo, WebSharing, DEFAULT_STYLES,
+ MultiplayerColors, Palette, PaletteColor, PaneId, PaneInfo, PaneManifest, PermissionType,
+ Resize, SessionInfo, StyleDeclaration, Styling, TabInfo, WebSharing, DEFAULT_STYLES,
};
use crate::envs::EnvironmentVariables;
use crate::home::{find_default_config_dir, get_layout_dir};
@@ -551,6 +551,7 @@ impl Action {
return Ok(Action::NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
});
} else {
let direction = Direction::from_str(string.as_str()).map_err(|_| {
@@ -742,6 +743,8 @@ impl Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd,
+ initial_panes: _,
+ first_pane_unblock_condition: _,
} => {
let mut node = KdlNode::new("NewTab");
let mut children = KdlDocument::new();
@@ -800,6 +803,7 @@ impl Action {
direction,
command: run_command_action,
pane_name: name,
+ near_current_pane: false,
} => {
let mut node = KdlNode::new("Run");
let mut node_children = KdlDocument::new();
@@ -849,6 +853,7 @@ impl Action {
command: run_command_action,
pane_name: name,
coordinates: floating_pane_coordinates,
+ near_current_pane: false,
} => {
let mut node = KdlNode::new("Run");
let mut node_children = KdlDocument::new();
@@ -939,6 +944,9 @@ impl Action {
Action::NewInPlacePane {
command: run_command_action,
pane_name: name,
+ near_current_pane: false,
+ pane_id_to_replace: None,
+ close_replace_pane: false,
} => {
let mut node = KdlNode::new("Run");
let mut node_children = KdlDocument::new();
@@ -979,6 +987,7 @@ impl Action {
Action::NewStackedPane {
command: run_command_action,
pane_name: name,
+ near_current_pane: _,
} => match run_command_action {
Some(run_command_action) => {
let mut node = KdlNode::new("Run");
@@ -1623,6 +1632,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
});
}
@@ -1692,6 +1703,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
})
} else {
let (layout, floating_panes_layout) = layout.new_tab();
@@ -1705,6 +1718,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
tab_name: name,
should_change_focus_to_new_tab,
cwd,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
})
}
},
@@ -1785,22 +1800,28 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
command: Some(run_command_action),
pane_name: name,
coordinates: FloatingPaneCoordinates::new(x, y, width, height, pinned),
+ near_current_pane: false,
})
} else if in_place {
Ok(Action::NewInPlacePane {
command: Some(run_command_action),
pane_name: name,
+ near_current_pane: false,
+ pane_id_to_replace: None,
+ close_replace_pane: false,
})
} else if stacked {
Ok(Action::NewStackedPane {
command: Some(run_command_action),
pane_name: name,
+ near_current_pane: false,
})
} else {
Ok(Action::NewTiledPane {
direction,
command: Some(run_command_action),
pane_name: name,
+ near_current_pane: false,
})
}
},
@@ -5073,6 +5094,50 @@ impl SessionInfo {
}
}
}
+ let mut pane_history = BTreeMap::new();
+ if let Some(kdl_pane_history) = kdl_document.get("pane_history").and_then(|p| p.children())
+ {
+ for client_node in kdl_pane_history.nodes() {
+ if let Some(client_id) = client_node.children().and_then(|c| {
+ c.get("id")
+ .and_then(|c| c.entries().iter().next().and_then(|e| e.value().as_i64()))
+ }) {
+ let mut history = vec![];
+ if let Some(history_node) =
+ client_node.children().and_then(|c| c.get("history"))
+ {
+ if let Some(history_children) = history_node.children() {
+ for pane_id_node in history_children.nodes() {
+ if pane_id_node.name().value() == "pane_id" {
+ let pane_type = pane_id_node
+ .entries()
+ .iter()
+ .find(|e| e.name().map(|n| n.value()) == Some("type"))
+ .and_then(|e| e.value().as_string());
+ let id = pane_id_node
+ .entries()
+ .iter()
+ .find(|e| e.name().is_none())
+ .and_then(|e| e.value().as_i64())
+ .map(|i| i as u32);
+ if let (Some(pane_type), Some(id)) = (pane_type, id) {
+ let pane_id = match pane_type {
+ "terminal" => Some(PaneId::Terminal(id)),
+ "plugin" => Some(PaneId::Plugin(id)),
+ _ => None,
+ };
+ if let Some(pane_id) = pane_id {
+ history.push(pane_id);
+ }
+ }
+ }
+ }
+ }
+ }
+ pane_history.insert(client_id as u16, history);
+ }
+ }
+ }
Ok(SessionInfo {
name,
tabs,
@@ -5084,6 +5149,7 @@ impl SessionInfo {
web_clients_allowed,
plugins: Default::default(), // we do not serialize plugin information
tab_history,
+ pane_history,
})
}
pub fn to_string(&self) -> String {
@@ -5148,6 +5214,35 @@ impl SessionInfo {
}
tab_history.set_children(tab_history_children);
+ let mut pane_history = KdlNode::new("pane_history");
+ let mut pane_history_children = KdlDocument::new();
+ for (client_id, client_pane_history) in &self.pane_history {
+ let mut client_document = KdlDocument::new();
+ let mut client_node = KdlNode::new("client");
+ let mut id = KdlNode::new("id");
+ id.push(*client_id as i64);
+ client_document.nodes_mut().push(id);
+ let mut history = KdlNode::new("history");
+ for pane_id in client_pane_history {
+ let mut pane_id_node = KdlNode::new("pane_id");
+ match pane_id {
+ PaneId::Terminal(id) => {
+ pane_id_node.push(KdlEntry::new_prop("type", "terminal"));
+ pane_id_node.push(*id as i64);
+ },
+ PaneId::Plugin(id) => {
+ pane_id_node.push(KdlEntry::new_prop("type", "plugin"));
+ pane_id_node.push(*id as i64);
+ },
+ }
+ history.ensure_children().nodes_mut().push(pane_id_node);
+ }
+ client_document.nodes_mut().push(history);
+ client_node.set_children(client_document);
+ pane_history_children.nodes_mut().push(client_node);
+ }
+ pane_history.set_children(pane_history_children);
+
kdl_document.nodes_mut().push(name);
kdl_document.nodes_mut().push(tabs);
kdl_document.nodes_mut().push(panes);
@@ -5156,6 +5251,7 @@ impl SessionInfo {
kdl_document.nodes_mut().push(web_client_count);
kdl_document.nodes_mut().push(available_layouts);
kdl_document.nodes_mut().push(tab_history);
+ kdl_document.nodes_mut().push(pane_history);
kdl_document.fmt();
kdl_document.to_string()
}
@@ -5714,6 +5810,7 @@ fn serialize_and_deserialize_session_info_with_data() {
web_client_count: 2,
web_clients_allowed: true,
tab_history: Default::default(),
+ pane_history: Default::default(),
};
let serialized = session_info.to_string();
let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
index 11f6e53d..6353ee0e 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
@@ -122,6 +122,12 @@ keybinds clear-defaults=true {
}
SwitchToMode "normal"
}
+ bind "q" {
+ LaunchPlugin "zellij:sequence" {
+ floating true
+ }
+ SwitchToMode "normal"
+ }
bind "s" {
LaunchOrFocusPlugin "zellij:share" {
floating true
@@ -255,6 +261,7 @@ plugins {
cwd "/"
}
plugin-manager location="zellij:plugin-manager"
+ sequence location="zellij:sequence"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
index ddf36a40..e562e030 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
@@ -122,6 +122,12 @@ keybinds clear-defaults=true {
}
SwitchToMode "normal"
}
+ bind "q" {
+ LaunchPlugin "zellij:sequence" {
+ floating true
+ }
+ SwitchToMode "normal"
+ }
bind "s" {
LaunchOrFocusPlugin "zellij:share" {
floating true
@@ -258,6 +264,7 @@ plugins {
cwd "/"
}
plugin-manager location="zellij:plugin-manager"
+ sequence location="zellij:sequence"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap
index 088504af..e43fcf3b 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap
@@ -14,3 +14,5 @@ available_layouts {
}
tab_history {
}
+pane_history {
+}
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap
index cd4ff687..44b2e8bf 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap
@@ -98,3 +98,5 @@ available_layouts {
}
tab_history {
}
+pane_history {
+}
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto
index 2772e725..c29884af 100644
--- a/zellij-utils/src/plugin_api/action.proto
+++ b/zellij-utils/src/plugin_api/action.proto
@@ -5,6 +5,163 @@ import "resize.proto";
package api.action;
+// Layout and related types
+
+enum SplitDirection {
+ SPLIT_DIRECTION_UNSPECIFIED = 0;
+ SPLIT_DIRECTION_HORIZONTAL = 1;
+ SPLIT_DIRECTION_VERTICAL = 2;
+}
+
+message PercentOrFixed {
+ oneof size_type {
+ uint32 percent = 1;
+ uint32 fixed = 2;
+ }
+}
+
+enum LayoutConstraint {
+ LAYOUT_CONSTRAINT_UNSPECIFIED = 0;
+ LAYOUT_CONSTRAINT_MAX_PANES = 1;
+ LAYOUT_CONSTRAINT_MIN_PANES = 2;
+ LAYOUT_CONSTRAINT_EXACT_PANES = 3;
+ LAYOUT_CONSTRAINT_NO_CONSTRAINT = 4;
+}
+
+message LayoutConstraintWithValue {
+ LayoutConstraint constraint_type = 1;
+ optional uint32 value = 2;
+}
+
+message PluginUserConfiguration {
+ map<string, string> configuration = 1;
+}
+
+enum RunPluginLocation {
+ RUN_PLUGIN_LOCATION_UNSPECIFIED = 0;
+ RUN_PLUGIN_LOCATION_FILE = 1;
+ RUN_PLUGIN_LOCATION_ZELLIJ = 2;
+ RUN_PLUGIN_LOCATION_REMOTE = 3;
+}
+
+message RunPluginLocationData {
+ RunPluginLocation location_type = 1;
+ oneof location_data {
+ string file_path = 2;
+ PluginTag zellij_tag = 3;
+ string remote_url = 4;
+ }
+}
+
+message PluginTag {
+ string tag = 1;
+}
+
+message RunPlugin {
+ bool _allow_exec_host_cmd = 1;
+ RunPluginLocationData location = 2;
+ PluginUserConfiguration configuration = 3;
+ optional string initial_cwd = 4;
+}
+
+message PluginAlias {
+ string name = 1;
+ optional PluginUserConfiguration configuration = 2;
+ optional string initial_cwd = 3;
+ optional RunPlugin run_plugin = 4;
+}
+
+message RunPluginOrAlias {
+ oneof plugin_type {
+ RunPlugin plugin = 1;
+ PluginAlias alias = 2;
+ }
+}
+
+message RunEditFileAction {
+ string file_path = 1;
+ optional uint32 line_number = 2;
+ optional string cwd = 3;
+}
+
+message PaneRun {
+ oneof run_type {
+ RunCommandAction command = 1;
+ RunPluginOrAlias plugin = 2;
+ RunEditFileAction edit_file = 3;
+ string cwd = 4;
+ }
+}
+
+message TiledPaneLayout {
+ SplitDirection children_split_direction = 1;
+ optional string name = 2;
+ repeated TiledPaneLayout children = 3;
+ optional SplitSize split_size = 4;
+ optional PaneRun run = 5;
+ bool borderless = 6;
+ optional string focus = 7;
+ optional bool exclude_from_sync = 8;
+ bool children_are_stacked = 9;
+ optional uint32 external_children_index = 10;
+ bool is_expanded_in_stack = 11;
+ bool hide_floating_panes = 12;
+ optional string pane_initial_contents = 13;
+}
+
+message FloatingPaneLayout {
+ optional string name = 1;
+ optional PercentOrFixed height = 2;
+ optional PercentOrFixed width = 3;
+ optional PercentOrFixed x = 4;
+ optional PercentOrFixed y = 5;
+ optional bool pinned = 6;
+ optional PaneRun run = 7;
+ optional bool focus = 8;
+ bool already_running = 9;
+ optional string pane_initial_contents = 10;
+ optional uint32 logical_position = 11;
+}
+
+message SwapTiledLayout {
+ repeated LayoutConstraintTiledPair constraint_map = 1;
+ optional string name = 2;
+}
+
+message SwapFloatingLayout {
+ repeated LayoutConstraintFloatingPair constraint_map = 1;
+ optional string name = 2;
+}
+
+message LayoutConstraintTiledPair {
+ LayoutConstraintWithValue constraint = 1;
+ TiledPaneLayout layout = 2;
+}
+
+message LayoutConstraintFloatingPair {
+ LayoutConstraintWithValue constraint = 1;
+ repeated FloatingPaneLayout layouts = 2;
+}
+
+message CommandOrPlugin {
+ oneof command_or_plugin_type {
+ RunCommandAction command = 1;
+ RunPluginOrAlias plugin = 2;
+ }
+}
+
+message NewTabPayload {
+ optional TiledPaneLayout tiled_layout = 1;
+ repeated FloatingPaneLayout floating_layouts = 2;
+ repeated SwapTiledLayout swap_tiled_layouts = 3;
+ repeated SwapFloatingLayout swap_floating_layouts = 4;
+ optional string tab_name = 5;
+ bool should_change_focus_to_new_tab = 6;
+ optional string cwd = 7;
+ repeated CommandOrPlugin initial_panes = 8;
+ optional UnblockCondition first_pane_unblock_condition = 9;
+}
+
message Action {
ActionName name = 1;
oneof optional_payload {
@@ -53,6 +210,9 @@ message Action {
CliPipePayload message_payload = 47;
MoveTabDirection move_tab_payload = 48;
MouseEventPayload mouse_event_payload = 49;
+ NewBlockingPanePayload new_blocking_pane_payload = 50;
+ NewTabPayload new_tab_payload = 51;
+ NewInPlacePanePayload new_in_place_pane_payload = 52;
}
}
@@ -71,6 +231,7 @@ message IdAndName {
message PaneIdAndShouldFloat {
uint32 pane_id = 1;
bool should_float = 2;
+ bool should_be_in_place = 3;
}
message NewPluginPanePayload {
@@ -111,11 +272,13 @@ message GoToTabNamePayload {
message NewFloatingPanePayload {
optional RunCommandAction command = 1;
+ bool near_current_pane = 2;
}
message NewTiledPanePayload {
optional RunCommandAction command = 1;
optional resize.ResizeDirection direction = 2;
+ bool near_current_pane = 3;
}
message MovePanePayload {
@@ -128,6 +291,7 @@ message EditFilePayload {
optional string cwd = 3;
optional resize.ResizeDirection direction = 4;
bool should_float = 5;
+ bool near_current_pane = 6;
}
message ScrollAtPayload {
@@ -139,12 +303,30 @@ message NewPanePayload {
optional string pane_name = 2;
}
+message NewBlockingPanePayload {
+ NewPanePlacement placement = 1;
+ optional string pane_name = 2;
+ optional RunCommandAction command = 3;
+ optional UnblockCondition unblock_condition = 4;
+ bool near_current_pane = 5;
+}
+
+message NewInPlacePanePayload {
+ optional RunCommandAction command = 1;
+ optional string pane_name = 2;
+ bool near_current_pane = 3;
+ optional PaneId pane_id_to_replace = 4;
+ bool close_replace_pane = 5;
+}
+
message SwitchToModePayload {
input_mode.InputMode input_mode = 1;
}
message WritePayload {
bytes bytes_to_write = 1;
+ optional KeyWithModifier key_with_modifier = 2;
+ bool is_kitty_keyboard_protocol = 3;
}
message WriteCharsPayload {
@@ -245,6 +427,8 @@ enum ActionName {
ToggleGroupMarking = 88;
NewStackedPane = 89;
SwitchSession = 90;
+ NewBlockingPane = 91;
+ NewInPlacePane = 92;
}
message Position {
@@ -252,6 +436,66 @@ message Position {
int64 column = 2;
}
+// SplitSize represents a dimension that can be either a percentage or fixed size
+message SplitSize {
+ oneof split_size_variant {
+ uint32 percent = 1; // 1 to 100
+ uint32 fixed = 2; // absolute number of columns or rows
+ }
+}
+
+// PaneId identifies either a terminal or plugin pane
+message PaneId {
+ oneof pane_id_variant {
+ uint32 terminal = 1;
+ uint32 plugin = 2;
+ }
+}
+
+// FloatingPaneCoordinates specifies the position and size of a floating pane
+message FloatingPaneCoordinates {
+ optional SplitSize x = 1;
+ optional SplitSize y = 2;
+ optional SplitSize width = 3;
+ optional SplitSize height = 4;
+ optional bool pinned = 5;
+}
+
+// UnblockCondition specifies when a blocking pane should unblock
+enum UnblockCondition {
+ UNBLOCK_ON_EXIT_SUCCESS = 0;
+ UNBLOCK_ON_EXIT_FAILURE = 1;
+ UNBLOCK_ON_ANY_EXIT = 2;
+}
+
+// NewPanePlacement specifies where a new pane should be placed
+message NewPanePlacement {
+ oneof placement_variant {
+ bool no_preference = 1;
+ TiledPlacement tiled = 2;
+ FloatingPlacement floating = 3;
+ InPlaceConfig in_place = 4;
+ StackedPlacement stacked = 5;
+ }
+}
+
+message TiledPlacement {
+ optional resize.ResizeDirection direction = 1;
+}
+
+message FloatingPlacement {
+ optional FloatingPaneCoordinates coordinates = 1;
+}
+
+message InPlaceConfig {
+ optional PaneId pane_id_to_replace = 1;
+ bool close_replaced_pane = 2;
+}
+
+message StackedPlacement {
+ optional PaneId pane_id = 1;
+}
+
message MouseEventPayload {
uint32 event_type = 1;
bool left = 2;
@@ -284,3 +528,54 @@ message NameAndValue {
string name = 1;
string value = 2;
}
+
+message KeyWithModifier {
+ BareKey bare_key = 1;
+ repeated KeyModifier key_modifiers = 2;
+ optional string character = 3; // Only set when bare_key is CHAR
+}
+
+enum BareKey {
+ BARE_KEY_UNSPECIFIED = 0;
+ BARE_KEY_PAGE_DOWN = 1;
+ BARE_KEY_PAGE_UP = 2;
+ BARE_KEY_LEFT = 3;
+ BARE_KEY_DOWN = 4;
+ BARE_KEY_UP = 5;
+ BARE_KEY_RIGHT = 6;
+ BARE_KEY_HOME = 7;
+ BARE_KEY_END = 8;
+ BARE_KEY_BACKSPACE = 9;
+ BARE_KEY_DELETE = 10;
+ BARE_KEY_INSERT = 11;
+ BARE_KEY_F1 = 12;
+ BARE_KEY_F2 = 13;
+ BARE_KEY_F3 = 14;
+ BARE_KEY_F4 = 15;
+ BARE_KEY_F5 = 16;
+ BARE_KEY_F6 = 17;
+ BARE_KEY_F7 = 18;
+ BARE_KEY_F8 = 19;
+ BARE_KEY_F9 = 20;
+ BARE_KEY_F10 = 21;
+ BARE_KEY_F11 = 22;
+ BARE_KEY_F12 = 23;
+ BARE_KEY_CHAR = 24;
+ BARE_KEY_TAB = 25;
+ BARE_KEY_ESC = 26;
+ BARE_KEY_ENTER = 27;
+ BARE_KEY_CAPS_LOCK = 28;
+ BARE_KEY_SCROLL_LOCK = 29;
+ BARE_KEY_NUM_LOCK = 30;
+ BARE_KEY_PRINT_SCREEN = 31;
+ BARE_KEY_PAUSE = 32;
+ BARE_KEY_MENU = 33;
+}
+
+enum KeyModifier {
+ KEY_MODIFIER_UNSPECIFIED = 0;
+ KEY_MODIFIER_CTRL = 1;
+ KEY_MODIFIER_ALT = 2;
+ KEY_MODIFIER_SHIFT = 3;
+ KEY_MODIFIER_SUPER = 4;
+}
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs
index c442144a..f8de7b76 100644
--- a/zellij-utils/src/plugin_api/action.rs
+++ b/zellij-utils/src/plugin_api/action.rs
@@ -1,26 +1,88 @@
pub use super::generated_api::api::{
action::{
- action::OptionalPayload, Action as ProtobufAction, ActionName as ProtobufActionName,
- DumpScreenPayload, EditFilePayload, GoToTabNamePayload, IdAndName,
- LaunchOrFocusPluginPayload, MouseEventPayload as ProtobufMouseEventPayload,
- MovePanePayload, MoveTabDirection as ProtobufMoveTabDirection,
- NameAndValue as ProtobufNameAndValue, NewFloatingPanePayload, NewPanePayload,
- NewPluginPanePayload, NewTiledPanePayload, PaneIdAndShouldFloat,
- PluginConfiguration as ProtobufPluginConfiguration, Position as ProtobufPosition,
- RunCommandAction as ProtobufRunCommandAction, ScrollAtPayload,
- SearchDirection as ProtobufSearchDirection, SearchOption as ProtobufSearchOption,
- SwitchToModePayload, WriteCharsPayload, WritePayload,
+ action::OptionalPayload,
+ command_or_plugin::CommandOrPluginType,
+ pane_run::RunType,
+ run_plugin_location_data::LocationData,
+ run_plugin_or_alias::PluginType,
+ Action as ProtobufAction,
+ ActionName as ProtobufActionName,
+ BareKey as ProtobufBareKey,
+ // New layout-related types
+ CommandOrPlugin as ProtobufCommandOrPlugin,
+ DumpScreenPayload,
+ EditFilePayload,
+ FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates,
+ FloatingPaneLayout as ProtobufFloatingPaneLayout,
+ FloatingPlacement as ProtobufFloatingPlacement,
+ GoToTabNamePayload,
+ IdAndName,
+ InPlaceConfig as ProtobufInPlaceConfig,
+ KeyModifier as ProtobufKeyModifier,
+ KeyWithModifier as ProtobufKeyWithModifier,
+ LaunchOrFocusPluginPayload,
+ LayoutConstraint as ProtobufLayoutConstraint,
+ LayoutConstraintFloatingPair as ProtobufLayoutConstraintFloatingPair,
+ LayoutConstraintTiledPair as ProtobufLayoutConstraintTiledPair,
+ LayoutConstraintWithValue as ProtobufLayoutConstraintWithValue,
+ MouseEventPayload as ProtobufMouseEventPayload,
+ MovePanePayload,
+ MoveTabDirection as ProtobufMoveTabDirection,
+ NameAndValue as ProtobufNameAndValue,
+ NewBlockingPanePayload,
+ NewFloatingPanePayload,
+ NewInPlacePanePayload,
+ NewPanePayload,
+ NewPanePlacement as ProtobufNewPanePlacement,
+ NewPluginPanePayload,
+ NewTabPayload,
+ NewTiledPanePayload,
+ PaneId as ProtobufPaneId,
+ PaneIdAndShouldFloat,
+ PaneRun as ProtobufPaneRun,
+ PercentOrFixed as ProtobufPercentOrFixed,
+ PluginAlias as ProtobufPluginAlias,
+ PluginConfiguration as ProtobufPluginConfiguration,
+ PluginTag as ProtobufPluginTag,
+ PluginUserConfiguration as ProtobufPluginUserConfiguration,
+ Position as ProtobufPosition,
+ RunCommandAction as ProtobufRunCommandAction,
+ RunEditFileAction as ProtobufRunEditFileAction,
+ RunPlugin as ProtobufRunPlugin,
+ RunPluginLocation as ProtobufRunPluginLocation,
+ RunPluginLocationData as ProtobufRunPluginLocationData,
+ RunPluginOrAlias as ProtobufRunPluginOrAlias,
+ ScrollAtPayload,
+ SearchDirection as ProtobufSearchDirection,
+ SearchOption as ProtobufSearchOption,
+ SplitDirection as ProtobufSplitDirection,
+ SplitSize as ProtobufSplitSize,
+ StackedPlacement as ProtobufStackedPlacement,
+ SwapFloatingLayout as ProtobufSwapFloatingLayout,
+ SwapTiledLayout as ProtobufSwapTiledLayout,
+ SwitchToModePayload,
+ TiledPaneLayout as ProtobufTiledPaneLayout,
+ TiledPlacement as ProtobufTiledPlacement,
+ UnblockCondition as ProtobufUnblockCondition,
+ WriteCharsPayload,
+ WritePayload,
},
input_mode::InputMode as ProtobufInputMode,
resize::{Resize as ProtobufResize, ResizeDirection as ProtobufResizeDirection},
};
-use crate::data::{Direction, InputMode, ResizeStrategy};
+use crate::data::{
+ CommandOrPlugin, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier,
+ NewPanePlacement, PaneId, PluginTag, ResizeStrategy, UnblockCondition,
+};
use crate::errors::prelude::*;
use crate::input::actions::Action;
use crate::input::actions::{SearchDirection, SearchOption};
use crate::input::command::{OpenFilePayload, RunCommandAction};
+use crate::input::layout::SplitSize;
use crate::input::layout::{
- PluginUserConfiguration, RunPlugin, RunPluginLocation, RunPluginOrAlias,
+ FloatingPaneLayout, LayoutConstraint, PercentOrFixed, PluginAlias, PluginUserConfiguration,
+ Run, RunPlugin, RunPluginLocation, RunPluginOrAlias, SplitDirection, SwapFloatingLayout,
+ SwapTiledLayout, TiledPaneLayout,
};
use crate::input::mouse::{MouseEvent, MouseEventType};
use crate::position::Position;
@@ -38,11 +100,16 @@ impl TryFrom<ProtobufAction> for Action {
None => Ok(Action::Quit),
},
Some(ProtobufActionName::Write) => match protobuf_action.optional_payload {
- Some(OptionalPayload::WritePayload(write_payload)) => Ok(Action::Write {
- key_with_modifier: None,
- bytes: write_payload.bytes_to_write,
- is_kitty_keyboard_protocol: false,
- }),
+ Some(OptionalPayload::WritePayload(write_payload)) => {
+ let key_with_modifier = write_payload
+ .key_with_modifier
+ .and_then(|k| k.try_into().ok());
+ Ok(Action::Write {
+ key_with_modifier,
+ bytes: write_payload.bytes_to_write,
+ is_kitty_keyboard_protocol: write_payload.is_kitty_keyboard_protocol,
+ })
+ },
_ => Err("Wrong payload for Action::Write"),
},
Some(ProtobufActionName::WriteChars) => match protobuf_action.optional_payload {
@@ -246,6 +313,7 @@ impl TryFrom<ProtobufAction> for Action {
.direction
.and_then(|d| ProtobufResizeDirection::from_i32(d))
.and_then(|d| d.try_into().ok());
+ let near_current_pane = payload.near_current_pane;
let should_float = payload.should_float;
let should_be_in_place = false;
Ok(Action::EditFile {
@@ -255,12 +323,14 @@ impl TryFrom<ProtobufAction> for Action {
in_place: should_be_in_place,
start_suppressed: false,
coordinates: None,
+ near_current_pane,
})
},
_ => Err("Wrong payload for Action::NewPane"),
},
Some(ProtobufActionName::NewFloatingPane) => match protobuf_action.optional_payload {
Some(OptionalPayload::NewFloatingPanePayload(payload)) => {
+ let near_current_pane = payload.near_current_pane;
if let Some(payload) = payload.command {
let pane_name = payload.pane_name.clone();
let run_command_action: RunCommandAction = payload.try_into()?;
@@ -268,12 +338,14 @@ impl TryFrom<ProtobufAction> for Action {
command: Some(run_command_action),
pane_name,
coordinates: None,
+ near_current_pane,
})
} else {
Ok(Action::NewFloatingPane {
command: None,
pane_name: None,
coordinates: None,
+ near_current_pane,
})
}
},
@@ -285,6 +357,7 @@ impl TryFrom<ProtobufAction> for Action {
.direction
.and_then(|d| ProtobufResizeDirection::from_i32(d))
.and_then(|d| d.try_into().ok());
+ let near_current_pane = payload.near_current_pane;
if let Some(payload) = payload.command {
let pane_name = payload.pane_name.clone();
let run_command_action: RunCommandAction = payload.try_into()?;
@@ -292,12 +365,14 @@ impl TryFrom<ProtobufAction> for Action {
direction,
command: Some(run_command_action),
pane_name,
+ near_current_pane,
})
} else {
Ok(Action::NewTiledPane {
direction,
command: None,
pane_name: None,
+ near_current_pane,
})
}
},
@@ -331,9 +406,77 @@ impl TryFrom<ProtobufAction> for Action {
},
Some(ProtobufActionName::NewTab) => {
match protobuf_action.optional_payload {
- Some(_) => Err("NewTab should not have a payload"),
+ Some(OptionalPayload::NewTabPayload(payload)) => {
+ // New behavior: extract all fields from payload
+ let tiled_layout =
+ payload.tiled_layout.map(|l| l.try_into()).transpose()?;
+
+ let floating_layouts = payload
+ .floating_layouts
+ .into_iter()
+ .map(|l| l.try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let swap_tiled_layouts = if payload.swap_tiled_layouts.is_empty() {
+ None
+ } else {
+ Some(
+ payload
+ .swap_tiled_layouts
+ .into_iter()
+ .map(|l| l.try_into())
+ .collect::<Result<Vec<_>, _>>()?,
+ )
+ };
+
+ let swap_floating_layouts = if payload.swap_floating_layouts.is_empty() {
+ None
+ } else {
+ Some(
+ payload
+ .swap_floating_layouts
+ .into_iter()
+ .map(|l| l.try_into())
+ .collect::<Result<Vec<_>, _>>()?,
+ )
+ };
+
+ let tab_name = payload.tab_name;
+ let should_change_focus_to_new_tab = payload.should_change_focus_to_new_tab;
+ let cwd = payload.cwd.map(PathBuf::from);
+
+ let initial_panes = if payload.initial_panes.is_empty() {
+ None
+ } else {
+ Some(
+ payload
+ .initial_panes
+ .into_iter()
+ .map(|p| p.try_into())
+ .collect::<Result<Vec<_>, _>>()?,
+ )
+ };
+
+ let first_pane_unblock_condition = payload
+ .first_pane_unblock_condition
+ .and_then(|uc| ProtobufUnblockCondition::from_i32(uc))
+ .and_then(|uc| uc.try_into().ok());
+
+ Ok(Action::NewTab {
+ tiled_layout,
+ floating_layouts,
+ swap_tiled_layouts,
+ swap_floating_layouts,
+ tab_name,
+ should_change_focus_to_new_tab,
+ cwd,
+ initial_panes,
+ first_pane_unblock_condition,
+ })
+ },
None => {
- // we do not serialize the layouts of this action
+ // Backwards compatibility: accept None payload for existing plugins
+ // Return the same defaults as before
Ok(Action::NewTab {
tiled_layout: None,
floating_layouts: vec![],
@@ -342,8 +485,11 @@ impl TryFrom<ProtobufAction> for Action {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
})
},
+ _ => Err("Wrong payload for Action::NewTab"),
}
},
Some(ProtobufActionName::NoOp) => match protobuf_action.optional_payload {
@@ -405,6 +551,7 @@ impl TryFrom<ProtobufAction> for Action {
let run_command_action = run_command_action.try_into()?;
Ok(Action::Run {
command: run_command_action,
+ near_current_pane: false,
})
},
_ => Err("Wrong payload for Action::Run"),
@@ -657,9 +804,11 @@ impl TryFrom<ProtobufAction> for Action {
Some(OptionalPayload::FocusTerminalPaneWithIdPayload(payload)) => {
let terminal_pane_id = payload.pane_id;
let should_float_if_hidden = payload.should_float;
+ let should_be_in_place_if_hidden = payload.should_be_in_place;
Ok(Action::FocusTerminalPaneWithId {
pane_id: terminal_pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
})
},
_ => Err("Wrong payload for Action::FocusTerminalPaneWithId"),
@@ -670,9 +819,11 @@ impl TryFrom<ProtobufAction> for Action {
Some(OptionalPayload::FocusPluginPaneWithIdPayload(payload)) => {
let plugin_pane_id = payload.pane_id;
let should_float_if_hidden = payload.should_float;
+ let should_be_in_place_if_hidden = payload.should_be_in_place;
Ok(Action::FocusPluginPaneWithId {
pane_id: plugin_pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
})
},
_ => Err("Wrong payload for Action::FocusPluginPaneWithId"),
@@ -768,8 +919,60 @@ impl TryFrom<ProtobufAction> for Action {
None => Ok(Action::NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
}),
},
+ Some(ProtobufActionName::NewBlockingPane) => match protobuf_action.optional_payload {
+ Some(OptionalPayload::NewBlockingPanePayload(payload)) => {
+ let placement: NewPanePlacement = payload
+ .placement
+ .ok_or("NewBlockingPanePayload must have a placement")?
+ .try_into()?;
+ let pane_name = payload.pane_name;
+ let command = payload.command.and_then(|c| c.try_into().ok());
+ let unblock_condition = payload
+ .unblock_condition
+ .and_then(|uc| ProtobufUnblockCondition::from_i32(uc))
+ .and_then(|uc| uc.try_into().ok());
+ let near_current_pane = payload.near_current_pane;
+ Ok(Action::NewBlockingPane {
+ placement,
+ pane_name,
+ command,
+ unblock_condition,
+ near_current_pane,
+ })
+ },
+ _ => Err("Wrong payload for Action::NewBlockingPane"),
+ },
+ Some(ProtobufActionName::NewInPlacePane) => match protobuf_action.optional_payload {
+ Some(OptionalPayload::NewInPlacePanePayload(payload)) => {
+ let near_current_pane = payload.near_current_pane;
+ let pane_id_to_replace =
+ payload.pane_id_to_replace.and_then(|p| p.try_into().ok());
+ let close_replace_pane = payload.close_replace_pane;
+ if let Some(command) = payload.command {
+ let pane_name = command.pane_name.clone();
+ let run_command_action: RunCommandAction = command.try_into()?;
+ Ok(Action::NewInPlacePane {
+ command: Some(run_command_action),
+ pane_name,
+ near_current_pane,
+ pane_id_to_replace,
+ close_replace_pane,
+ })
+ } else {
+ Ok(Action::NewInPlacePane {
+ command: None,
+ pane_name: payload.pane_name,
+ near_current_pane,
+ pane_id_to_replace,
+ close_replace_pane,
+ })
+ }
+ },
+ _ => Err("Wrong payload for Action::NewInPlacePane"),
+ },
_ => Err("Unknown Action"),
}
}
@@ -784,15 +987,20 @@ impl TryFrom<Action> for ProtobufAction {
optional_payload: None,
}),
Action::Write {
- key_with_modifier: _,
+ key_with_modifier,
bytes,
- is_kitty_keyboard_protocol: _,
- } => Ok(ProtobufAction {
- name: ProtobufActionName::Write as i32,
- optional_payload: Some(OptionalPayload::WritePayload(WritePayload {
- bytes_to_write: bytes,
- })),
- }),
+ is_kitty_keyboard_protocol,
+ } => {
+ let protobuf_key_with_modifier = key_with_modifier.and_then(|k| k.try_into().ok());
+ Ok(ProtobufAction {
+ name: ProtobufActionName::Write as i32,
+ optional_payload: Some(OptionalPayload::WritePayload(WritePayload {
+ key_with_modifier: protobuf_key_with_modifier,
+ bytes_to_write: bytes,
+ is_kitty_keyboard_protocol,
+ })),
+ })
+ },
Action::WriteChars {
chars: chars_to_write,
} => Ok(ProtobufAction {
@@ -982,6 +1190,7 @@ impl TryFrom<Action> for ProtobufAction {
in_place: _should_be_in_place,
start_suppressed: _start_suppressed,
coordinates: _floating_pane_coordinates,
+ near_current_pane,
} => {
let file_to_edit = open_file_payload.path.display().to_string();
let cwd = open_file_payload.cwd.map(|cwd| cwd.display().to_string());
@@ -997,6 +1206,7 @@ impl TryFrom<Action> for ProtobufAction {
should_float,
direction,
cwd,
+ near_current_pane,
})),
})
},
@@ -1004,6 +1214,7 @@ impl TryFrom<Action> for ProtobufAction {
command: run_command_action,
pane_name,
coordinates: _coordinates,
+ near_current_pane,
} => {
let command = run_command_action.and_then(|r| {
let mut protobuf_run_command_action: ProtobufRunCommandAction =
@@ -1014,7 +1225,10 @@ impl TryFrom<Action> for ProtobufAction {
Ok(ProtobufAction {
name: ProtobufActionName::NewFloatingPane as i32,
optional_payload: Some(OptionalPayload::NewFloatingPanePayload(
- NewFloatingPanePayload { command },
+ NewFloatingPanePayload {
+ command,
+ near_current_pane,
+ },
)),
})
},
@@ -1022,6 +1236,7 @@ impl TryFrom<Action> for ProtobufAction {
direction,
command: run_command_action,
pane_name,
+ near_current_pane,
} => {
let direction = direction.and_then(|direction| {
let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?;
@@ -1037,7 +1252,11 @@ impl TryFrom<Action> for ProtobufAction {
Ok(ProtobufAction {
name: ProtobufActionName::NewTiledPane as i32,
optional_payload: Some(OptionalPayload::NewTiledPanePayload(
- NewTiledPanePayload { direction, command },
+ NewTiledPanePayload {
+ direction,
+ command,
+ near_current_pane,
+ },
)),
})
},
@@ -1062,18 +1281,82 @@ impl TryFrom<Action> for ProtobufAction {
optional_payload: None,
}),
Action::NewTab {
- tiled_layout: _,
- floating_layouts: _,
- swap_tiled_layouts: _,
- swap_floating_layouts: _,
- tab_name: _,
- should_change_focus_to_new_tab: _,
- cwd: _,
+ tiled_layout,
+ floating_layouts,
+ swap_tiled_layouts,
+ swap_floating_layouts,
+ tab_name,
+ should_change_focus_to_new_tab,
+ cwd,
+ initial_panes,
+ first_pane_unblock_condition,
} => {
- // we do not serialize the various newtab payloads
+ // Always send payload (even if all fields are default)
+ let protobuf_tiled_layout = tiled_layout
+ .as_ref()
+ .map(|l| l.clone().try_into())
+ .transpose()?;
+
+ let protobuf_floating_layouts = floating_layouts
+ .iter()
+ .map(|l| l.clone().try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let protobuf_swap_tiled_layouts = swap_tiled_layouts
+ .as_ref()
+ .map(|layouts| {
+ layouts
+ .iter()
+ .map(|l| l.clone().try_into())
+ .collect::<Result<Vec<_>, _>>()
+ })
+ .transpose()?
+ .unwrap_or_default();
+
+ let protobuf_swap_floating_layouts = swap_floating_layouts
+ .as_ref()
+ .map(|layouts| {
+ layouts
+ .iter()
+ .map(|l| l.clone().try_into())
+ .collect::<Result<Vec<_>, _>>()
+ })
+ .transpose()?
+ .unwrap_or_default();
+
+ let cwd_string = cwd.as_ref().map(|p| p.display().to_string());
+
+ let protobuf_initial_panes = initial_panes
+ .as_ref()
+ .map(|panes| {
+ panes
+ .iter()
+ .map(|p| p.clone().try_into())
+ .collect::<Result<Vec<_>, _>>()
+ })
+ .transpose()?
+ .unwrap_or_default();
+
+ let protobuf_first_pane_unblock_condition = first_pane_unblock_condition
+ .map(|uc| {
+ let protobuf_uc: ProtobufUnblockCondition = uc.try_into().ok()?;
+ Some(protobuf_uc as i32)
+ })
+ .flatten();
+
Ok(ProtobufAction {
name: ProtobufActionName::NewTab as i32,
- optional_payload: None,
+ optional_payload: Some(OptionalPayload::NewTabPayload(NewTabPayload {
+ tiled_layout: protobuf_tiled_layout,
+ floating_layouts: protobuf_floating_layouts,
+ swap_tiled_layouts: protobuf_swap_tiled_layouts,
+ swap_floating_layouts: protobuf_swap_floating_layouts,
+ tab_name: tab_name.clone(),
+ should_change_focus_to_new_tab,
+ cwd: cwd_string,
+ initial_panes: protobuf_initial_panes,
+ first_pane_unblock_condition: protobuf_first_pane_unblock_condition,
+ })),
})
},
Action::GoToNextTab => Ok(ProtobufAction {
@@ -1123,6 +1406,7 @@ impl TryFrom<Action> for ProtobufAction {
},
Action::Run {
command: run_command_action,
+ near_current_pane: _,
} => {
let run_command_action: ProtobufRunCommandAction = run_command_action.try_into()?;
Ok(ProtobufAction {
@@ -1277,24 +1561,28 @@ impl TryFrom<Action> for ProtobufAction {
Action::FocusTerminalPaneWithId {
pane_id: terminal_pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
} => Ok(ProtobufAction {
name: ProtobufActionName::FocusTerminalPaneWithId as i32,
optional_payload: Some(OptionalPayload::FocusTerminalPaneWithIdPayload(
PaneIdAndShouldFloat {
pane_id: terminal_pane_id,
should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
},
)),
}),
Action::FocusPluginPaneWithId {
pane_id: plugin_pane_id,
should_float_if_hidden,
+ should_be_in_place_if_hidden,
} => Ok(ProtobufAction {
name: ProtobufActionName::FocusPluginPaneWithId as i32,
optional_payload: Some(OptionalPayload::FocusPluginPaneWithIdPayload(
PaneIdAndShouldFloat {
pane_id: plugin_pane_id,
should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
},
)),
}),
@@ -1363,16 +1651,72 @@ impl TryFrom<Action> for ProtobufAction {
Action::NewStackedPane {
command: _,
pane_name: _,
+ near_current_pane: _,
} => Ok(ProtobufAction {
name: ProtobufActionName::NewStackedPane as i32,
optional_payload: None,
}),
+ Action::NewBlockingPane {
+ placement,
+ pane_name,
+ command,
+ unblock_condition,
+ near_current_pane,
+ } => {
+ let placement: ProtobufNewPanePlacement = placement.try_into()?;
+ let command = command.and_then(|c| {
+ let protobuf_command: ProtobufRunCommandAction = c.try_into().ok()?;
+ Some(protobuf_command)
+ });
+ let unblock_condition = unblock_condition
+ .map(|uc| {
+ let protobuf_uc: ProtobufUnblockCondition = uc.try_into().ok()?;
+ Some(protobuf_uc as i32)
+ })
+ .flatten();
+ Ok(ProtobufAction {
+ name: ProtobufActionName::NewBlockingPane as i32,
+ optional_payload: Some(OptionalPayload::NewBlockingPanePayload(
+ NewBlockingPanePayload {
+ placement: Some(placement),
+ pane_name,
+ command,
+ unblock_condition,
+ near_current_pane,
+ },
+ )),
+ })
+ },
+ Action::NewInPlacePane {
+ command: run_command_action,
+ pane_name,
+ near_current_pane,
+ pane_id_to_replace,
+ close_replace_pane,
+ } => {
+ let command = run_command_action.and_then(|r| {
+ let mut protobuf_run_command_action: ProtobufRunCommandAction =
+ r.try_into().ok()?;
+ let pane_name = pane_name.and_then(|n| n.try_into().ok());
+ protobuf_run_command_action.pane_name = pane_name;
+ Some(protobuf_run_command_action)
+ });
+ let pane_id_to_replace = pane_id_to_replace.and_then(|p| p.try_into().ok());
+ Ok(ProtobufAction {
+ name: ProtobufActionName::NewInPlacePane as i32,
+ optional_payload: Some(OptionalPayload::NewInPlacePanePayload(
+ NewInPlacePanePayload {
+ command,
+ pane_name: None, // pane_name is already embedded in command
+ near_current_pane,
+ pane_id_to_replace,
+ close_replace_pane,
+ },
+ )),
+ })
+ },
Action::NoOp
| Action::Confirm
- | Action::NewInPlacePane {
- command: _,
- pane_name: _,
- }
| Action::NewInPlacePluginPane {
plugin: _,
pane_name: _,
@@ -1389,7 +1733,6 @@ impl TryFrom<Action> for ProtobufAction {
coordinates: _,
}
| Action::SkipConfirm { action: _ }
- | Action::NewBlockingPane { .. }
| Action::SwitchSession { .. } => Err("Unsupported action"),
}
}
@@ -1616,3 +1959,914 @@ impl TryFrom<&ProtobufPluginConfiguration> for BTreeMap<String, String> {
Ok(converted)
}
}
+
+impl TryFrom<ProtobufKeyWithModifier> for KeyWithModifier {
+ type Error = &'static str;
+ fn try_from(protobuf_key: ProtobufKeyWithModifier) -> Result<Self, &'static str> {
+ let bare_key = match ProtobufBareKey::from_i32(protobuf_key.bare_key) {
+ Some(ProtobufBareKey::PageDown) => crate::data::BareKey::PageDown,
+ Some(ProtobufBareKey::PageUp) => crate::data::BareKey::PageUp,
+ Some(ProtobufBareKey::Left) => crate::data::BareKey::Left,
+ Some(ProtobufBareKey::Down) => crate::data::BareKey::Down,
+ Some(ProtobufBareKey::Up) => crate::data::BareKey::Up,
+ Some(ProtobufBareKey::Right) => crate::data::BareKey::Right,
+ Some(ProtobufBareKey::Home) => crate::data::BareKey::Home,
+ Some(ProtobufBareKey::End) => crate::data::BareKey::End,
+ Some(ProtobufBareKey::Backspace) => crate::data::BareKey::Backspace,
+ Some(ProtobufBareKey::Delete) => crate::data::BareKey::Delete,
+ Some(ProtobufBareKey::Insert) => crate::data::BareKey::Insert,
+ Some(ProtobufBareKey::F1) => crate::data::BareKey::F(1),
+ Some(ProtobufBareKey::F2) => crate::data::BareKey::F(2),
+ Some(ProtobufBareKey::F3) => crate::data::BareKey::F(3),
+ Some(ProtobufBareKey::F4) => crate::data::BareKey::F(4),
+ Some(ProtobufBareKey::F5) => crate::data::BareKey::F(5),
+ Some(ProtobufBareKey::F6) => crate::data::BareKey::F(6),
+ Some(ProtobufBareKey::F7) => crate::data::BareKey::F(7),
+ Some(ProtobufBareKey::F8) => crate::data::BareKey::F(8),
+ Some(ProtobufBareKey::F9) => crate::data::BareKey::F(9),
+ Some(ProtobufBareKey::F10) => crate::data::BareKey::F(10),
+ Some(ProtobufBareKey::F11) => crate::data::BareKey::F(11),
+ Some(ProtobufBareKey::F12) => crate::data::BareKey::F(12),
+ Some(ProtobufBareKey::Char) => {
+ if let Some(character) = protobuf_key.character {
+ let ch = character
+ .chars()
+ .next()
+ .ok_or("BareKey::Char requires a character")?;
+ crate::data::BareKey::Char(ch)
+ } else {
+ return Err("BareKey::Char requires a character");
+ }
+ },
+ Some(ProtobufBareKey::Tab) => crate::data::BareKey::Tab,
+ Some(ProtobufBareKey::Esc) => crate::data::BareKey::Esc,
+ Some(ProtobufBareKey::Enter) => crate::data::BareKey::Enter,
+ Some(ProtobufBareKey::CapsLock) => crate::data::BareKey::CapsLock,
+ Some(ProtobufBareKey::ScrollLock) => crate::data::BareKey::ScrollLock,
+ Some(ProtobufBareKey::NumLock) => crate::data::BareKey::NumLock,
+ Some(ProtobufBareKey::PrintScreen) => crate::data::BareKey::PrintScreen,
+ Some(ProtobufBareKey::Pause) => crate::data::BareKey::Pause,
+ Some(ProtobufBareKey::Menu) => crate::data::BareKey::Menu,
+ _ => return Err("Unknown BareKey"),
+ };
+
+ let mut key_modifiers = std::collections::BTreeSet::new();
+ for modifier in protobuf_key.key_modifiers {
+ let key_modifier = match ProtobufKeyModifier::from_i32(modifier) {
+ Some(ProtobufKeyModifier::Ctrl) => crate::data::KeyModifier::Ctrl,
+ Some(ProtobufKeyModifier::Alt) => crate::data::KeyModifier::Alt,
+ Some(ProtobufKeyModifier::Shift) => crate::data::KeyModifier::Shift,
+ Some(ProtobufKeyModifier::Super) => crate::data::KeyModifier::Super,
+ _ => continue,
+ };
+ key_modifiers.insert(key_modifier);
+ }
+
+ Ok(KeyWithModifier {
+ bare_key,
+ key_modifiers,
+ })
+ }
+}
+
+impl TryFrom<KeyWithModifier> for ProtobufKeyWithModifier {
+ type Error = &'static str;
+ fn try_from(key: KeyWithModifier) -> Result<Self, &'static str> {
+ let (bare_key, character) = match key.bare_key {
+ crate::data::BareKey::PageDown => (ProtobufBareKey::PageDown as i32, None),
+ crate::data::BareKey::PageUp => (ProtobufBareKey::PageUp as i32, None),
+ crate::data::BareKey::Left => (ProtobufBareKey::Left as i32, None),
+ crate::data::BareKey::Down => (ProtobufBareKey::Down as i32, None),
+ crate::data::BareKey::Up => (ProtobufBareKey::Up as i32, None),
+ crate::data::BareKey::Right => (ProtobufBareKey::Right as i32, None),
+ crate::data::BareKey::Home => (ProtobufBareKey::Home as i32, None),
+ crate::data::BareKey::End => (ProtobufBareKey::End as i32, None),
+ crate::data::BareKey::Backspace => (ProtobufBareKey::Backspace as i32, None),
+ crate::data::BareKey::Delete => (ProtobufBareKey::Delete as i32, None),
+ crate::data::BareKey::Insert => (ProtobufBareKey::Insert as i32, None),
+ crate::data::BareKey::F(1) => (ProtobufBareKey::F1 as i32, None),
+ crate::data::BareKey::F(2) => (ProtobufBareKey::F2 as i32, None),
+ crate::data::BareKey::F(3) => (ProtobufBareKey::F3 as i32, None),
+ crate::data::BareKey::F(4) => (ProtobufBareKey::F4 as i32, None),
+ crate::data::BareKey::F(5) => (ProtobufBareKey::F5 as i32, None),
+ crate::data::BareKey::F(6) => (ProtobufBareKey::F6 as i32, None),
+ crate::data::BareKey::F(7) => (ProtobufBareKey::F7 as i32, None),
+ crate::data::BareKey::F(8) => (ProtobufBareKey::F8 as i32, None),
+ crate::data::BareKey::F(9) => (ProtobufBareKey::F9 as i32, None),
+ crate::data::BareKey::F(10) => (ProtobufBareKey::F10 as i32, None),
+ crate::data::BareKey::F(11) => (ProtobufBareKey::F11 as i32, None),
+ crate::data::BareKey::F(12) => (ProtobufBareKey::F12 as i32, None),
+ crate::data::BareKey::Char(c) => (ProtobufBareKey::Char as i32, Some(c.to_string())),
+ crate::data::BareKey::Tab => (ProtobufBareKey::Tab as i32, None),
+ crate::data::BareKey::Esc => (ProtobufBareKey::Esc as i32, None),
+ crate::data::BareKey::Enter => (ProtobufBareKey::Enter as i32, None),
+ crate::data::BareKey::CapsLock => (ProtobufBareKey::CapsLock as i32, None),
+ crate::data::BareKey::ScrollLock => (ProtobufBareKey::ScrollLock as i32, None),
+ crate::data::BareKey::NumLock => (ProtobufBareKey::NumLock as i32, None),
+ crate::data::BareKey::PrintScreen => (ProtobufBareKey::PrintScreen as i32, None),
+ crate::data::BareKey::Pause => (ProtobufBareKey::Pause as i32, None),
+ crate::data::BareKey::Menu => (ProtobufBareKey::Menu as i32, None),
+ _ => return Err("Unsupported BareKey"),
+ };
+
+ let key_modifiers: Vec<i32> = key
+ .key_modifiers
+ .iter()
+ .map(|m| match m {
+ crate::data::KeyModifier::Ctrl => ProtobufKeyModifier::Ctrl as i32,
+ crate::data::KeyModifier::Alt => ProtobufKeyModifier::Alt as i32,
+ crate::data::KeyModifier::Shift => ProtobufKeyModifier::Shift as i32,
+ crate::data::KeyModifier::Super => ProtobufKeyModifier::Super as i32,
+ })
+ .collect();
+
+ Ok(ProtobufKeyWithModifier {
+ bare_key,
+ key_modifiers,
+ character,
+ })
+ }
+}
+
+// UnblockCondition conversions
+impl TryFrom<ProtobufUnblockCondition> for UnblockCondition {
+ type Error = &'static str;
+ fn try_from(protobuf_uc: ProtobufUnblockCondition) -> Result<Self, &'static str> {
+ match protobuf_uc {
+ ProtobufUnblockCondition::UnblockOnExitSuccess => Ok(UnblockCondition::OnExitSuccess),
+ ProtobufUnblockCondition::UnblockOnExitFailure => Ok(UnblockCondition::OnExitFailure),
+ ProtobufUnblockCondition::UnblockOnAnyExit => Ok(UnblockCondition::OnAnyExit),
+ }
+ }
+}
+
+impl TryFrom<UnblockCondition> for ProtobufUnblockCondition {
+ type Error = &'static str;
+ fn try_from(uc: UnblockCondition) -> Result<Self, &'static str> {
+ match uc {
+ UnblockCondition::OnExitSuccess => Ok(ProtobufUnblockCondition::UnblockOnExitSuccess),
+ UnblockCondition::OnExitFailure => Ok(ProtobufUnblockCondition::UnblockOnExitFailure),
+ UnblockCondition::OnAnyExit => Ok(ProtobufUnblockCondition::UnblockOnAnyExit),
+ }
+ }
+}
+
+impl TryFrom<i32> for UnblockCondition {
+ type Error = &'static str;
+ fn try_from(value: i32) -> Result<Self, &'static str> {
+ match ProtobufUnblockCondition::from_i32(value) {
+ Some(uc) => uc.try_into(),
+ None => Err("Invalid UnblockCondition value"),
+ }
+ }
+}
+
+// PaneId conversions
+impl TryFrom<ProtobufPaneId> for PaneId {
+ type Error = &'static str;
+ fn try_from(protobuf_pane_id: ProtobufPaneId) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::pane_id::PaneIdVariant;
+
+ match protobuf_pane_id.pane_id_variant {
+ Some(PaneIdVariant::Terminal(id)) => Ok(PaneId::Terminal(id)),
+ Some(PaneIdVariant::Plugin(id)) => Ok(PaneId::Plugin(id)),
+ None => Err("PaneId must have either terminal or plugin id"),
+ }
+ }
+}
+
+impl TryFrom<PaneId> for ProtobufPaneId {
+ type Error = &'static str;
+ fn try_from(pane_id: PaneId) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::pane_id::PaneIdVariant;
+
+ let pane_id_variant = match pane_id {
+ PaneId::Terminal(id) => Some(PaneIdVariant::Terminal(id)),
+ PaneId::Plugin(id) => Some(PaneIdVariant::Plugin(id)),
+ };
+ Ok(ProtobufPaneId { pane_id_variant })
+ }
+}
+
+// SplitSize conversions
+impl TryFrom<ProtobufSplitSize> for SplitSize {
+ type Error = &'static str;
+ fn try_from(protobuf_split_size: ProtobufSplitSize) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::split_size::SplitSizeVariant;
+
+ match protobuf_split_size.split_size_variant {
+ Some(SplitSizeVariant::Percent(p)) => Ok(SplitSize::Percent(p as usize)),
+ Some(SplitSizeVariant::Fixed(f)) => Ok(SplitSize::Fixed(f as usize)),
+ None => Err("SplitSize must have either percent or fixed value"),
+ }
+ }
+}
+
+impl TryFrom<SplitSize> for ProtobufSplitSize {
+ type Error = &'static str;
+ fn try_from(split_size: SplitSize) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::split_size::SplitSizeVariant;
+
+ let split_size_variant = match split_size {
+ SplitSize::Percent(p) => Some(SplitSizeVariant::Percent(p as u32)),
+ SplitSize::Fixed(f) => Some(SplitSizeVariant::Fixed(f as u32)),
+ };
+ Ok(ProtobufSplitSize { split_size_variant })
+ }
+}
+
+// FloatingPaneCoordinates conversions
+impl TryFrom<ProtobufFloatingPaneCoordinates> for FloatingPaneCoordinates {
+ type Error = &'static str;
+ fn try_from(protobuf_coords: ProtobufFloatingPaneCoordinates) -> Result<Self, &'static str> {
+ Ok(FloatingPaneCoordinates {
+ x: protobuf_coords.x.and_then(|x| x.try_into().ok()),
+ y: protobuf_coords.y.and_then(|y| y.try_into().ok()),
+ width: protobuf_coords.width.and_then(|w| w.try_into().ok()),
+ height: protobuf_coords.height.and_then(|h| h.try_into().ok()),
+ pinned: protobuf_coords.pinned,
+ })
+ }
+}
+
+impl TryFrom<FloatingPaneCoordinates> for ProtobufFloatingPaneCoordinates {
+ type Error = &'static str;
+ fn try_from(coords: FloatingPaneCoordinates) -> Result<Self, &'static str> {
+ Ok(ProtobufFloatingPaneCoordinates {
+ x: coords.x.and_then(|x| x.try_into().ok()),
+ y: coords.y.and_then(|y| y.try_into().ok()),
+ width: coords.width.and_then(|w| w.try_into().ok()),
+ height: coords.height.and_then(|h| h.try_into().ok()),
+ pinned: coords.pinned,
+ })
+ }
+}
+
+// NewPanePlacement conversions
+impl TryFrom<ProtobufNewPanePlacement> for NewPanePlacement {
+ type Error = &'static str;
+ fn try_from(protobuf_placement: ProtobufNewPanePlacement) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::new_pane_placement::PlacementVariant;
+
+ match protobuf_placement.placement_variant {
+ Some(PlacementVariant::NoPreference(_)) => Ok(NewPanePlacement::NoPreference),
+ Some(PlacementVariant::Tiled(tiled)) => {
+ let direction = tiled
+ .direction
+ .and_then(|d| ProtobufResizeDirection::from_i32(d))
+ .and_then(|d| d.try_into().ok());
+ Ok(NewPanePlacement::Tiled(direction))
+ },
+ Some(PlacementVariant::Floating(floating)) => {
+ let coords = floating.coordinates.and_then(|c| c.try_into().ok());
+ Ok(NewPanePlacement::Floating(coords))
+ },
+ Some(PlacementVariant::InPlace(config)) => {
+ let pane_id_to_replace =
+ config.pane_id_to_replace.and_then(|id| id.try_into().ok());
+ Ok(NewPanePlacement::InPlace {
+ pane_id_to_replace,
+ close_replaced_pane: config.close_replaced_pane,
+ })
+ },
+ Some(PlacementVariant::Stacked(stacked)) => {
+ let pane_id = stacked.pane_id.and_then(|id| id.try_into().ok());
+ Ok(NewPanePlacement::Stacked(pane_id))
+ },
+ None => Err("NewPanePlacement must have a placement variant"),
+ }
+ }
+}
+
+impl TryFrom<NewPanePlacement> for ProtobufNewPanePlacement {
+ type Error = &'static str;
+ fn try_from(placement: NewPanePlacement) -> Result<Self, &'static str> {
+ use super::generated_api::api::action::new_pane_placement::PlacementVariant;
+
+ let placement_variant = match placement {
+ NewPanePlacement::NoPreference => Some(PlacementVariant::NoPreference(true)),
+ NewPanePlacement::Tiled(direction) => {
+ let direction = direction.and_then(|d| {
+ let protobuf_direction: ProtobufResizeDirection = d.try_into().ok()?;
+ Some(protobuf_direction as i32)
+ });
+ Some(PlacementVariant::Tiled(ProtobufTiledPlacement {
+ direction,
+ }))
+ },
+ NewPanePlacement::Floating(coords) => {
+ let coordinates = coords.and_then(|c| c.try_into().ok());
+ Some(PlacementVariant::Floating(ProtobufFloatingPlacement {
+ coordinates,
+ }))
+ },
+ NewPanePlacement::InPlace {
+ pane_id_to_replace,
+ close_replaced_pane,
+ } => {
+ let pane_id_to_replace = pane_id_to_replace.and_then(|id| id.try_into().ok());
+ Some(PlacementVariant::InPlace(ProtobufInPlaceConfig {
+ pane_id_to_replace,
+ close_replaced_pane,
+ }))
+ },
+ NewPanePlacement::Stacked(pane_id) => {
+ let pane_id = pane_id.and_then(|id| id.try_into().ok());
+ Some(PlacementVariant::Stacked(ProtobufStackedPlacement {
+ pane_id,
+ }))
+ },
+ };
+
+ Ok(ProtobufNewPanePlacement { placement_variant })
+ }
+}
+
+// Layout type conversions
+
+impl TryFrom<ProtobufPercentOrFixed> for PercentOrFixed {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufPercentOrFixed) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::percent_or_fixed::SizeType;
+ match protobuf.size_type {
+ Some(SizeType::Percent(p)) => Ok(PercentOrFixed::Percent(p as usize)),
+ Some(SizeType::Fixed(f)) => Ok(PercentOrFixed::Fixed(f as usize)),
+ None => Err("PercentOrFixed must have a size_type"),
+ }
+ }
+}
+
+impl TryFrom<PercentOrFixed> for ProtobufPercentOrFixed {
+ type Error = &'static str;
+ fn try_from(internal: PercentOrFixed) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::percent_or_fixed::SizeType;
+ let size_type = match internal {
+ PercentOrFixed::Percent(p) => Some(SizeType::Percent(p as u32)),
+ PercentOrFixed::Fixed(f) => Some(SizeType::Fixed(f as u32)),
+ };
+ Ok(ProtobufPercentOrFixed { size_type })
+ }
+}
+
+impl TryFrom<ProtobufSplitDirection> for SplitDirection {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufSplitDirection) -> Result<Self, Self::Error> {
+ match protobuf {
+ ProtobufSplitDirection::Horizontal => Ok(SplitDirection::Horizontal),
+ ProtobufSplitDirection::Vertical => Ok(SplitDirection::Vertical),
+ ProtobufSplitDirection::Unspecified => Err("SplitDirection cannot be unspecified"),
+ }
+ }
+}
+
+impl TryFrom<SplitDirection> for ProtobufSplitDirection {
+ type Error = &'static str;
+ fn try_from(internal: SplitDirection) -> Result<Self, Self::Error> {
+ Ok(match internal {
+ SplitDirection::Horizontal => ProtobufSplitDirection::Horizontal,
+ SplitDirection::Vertical => ProtobufSplitDirection::Vertical,
+ })
+ }
+}
+
+impl TryFrom<ProtobufLayoutConstraint> for LayoutConstraint {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufLayoutConstraint) -> Result<Self, Self::Error> {
+ match protobuf {
+ ProtobufLayoutConstraint::MaxPanes => Ok(LayoutConstraint::MaxPanes(0)),
+ ProtobufLayoutConstraint::MinPanes => Ok(LayoutConstraint::MinPanes(0)),
+ ProtobufLayoutConstraint::ExactPanes => Ok(LayoutConstraint::ExactPanes(0)),
+ ProtobufLayoutConstraint::NoConstraint => Ok(LayoutConstraint::NoConstraint),
+ ProtobufLayoutConstraint::Unspecified => Err("LayoutConstraint cannot be unspecified"),
+ }
+ }
+}
+
+impl TryFrom<LayoutConstraint> for ProtobufLayoutConstraint {
+ type Error = &'static str;
+ fn try_from(internal: LayoutConstraint) -> Result<Self, Self::Error> {
+ Ok(match internal {
+ LayoutConstraint::MaxPanes(_) => ProtobufLayoutConstraint::MaxPanes,
+ LayoutConstraint::MinPanes(_) => ProtobufLayoutConstraint::MinPanes,
+ LayoutConstraint::ExactPanes(_) => ProtobufLayoutConstraint::ExactPanes,
+ LayoutConstraint::NoConstraint => ProtobufLayoutConstraint::NoConstraint,
+ })
+ }
+}
+
+impl TryFrom<ProtobufLayoutConstraintWithValue> for LayoutConstraint {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufLayoutConstraintWithValue) -> Result<Self, Self::Error> {
+ let constraint_type = ProtobufLayoutConstraint::from_i32(protobuf.constraint_type)
+ .ok_or("Invalid constraint type")?;
+ match constraint_type {
+ ProtobufLayoutConstraint::MaxPanes => Ok(LayoutConstraint::MaxPanes(
+ protobuf.value.unwrap_or(0) as usize,
+ )),
+ ProtobufLayoutConstraint::MinPanes => Ok(LayoutConstraint::MinPanes(
+ protobuf.value.unwrap_or(0) as usize,
+ )),
+ ProtobufLayoutConstraint::ExactPanes => Ok(LayoutConstraint::ExactPanes(
+ protobuf.value.unwrap_or(0) as usize,
+ )),
+ ProtobufLayoutConstraint::NoConstraint => Ok(LayoutConstraint::NoConstraint),
+ ProtobufLayoutConstraint::Unspecified => Err("LayoutConstraint cannot be unspecified"),
+ }
+ }
+}
+
+impl TryFrom<LayoutConstraint> for ProtobufLayoutConstraintWithValue {
+ type Error = &'static str;
+ fn try_from(internal: LayoutConstraint) -> Result<Self, Self::Error> {
+ let (constraint_type, value) = match internal {
+ LayoutConstraint::MaxPanes(v) => {
+ (ProtobufLayoutConstraint::MaxPanes as i32, Some(v as u32))
+ },
+ LayoutConstraint::MinPanes(v) => {
+ (ProtobufLayoutConstraint::MinPanes as i32, Some(v as u32))
+ },
+ LayoutConstraint::ExactPanes(v) => {
+ (ProtobufLayoutConstraint::ExactPanes as i32, Some(v as u32))
+ },
+ LayoutConstraint::NoConstraint => (ProtobufLayoutConstraint::NoConstraint as i32, None),
+ };
+ Ok(ProtobufLayoutConstraintWithValue {
+ constraint_type,
+ value,
+ })
+ }
+}
+
+impl TryFrom<ProtobufPluginTag> for PluginTag {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufPluginTag) -> Result<Self, Self::Error> {
+ Ok(PluginTag::new(protobuf.tag))
+ }
+}
+
+impl TryFrom<PluginTag> for ProtobufPluginTag {
+ type Error = &'static str;
+ fn try_from(internal: PluginTag) -> Result<Self, Self::Error> {
+ Ok(ProtobufPluginTag {
+ tag: internal.into(),
+ })
+ }
+}
+
+impl TryFrom<ProtobufPluginUserConfiguration> for PluginUserConfiguration {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufPluginUserConfiguration) -> Result<Self, Self::Error> {
+ let btree_map: BTreeMap<String, String> = protobuf.configuration.into_iter().collect();
+ Ok(PluginUserConfiguration::new(btree_map))
+ }
+}
+
+impl TryFrom<PluginUserConfiguration> for ProtobufPluginUserConfiguration {
+ type Error = &'static str;
+ fn try_from(internal: PluginUserConfiguration) -> Result<Self, Self::Error> {
+ let configuration = internal.inner().clone().into_iter().collect();
+ Ok(ProtobufPluginUserConfiguration { configuration })
+ }
+}
+
+impl TryFrom<ProtobufRunPluginLocationData> for RunPluginLocation {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufRunPluginLocationData) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::run_plugin_location_data::LocationData;
+ match protobuf.location_data {
+ Some(LocationData::FilePath(path)) => Ok(RunPluginLocation::File(PathBuf::from(path))),
+ Some(LocationData::ZellijTag(tag)) => Ok(RunPluginLocation::Zellij(tag.try_into()?)),
+ Some(LocationData::RemoteUrl(url)) => Ok(RunPluginLocation::Remote(url)),
+ None => Err("RunPluginLocationData must have location_data"),
+ }
+ }
+}
+
+impl TryFrom<RunPluginLocation> for ProtobufRunPluginLocationData {
+ type Error = &'static str;
+ fn try_from(internal: RunPluginLocation) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::{
+ run_plugin_location_data::LocationData,
+ RunPluginLocation as ProtobufRunPluginLocationType,
+ };
+ let (location_type, location_data) = match internal {
+ RunPluginLocation::File(path) => (
+ ProtobufRunPluginLocationType::File as i32,
+ Some(LocationData::FilePath(path.display().to_string())),
+ ),
+ RunPluginLocation::Zellij(tag) => (
+ ProtobufRunPluginLocationType::Zellij as i32,
+ Some(LocationData::ZellijTag(tag.try_into()?)),
+ ),
+ RunPluginLocation::Remote(url) => (
+ ProtobufRunPluginLocationType::Remote as i32,
+ Some(LocationData::RemoteUrl(url)),
+ ),
+ };
+ Ok(ProtobufRunPluginLocationData {
+ location_type,
+ location_data,
+ })
+ }
+}
+
+impl TryFrom<ProtobufRunPlugin> for RunPlugin {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufRunPlugin) -> Result<Self, Self::Error> {
+ let location = protobuf
+ .location
+ .ok_or("RunPlugin must have location")?
+ .try_into()?;
+ let configuration = protobuf
+ .configuration
+ .ok_or("RunPlugin must have configuration")?
+ .try_into()?;
+ let initial_cwd = protobuf.initial_cwd.map(PathBuf::from);
+ Ok(RunPlugin {
+ _allow_exec_host_cmd: protobuf.allow_exec_host_cmd,
+ location,
+ configuration,
+ initial_cwd,
+ })
+ }
+}
+
+impl TryFrom<RunPlugin> for ProtobufRunPlugin {
+ type Error = &'static str;
+ fn try_from(internal: RunPlugin) -> Result<Self, Self::Error> {
+ Ok(ProtobufRunPlugin {
+ allow_exec_host_cmd: internal._allow_exec_host_cmd,
+ location: Some(internal.location.try_into()?),
+ configuration: Some(internal.configuration.try_into()?),
+ initial_cwd: internal.initial_cwd.map(|p| p.display().to_string()),
+ })
+ }
+}
+
+impl TryFrom<ProtobufPluginAlias> for PluginAlias {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufPluginAlias) -> Result<Self, Self::Error> {
+ let configuration = protobuf.configuration.map(|c| c.try_into()).transpose()?;
+ let initial_cwd = protobuf.initial_cwd.map(PathBuf::from);
+ let run_plugin = protobuf.run_plugin.map(|r| r.try_into()).transpose()?;
+ Ok(PluginAlias {
+ name: protobuf.name,
+ configuration,
+ initial_cwd,
+ run_plugin,
+ })
+ }
+}
+
+impl TryFrom<PluginAlias> for ProtobufPluginAlias {
+ type Error = &'static str;
+ fn try_from(internal: PluginAlias) -> Result<Self, Self::Error> {
+ Ok(ProtobufPluginAlias {
+ name: internal.name,
+ configuration: internal.configuration.map(|c| c.try_into()).transpose()?,
+ initial_cwd: internal.initial_cwd.map(|p| p.display().to_string()),
+ run_plugin: internal.run_plugin.map(|r| r.try_into()).transpose()?,
+ })
+ }
+}
+
+impl TryFrom<ProtobufRunPluginOrAlias> for RunPluginOrAlias {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufRunPluginOrAlias) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::run_plugin_or_alias::PluginType;
+ match protobuf.plugin_type {
+ Some(PluginType::Plugin(plugin)) => Ok(RunPluginOrAlias::RunPlugin(plugin.try_into()?)),
+ Some(PluginType::Alias(alias)) => Ok(RunPluginOrAlias::Alias(alias.try_into()?)),
+ None => Err("RunPluginOrAlias must have plugin_type"),
+ }
+ }
+}
+
+impl TryFrom<RunPluginOrAlias> for ProtobufRunPluginOrAlias {
+ type Error = &'static str;
+ fn try_from(internal: RunPluginOrAlias) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::run_plugin_or_alias::PluginType;
+ let plugin_type = match internal {
+ RunPluginOrAlias::RunPlugin(plugin) => Some(PluginType::Plugin(plugin.try_into()?)),
+ RunPluginOrAlias::Alias(alias) => Some(PluginType::Alias(alias.try_into()?)),
+ };
+ Ok(ProtobufRunPluginOrAlias { plugin_type })
+ }
+}
+
+impl TryFrom<ProtobufRunEditFileAction> for Run {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufRunEditFileAction) -> Result<Self, Self::Error> {
+ let file_path = PathBuf::from(protobuf.file_path);
+ let line_number = protobuf.line_number.map(|n| n as usize);
+ let cwd = protobuf.cwd.map(PathBuf::from);
+ Ok(Run::EditFile(file_path, line_number, cwd))
+ }
+}
+
+impl TryFrom<ProtobufPaneRun> for Run {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufPaneRun) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::pane_run::RunType;
+ use crate::input::command::RunCommand;
+ match protobuf.run_type {
+ Some(RunType::Command(cmd)) => {
+ let run_command_action: RunCommandAction = cmd.try_into()?;
+ let run_command: RunCommand = run_command_action.into();
+ Ok(Run::Command(run_command))
+ },
+ Some(RunType::Plugin(plugin)) => Ok(Run::Plugin(plugin.try_into()?)),
+ Some(RunType::EditFile(edit)) => edit.try_into(),
+ Some(RunType::Cwd(cwd)) => Ok(Run::Cwd(PathBuf::from(cwd))),
+ None => Err("PaneRun must have run_type"),
+ }
+ }
+}
+
+impl TryFrom<Run> for ProtobufPaneRun {
+ type Error = &'static str;
+ fn try_from(internal: Run) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::pane_run::RunType;
+ let run_type = match internal {
+ Run::Command(cmd) => {
+ let run_command_action: RunCommandAction = cmd.into();
+ Some(RunType::Command(run_command_action.try_into()?))
+ },
+ Run::Plugin(plugin) => Some(RunType::Plugin(plugin.try_into()?)),
+ Run::EditFile(file_path, line_number, cwd) => {
+ Some(RunType::EditFile(ProtobufRunEditFileAction {
+ file_path: file_path.display().to_string(),
+ line_number: line_number.map(|n| n as u32),
+ cwd: cwd.map(|p| p.display().to_string()),
+ }))
+ },
+ Run::Cwd(cwd) => Some(RunType::Cwd(cwd.display().to_string())),
+ };
+ Ok(ProtobufPaneRun { run_type })
+ }
+}
+
+impl TryFrom<ProtobufCommandOrPlugin> for CommandOrPlugin {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufCommandOrPlugin) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::command_or_plugin::CommandOrPluginType;
+ match protobuf.command_or_plugin_type {
+ Some(CommandOrPluginType::Command(cmd)) => {
+ Ok(CommandOrPlugin::Command(cmd.try_into()?))
+ },
+ Some(CommandOrPluginType::Plugin(plugin)) => {
+ Ok(CommandOrPlugin::Plugin(plugin.try_into()?))
+ },
+ None => Err("CommandOrPlugin must have command_or_plugin_type"),
+ }
+ }
+}
+
+impl TryFrom<CommandOrPlugin> for ProtobufCommandOrPlugin {
+ type Error = &'static str;
+ fn try_from(internal: CommandOrPlugin) -> Result<Self, Self::Error> {
+ use super::generated_api::api::action::command_or_plugin::CommandOrPluginType;
+ let command_or_plugin_type = match internal {
+ CommandOrPlugin::Command(cmd) => Some(CommandOrPluginType::Command(cmd.try_into()?)),
+ CommandOrPlugin::Plugin(plugin) => {
+ Some(CommandOrPluginType::Plugin(plugin.try_into()?))
+ },
+ };
+ Ok(ProtobufCommandOrPlugin {
+ command_or_plugin_type,
+ })
+ }
+}
+
+impl TryFrom<ProtobufTiledPaneLayout> for TiledPaneLayout {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufTiledPaneLayout) -> Result<Self, Self::Error> {
+ let children_split_direction =
+ ProtobufSplitDirection::from_i32(protobuf.children_split_direction)
+ .ok_or("Invalid split direction")?
+ .try_into()?;
+ let children = protobuf
+ .children
+ .into_iter()
+ .map(|c| c.try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+ let split_size = protobuf.split_size.map(|s| s.try_into()).transpose()?;
+ let run = protobuf.run.map(|r| r.try_into()).transpose()?;
+ let focus = protobuf.focus.and_then(|f| {
+ if f == "true" {
+ Some(true)
+ } else if f == "false" {
+ Some(false)
+ } else {
+ None
+ }
+ });
+ let run_instructions_to_ignore = vec![]; // Not serialized in protobuf
+ Ok(TiledPaneLayout {
+ children_split_direction,
+ name: protobuf.name,
+ children,
+ split_size,
+ run,
+ borderless: protobuf.borderless,
+ focus,
+ external_children_index: protobuf.external_children_index.map(|i| i as usize),
+ children_are_stacked: protobuf.children_are_stacked,
+ is_expanded_in_stack: protobuf.is_expanded_in_stack,
+ exclude_from_sync: protobuf.exclude_from_sync,
+ run_instructions_to_ignore,
+ hide_floating_panes: protobuf.hide_floating_panes,
+ pane_initial_contents: protobuf.pane_initial_contents,
+ })
+ }
+}
+
+impl TryFrom<TiledPaneLayout> for ProtobufTiledPaneLayout {
+ type Error = &'static str;
+ fn try_from(internal: TiledPaneLayout) -> Result<Self, Self::Error> {
+ let children_split_direction: ProtobufSplitDirection =
+ internal.children_split_direction.try_into()?;
+ let children = internal
+ .children
+ .into_iter()
+ .map(|c| c.try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+ let split_size = internal.split_size.map(|s| s.try_into()).transpose()?;
+ let run = internal.run.map(|r| r.try_into()).transpose()?;
+ let focus = internal.focus.map(|f| f.to_string());
+ Ok(ProtobufTiledPaneLayout {
+ children_split_direction: children_split_direction as i32,
+ name: internal.name,
+ children,
+ split_size,
+ run,
+ borderless: internal.borderless,
+ focus,
+ external_children_index: internal.external_children_index.map(|i| i as u32),
+ children_are_stacked: internal.children_are_stacked,
+ is_expanded_in_stack: internal.is_expanded_in_stack,
+ exclude_from_sync: internal.exclude_from_sync,
+ hide_floating_panes: internal.hide_floating_panes,
+ pane_initial_contents: internal.pane_initial_contents,
+ })
+ }
+}
+
+impl TryFrom<ProtobufFloatingPaneLayout> for FloatingPaneLayout {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufFloatingPaneLayout) -> Result<Self, Self::Error> {
+ let height = protobuf.height.map(|h| h.try_into()).transpose()?;
+ let width = protobuf.width.map(|w| w.try_into()).transpose()?;
+ let x = protobuf.x.map(|x| x.try_into()).transpose()?;
+ let y = protobuf.y.map(|y| y.try_into()).transpose()?;
+ let run = protobuf.run.map(|r| r.try_into()).transpose()?;
+ Ok(FloatingPaneLayout {
+ name: protobuf.name,
+ height,
+ width,
+ x,
+ y,
+ pinned: protobuf.pinned,
+ run,
+ focus: protobuf.focus,
+ already_running: protobuf.already_running,
+ pane_initial_contents: protobuf.pane_initial_contents,
+ logical_position: protobuf.logical_position.map(|p| p as usize),
+ })
+ }
+}
+
+impl TryFrom<FloatingPaneLayout> for ProtobufFloatingPaneLayout {
+ type Error = &'static str;
+ fn try_from(internal: FloatingPaneLayout) -> Result<Self, Self::Error> {
+ let height = internal.height.map(|h| h.try_into()).transpose()?;
+ let width = internal.width.map(|w| w.try_into()).transpose()?;
+ let x = internal.x.map(|x| x.try_into()).transpose()?;
+ let y = internal.y.map(|y| y.try_into()).transpose()?;
+ let run = internal.run.map(|r| r.try_into()).transpose()?;
+ Ok(ProtobufFloatingPaneLayout {
+ name: internal.name,
+ height,
+ width,
+ x,
+ y,
+ pinned: internal.pinned,
+ run,
+ focus: internal.focus,
+ already_running: internal.already_running,
+ pane_initial_contents: internal.pane_initial_contents,
+ logical_position: internal.logical_position.map(|p| p as u32),
+ })
+ }
+}
+
+impl TryFrom<ProtobufLayoutConstraintTiledPair> for (LayoutConstraint, TiledPaneLayout) {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufLayoutConstraintTiledPair) -> Result<Self, Self::Error> {
+ let constraint = protobuf
+ .constraint
+ .ok_or("LayoutConstraintTiledPair must have constraint")?
+ .try_into()?;
+ let layout = protobuf
+ .layout
+ .ok_or("LayoutConstraintTiledPair must have layout")?
+ .try_into()?;
+ Ok((constraint, layout))
+ }
+}
+
+impl TryFrom<(LayoutConstraint, TiledPaneLayout)> for ProtobufLayoutConstraintTiledPair {
+ type Error = &'static str;
+ fn try_from(internal: (LayoutConstraint, TiledPaneLayout)) -> Result<Self, Self::Error> {
+ Ok(ProtobufLayoutConstraintTiledPair {
+ constraint: Some(internal.0.try_into()?),
+ layout: Some(internal.1.try_into()?),
+ })
+ }
+}
+
+impl TryFrom<ProtobufLayoutConstraintFloatingPair> for (LayoutConstraint, Vec<FloatingPaneLayout>) {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufLayoutConstraintFloatingPair) -> Result<Self, Self::Error> {
+ let constraint = protobuf
+ .constraint
+ .ok_or("LayoutConstraintFloatingPair must have constraint")?
+ .try_into()?;
+ let layouts = protobuf
+ .layouts
+ .into_iter()
+ .map(|l| l.try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok((constraint, layouts))
+ }
+}
+
+impl TryFrom<(LayoutConstraint, Vec<FloatingPaneLayout>)> for ProtobufLayoutConstraintFloatingPair {
+ type Error = &'static str;
+ fn try_from(
+ internal: (LayoutConstraint, Vec<FloatingPaneLayout>),
+ ) -> Result<Self, Self::Error> {
+ Ok(ProtobufLayoutConstraintFloatingPair {
+ constraint: Some(internal.0.try_into()?),
+ layouts: internal
+ .1
+ .into_iter()
+ .map(|l| l.try_into())
+ .collect::<Result<Vec<_>, _>>()?,
+ })
+ }
+}
+
+impl TryFrom<ProtobufSwapTiledLayout> for SwapTiledLayout {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufSwapTiledLayout) -> Result<Self, Self::Error> {
+ let constraint_map: BTreeMap<LayoutConstraint, TiledPaneLayout> = protobuf
+ .constraint_map
+ .into_iter()
+ .map(|pair| pair.try_into())
+ .collect::<Result<BTreeMap<_, _>, _>>()?;
+ Ok((constraint_map, protobuf.name))
+ }
+}
+
+impl TryFrom<SwapTiledLayout> for ProtobufSwapTiledLayout {
+ type Error = &'static str;
+ fn try_from(internal: SwapTiledLayout) -> Result<Self, Self::Error> {
+ let constraint_map = internal
+ .0
+ .into_iter()
+ .map(|(constraint, layout)| (constraint, layout).try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok(ProtobufSwapTiledLayout {
+ constraint_map,
+ name: internal.1,
+ })
+ }
+}
+
+impl TryFrom<ProtobufSwapFloatingLayout> for SwapFloatingLayout {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufSwapFloatingLayout) -> Result<Self, Self::Error> {
+ let constraint_map: BTreeMap<LayoutConstraint, Vec<FloatingPaneLayout>> = protobuf
+ .constraint_map
+ .into_iter()
+ .map(|pair| pair.try_into())
+ .collect::<Result<BTreeMap<_, _>, _>>()?;
+ Ok((constraint_map, protobuf.name))
+ }
+}
+
+impl TryFrom<SwapFloatingLayout> for ProtobufSwapFloatingLayout {
+ type Error = &'static str;
+ fn try_from(internal: SwapFloatingLayout) -> Result<Self, Self::Error> {
+ let constraint_map = internal
+ .0
+ .into_iter()
+ .map(|(constraint, layouts)| (constraint, layouts).try_into())
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok(ProtobufSwapFloatingLayout {
+ constraint_map,
+ name: internal.1,
+ })
+ }
+}
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto
index f5059131..3bdeea19 100644
--- a/zellij-utils/src/plugin_api/event.proto
+++ b/zellij-utils/src/plugin_api/event.proto
@@ -59,6 +59,9 @@ enum EventType {
FailedToStartWebServer = 34;
InterceptedKeyPress = 35;
PaneRenderReport = 36;
+ UserAction = 37;
+ ActionComplete = 38;
+ CwdChanged = 39;
}
message EventNameList {
@@ -97,9 +100,18 @@ message Event {
FailedToStartWebServerPayload failed_to_start_web_server_payload = 28;
key.Key intercepted_key_payload = 29;
PaneRenderReportPayload pane_render_report_payload = 30;
+ UserActionPayload user_action_payload = 31;
+ ActionCompletePayload action_complete_payload = 32;
+ CwdChangedPayload cwd_changed_payload = 33;
}
}
+message CwdChangedPayload {
+ PaneId pane_id = 1;
+ string new_cwd = 2;
+ repeated uint32 focused_client_ids = 3;
+}
+
message FailedToStartWebServerPayload {
string error = 1;
}
@@ -284,6 +296,7 @@ message SessionManifest {
bool web_clients_allowed = 8;
uint32 web_client_count = 9;
repeated ClientTabHistory tab_history = 10;
+ repeated ClientPaneHistory pane_history = 11;
}
message ClientTabHistory {
@@ -291,6 +304,11 @@ message ClientTabHistory {
repeated uint32 tab_history = 2;
}
+message ClientPaneHistory {
+ uint32 client_id = 1;
+ repeated PaneId pane_history = 2;
+}
+
message PluginInfo {
uint32 plugin_id = 1;
string plugin_url = 2;
@@ -418,3 +436,16 @@ message SelectedText {
action.Position start = 1;
action.Position end = 2;
}
+
+message UserActionPayload {
+ action.Action action = 1;
+ uint32 client_id = 2;
+ optional uint32 terminal_id = 3;
+ optional uint32 cli_client_id = 4;
+}
+
+message ActionCompletePayload {
+ action.Action action = 1;
+ optional PaneId pane_id = 2;
+ repeated ContextItem context = 3;
+}
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index 02ef1842..4e430b0b 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -2,20 +2,24 @@ pub use super::generated_api::api::{
action::{Action as ProtobufAction, Position as ProtobufPosition},
event::{
event::Payload as ProtobufEventPayload, pane_scrollback_response,
- ClientInfo as ProtobufClientInfo, ClientTabHistory as ProtobufClientTabHistory,
- CopyDestination as ProtobufCopyDestination, Event as ProtobufEvent,
- EventNameList as ProtobufEventNameList, EventType as ProtobufEventType,
- FileMetadata as ProtobufFileMetadata, InputModeKeybinds as ProtobufInputModeKeybinds,
- KeyBind as ProtobufKeyBind, LayoutInfo as ProtobufLayoutInfo,
- ModeUpdatePayload as ProtobufModeUpdatePayload, PaneContents as ProtobufPaneContents,
- PaneContentsEntry as ProtobufPaneContentsEntry, PaneId as ProtobufPaneId,
- PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
+ ActionCompletePayload as ProtobufActionCompletePayload, ClientInfo as ProtobufClientInfo,
+ ClientPaneHistory as ProtobufClientPaneHistory,
+ ClientTabHistory as ProtobufClientTabHistory, ContextItem as ProtobufContextItem,
+ CopyDestination as ProtobufCopyDestination, CwdChangedPayload as ProtobufCwdChangedPayload,
+ Event as ProtobufEvent, EventNameList as ProtobufEventNameList,
+ EventType as ProtobufEventType, FileMetadata as ProtobufFileMetadata,
+ InputModeKeybinds as ProtobufInputModeKeybinds, KeyBind as ProtobufKeyBind,
+ LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload,
+ PaneContents as ProtobufPaneContents, PaneContentsEntry as ProtobufPaneContentsEntry,
+ PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo,
+ PaneManifest as ProtobufPaneManifest,
PaneRenderReportPayload as ProtobufPaneRenderReportPayload,
PaneScrollbackResponse as ProtobufPaneScrollbackResponse, PaneType as ProtobufPaneType,
PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession,
SelectedText as ProtobufSelectedText, SessionManifest as ProtobufSessionManifest,
- TabInfo as ProtobufTabInfo, WebServerStatusPayload as ProtobufWebServerStatusPayload,
- WebSharing as ProtobufWebSharing, *,
+ TabInfo as ProtobufTabInfo, UserActionPayload as ProtobufUserActionPayload,
+ WebServerStatusPayload as ProtobufWebServerStatusPayload, WebSharing as ProtobufWebSharing,
+ *,
},
input_mode::InputMode as ProtobufInputMode,
key::Key as ProtobufKey,
@@ -23,8 +27,8 @@ pub use super::generated_api::api::{
};
#[allow(hidden_glob_reexports)]
use crate::data::{
- ClientInfo, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier,
- LayoutInfo, ModeInfo, Mouse, PaneContents, PaneId, PaneInfo, PaneManifest,
+ ClientId, ClientInfo, CopyDestination, Event, EventType, FileMetadata, InputMode,
+ KeyWithModifier, LayoutInfo, ModeInfo, Mouse, PaneContents, PaneId, PaneInfo, PaneManifest,
PaneScrollbackResponse, PermissionStatus, PluginCapabilities, PluginInfo, SelectedText,
SessionInfo, Style, TabInfo, WebServerStatus, WebSharing,
};
@@ -397,6 +401,63 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for the PaneRenderReport Event"),
},
+ Some(ProtobufEventType::UserAction) => match protobuf_event.payload {
+ Some(ProtobufEventPayload::UserActionPayload(protobuf_payload)) => {
+ let action: Action = protobuf_payload
+ .action
+ .ok_or("Missing action in UserAction payload")?
+ .try_into()
+ .map_err(|_| "Failed to convert Action in UserAction payload")?;
+ let client_id = protobuf_payload.client_id as u16;
+ let terminal_id = protobuf_payload.terminal_id;
+ let cli_client_id = protobuf_payload.cli_client_id.map(|id| id as u16);
+ Ok(Event::UserAction(
+ action,
+ client_id,
+ terminal_id,
+ cli_client_id,
+ ))
+ },
+ _ => Err("Malformed payload for the UserAction Event"),
+ },
+ Some(ProtobufEventType::ActionComplete) => match protobuf_event.payload {
+ Some(ProtobufEventPayload::ActionCompletePayload(protobuf_payload)) => {
+ let action: Action = protobuf_payload
+ .action
+ .ok_or("Missing action in ActionComplete payload")?
+ .try_into()
+ .map_err(|_| "Failed to convert Action in ActionComplete payload")?;
+ let pane_id = protobuf_payload
+ .pane_id
+ .map(|id| id.try_into())
+ .transpose()
+ .map_err(|_| "Failed to convert PaneId in ActionComplete payload")?;
+ let context: BTreeMap<String, String> = protobuf_payload
+ .context
+ .into_iter()
+ .map(|item| (item.name, item.value))
+ .collect();
+ Ok(Event::ActionComplete(action, pane_id, context))
+ },
+ _ => Err("Malformed payload for the ActionComplete Event"),
+ },
+ Some(ProtobufEventType::CwdChanged) => match protobuf_event.payload {
+ Some(ProtobufEventPayload::CwdChangedPayload(protobuf_payload)) => {
+ let pane_id: PaneId = protobuf_payload
+ .pane_id
+ .ok_or("Missing pane_id in CwdChanged payload")?
+ .try_into()
+ .map_err(|_| "Failed to convert PaneId in CwdChanged payload")?;
+ let new_cwd = PathBuf::from(protobuf_payload.new_cwd);
+ let focused_client_ids: Vec<ClientId> = protobuf_payload
+ .focused_client_ids
+ .into_iter()
+ .map(|id| id as u16)
+ .collect();
+ Ok(Event::CwdChanged(pane_id, new_cwd, focused_client_ids))
+ },
+ _ => Err("Malformed payload for the CwdChanged Event"),
+ },
None => Err("Unknown Protobuf Event"),
}
}
@@ -797,6 +858,58 @@ impl TryFrom<Event> for ProtobufEvent {
pane_contents_map.try_into()?,
)),
}),
+ Event::UserAction(action, client_id, terminal_id, cli_client_id) => {
+ let protobuf_action: ProtobufAction = action
+ .try_into()
+ .map_err(|_| "Failed to convert Action to protobuf")?;
+ let protobuf_payload = ProtobufUserActionPayload {
+ action: Some(protobuf_action),
+ client_id: client_id as u32,
+ terminal_id,
+ cli_client_id: cli_client_id.map(|id| id as u32),
+ };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::UserAction as i32,
+ payload: Some(event::Payload::UserActionPayload(protobuf_payload)),
+ })
+ },
+ Event::ActionComplete(action, pane_id, context) => {
+ let protobuf_action = action.try_into()?;
+ let protobuf_pane_id = pane_id.map(|id| id.try_into()).transpose()?;
+ let context_items: Vec<ProtobufContextItem> = context
+ .into_iter()
+ .map(|(name, value)| ProtobufContextItem { name, value })
+ .collect();
+ let action_complete_payload = ProtobufActionCompletePayload {
+ action: Some(protobuf_action),
+ pane_id: protobuf_pane_id,
+ context: context_items,
+ };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::ActionComplete as i32,
+ payload: Some(event::Payload::ActionCompletePayload(
+ action_complete_payload,
+ )),
+ })
+ },
+ Event::CwdChanged(pane_id, new_cwd, focused_client_ids) => {
+ let protobuf_pane_id: ProtobufPaneId = pane_id.try_into()?;
+ let new_cwd_string = new_cwd
+ .to_str()
+ .ok_or("Failed to convert PathBuf to string")?
+ .to_string();
+ let focused_client_ids_u32: Vec<u32> =
+ focused_client_ids.into_iter().map(|id| id as u32).collect();
+ let cwd_changed_payload = ProtobufCwdChangedPayload {
+ pane_id: Some(protobuf_pane_id),
+ new_cwd: new_cwd_string,
+ focused_client_ids: focused_client_ids_u32,
+ };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::CwdChanged as i32,
+ payload: Some(event::Payload::CwdChangedPayload(cwd_changed_payload)),
+ })
+ },
}
}
}
@@ -842,6 +955,11 @@ impl TryFrom<SessionInfo> for ProtobufSessionManifest {
.into_iter()
.map(|t| ProtobufClientTabHistory::from(t))
.collect(),
+ pane_history: session_info
+ .pane_history
+ .into_iter()
+ .map(|p| ProtobufClientPaneHistory::from(p))
+ .collect(),
})
}
}
@@ -854,6 +972,18 @@ impl From<(u16, Vec<usize>)> for ProtobufClientTabHistory {
}
}
}
+
+impl From<(u16, Vec<PaneId>)> for ProtobufClientPaneHistory {
+ fn from((client_id, pane_history): (u16, Vec<PaneId>)) -> ProtobufClientPaneHistory {
+ ProtobufClientPaneHistory {
+ client_id: client_id as u32,
+ pane_history: pane_history
+ .into_iter()
+ .filter_map(|p| p.try_into().ok())
+ .collect(),
+ }
+ }
+}
impl From<(u32, PluginInfo)> for ProtobufPluginInfo {
fn from((plugin_id, plugin_info): (u32, PluginInfo)) -> ProtobufPluginInfo {
ProtobufPluginInfo {
@@ -910,6 +1040,16 @@ impl TryFrom<ProtobufSessionManifest> for SessionInfo {
.collect();
tab_history.insert(client_id as u16, tab_history_for_client);
}
+ let mut pane_history = BTreeMap::new();
+ for client_pane_history in protobuf_session_manifest.pane_history.into_iter() {
+ let client_id = client_pane_history.client_id;
+ let pane_history_for_client = client_pane_history
+ .pane_history
+ .into_iter()
+ .filter_map(|p| p.try_into().ok())
+ .collect();
+ pane_history.insert(client_id as u16, pane_history_for_client);
+ }
Ok(SessionInfo {
name: protobuf_session_manifest.name,
tabs: protobuf_session_manifest
@@ -929,6 +1069,7 @@ impl TryFrom<ProtobufSessionManifest> for SessionInfo {
web_clients_allowed: protobuf_session_manifest.web_clients_allowed,
web_client_count: protobuf_session_manifest.web_client_count as usize,
tab_history,
+ pane_history,
})
}
}
@@ -1476,6 +1617,9 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::FailedToStartWebServer => EventType::FailedToStartWebServer,
ProtobufEventType::InterceptedKeyPress => EventType::InterceptedKeyPress,
ProtobufEventType::PaneRenderReport => EventType::PaneRenderReport,
+ ProtobufEventType::UserAction => EventType::UserAction,
+ ProtobufEventType::ActionComplete => EventType::ActionComplete,
+ ProtobufEventType::CwdChanged => EventType::CwdChanged,
})
}
}
@@ -1520,6 +1664,9 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::FailedToStartWebServer => ProtobufEventType::FailedToStartWebServer,
EventType::InterceptedKeyPress => ProtobufEventType::InterceptedKeyPress,
EventType::PaneRenderReport => ProtobufEventType::PaneRenderReport,
+ EventType::UserAction => ProtobufEventType::UserAction,
+ EventType::ActionComplete => ProtobufEventType::ActionComplete,
+ EventType::CwdChanged => ProtobufEventType::CwdChanged,
})
}
}
@@ -2129,6 +2276,7 @@ fn serialize_session_update_event_with_non_default_values() {
web_clients_allowed: false,
web_client_count: 1,
tab_history,
+ pane_history: Default::default(),
};
let session_info_2 = SessionInfo {
name: "session 2".to_owned(),
@@ -2147,6 +2295,7 @@ fn serialize_session_update_event_with_non_default_values() {
web_clients_allowed: false,
web_client_count: 0,
tab_history: Default::default(),
+ pane_history: Default::default(),
};
let session_infos = vec![session_info_1, session_info_2];
let resurrectable_sessions = vec![];
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index 7ffe2fc1..f662c93b 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -160,6 +160,12 @@ enum CommandName {
ClearKeyPressesIntercepts = 144;
ReplacePaneWithExistingPane = 155;
GetPaneScrollback = 163;
+ RunAction = 164;
+ ShowCursor = 169;
+ CopyToClipboard = 170;
+ SendSigintToPaneId = 171;
+ SendSigkillToPaneId = 172;
+ GetPanePid = 173;
}
message PluginCommand {
@@ -268,6 +274,12 @@ message PluginCommand {
ReplacePaneWithExistingPanePayload replace_pane_with_existing_pane_payload = 111;
NewTabPayload new_tab_payload = 112;
GetPaneScrollbackPayload get_pane_scrollback_payload = 113;
+ RunActionPayload run_action_payload = 114;
+ ShowCursorPayload show_cursor_payload = 119;
+ CopyToClipboardPayload copy_to_clipboard_payload = 120;
+ PaneId send_sigint_to_pane_id_payload = 121;
+ PaneId send_sigkill_to_pane_id_payload = 122;
+ GetPanePidPayload get_pane_pid_payload = 123;
}
}
@@ -276,9 +288,15 @@ message NewTabPayload {
optional string cwd = 2;
}
+message RunActionPayload {
+ action.Action action = 1;
+ repeated ContextItem context = 2;
+}
+
message ReplacePaneWithExistingPanePayload {
PaneId pane_id_to_replace = 1;
PaneId existing_pane_id = 2;
+ bool suppress_replaced_pane = 3;
}
message RenameWebLoginTokenPayload {
@@ -516,6 +534,7 @@ message HidePaneWithIdPayload {
message ShowPaneWithIdPayload {
PaneId pane_id = 1;
bool should_float_if_hidden = 2;
+ bool should_focus_pane = 3;
}
message NewTabsWithLayoutInfoPayload {
@@ -710,3 +729,27 @@ message GetPaneScrollbackPayload {
PaneId pane_id = 1;
bool get_full_scrollback = 2;
}
+
+message ShowCursorPayload {
+ optional CursorPosition position = 1;
+}
+
+message CursorPosition {
+ uint32 x = 1;
+ uint32 y = 2;
+}
+
+message CopyToClipboardPayload {
+ string text = 1;
+}
+
+message GetPanePidPayload {
+ PaneId pane_id = 1;
+}
+
+message GetPanePidResponse {
+ oneof result {
+ int32 pid = 1;
+ string error = 2;
+ }
+}
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index c1ba877e..771bb9ec 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -1,17 +1,19 @@
pub use super::generated_api::api::{
- action::{PaneIdAndShouldFloat, SwitchToModePayload},
+ action::{Action as ProtobufAction, PaneIdAndShouldFloat, SwitchToModePayload},
event::{EventNameList as ProtobufEventNameList, Header},
input_mode::InputMode as ProtobufInputMode,
plugin_command::{
- plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload,
- ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload,
- ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseMultiplePanesPayload,
- CloseTabWithIndexPayload, CommandName, ContextItem,
- CreateTokenResponse as ProtobufCreateTokenResponse, CreateTokenResponse,
- EditScrollbackForPaneWithIdPayload, EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload,
+ get_pane_pid_response, plugin_command::Payload, BreakPanesToNewTabPayload,
+ BreakPanesToTabWithIndexPayload, ChangeFloatingPanesCoordinatesPayload,
+ ChangeHostFolderPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload,
+ CloseMultiplePanesPayload, CloseTabWithIndexPayload, CommandName, ContextItem,
+ CopyToClipboardPayload, CreateTokenResponse as ProtobufCreateTokenResponse,
+ CreateTokenResponse, CursorPosition, EditScrollbackForPaneWithIdPayload,
+ EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload,
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatMultiplePanesPayload,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, GenerateWebLoginTokenPayload,
+ GetPanePidPayload, GetPanePidResponse as ProtobufGetPanePidResponse,
GetPaneScrollbackPayload, GroupAndUngroupPanesPayload, HidePaneWithIdPayload,
HighlightAndUnhighlightPanesPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName,
KeyToRebind, KeyToUnbind, KillSessionsPayload, ListTokensResponse, LoadNewPluginPayload,
@@ -29,20 +31,21 @@ pub use super::generated_api::api::{
RenameWebTokenResponse, ReplacePaneWithExistingPanePayload, RequestPluginPermissionPayload,
RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload,
RevokeAllWebTokensResponse, RevokeTokenResponse, RevokeWebLoginTokenPayload,
- RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
- ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetFloatingPanePinnedPayload,
- SetSelfMouseSelectionSupportPayload, SetTimeoutPayload, ShowPaneWithIdPayload,
- StackPanesPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload,
- TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload,
- WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
+ RunActionPayload, RunCommandPayload, ScrollDownInPaneIdPayload,
+ ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload,
+ SetFloatingPanePinnedPayload, SetSelfMouseSelectionSupportPayload, SetTimeoutPayload,
+ ShowCursorPayload, ShowPaneWithIdPayload, StackPanesPayload, SubscribePayload,
+ SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
+ TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
+ WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
};
use crate::data::{
- ConnectToSession, FloatingPaneCoordinates, HttpVerb, InputMode, KeyWithModifier,
- MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand,
+ ConnectToSession, FloatingPaneCoordinates, GetPanePidResponse, HttpVerb, InputMode,
+ KeyWithModifier, MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand,
};
use crate::input::actions::Action;
use crate::input::layout::SplitSize;
@@ -200,6 +203,30 @@ impl TryFrom<PaneId> for ProtobufPaneId {
}
}
+impl TryFrom<ProtobufGetPanePidResponse> for GetPanePidResponse {
+ type Error = &'static str;
+ fn try_from(protobuf_response: ProtobufGetPanePidResponse) -> Result<Self, &'static str> {
+ match protobuf_response.result {
+ Some(get_pane_pid_response::Result::Pid(pid)) => Ok(GetPanePidResponse::Ok(pid)),
+ Some(get_pane_pid_response::Result::Error(error)) => Ok(GetPanePidResponse::Err(error)),
+ None => Err("Empty GetPanePidResponse"),
+ }
+ }
+}
+
+impl From<GetPanePidResponse> for ProtobufGetPanePidResponse {
+ fn from(response: GetPanePidResponse) -> Self {
+ match response {
+ GetPanePidResponse::Ok(pid) => ProtobufGetPanePidResponse {
+ result: Some(get_pane_pid_response::Result::Pid(pid)),
+ },
+ GetPanePidResponse::Err(error) => ProtobufGetPanePidResponse {
+ result: Some(get_pane_pid_response::Result::Error(error)),
+ },
+ }
+ }
+}
+
impl TryFrom<(InputMode, KeyWithModifier, Vec<Action>)> for KeyToRebind {
type Error = &'static str;
fn try_from(
@@ -288,6 +315,14 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for SetSelectable"),
},
+ Some(CommandName::ShowCursor) => match protobuf_plugin_command.payload {
+ Some(Payload::ShowCursorPayload(payload)) => {
+ let cursor_position =
+ payload.position.map(|pos| (pos.x as usize, pos.y as usize));
+ Ok(PluginCommand::ShowCursor(cursor_position))
+ },
+ _ => Err("Mismatched payload for ShowCursor"),
+ },
Some(CommandName::GetPluginIds) => {
if protobuf_plugin_command.payload.is_some() {
Err("GetPluginIds should not have a payload")
@@ -734,7 +769,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(Payload::FocusTerminalPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
- Ok(PluginCommand::FocusTerminalPane(pane_id, should_float))
+ let should_be_in_place = payload.should_be_in_place;
+ Ok(PluginCommand::FocusTerminalPane(
+ pane_id,
+ should_float,
+ should_be_in_place,
+ ))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
@@ -742,7 +782,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(Payload::FocusPluginPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
- Ok(PluginCommand::FocusPluginPane(pane_id, should_float))
+ let should_be_in_place = payload.should_be_in_place;
+ Ok(PluginCommand::FocusPluginPane(
+ pane_id,
+ should_float,
+ should_be_in_place,
+ ))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
@@ -1048,9 +1093,11 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
.and_then(|p_id| PaneId::try_from(p_id).ok())
.ok_or("Failed to parse ShowPaneWithId command")?;
let should_float_if_hidden = show_pane_with_id_payload.should_float_if_hidden;
+ let should_focus_pane = show_pane_with_id_payload.should_focus_pane;
Ok(PluginCommand::ShowPaneWithId(
pane_id,
should_float_if_hidden,
+ should_focus_pane,
))
},
_ => Err("Mismatched payload for ShowPaneWithId"),
@@ -1145,6 +1192,29 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for WriteCharsCharsToPaneId"),
},
+ Some(CommandName::SendSigintToPaneId) => match protobuf_plugin_command.payload {
+ Some(Payload::SendSigintToPaneIdPayload(pane_id)) => {
+ Ok(PluginCommand::SendSigintToPaneId(pane_id.try_into()?))
+ },
+ _ => Err("Mismatched payload for SendSigintToPaneId"),
+ },
+ Some(CommandName::SendSigkillToPaneId) => match protobuf_plugin_command.payload {
+ Some(Payload::SendSigkillToPaneIdPayload(pane_id)) => {
+ Ok(PluginCommand::SendSigkillToPaneId(pane_id.try_into()?))
+ },
+ _ => Err("Mismatched payload for SendSigkillToPaneId"),
+ },
+ Some(CommandName::GetPanePid) => match protobuf_plugin_command.payload {
+ Some(Payload::GetPanePidPayload(get_pane_pid_payload)) => {
+ match get_pane_pid_payload.pane_id {
+ Some(pane_id) => Ok(PluginCommand::GetPanePid {
+ pane_id: pane_id.try_into()?,
+ }),
+ _ => Err("Malformed get_pane_pid payload"),
+ }
+ },
+ _ => Err("Mismatched payload for GetPanePid"),
+ },
Some(CommandName::MovePaneWithPaneId) => match protobuf_plugin_command.payload {
Some(Payload::MovePaneWithPaneIdPayload(move_pane_with_pane_id_payload)) => {
match move_pane_with_pane_id_payload.pane_id {
@@ -1745,9 +1815,35 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
.existing_pane_id
.and_then(|p_id| PaneId::try_from(p_id).ok())
.ok_or("Failed to parse ReplacePaneWithExistingPanePayload")?,
+ replace_pane_with_other_pane_payload.suppress_replaced_pane,
)),
_ => Err("Mismatched payload for ReplacePaneWithExistingPane"),
},
+ Some(CommandName::RunAction) => match protobuf_plugin_command.payload {
+ Some(Payload::RunActionPayload(protobuf_payload)) => {
+ let action = Action::try_from(
+ protobuf_payload
+ .action
+ .ok_or("Missing action in RunAction payload")?,
+ )
+ .map_err(|_| "Failed to convert protobuf action")?;
+
+ let context: BTreeMap<String, String> = protobuf_payload
+ .context
+ .into_iter()
+ .map(|item| (item.name, item.value))
+ .collect();
+
+ Ok(PluginCommand::RunAction(action, context))
+ },
+ _ => Err("Mismatched payload for RunAction"),
+ },
+ Some(CommandName::CopyToClipboard) => match protobuf_plugin_command.payload {
+ Some(Payload::CopyToClipboardPayload(payload)) => {
+ Ok(PluginCommand::CopyToClipboard(payload.text))
+ },
+ _ => Err("Mismatched payload for CopyToClipboard"),
+ },
None => Err("Unrecognized plugin command"),
}
}
@@ -1779,6 +1875,16 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::SetSelectable as i32,
payload: Some(Payload::SetSelectablePayload(should_be_selectable)),
}),
+ PluginCommand::ShowCursor(cursor_position) => {
+ let position = cursor_position.map(|(x, y)| CursorPosition {
+ x: x as u32,
+ y: y as u32,
+ });
+ Ok(ProtobufPluginCommand {
+ name: CommandName::ShowCursor as i32,
+ payload: Some(Payload::ShowCursorPayload(ShowCursorPayload { position })),
+ })
+ },
PluginCommand::GetPluginIds => Ok(ProtobufPluginCommand {
name: CommandName::GetPluginIds as i32,
payload: None,
@@ -2077,24 +2183,30 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::ClosePluginPane as i32,
payload: Some(Payload::ClosePluginPanePayload(pane_id)),
}),
- PluginCommand::FocusTerminalPane(pane_id, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::FocusTerminalPane as i32,
- payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat {
- pane_id: pane_id,
- should_float: should_float_if_hidden,
- })),
- })
- },
- PluginCommand::FocusPluginPane(pane_id, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::FocusPluginPane as i32,
- payload: Some(Payload::FocusPluginPanePayload(PaneIdAndShouldFloat {
- pane_id: pane_id,
- should_float: should_float_if_hidden,
- })),
- })
- },
+ PluginCommand::FocusTerminalPane(
+ pane_id,
+ should_float_if_hidden,
+ should_be_in_place_if_hidden,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::FocusTerminalPane as i32,
+ payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat {
+ pane_id: pane_id,
+ should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
+ })),
+ }),
+ PluginCommand::FocusPluginPane(
+ pane_id,
+ should_float_if_hidden,
+ should_be_in_place_if_hidden,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::FocusPluginPane as i32,
+ payload: Some(Payload::FocusPluginPanePayload(PaneIdAndShouldFloat {
+ pane_id: pane_id,
+ should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
+ })),
+ }),
PluginCommand::RenameTerminalPane(pane_id, new_name) => Ok(ProtobufPluginCommand {
name: CommandName::RenameTerminalPane as i32,
payload: Some(Payload::RenameTerminalPanePayload(IdAndNewName {
@@ -2335,15 +2447,18 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
pane_id: ProtobufPaneId::try_from(pane_id_to_hide).ok(),
})),
}),
- PluginCommand::ShowPaneWithId(pane_id_to_show, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::ShowPaneWithId as i32,
- payload: Some(Payload::ShowPaneWithIdPayload(ShowPaneWithIdPayload {
- pane_id: ProtobufPaneId::try_from(pane_id_to_show).ok(),
- should_float_if_hidden,
- })),
- })
- },
+ PluginCommand::ShowPaneWithId(
+ pane_id_to_show,
+ should_float_if_hidden,
+ should_focus_pane,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::ShowPaneWithId as i32,
+ payload: Some(Payload::ShowPaneWithIdPayload(ShowPaneWithIdPayload {
+ pane_id: ProtobufPaneId::try_from(pane_id_to_show).ok(),
+ should_float_if_hidden,
+ should_focus_pane,
+ })),
+ }),
PluginCommand::OpenCommandPaneBackground(command_to_run, context) => {
let context: Vec<_> = context
.into_iter()
@@ -2415,6 +2530,20 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
)),
})
},
+ PluginCommand::SendSigintToPaneId(pane_id) => Ok(ProtobufPluginCommand {
+ name: CommandName::SendSigintToPaneId as i32,
+ payload: Some(Payload::SendSigintToPaneIdPayload(pane_id.try_into()?)),
+ }),
+ PluginCommand::SendSigkillToPaneId(pane_id) => Ok(ProtobufPluginCommand {
+ name: CommandName::SendSigkillToPaneId as i32,
+ payload: Some(Payload::SendSigkillToPaneIdPayload(pane_id.try_into()?)),
+ }),
+ PluginCommand::GetPanePid { pane_id } => Ok(ProtobufPluginCommand {
+ name: CommandName::GetPanePid as i32,
+ payload: Some(Payload::GetPanePidPayload(GetPanePidPayload {
+ pane_id: Some(pane_id.try_into()?),
+ })),
+ }),
PluginCommand::MovePaneWithPaneId(pane_id) => Ok(ProtobufPluginCommand {
name: CommandName::MovePaneWithPaneId as i32,
payload: Some(Payload::MovePaneWithPaneIdPayload(
@@ -2901,17 +3030,43 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::ClearKeyPressesIntercepts as i32,
payload: None,
}),
- PluginCommand::ReplacePaneWithExistingPane(pane_id_to_replace, existing_pane_id) => {
+ PluginCommand::ReplacePaneWithExistingPane(
+ pane_id_to_replace,
+ existing_pane_id,
+ suppress_replaced_pane,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::ReplacePaneWithExistingPane as i32,
+ payload: Some(Payload::ReplacePaneWithExistingPanePayload(
+ ReplacePaneWithExistingPanePayload {
+ pane_id_to_replace: ProtobufPaneId::try_from(pane_id_to_replace).ok(),
+ existing_pane_id: ProtobufPaneId::try_from(existing_pane_id).ok(),
+ suppress_replaced_pane,
+ },
+ )),
+ }),
+ PluginCommand::RunAction(action, context) => {
+ let protobuf_action = ProtobufAction::try_from(action)
+ .map_err(|_| "Failed to convert action to protobuf")?;
+
+ let context_items: Vec<ContextItem> = context
+ .into_iter()
+ .map(|(name, value)| ContextItem { name, value })
+ .collect();
+
Ok(ProtobufPluginCommand {
- name: CommandName::ReplacePaneWithExistingPane as i32,
- payload: Some(Payload::ReplacePaneWithExistingPanePayload(
- ReplacePaneWithExistingPanePayload {
- pane_id_to_replace: ProtobufPaneId::try_from(pane_id_to_replace).ok(),
- existing_pane_id: ProtobufPaneId::try_from(existing_pane_id).ok(),
- },
- )),
+ name: CommandName::RunAction as i32,
+ payload: Some(Payload::RunActionPayload(RunActionPayload {
+ action: Some(protobuf_action),
+ context: context_items,
+ })),
})
},
+ PluginCommand::CopyToClipboard(text) => Ok(ProtobufPluginCommand {
+ name: CommandName::CopyToClipboard as i32,
+ payload: Some(Payload::CopyToClipboardPayload(CopyToClipboardPayload {
+ text,
+ })),
+ }),
}
}
}
diff --git a/zellij-utils/src/plugin_api/plugin_permission.proto b/zellij-utils/src/plugin_api/plugin_permission.proto
index 85126830..779f6b32 100644
--- a/zellij-utils/src/plugin_api/plugin_permission.proto
+++ b/zellij-utils/src/plugin_api/plugin_permission.proto
@@ -17,4 +17,6 @@ enum PermissionType {
StartWebServer = 11;
InterceptInput = 12;
ReadPaneContents = 13;
+ RunActionsAsUser = 14;
+ WriteToClipboard = 15;
}
diff --git a/zellij-utils/src/plugin_api/plugin_permission.rs b/zellij-utils/src/plugin_api/plugin_permission.rs
index 307d3576..37782a08 100644
--- a/zellij-utils/src/plugin_api/plugin_permission.rs
+++ b/zellij-utils/src/plugin_api/plugin_permission.rs
@@ -29,6 +29,8 @@ impl TryFrom<ProtobufPermissionType> for PermissionType {
ProtobufPermissionType::StartWebServer => Ok(PermissionType::StartWebServer),
ProtobufPermissionType::InterceptInput => Ok(PermissionType::InterceptInput),
ProtobufPermissionType::ReadPaneContents => Ok(PermissionType::ReadPaneContents),
+ ProtobufPermissionType::RunActionsAsUser => Ok(PermissionType::RunActionsAsUser),
+ ProtobufPermissionType::WriteToClipboard => Ok(PermissionType::WriteToClipboard),
}
}
}
@@ -59,6 +61,8 @@ impl TryFrom<PermissionType> for ProtobufPermissionType {
PermissionType::StartWebServer => Ok(ProtobufPermissionType::StartWebServer),
PermissionType::InterceptInput => Ok(ProtobufPermissionType::InterceptInput),
PermissionType::ReadPaneContents => Ok(ProtobufPermissionType::ReadPaneContents),
+ PermissionType::RunActionsAsUser => Ok(ProtobufPermissionType::RunActionsAsUser),
+ PermissionType::WriteToClipboard => Ok(ProtobufPermissionType::WriteToClipboard),
}
}
}
diff --git a/zellij-utils/src/position.rs b/zellij-utils/src/position.rs
index 3566625b..95987c27 100644
--- a/zellij-utils/src/position.rs
+++ b/zellij-utils/src/position.rs
@@ -37,6 +37,12 @@ impl Position {
}
}
+impl Default for Position {
+ fn default() -> Self {
+ Position::new(0, 0)
+ }
+}
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)]
pub struct Line(pub isize);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)]
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index e01b7410..3e60c317 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -1348,6 +1348,7 @@ Config {
NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
},
SwitchToMode {
input_mode: Normal,
@@ -1853,6 +1854,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -4446,6 +4449,36 @@ Config {
bare_key: Char(
'q',
),
+ key_modifiers: {},
+ }: [
+ LaunchPlugin {
+ plugin: RunPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
+ ),
+ should_float: true,
+ should_open_in_place: false,
+ skip_cache: false,
+ cwd: None,
+ },
+ SwitchToMode {
+ input_mode: Normal,
+ },
+ ],
+ KeyWithModifier {
+ bare_key: Char(
+ 'q',
+ ),
key_modifiers: {
Ctrl,
},
@@ -5584,6 +5617,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -6036,6 +6071,18 @@ Config {
),
initial_cwd: None,
},
+ "sequence": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index 9c280d9c..e0fa22f9 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -1348,6 +1348,7 @@ Config {
NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
},
SwitchToMode {
input_mode: Normal,
@@ -1853,6 +1854,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -4446,6 +4449,36 @@ Config {
bare_key: Char(
'q',
),
+ key_modifiers: {},
+ }: [
+ LaunchPlugin {
+ plugin: RunPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
+ ),
+ should_float: true,
+ should_open_in_place: false,
+ skip_cache: false,
+ cwd: None,
+ },
+ SwitchToMode {
+ input_mode: Normal,
+ },
+ ],
+ KeyWithModifier {
+ bare_key: Char(
+ 'q',
+ ),
key_modifiers: {
Ctrl,
},
@@ -5584,6 +5617,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -6036,6 +6071,18 @@ Config {
),
initial_cwd: None,
},
+ "sequence": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
index 773ae7e4..1d245a3e 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
@@ -195,6 +195,18 @@ Config {
),
initial_cwd: None,
},
+ "sequence": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index 0406faa8..e9935ef3 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -1348,6 +1348,7 @@ Config {
NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
},
SwitchToMode {
input_mode: Normal,
@@ -1853,6 +1854,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -4446,6 +4449,36 @@ Config {
bare_key: Char(
'q',
),
+ key_modifiers: {},
+ }: [
+ LaunchPlugin {
+ plugin: RunPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
+ ),
+ should_float: true,
+ should_open_in_place: false,
+ skip_cache: false,
+ cwd: None,
+ },
+ SwitchToMode {
+ input_mode: Normal,
+ },
+ ],
+ KeyWithModifier {
+ bare_key: Char(
+ 'q',
+ ),
key_modifiers: {
Ctrl,
},
@@ -5584,6 +5617,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -7807,6 +7842,18 @@ Config {
),
initial_cwd: None,
},
+ "sequence": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index ca2b7bae..368b5d88 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -1348,6 +1348,7 @@ Config {
NewStackedPane {
command: None,
pane_name: None,
+ near_current_pane: false,
},
SwitchToMode {
input_mode: Normal,
@@ -1853,6 +1854,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -4446,6 +4449,36 @@ Config {
bare_key: Char(
'q',
),
+ key_modifiers: {},
+ }: [
+ LaunchPlugin {
+ plugin: RunPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
+ ),
+ should_float: true,
+ should_open_in_place: false,
+ skip_cache: false,
+ cwd: None,
+ },
+ SwitchToMode {
+ input_mode: Normal,
+ },
+ ],
+ KeyWithModifier {
+ bare_key: Char(
+ 'q',
+ ),
key_modifiers: {
Ctrl,
},
@@ -5584,6 +5617,8 @@ Config {
tab_name: None,
should_change_focus_to_new_tab: true,
cwd: None,
+ initial_panes: None,
+ first_pane_unblock_condition: None,
},
SwitchToMode {
input_mode: Normal,
@@ -6036,6 +6071,18 @@ Config {
),
initial_cwd: None,
},
+ "sequence": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "sequence",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(