diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-12-11 14:25:42 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-11 14:25:42 +0100 |
| commit | 693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch) | |
| tree | b3f651462bbc1cd2fa5b2d79207343413ef6b83c /zellij-utils/src/plugin_api | |
| parent | fix(zellij-server): unblock building on 32bit targets (#4484) (diff) | |
| download | sl0ppij-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.proto | 295 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 1340 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/event.proto | 31 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/event.rs | 173 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 43 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 259 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.proto | 2 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.rs | 4 |
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), } } } |
