aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/plugin_api
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/plugin_api
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/plugin_api')
-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
8 files changed, 2040 insertions, 107 deletions
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),
}
}
}