diff options
| author | Aram Drevekenin <aram@poor.dev> | 2023-08-09 22:26:00 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-09 22:26:00 +0200 |
| commit | 1bedfc90021558cb201695444107afe5bddd2c17 (patch) | |
| tree | 38dd31b5ab112aa9b1c3a54edb07331013bf7d87 /zellij-utils/src/plugin_api | |
| parent | docs(changelog): plug memory leak (diff) | |
| download | sl0ppij-1bedfc90021558cb201695444107afe5bddd2c17.tar.gz sl0ppij-1bedfc90021558cb201695444107afe5bddd2c17.tar.xz sl0ppij-1bedfc90021558cb201695444107afe5bddd2c17.zip | |
feat(plugins): use protocol buffers for serializing across the wasm boundary (#2686)
* work
* almost done with command protobuffers
* done translating command data structures
* mid transferring of every command to protobuff command
* transferred plugin_command.rs, now moving on to shim.rs
* plugin command working with protobufs
* protobuffers in update
* protobuf event tests
* various TODOs and comments
* fix zellij-tile
* clean up prost deps
* remove version mismatch error
* fix panic
* some cleanups
* clean up event protobuffers
* clean up command protobuffers
* clean up various protobufs
* refactor protobufs
* update comments
* some transformation fixes
* use protobufs for workers
* style(fmt): rustfmt
* style(fmt): rustfmt
* chore(build): add protoc
* chore(build): authenticate protoc
Diffstat (limited to 'zellij-utils/src/plugin_api')
23 files changed, 4779 insertions, 0 deletions
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto new file mode 100644 index 00000000..05fa5c6e --- /dev/null +++ b/zellij-utils/src/plugin_api/action.proto @@ -0,0 +1,246 @@ +syntax = "proto3"; + +import "input_mode.proto"; +import "resize.proto"; + +package api.action; + +message Action { + ActionName name = 1; + oneof optional_payload { + SwitchToModePayload switch_to_mode_payload = 2; + WritePayload write_payload = 3; + WriteCharsPayload write_chars_payload = 4; + SwitchToModePayload switch_mode_for_all_clients_payload = 5; + resize.Resize resize_payload = 6; + resize.ResizeDirection move_focus_payload = 7; + resize.ResizeDirection move_focus_or_tab_payload = 8; + MovePanePayload move_pane_payload = 9; + DumpScreenPayload dump_screen_payload = 10; + ScrollAtPayload scroll_up_at_payload = 11; + ScrollAtPayload scroll_down_at_payload = 12; + NewPanePayload new_pane_payload = 13; + EditFilePayload edit_file_payload = 14; + NewFloatingPanePayload new_floating_pane_payload = 15; + NewTiledPanePayload new_tiled_pane_payload = 16; + bytes pane_name_input_payload = 17; + uint32 go_to_tab_payload = 18; + GoToTabNamePayload go_to_tab_name_payload = 19; + bytes tab_name_input_payload = 20; + RunCommandAction run_payload = 21; + Position left_click_payload = 22; + Position right_click_payload = 23; + Position middle_click_payload = 24; + LaunchOrFocusPluginPayload launch_or_focus_plugin_payload = 25; + Position left_mouse_release_payload = 26; + Position right_mouse_release_payload = 27; + Position middle_mouse_release_payload = 28; + Position mouse_hold_left_payload = 29; + Position mouse_hold_right_payload = 30; + Position mouse_hold_middle_payload = 31; + bytes search_input_payload = 32; + SearchDirection search_payload = 33; + SearchOption search_toggle_option_payload = 34; + NewPluginPanePayload new_tiled_plugin_pane_payload = 35; + NewPluginPanePayload new_floating_plugin_pane_payload = 36; + string start_or_reload_plugin_payload = 37; + uint32 close_terminal_pane_payload = 38; + uint32 close_plugin_pane_payload = 39; + PaneIdAndShouldFloat focus_terminal_pane_with_id_payload = 40; + PaneIdAndShouldFloat focus_plugin_pane_with_id_payload = 41; + IdAndName rename_terminal_pane_payload = 42; + IdAndName rename_plugin_pane_payload = 43; + IdAndName rename_tab_payload = 44; + } +} + +message IdAndName { + bytes name = 1; + uint32 id = 2; +} + +message PaneIdAndShouldFloat { + uint32 pane_id = 1; + bool should_float_if_hidden = 2; +} + +message NewPluginPanePayload { + string plugin_url = 1; + optional string pane_name = 2; +} + +enum SearchDirection { + Up = 0; + Down = 1; +} + +enum SearchOption { + CaseSensitivity = 0; + WholeWord = 1; + Wrap = 2; +} + +message LaunchOrFocusPluginPayload { + string plugin_url = 1; + bool should_float = 2; + optional PluginConfiguration plugin_configuration = 3; +} + +message GoToTabNamePayload { + string tab_name = 1; + bool create = 2; +} + +message NewFloatingPanePayload { + optional RunCommandAction command = 1; +} + +message NewTiledPanePayload { + optional RunCommandAction command = 1; + optional resize.ResizeDirection direction = 2; +} + +message MovePanePayload { + optional resize.ResizeDirection direction = 1; +} + +message EditFilePayload { + string file_to_edit = 1; + optional uint32 line_number = 2; + optional string cwd = 3; + optional resize.ResizeDirection direction = 4; + bool should_float = 5; +} + +message ScrollAtPayload { + Position position = 1; +} + +message NewPanePayload { + optional resize.ResizeDirection direction = 1; + optional string pane_name = 2; +} + +message SwitchToModePayload { + input_mode.InputMode input_mode = 1; +} + +message WritePayload { + bytes bytes_to_write = 1; +} + +message WriteCharsPayload { + string chars = 1; +} + +message DumpScreenPayload { + string file_path = 1; + bool include_scrollback = 2; +} + +enum ActionName { + Quit = 0; + Write = 1; + WriteChars = 2; + SwitchToMode = 3; + SwitchModeForAllClients = 4; + Resize = 5; + FocusNextPane = 6; + FocusPreviousPane = 7; + SwitchFocus = 8; + MoveFocus = 9; + MoveFocusOrTab = 10; + MovePane = 11; + MovePaneBackwards = 12; + ClearScreen = 13; + DumpScreen = 14; + EditScrollback = 15; + ScrollUp = 16; + ScrollUpAt = 17; + ScrollDown = 18; + ScrollDownAt = 19; + ScrollToBottom = 20; + ScrollToTop = 21; + PageScrollUp = 22; + PageScrollDown = 23; + HalfPageScrollUp = 24; + HalfPageScrollDown = 25; + ToggleFocusFullscreen = 26; + TogglePaneFrames = 27; + ToggleActiveSyncTab = 28; + NewPane = 29; + EditFile = 30; + NewFloatingPane = 31; + NewTiledPane = 32; + TogglePaneEmbedOrFloating = 33; + ToggleFloatingPanes = 34; + CloseFocus = 35; + PaneNameInput = 36; + UndoRenamePane = 37; + NewTab = 38; + NoOp = 39; + GoToNextTab = 40; + GoToPreviousTab = 41; + CloseTab = 42; + GoToTab = 43; + GoToTabName = 44; + ToggleTab = 45; + TabNameInput = 46; + UndoRenameTab = 47; + Run = 48; + Detach = 49; + LeftClick = 50; + RightClick = 51; + MiddleClick = 52; + LaunchOrFocusPlugin = 53; + LeftMouseRelease = 54; + RightMouseRelease = 55; + MiddleMouseRelease = 56; + MouseHoldLeft = 57; + MouseHoldRight = 58; + MouseHoldMiddle = 59; + SearchInput = 60; + Search = 61; + SearchToggleOption = 62; + ToggleMouseMode = 63; + PreviousSwapLayout = 64; + NextSwapLayout = 65; + QueryTabNames = 66; + NewTiledPluginPane = 67; + NewFloatingPluginPane = 68; + StartOrReloadPlugin = 69; + CloseTerminalPane = 70; + ClosePluginPane = 71; + FocusTerminalPaneWithId = 72; + FocusPluginPaneWithId = 73; + RenameTerminalPane = 74; + RenamePluginPane = 75; + RenameTab = 76; + BreakPane = 77; + BreakPaneRight = 78; + BreakPaneLeft = 79; +} + +message Position { + int64 line = 1; + int64 column = 2; +} + +message RunCommandAction { + string command = 1; + repeated string args = 2; + optional string cwd = 3; + optional resize.ResizeDirection direction = 4; + optional string pane_name = 5; + bool hold_on_close = 6; + bool hold_on_start = 7; +} + +message PluginConfiguration { + repeated NameAndValue name_and_value = 1; +} + +message NameAndValue { + string name = 1; + string value = 2; +} diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs new file mode 100644 index 00000000..1a964fa9 --- /dev/null +++ b/zellij-utils/src/plugin_api/action.rs @@ -0,0 +1,1304 @@ +pub use super::generated_api::api::{ + action::{ + action::OptionalPayload, Action as ProtobufAction, ActionName as ProtobufActionName, + DumpScreenPayload, EditFilePayload, GoToTabNamePayload, IdAndName, + LaunchOrFocusPluginPayload, MovePanePayload, 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, + }, + input_mode::InputMode as ProtobufInputMode, + resize::{Resize as ProtobufResize, ResizeDirection as ProtobufResizeDirection}, +}; +use crate::data::{Direction, InputMode, ResizeStrategy}; +use crate::errors::prelude::*; +use crate::input::actions::Action; +use crate::input::actions::{SearchDirection, SearchOption}; +use crate::input::command::RunCommandAction; +use crate::input::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation}; +use crate::position::Position; +use url::Url; + +use std::collections::BTreeMap; +use std::convert::TryFrom; +use std::path::PathBuf; + +impl TryFrom<ProtobufAction> for Action { + type Error = &'static str; + fn try_from(protobuf_action: ProtobufAction) -> Result<Self, &'static str> { + match ProtobufActionName::from_i32(protobuf_action.name) { + Some(ProtobufActionName::Quit) => match protobuf_action.optional_payload { + Some(_) => Err("The Quit Action should not have a payload"), + None => Ok(Action::Quit), + }, + Some(ProtobufActionName::Write) => match protobuf_action.optional_payload { + Some(OptionalPayload::WritePayload(write_payload)) => { + Ok(Action::Write(write_payload.bytes_to_write)) + }, + _ => Err("Wrong payload for Action::Write"), + }, + Some(ProtobufActionName::WriteChars) => match protobuf_action.optional_payload { + Some(OptionalPayload::WriteCharsPayload(write_chars_payload)) => { + Ok(Action::WriteChars(write_chars_payload.chars)) + }, + _ => Err("Wrong payload for Action::WriteChars"), + }, + Some(ProtobufActionName::SwitchToMode) => match protobuf_action.optional_payload { + Some(OptionalPayload::SwitchToModePayload(switch_to_mode_payload)) => { + let input_mode: InputMode = + ProtobufInputMode::from_i32(switch_to_mode_payload.input_mode) + .ok_or("Malformed input mode for SwitchToMode Action")? + .try_into()?; + Ok(Action::SwitchToMode(input_mode)) + }, + _ => Err("Wrong payload for Action::SwitchToModePayload"), + }, + Some(ProtobufActionName::SwitchModeForAllClients) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::SwitchModeForAllClientsPayload( + switch_to_mode_payload, + )) => { + let input_mode: InputMode = + ProtobufInputMode::from_i32(switch_to_mode_payload.input_mode) + .ok_or("Malformed input mode for SwitchToMode Action")? + .try_into()?; + Ok(Action::SwitchModeForAllClients(input_mode)) + }, + _ => Err("Wrong payload for Action::SwitchModeForAllClients"), + } + }, + Some(ProtobufActionName::Resize) => match protobuf_action.optional_payload { + Some(OptionalPayload::ResizePayload(resize_payload)) => { + let resize_strategy: ResizeStrategy = resize_payload.try_into()?; + Ok(Action::Resize( + resize_strategy.resize, + resize_strategy.direction, + )) + }, + _ => Err("Wrong payload for Action::Resize"), + }, + Some(ProtobufActionName::FocusNextPane) => match protobuf_action.optional_payload { + Some(_) => Err("FocusNextPane should not have a payload"), + None => Ok(Action::FocusNextPane), + }, + Some(ProtobufActionName::FocusPreviousPane) => match protobuf_action.optional_payload { + Some(_) => Err("FocusPreviousPane should not have a payload"), + None => Ok(Action::FocusPreviousPane), + }, + Some(ProtobufActionName::SwitchFocus) => match protobuf_action.optional_payload { + Some(_) => Err("SwitchFocus should not have a payload"), + None => Ok(Action::SwitchFocus), + }, + Some(ProtobufActionName::MoveFocus) => match protobuf_action.optional_payload { + Some(OptionalPayload::MoveFocusPayload(move_focus_payload)) => { + let direction: Direction = + ProtobufResizeDirection::from_i32(move_focus_payload) + .ok_or("Malformed resize direction for Action::MoveFocus")? + .try_into()?; + Ok(Action::MoveFocus(direction)) + }, + _ => Err("Wrong payload for Action::MoveFocus"), + }, + Some(ProtobufActionName::MoveFocusOrTab) => match protobuf_action.optional_payload { + Some(OptionalPayload::MoveFocusOrTabPayload(move_focus_or_tab_payload)) => { + let direction: Direction = + ProtobufResizeDirection::from_i32(move_focus_or_tab_payload) + .ok_or("Malformed resize direction for Action::MoveFocusOrTab")? + .try_into()?; + Ok(Action::MoveFocusOrTab(direction)) + }, + _ => Err("Wrong payload for Action::MoveFocusOrTab"), + }, + Some(ProtobufActionName::MovePane) => match protobuf_action.optional_payload { + Some(OptionalPayload::MovePanePayload(payload)) => { + let direction: Option<Direction> = payload + .direction + .and_then(|d| ProtobufResizeDirection::from_i32(d)) + .and_then(|d| d.try_into().ok()); + Ok(Action::MovePane(direction)) + }, + _ => Err("Wrong payload for Action::MovePane"), + }, + Some(ProtobufActionName::MovePaneBackwards) => match protobuf_action.optional_payload { + Some(_) => Err("MovePaneBackwards should not have a payload"), + None => Ok(Action::MovePaneBackwards), + }, + Some(ProtobufActionName::ClearScreen) => match protobuf_action.optional_payload { + Some(_) => Err("ClearScreen should not have a payload"), + None => Ok(Action::ClearScreen), + }, + Some(ProtobufActionName::DumpScreen) => match protobuf_action.optional_payload { + Some(OptionalPayload::DumpScreenPayload(payload)) => { + let file_path = payload.file_path; + let include_scrollback = payload.include_scrollback; + Ok(Action::DumpScreen(file_path, include_scrollback)) + }, + _ => Err("Wrong payload for Action::DumpScreen"), + }, + Some(ProtobufActionName::EditScrollback) => match protobuf_action.optional_payload { + Some(_) => Err("EditScrollback should not have a payload"), + None => Ok(Action::EditScrollback), + }, + Some(ProtobufActionName::ScrollUp) => match protobuf_action.optional_payload { + Some(_) => Err("ScrollUp should not have a payload"), + None => Ok(Action::ScrollUp), + }, + Some(ProtobufActionName::ScrollDown) => match protobuf_action.optional_payload { + Some(_) => Err("ScrollDown should not have a payload"), + None => Ok(Action::ScrollDown), + }, + Some(ProtobufActionName::ScrollUpAt) => match protobuf_action.optional_payload { + Some(OptionalPayload::ScrollUpAtPayload(payload)) => { + let position = payload + .position + .ok_or("ScrollUpAtPayload must have a position")? + .try_into()?; + Ok(Action::ScrollUpAt(position)) + }, + _ => Err("Wrong payload for Action::ScrollUpAt"), + }, + Some(ProtobufActionName::ScrollDownAt) => match protobuf_action.optional_payload { + Some(OptionalPayload::ScrollDownAtPayload(payload)) => { + let position = payload + .position + .ok_or("ScrollDownAtPayload must have a position")? + .try_into()?; + Ok(Action::ScrollDownAt(position)) + }, + _ => Err("Wrong payload for Action::ScrollDownAt"), + }, + Some(ProtobufActionName::ScrollToBottom) => match protobuf_action.optional_payload { + Some(_) => Err("ScrollToBottom should not have a payload"), + None => Ok(Action::ScrollToBottom), + }, + Some(ProtobufActionName::ScrollToTop) => match protobuf_action.optional_payload { + Some(_) => Err("ScrollToTop should not have a payload"), + None => Ok(Action::ScrollToTop), + }, + Some(ProtobufActionName::PageScrollUp) => match protobuf_action.optional_payload { + Some(_) => Err("PageScrollUp should not have a payload"), + None => Ok(Action::PageScrollUp), + }, + Some(ProtobufActionName::PageScrollDown) => match protobuf_action.optional_payload { + Some(_) => Err("PageScrollDown should not have a payload"), + None => Ok(Action::PageScrollDown), + }, + Some(ProtobufActionName::HalfPageScrollUp) => match protobuf_action.optional_payload { + Some(_) => Err("HalfPageScrollUp should not have a payload"), + None => Ok(Action::HalfPageScrollUp), + }, + Some(ProtobufActionName::HalfPageScrollDown) => { + match protobuf_action.optional_payload { + Some(_) => Err("HalfPageScrollDown should not have a payload"), + None => Ok(Action::HalfPageScrollDown), + } + }, + Some(ProtobufActionName::ToggleFocusFullscreen) => { + match protobuf_action.optional_payload { + Some(_) => Err("ToggleFocusFullscreen should not have a payload"), + None => Ok(Action::ToggleFocusFullscreen), + } + }, + Some(ProtobufActionName::TogglePaneFrames) => match protobuf_action.optional_payload { + Some(_) => Err("TogglePaneFrames should not have a payload"), + None => Ok(Action::TogglePaneFrames), + }, + Some(ProtobufActionName::ToggleActiveSyncTab) => { + match protobuf_action.optional_payload { + Some(_) => Err("ToggleActiveSyncTab should not have a payload"), + None => Ok(Action::ToggleActiveSyncTab), + } + }, + Some(ProtobufActionName::NewPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::NewPanePayload(payload)) => { + let direction: Option<Direction> = payload + .direction + .and_then(|d| ProtobufResizeDirection::from_i32(d)) + .and_then(|d| d.try_into().ok()); + let pane_name = payload.pane_name; + Ok(Action::NewPane(direction, pane_name)) + }, + _ => Err("Wrong payload for Action::NewPane"), + }, + Some(ProtobufActionName::EditFile) => match protobuf_action.optional_payload { + Some(OptionalPayload::EditFilePayload(payload)) => { + let file_to_edit = PathBuf::from(payload.file_to_edit); + let line_number: Option<usize> = payload.line_number.map(|l| l as usize); + let cwd: Option<PathBuf> = payload.cwd.map(|p| PathBuf::from(p)); + let direction: Option<Direction> = payload + .direction + .and_then(|d| ProtobufResizeDirection::from_i32(d)) + .and_then(|d| d.try_into().ok()); + let should_float = payload.should_float; + Ok(Action::EditFile( + file_to_edit, + line_number, + cwd, + direction, + should_float, + )) + }, + _ => Err("Wrong payload for Action::NewPane"), + }, + Some(ProtobufActionName::NewFloatingPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::NewFloatingPanePayload(payload)) => { + if let Some(payload) = payload.command { + let pane_name = payload.pane_name.clone(); + let run_command_action: RunCommandAction = payload.try_into()?; + Ok(Action::NewFloatingPane(Some(run_command_action), pane_name)) + } else { + Ok(Action::NewFloatingPane(None, None)) + } + }, + _ => Err("Wrong payload for Action::NewFloatingPane"), + }, + Some(ProtobufActionName::NewTiledPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::NewTiledPanePayload(payload)) => { + let direction: Option<Direction> = payload + .direction + .and_then(|d| ProtobufResizeDirection::from_i32(d)) + .and_then(|d| d.try_into().ok()); + if let Some(payload) = payload.command { + let pane_name = payload.pane_name.clone(); + let run_command_action: RunCommandAction = payload.try_into()?; + Ok(Action::NewTiledPane( + direction, + Some(run_command_action), + pane_name, + )) + } else { + Ok(Action::NewTiledPane(direction, None, None)) + } + }, + _ => Err("Wrong payload for Action::NewTiledPane"), + }, + Some(ProtobufActionName::TogglePaneEmbedOrFloating) => { + match protobuf_action.optional_payload { + Some(_) => Err("TogglePaneEmbedOrFloating should not have a payload"), + None => Ok(Action::TogglePaneEmbedOrFloating), + } + }, + Some(ProtobufActionName::ToggleFloatingPanes) => { + match protobuf_action.optional_payload { + Some(_) => Err("ToggleFloatingPanes should not have a payload"), + None => Ok(Action::ToggleFloatingPanes), + } + }, + Some(ProtobufActionName::CloseFocus) => match protobuf_action.optional_payload { + Some(_) => Err("CloseFocus should not have a payload"), + None => Ok(Action::CloseFocus), + }, + Some(ProtobufActionName::PaneNameInput) => match protobuf_action.optional_payload { + Some(OptionalPayload::PaneNameInputPayload(bytes)) => { + Ok(Action::PaneNameInput(bytes)) + }, + _ => Err("Wrong payload for Action::PaneNameInput"), + }, + Some(ProtobufActionName::UndoRenamePane) => match protobuf_action.optional_payload { + Some(_) => Err("UndoRenamePane should not have a payload"), + None => Ok(Action::UndoRenamePane), + }, + Some(ProtobufActionName::NewTab) => { + match protobuf_action.optional_payload { + Some(_) => Err("NewTab should not have a payload"), + None => { + // we do not serialize the layouts of this action + Ok(Action::NewTab(None, vec![], None, None, None)) + }, + } + }, + Some(ProtobufActionName::NoOp) => match protobuf_action.optional_payload { + Some(_) => Err("NoOp should not have a payload"), + None => Ok(Action::NoOp), + }, + Some(ProtobufActionName::GoToNextTab) => match protobuf_action.optional_payload { + Some(_) => Err("GoToNextTab should not have a payload"), + None => Ok(Action::GoToNextTab), + }, + Some(ProtobufActionName::GoToPreviousTab) => match protobuf_action.optional_payload { + Some(_) => Err("GoToPreviousTab should not have a payload"), + None => Ok(Action::GoToPreviousTab), + }, + Some(ProtobufActionName::CloseTab) => match protobuf_action.optional_payload { + Some(_) => Err("CloseTab should not have a payload"), + None => Ok(Action::CloseTab), + }, + Some(ProtobufActionName::GoToTab) => match protobuf_action.optional_payload { + Some(OptionalPayload::GoToTabPayload(index)) => Ok(Action::GoToTab(index)), + _ => Err("Wrong payload for Action::GoToTab"), + }, + Some(ProtobufActionName::GoToTabName) => match protobuf_action.optional_payload { + Some(OptionalPayload::GoToTabNamePayload(payload)) => { + let tab_name = payload.tab_name; + let create = payload.create; + Ok(Action::GoToTabName(tab_name, create)) + }, + _ => Err("Wrong payload for Action::GoToTabName"), + }, + Some(ProtobufActionName::ToggleTab) => match protobuf_action.optional_payload { + Some(_) => Err("ToggleTab should not have a payload"), + None => Ok(Action::ToggleTab), + }, + Some(ProtobufActionName::TabNameInput) => match protobuf_action.optional_payload { + Some(OptionalPayload::TabNameInputPayload(bytes)) => { + Ok(Action::TabNameInput(bytes)) + }, + _ => Err("Wrong payload for Action::TabNameInput"), + }, + Some(ProtobufActionName::UndoRenameTab) => match protobuf_action.optional_payload { + Some(_) => Err("UndoRenameTab should not have a payload"), + None => Ok(Action::UndoRenameTab), + }, + Some(ProtobufActionName::Run) => match protobuf_action.optional_payload { + Some(OptionalPayload::RunPayload(run_command_action)) => { + let run_command_action = run_command_action.try_into()?; + Ok(Action::Run(run_command_action)) + }, + _ => Err("Wrong payload for Action::Run"), + }, + Some(ProtobufActionName::Detach) => match protobuf_action.optional_payload { + Some(_) => Err("Detach should not have a payload"), + None => Ok(Action::Detach), + }, + Some(ProtobufActionName::LeftClick) => match protobuf_action.optional_payload { + Some(OptionalPayload::LeftClickPayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::LeftClick(position)) + }, + _ => Err("Wrong payload for Action::LeftClick"), + }, + Some(ProtobufActionName::RightClick) => match protobuf_action.optional_payload { + Some(OptionalPayload::RightClickPayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::RightClick(position)) + }, + _ => Err("Wrong payload for Action::RightClick"), + }, + Some(ProtobufActionName::MiddleClick) => match protobuf_action.optional_payload { + Some(OptionalPayload::MiddleClickPayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::MiddleClick(position)) + }, + _ => Err("Wrong payload for Action::MiddleClick"), + }, + Some(ProtobufActionName::LaunchOrFocusPlugin) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::LaunchOrFocusPluginPayload(payload)) => { + let run_plugin_location = + RunPluginLocation::parse(&payload.plugin_url, None) + .map_err(|_| "Malformed LaunchOrFocusPlugin payload")?; + let configuration: PluginUserConfiguration = payload + .plugin_configuration + .and_then(|p| PluginUserConfiguration::try_from(p).ok()) + .unwrap_or_default(); + let run_plugin = RunPlugin { + _allow_exec_host_cmd: false, + location: run_plugin_location, + configuration, + }; + let should_float = payload.should_float; + Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float)) + }, + _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"), + } + }, + Some(ProtobufActionName::LeftMouseRelease) => match protobuf_action.optional_payload { + Some(OptionalPayload::LeftMouseReleasePayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::LeftMouseRelease(position)) + }, + _ => Err("Wrong payload for Action::LeftMouseRelease"), + }, + Some(ProtobufActionName::RightMouseRelease) => match protobuf_action.optional_payload { + Some(OptionalPayload::RightMouseReleasePayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::RightMouseRelease(position)) + }, + _ => Err("Wrong payload for Action::RightMouseRelease"), + }, + Some(ProtobufActionName::MiddleMouseRelease) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::MiddleMouseReleasePayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::MiddleMouseRelease(position)) + }, + _ => Err("Wrong payload for Action::MiddleMouseRelease"), + } + }, + Some(ProtobufActionName::MouseHoldLeft) => match protobuf_action.optional_payload { + Some(OptionalPayload::MouseHoldLeftPayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::MouseHoldLeft(position)) + }, + _ => Err("Wrong payload for Action::MouseHoldLeft"), + }, + Some(ProtobufActionName::MouseHoldRight) => match protobuf_action.optional_payload { + Some(OptionalPayload::MouseHoldRightPayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::MouseHoldRight(position)) + }, + _ => Err("Wrong payload for Action::MouseHoldRight"), + }, + Some(ProtobufActionName::MouseHoldMiddle) => match protobuf_action.optional_payload { + Some(OptionalPayload::MouseHoldMiddlePayload(payload)) => { + let position = payload.try_into()?; + Ok(Action::MouseHoldMiddle(position)) + }, + _ => Err("Wrong payload for Action::MouseHoldMiddle"), + }, + Some(ProtobufActionName::SearchInput) => match protobuf_action.optional_payload { + Some(OptionalPayload::SearchInputPayload(payload)) => { + Ok(Action::SearchInput(payload)) + }, + _ => Err("Wrong payload for Action::SearchInput"), + }, + Some(ProtobufActionName::Search) => match protobuf_action.optional_payload { + Some(OptionalPayload::SearchPayload(search_direction)) => Ok(Action::Search( + ProtobufSearchDirection::from_i32(search_direction) + .ok_or("Malformed payload for Action::Search")? + .try_into()?, + )), + _ => Err("Wrong payload for Action::Search"), + }, + Some(ProtobufActionName::SearchToggleOption) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::SearchToggleOptionPayload(search_option)) => { + Ok(Action::SearchToggleOption( + ProtobufSearchOption::from_i32(search_option) + .ok_or("Malformed payload for Action::SearchToggleOption")? + .try_into()?, + )) + }, + _ => Err("Wrong payload for Action::SearchToggleOption"), + } + }, + Some(ProtobufActionName::ToggleMouseMode) => match protobuf_action.optional_payload { + Some(_) => Err("ToggleMouseMode should not have a payload"), + None => Ok(Action::ToggleMouseMode), + }, + Some(ProtobufActionName::PreviousSwapLayout) => { + match protobuf_action.optional_payload { + Some(_) => Err("PreviousSwapLayout should not have a payload"), + None => Ok(Action::PreviousSwapLayout), + } + }, + Some(ProtobufActionName::NextSwapLayout) => match protobuf_action.optional_payload { + Some(_) => Err("NextSwapLayout should not have a payload"), + None => Ok(Action::NextSwapLayout), + }, + Some(ProtobufActionName::QueryTabNames) => match protobuf_action.optional_payload { + Some(_) => Err("QueryTabNames should not have a payload"), + None => Ok(Action::QueryTabNames), + }, + Some(ProtobufActionName::NewTiledPluginPane) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::NewTiledPluginPanePayload(payload)) => { + let run_plugin_location = + RunPluginLocation::parse(&payload.plugin_url, None) + .map_err(|_| "Malformed NewTiledPluginPane payload")?; + let run_plugin = RunPlugin { + location: run_plugin_location, + _allow_exec_host_cmd: false, + configuration: PluginUserConfiguration::default(), + }; + let pane_name = payload.pane_name; + Ok(Action::NewTiledPluginPane(run_plugin, pane_name)) + }, + _ => Err("Wrong payload for Action::NewTiledPluginPane"), + } + }, + Some(ProtobufActionName::NewFloatingPluginPane) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::NewFloatingPluginPanePayload(payload)) => { + let run_plugin_location = + RunPluginLocation::parse(&payload.plugin_url, None) + .map_err(|_| "Malformed NewTiledPluginPane payload")?; + let run_plugin = RunPlugin { + location: run_plugin_location, + _allow_exec_host_cmd: false, + configuration: PluginUserConfiguration::default(), + }; + let pane_name = payload.pane_name; + Ok(Action::NewFloatingPluginPane(run_plugin, pane_name)) + }, + _ => Err("Wrong payload for Action::MiddleClick"), + } + }, + Some(ProtobufActionName::StartOrReloadPlugin) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::StartOrReloadPluginPayload(payload)) => { + let run_plugin_location = RunPluginLocation::parse(&payload, None) + .map_err(|_| "Malformed StartOrReloadPluginPayload payload")?; + let run_plugin = RunPlugin { + _allow_exec_host_cmd: false, + location: run_plugin_location, + configuration: PluginUserConfiguration::default(), + }; + Ok(Action::StartOrReloadPlugin(run_plugin)) + }, + _ => Err("Wrong payload for Action::StartOrReloadPlugin"), + } + }, + Some(ProtobufActionName::CloseTerminalPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::CloseTerminalPanePayload(payload)) => { + Ok(Action::CloseTerminalPane(payload)) + }, + _ => Err("Wrong payload for Action::CloseTerminalPane"), + }, + Some(ProtobufActionName::ClosePluginPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::ClosePluginPanePayload(payload)) => { + Ok(Action::ClosePluginPane(payload)) + }, + _ => Err("Wrong payload for Action::ClosePluginPane"), + }, + Some(ProtobufActionName::FocusTerminalPaneWithId) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::FocusTerminalPaneWithIdPayload(payload)) => { + let terminal_pane_id = payload.pane_id; + let should_float_if_hidden = payload.should_float_if_hidden; + Ok(Action::FocusTerminalPaneWithId( + terminal_pane_id, + should_float_if_hidden, + )) + }, + _ => Err("Wrong payload for Action::FocusTerminalPaneWithId"), + } + }, + Some(ProtobufActionName::FocusPluginPaneWithId) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::FocusPluginPaneWithIdPayload(payload)) => { + let plugin_pane_id = payload.pane_id; + let should_float_if_hidden = payload.should_float_if_hidden; + Ok(Action::FocusPluginPaneWithId( + plugin_pane_id, + should_float_if_hidden, + )) + }, + _ => Err("Wrong payload for Action::FocusPluginPaneWithId"), + } + }, + Some(ProtobufActionName::RenameTerminalPane) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::RenameTerminalPanePayload(payload)) => { + let terminal_pane_id = payload.id; + let new_pane_name = payload.name; + Ok(Action::RenameTerminalPane(terminal_pane_id, new_pane_name)) + }, + _ => Err("Wrong payload for Action::RenameTerminalPane"), + } + }, + Some(ProtobufActionName::RenamePluginPane) => match protobuf_action.optional_payload { + Some(OptionalPayload::RenamePluginPanePayload(payload)) => { + let plugin_pane_id = payload.id; + let new_pane_name = payload.name; + Ok(Action::RenamePluginPane(plugin_pane_id, new_pane_name)) + }, + _ => Err("Wrong payload for Action::RenamePluginPane"), + }, + Some(ProtobufActionName::RenameTab) => match protobuf_action.optional_payload { + Some(OptionalPayload::RenameTabPayload(payload)) => { + let tab_index = payload.id; + let new_tab_name = payload.name; + Ok(Action::RenameTab(tab_index, new_tab_name)) + }, + _ => Err("Wrong payload for Action::RenameTab"), + }, + Some(ProtobufActionName::BreakPane) => match protobuf_action.optional_payload { + Some(_) => Err("BreakPane should not have a payload"), + None => Ok(Action::BreakPane), + }, + Some(ProtobufActionName::BreakPaneRight) => match protobuf_action.optional_payload { + Some(_) => Err("BreakPaneRight should not have a payload"), + None => Ok(Action::BreakPaneRight), + }, + Some(ProtobufActionName::BreakPaneLeft) => match protobuf_action.optional_payload { + Some(_) => Err("BreakPaneLeft should not have a payload"), + None => Ok(Action::BreakPaneLeft), + }, + _ => Err("Unknown Action"), + } + } +} + +impl TryFrom<Action> for ProtobufAction { + type Error = &'static str; + fn try_from(action: Action) -> Result<Self, &'static str> { + match action { + Action::Quit => Ok(ProtobufAction { + name: ProtobufActionName::Quit as i32, + optional_payload: None, + }), + Action::Write(bytes) => Ok(ProtobufAction { + name: ProtobufActionName::Write as i32, + optional_payload: Some(OptionalPayload::WritePayload(WritePayload { + bytes_to_write: bytes, + })), + }), + Action::WriteChars(chars_to_write) => Ok(ProtobufAction { + name: ProtobufActionName::WriteChars as i32, + optional_payload: Some(OptionalPayload::WriteCharsPayload(WriteCharsPayload { + chars: chars_to_write, + })), + }), + Action::SwitchToMode(input_mode) => { + let input_mode: ProtobufInputMode = input_mode.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::SwitchToMode as i32, + optional_payload: Some(OptionalPayload::SwitchToModePayload( + SwitchToModePayload { + input_mode: input_mode as i32, + }, + )), + }) + }, + Action::SwitchModeForAllClients(input_mode) => { + let input_mode: ProtobufInputMode = input_mode.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::SwitchModeForAllClients as i32, + optional_payload: Some(OptionalPayload::SwitchModeForAllClientsPayload( + SwitchToModePayload { + input_mode: input_mode as i32, + }, + )), + }) + }, + Action::Resize(resize, direction) => { + let mut resize: ProtobufResize = resize.try_into()?; + resize.direction = direction.and_then(|d| { + let resize_direction: ProtobufResizeDirection = d.try_into().ok()?; + Some(resize_direction as i32) + }); + Ok(ProtobufAction { + name: ProtobufActionName::Resize as i32, + optional_payload: Some(OptionalPayload::ResizePayload(resize)), + }) + }, + Action::FocusNextPane => Ok(ProtobufAction { + name: ProtobufActionName::FocusNextPane as i32, + optional_payload: None, + }), + Action::FocusPreviousPane => Ok(ProtobufAction { + name: ProtobufActionName::FocusPreviousPane as i32, + optional_payload: None, + }), + Action::SwitchFocus => Ok(ProtobufAction { + name: ProtobufActionName::SwitchFocus as i32, + optional_payload: None, + }), + Action::MoveFocus(direction) => { + let direction: ProtobufResizeDirection = direction.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MoveFocus as i32, + optional_payload: Some(OptionalPayload::MoveFocusPayload(direction as i32)), + }) + }, + Action::MoveFocusOrTab(direction) => { + let direction: ProtobufResizeDirection = direction.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MoveFocusOrTab as i32, + optional_payload: Some(OptionalPayload::MoveFocusOrTabPayload( + direction as i32, + )), + }) + }, + Action::MovePane(direction) => { + let direction = direction.and_then(|direction| { + let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?; + Some(protobuf_direction as i32) + }); + Ok(ProtobufAction { + name: ProtobufActionName::MovePane as i32, + optional_payload: Some(OptionalPayload::MovePanePayload(MovePanePayload { + direction, + })), + }) + }, + Action::MovePaneBackwards => Ok(ProtobufAction { + name: ProtobufActionName::MovePaneBackwards as i32, + optional_payload: None, + }), + Action::ClearScreen => Ok(ProtobufAction { + name: ProtobufActionName::ClearScreen as i32, + optional_payload: None, + }), + Action::DumpScreen(file_path, include_scrollback) => Ok(ProtobufAction { + name: ProtobufActionName::DumpScreen as i32, + optional_payload: Some(OptionalPayload::DumpScreenPayload(DumpScreenPayload { + file_path, + include_scrollback, + })), + }), + Action::EditScrollback => Ok(ProtobufAction { + name: ProtobufActionName::EditScrollback as i32, + optional_payload: None, + }), + Action::ScrollUp => Ok(ProtobufAction { + name: ProtobufActionName::ScrollUp as i32, + optional_payload: None, + }), + Action::ScrollUpAt(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::ScrollUpAt as i32, + optional_payload: Some(OptionalPayload::ScrollUpAtPayload(ScrollAtPayload { + position: Some(position), + })), + }) + }, + Action::ScrollDown => Ok(ProtobufAction { + name: ProtobufActionName::ScrollDown as i32, + optional_payload: None, + }), + Action::ScrollDownAt(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::ScrollDownAt as i32, + optional_payload: Some(OptionalPayload::ScrollDownAtPayload(ScrollAtPayload { + position: Some(position), + })), + }) + }, + Action::ScrollToBottom => Ok(ProtobufAction { + name: ProtobufActionName::ScrollToBottom as i32, + optional_payload: None, + }), + Action::ScrollToTop => Ok(ProtobufAction { + name: ProtobufActionName::ScrollToTop as i32, + optional_payload: None, + }), + Action::PageScrollUp => Ok(ProtobufAction { + name: ProtobufActionName::PageScrollUp as i32, + optional_payload: None, + }), + Action::PageScrollDown => Ok(ProtobufAction { + name: ProtobufActionName::PageScrollDown as i32, + optional_payload: None, + }), + Action::HalfPageScrollUp => Ok(ProtobufAction { + name: ProtobufActionName::HalfPageScrollUp as i32, + optional_payload: None, + }), + Action::HalfPageScrollDown => Ok(ProtobufAction { + name: ProtobufActionName::HalfPageScrollDown as i32, + optional_payload: None, + }), + Action::ToggleFocusFullscreen => Ok(ProtobufAction { + name: ProtobufActionName::ToggleFocusFullscreen as i32, + optional_payload: None, + }), + Action::TogglePaneFrames => Ok(ProtobufAction { + name: ProtobufActionName::TogglePaneFrames as i32, + optional_payload: None, + }), + Action::ToggleActiveSyncTab => Ok(ProtobufAction { + name: ProtobufActionName::ToggleActiveSyncTab as i32, + optional_payload: None, + }), + Action::NewPane(direction, new_pane_name) => { + let direction = direction.and_then(|direction| { + let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?; + Some(protobuf_direction as i32) + }); + Ok(ProtobufAction { + name: ProtobufActionName::NewPane as i32, + optional_payload: Some(OptionalPayload::NewPanePayload(NewPanePayload { + direction, + pane_name: new_pane_name, + })), + }) + }, + Action::EditFile(path_to_file, line_number, cwd, direction, should_float) => { + let file_to_edit = path_to_file.display().to_string(); + let cwd = cwd.map(|cwd| cwd.display().to_string()); + let direction: Option<i32> = direction + .and_then(|d| ProtobufResizeDirection::try_from(d).ok()) + .map(|d| d as i32); + let line_number = line_number.map(|l| l as u32); + Ok(ProtobufAction { + name: ProtobufActionName::EditFile as i32, + optional_payload: Some(OptionalPayload::EditFilePayload(EditFilePayload { + file_to_edit, + line_number, + should_float, + direction, + cwd, + })), + }) + }, + Action::NewFloatingPane(run_command_action, pane_name) => { + let command = run_command_action.and_then(|r| { + let mut protobuf_run_command_action: ProtobufRunCommandAction = + r.try_into().ok()?; + protobuf_run_command_action.pane_name = pane_name; + Some(protobuf_run_command_action) + }); + Ok(ProtobufAction { + name: ProtobufActionName::NewFloatingPane as i32, + optional_payload: Some(OptionalPayload::NewFloatingPanePayload( + NewFloatingPanePayload { command }, + )), + }) + }, + Action::NewTiledPane(direction, run_command_action, pane_name) => { + let direction = direction.and_then(|direction| { + let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?; + Some(protobuf_direction as i32) + }); + 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) + }); + Ok(ProtobufAction { + name: ProtobufActionName::NewTiledPane as i32, + optional_payload: Some(OptionalPayload::NewTiledPanePayload( + NewTiledPanePayload { direction, command }, + )), + }) + }, + Action::TogglePaneEmbedOrFloating => Ok(ProtobufAction { + name: ProtobufActionName::TogglePaneEmbedOrFloating as i32, + optional_payload: None, + }), + Action::ToggleFloatingPanes => Ok(ProtobufAction { + name: ProtobufActionName::ToggleFloatingPanes as i32, + optional_payload: None, + }), + Action::CloseFocus => Ok(ProtobufAction { + name: ProtobufActionName::CloseFocus as i32, + optional_payload: None, + }), + Action::PaneNameInput(bytes) => Ok(ProtobufAction { + name: ProtobufActionName::PaneNameInput as i32, + optional_payload: Some(OptionalPayload::PaneNameInputPayload(bytes)), + }), + Action::UndoRenamePane => Ok(ProtobufAction { + name: ProtobufActionName::UndoRenamePane as i32, + optional_payload: None, + }), + Action::NewTab(..) => { + // we do not serialize the various newtab payloads + Ok(ProtobufAction { + name: ProtobufActionName::NewTab as i32, + optional_payload: None, + }) + }, + Action::GoToNextTab => Ok(ProtobufAction { + name: ProtobufActionName::GoToNextTab as i32, + optional_payload: None, + }), + Action::GoToPreviousTab => Ok(ProtobufAction { + name: ProtobufActionName::GoToPreviousTab as i32, + optional_payload: None, + }), + Action::CloseTab => Ok(ProtobufAction { + name: ProtobufActionName::CloseTab as i32, + optional_payload: None, + }), + Action::GoToTab(tab_index) => Ok(ProtobufAction { + name: ProtobufActionName::GoToTab as i32, + optional_payload: Some(OptionalPayload::GoToTabPayload(tab_index)), + }), + Action::GoToTabName(tab_name, create) => Ok(ProtobufAction { + name: ProtobufActionName::GoToTabName as i32, + optional_payload: Some(OptionalPayload::GoToTabNamePayload(GoToTabNamePayload { + tab_name, + create, + })), + }), + Action::ToggleTab => Ok(ProtobufAction { + name: ProtobufActionName::ToggleTab as i32, + optional_payload: None, + }), + Action::TabNameInput(bytes) => Ok(ProtobufAction { + name: ProtobufActionName::TabNameInput as i32, + optional_payload: Some(OptionalPayload::TabNameInputPayload(bytes)), + }), + Action::UndoRenameTab => Ok(ProtobufAction { + name: ProtobufActionName::UndoRenameTab as i32, + optional_payload: None, + }), + Action::Run(run_command_action) => { + let run_command_action: ProtobufRunCommandAction = run_command_action.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::Run as i32, + optional_payload: Some(OptionalPayload::RunPayload(run_command_action)), + }) + }, + Action::Detach => Ok(ProtobufAction { + name: ProtobufActionName::Detach as i32, + optional_payload: None, + }), + Action::LeftClick(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::LeftClick as i32, + optional_payload: Some(OptionalPayload::LeftClickPayload(position)), + }) + }, + Action::RightClick(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::RightClick as i32, + optional_payload: Some(OptionalPayload::RightClickPayload(position)), + }) + }, + Action::MiddleClick(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MiddleClick as i32, + optional_payload: Some(OptionalPayload::MiddleClickPayload(position)), + }) + }, + Action::LaunchOrFocusPlugin(run_plugin, should_float) => { + let url: Url = Url::from(&run_plugin.location); + Ok(ProtobufAction { + name: ProtobufActionName::LaunchOrFocusPlugin as i32, + optional_payload: Some(OptionalPayload::LaunchOrFocusPluginPayload( + LaunchOrFocusPluginPayload { + plugin_url: url.into(), + should_float, + plugin_configuration: Some(run_plugin.configuration.try_into()?), + }, + )), + }) + }, + Action::LeftMouseRelease(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::LeftMouseRelease as i32, + optional_payload: Some(OptionalPayload::LeftMouseReleasePayload(position)), + }) + }, + Action::RightMouseRelease(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::RightMouseRelease as i32, + optional_payload: Some(OptionalPayload::RightMouseReleasePayload(position)), + }) + }, + Action::MiddleMouseRelease(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MiddleMouseRelease as i32, + optional_payload: Some(OptionalPayload::MiddleMouseReleasePayload(position)), + }) + }, + Action::MouseHoldLeft(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MouseHoldLeft as i32, + optional_payload: Some(OptionalPayload::MouseHoldLeftPayload(position)), + }) + }, + Action::MouseHoldRight(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MouseHoldRight as i32, + optional_payload: Some(OptionalPayload::MouseHoldRightPayload(position)), + }) + }, + Action::MouseHoldMiddle(position) => { + let position: ProtobufPosition = position.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::MouseHoldMiddle as i32, + optional_payload: Some(OptionalPayload::MouseHoldMiddlePayload(position)), + }) + }, + Action::SearchInput(bytes) => Ok(ProtobufAction { + name: ProtobufActionName::SearchInput as i32, + optional_payload: Some(OptionalPayload::SearchInputPayload(bytes)), + }), + Action::Search(search_direction) => { + let search_direction: ProtobufSearchDirection = search_direction.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::Search as i32, + optional_payload: Some(OptionalPayload::SearchPayload(search_direction as i32)), + }) + }, + Action::SearchToggleOption(search_option) => { + let search_option: ProtobufSearchOption = search_option.try_into()?; + Ok(ProtobufAction { + name: ProtobufActionName::SearchToggleOption as i32, + optional_payload: Some(OptionalPayload::SearchToggleOptionPayload( + search_option as i32, + )), + }) + }, + Action::ToggleMouseMode => Ok(ProtobufAction { + name: ProtobufActionName::ToggleMouseMode as i32, + optional_payload: None, + }), + Action::PreviousSwapLayout => Ok(ProtobufAction { + name: ProtobufActionName::PreviousSwapLayout as i32, + optional_payload: None, + }), + Action::NextSwapLayout => Ok(ProtobufAction { + name: ProtobufActionName::NextSwapLayout as i32, + optional_payload: None, + }), + Action::QueryTabNames => Ok(ProtobufAction { + name: ProtobufActionName::QueryTabNames as i32, + optional_payload: None, + }), + Action::NewTiledPluginPane(run_plugin, pane_name) => { + let plugin_url: Url = Url::from(&run_plugin.location); + Ok(ProtobufAction { + name: ProtobufActionName::NewTiledPluginPane as i32, + optional_payload: Some(OptionalPayload::NewTiledPluginPanePayload( + NewPluginPanePayload { + plugin_url: plugin_url.into(), + pane_name, + }, + )), + }) + }, + Action::NewFloatingPluginPane(run_plugin, pane_name) => { + let plugin_url: Url = Url::from(&run_plugin.location); + Ok(ProtobufAction { + name: ProtobufActionName::NewFloatingPluginPane as i32, + optional_payload: Some(OptionalPayload::NewFloatingPluginPanePayload( + NewPluginPanePayload { + plugin_url: plugin_url.into(), + pane_name, + }, + )), + }) + }, + Action::StartOrReloadPlugin(run_plugin) => { + let plugin_url: Url = Url::from(&run_plugin.location); + Ok(ProtobufAction { + name: ProtobufActionName::StartOrReloadPlugin as i32, + optional_payload: Some(OptionalPayload::StartOrReloadPluginPayload( + plugin_url.into(), + )), + }) + }, + Action::CloseTerminalPane(terminal_pane_id) => Ok(ProtobufAction { + name: ProtobufActionName::CloseTerminalPane as i32, + optional_payload: Some(OptionalPayload::CloseTerminalPanePayload(terminal_pane_id)), + }), + Action::ClosePluginPane(plugin_pane_id) => Ok(ProtobufAction { + name: ProtobufActionName::ClosePluginPane as i32, + optional_payload: Some(OptionalPayload::ClosePluginPanePayload(plugin_pane_id)), + }), + Action::FocusTerminalPaneWithId(terminal_pane_id, should_float_if_hidden) => { + Ok(ProtobufAction { + name: ProtobufActionName::FocusTerminalPaneWithId as i32, + optional_payload: Some(OptionalPayload::FocusTerminalPaneWithIdPayload( + PaneIdAndShouldFloat { + pane_id: terminal_pane_id, + should_float_if_hidden, + }, + )), + }) + }, + Action::FocusPluginPaneWithId(plugin_pane_id, should_float_if_hidden) => { + Ok(ProtobufAction { + name: ProtobufActionName::FocusPluginPaneWithId as i32, + optional_payload: Some(OptionalPayload::FocusPluginPaneWithIdPayload( + PaneIdAndShouldFloat { + pane_id: plugin_pane_id, + should_float_if_hidden, + }, + )), + }) + }, + Action::RenameTerminalPane(terminal_pane_id, new_name) => Ok(ProtobufAction { + name: ProtobufActionName::RenameTerminalPane as i32, + optional_payload: Some(OptionalPayload::RenameTerminalPanePayload(IdAndName { + name: new_name, + id: terminal_pane_id, + })), + }), + Action::RenamePluginPane(plugin_pane_id, new_name) => Ok(ProtobufAction { + name: ProtobufActionName::RenamePluginPane as i32, + optional_payload: Some(OptionalPayload::RenamePluginPanePayload(IdAndName { + name: new_name, + id: plugin_pane_id, + })), + }), + Action::RenameTab(tab_index, new_name) => Ok(ProtobufAction { + name: ProtobufActionName::RenameTab as i32, + optional_payload: Some(OptionalPayload::RenameTabPayload(IdAndName { + name: new_name, + id: tab_index, + })), + }), + Action::BreakPane => Ok(ProtobufAction { + name: ProtobufActionName::BreakPane as i32, + optional_payload: None, + }), + Action::BreakPaneRight => Ok(ProtobufAction { + name: ProtobufActionName::BreakPaneRight as i32, + optional_payload: None, + }), + Action::BreakPaneLeft => Ok(ProtobufAction { + name: ProtobufActionName::BreakPaneLeft as i32, + optional_payload: None, + }), + Action::NoOp + | Action::Confirm + | Action::Deny + | Action::Copy + | Action::SkipConfirm(..) => Err("Unsupported action"), + } + } +} + +impl TryFrom<ProtobufSearchOption> for SearchOption { + type Error = &'static str; + fn try_from(protobuf_search_option: ProtobufSearchOption) -> Result<Self, &'static str> { + match protobuf_search_option { + ProtobufSearchOption::CaseSensitivity => Ok(SearchOption::CaseSensitivity), + ProtobufSearchOption::WholeWord => Ok(SearchOption::WholeWord), + ProtobufSearchOption::Wrap => Ok(SearchOption::Wrap), + } + } +} + +impl TryFrom<SearchOption> for ProtobufSearchOption { + type Error = &'static str; + fn try_from(search_option: SearchOption) -> Result<Self, &'static str> { + match search_option { + SearchOption::CaseSensitivity => Ok(ProtobufSearchOption::CaseSensitivity), + SearchOption::WholeWord => Ok(ProtobufSearchOption::WholeWord), + SearchOption::Wrap => Ok(ProtobufSearchOption::Wrap), + } + } +} + +impl TryFrom<ProtobufSearchDirection> for SearchDirection { + type Error = &'static str; + fn try_from(protobuf_search_direction: ProtobufSearchDirection) -> Result<Self, &'static str> { + match protobuf_search_direction { + ProtobufSearchDirection::Up => Ok(SearchDirection::Up), + ProtobufSearchDirection::Down => Ok(SearchDirection::Down), + } + } +} + +impl TryFrom<SearchDirection> for ProtobufSearchDirection { + type Error = &'static str; + fn try_from(search_direction: SearchDirection) -> Result<Self, &'static str> { + match search_direction { + SearchDirection::Up => Ok(ProtobufSearchDirection::Up), + SearchDirection::Down => Ok(ProtobufSearchDirection::Down), + } + } +} + +impl TryFrom<ProtobufRunCommandAction> for RunCommandAction { + type Error = &'static str; + fn try_from( + protobuf_run_command_action: ProtobufRunCommandAction, + ) -> Result<Self, &'static str> { + let command = PathBuf::from(protobuf_run_command_action.command); + let args: Vec<String> = protobuf_run_command_action.args; + let cwd: Option<PathBuf> = protobuf_run_command_action.cwd.map(|c| PathBuf::from(c)); + let direction: Option<Direction> = protobuf_run_command_action + .direction + .and_then(|d| ProtobufResizeDirection::from_i32(d)) + .and_then(|d| d.try_into().ok()); + let hold_on_close = protobuf_run_command_action.hold_on_close; + let hold_on_start = protobuf_run_command_action.hold_on_start; + Ok(RunCommandAction { + command, + args, + cwd, + direction, + hold_on_close, + hold_on_start, + }) + } +} + +impl TryFrom<RunCommandAction> for ProtobufRunCommandAction { + type Error = &'static str; + fn try_from(run_command_action: RunCommandAction) -> Result<Self, &'static str> { + let command = run_command_action.command.display().to_string(); + let args: Vec<String> = run_command_action.args; + let cwd = run_command_action.cwd.map(|c| c.display().to_string()); + let direction = run_command_action.direction.and_then(|p| { + let direction: ProtobufResizeDirection = p.try_into().ok()?; + Some(direction as i32) + }); + let hold_on_close = run_command_action.hold_on_close; + let hold_on_start = run_command_action.hold_on_start; + Ok(ProtobufRunCommandAction { + command, + args, + cwd, + direction, + hold_on_close, + hold_on_start, + pane_name: None, + }) + } +} + +impl TryFrom<ProtobufPosition> for Position { + type Error = &'static str; + fn try_from(protobuf_position: ProtobufPosition) -> Result<Self, &'static str> { + Ok(Position::new( + protobuf_position.line as i32, + protobuf_position.column as u16, + )) + } +} + +impl TryFrom<Position> for ProtobufPosition { + type Error = &'static str; + fn try_from(position: Position) -> Result<Self, &'static str> { + Ok(ProtobufPosition { + line: position.line.0 as i64, + column: position.column.0 as i64, + }) + } +} + +impl TryFrom<ProtobufPluginConfiguration> for PluginUserConfiguration { + type Error = &'static str; + fn try_from(plugin_configuration: ProtobufPluginConfiguration) -> Result<Self, &'static str> { + let mut converted = BTreeMap::new(); + for name_and_value in plugin_configuration.name_and_value { + converted.insert(name_and_value.name, name_and_value.value); + } + Ok(PluginUserConfiguration::new(converted)) + } +} + +impl TryFrom<PluginUserConfiguration> for ProtobufPluginConfiguration { + type Error = &'static str; + fn try_from(plugin_configuration: PluginUserConfiguration) -> Result<Self, &'static str> { + let mut converted = vec![]; + for (name, value) in plugin_configuration.inner() { + let name_and_value = ProtobufNameAndValue { + name: name.to_owned(), + value: value.to_owned(), + }; + converted.push(name_and_value); + } + Ok(ProtobufPluginConfiguration { + name_and_value: converted, + }) + } +} + +impl TryFrom<&ProtobufPluginConfiguration> for BTreeMap<String, String> { + type Error = &'static str; + fn try_from(plugin_configuration: &ProtobufPluginConfiguration) -> Result<Self, &'static str> { + let mut converted = BTreeMap::new(); + for name_and_value in &plugin_configuration.name_and_value { + converted.insert( + name_and_value.name.to_owned(), + name_and_value.value.to_owned(), + ); + } + Ok(converted) + } +} diff --git a/zellij-utils/src/plugin_api/command.proto b/zellij-utils/src/plugin_api/command.proto new file mode 100644 index 00000000..70402f25 --- /dev/null +++ b/zellij-utils/src/plugin_api/command.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package api.command; + +message Command { + string path = 1; + repeated string args = 2; + optional string cwd = 3; +} diff --git a/zellij-utils/src/plugin_api/command.rs b/zellij-utils/src/plugin_api/command.rs new file mode 100644 index 00000000..2f8bf300 --- /dev/null +++ b/zellij-utils/src/plugin_api/command.rs @@ -0,0 +1,26 @@ +pub use super::generated_api::api::command::Command as ProtobufCommand; +use crate::data::CommandToRun; + +use std::convert::TryFrom; +use std::path::PathBuf; + +impl TryFrom<ProtobufCommand> for CommandToRun { + type Error = &'static str; + fn try_from(protobuf_command: ProtobufCommand) -> Result<Self, &'static str> { + let path = PathBuf::from(protobuf_command.path); + let args = protobuf_command.args; + let cwd = protobuf_command.cwd.map(|c| PathBuf::from(c)); + Ok(CommandToRun { path, args, cwd }) + } +} + +impl TryFrom<CommandToRun> for ProtobufCommand { + type Error = &'static str; + fn try_from(command_to_run: CommandToRun) -> Result<Self, &'static str> { + Ok(ProtobufCommand { + path: command_to_run.path.display().to_string(), + args: command_to_run.args, + cwd: command_to_run.cwd.map(|c| c.display().to_string()), + }) + } +} diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto new file mode 100644 index 00000000..95928ae3 --- /dev/null +++ b/zellij-utils/src/plugin_api/event.proto @@ -0,0 +1,162 @@ +syntax = "proto3"; + +import "input_mode.proto"; +import "key.proto"; +import "style.proto"; +import "action.proto"; + +package api.event; + +enum EventType { + /// The input mode or relevant metadata changed + ModeUpdate = 0; + /// The tab state in the app was changed + TabUpdate = 1; + /// The pane state in the app was changed + PaneUpdate = 2; + /// A key was pressed while the user is focused on this plugin's pane + Key = 3; + /// A mouse event happened while the user is focused on this plugin's pane + Mouse = 4; + /// A timer expired set by the `set_timeout` method exported by `zellij-tile`. + Timer = 5; + /// Text was copied to the clipboard anywhere in the app + CopyToClipboard = 6; + /// Failed to copy text to clipboard anywhere in the app + SystemClipboardFailure = 7; + /// Input was received anywhere in the app + InputReceived = 8; + /// This plugin became visible or invisible + Visible = 9; + /// A message from one of the plugin's workers + CustomMessage = 10; + /// A file was created somewhere in the Zellij CWD folder + FileSystemCreate = 11; + /// A file was accessed somewhere in the Zellij CWD folder + FileSystemRead = 12; + /// A file was modified somewhere in the Zellij CWD folder + FileSystemUpdate = 13; + /// A file was deleted somewhere in the Zellij CWD folder + FileSystemDelete = 14; +} + +message EventNameList { + repeated EventType event_types = 1; +} + +message Event { + EventType name = 1; + oneof payload { + ModeUpdatePayload mode_update_payload = 2; + TabUpdatePayload tab_update_payload = 3; + PaneUpdatePayload pane_update_payload = 4; + key.Key key_payload = 5; + MouseEventPayload mouse_event_payload = 6; + float timer_payload = 7; + CopyDestination copy_to_clipboard_payload = 8; + bool visible_payload = 9; + CustomMessagePayload custom_message_payload = 10; + FileListPayload file_list_payload = 11; + } +} + +message FileListPayload { + repeated string paths = 1; +} + +message CustomMessagePayload { + string message_name = 1; + string payload = 2; +} + +enum CopyDestination { + Command = 0; + Primary = 1; + System = 2; +} + +message MouseEventPayload { + MouseEventName mouse_event_name = 1; + oneof mouse_event_payload { + uint32 line_count = 2; + action.Position position = 3; + } +} + +enum MouseEventName { + MouseScrollUp = 0; + MouseScrollDown = 1; + MouseLeftClick = 2; + MouseRightClick = 3; + MouseHold = 4; + MouseRelease = 5; +} + +message TabUpdatePayload { + repeated TabInfo tab_info = 1; +} + +message PaneUpdatePayload { + repeated PaneManifest pane_manifest = 1; +} + +message PaneManifest { + uint32 tab_index = 1; + repeated PaneInfo panes = 2; +} + +message PaneInfo { + uint32 id = 1; + bool is_plugin = 2; + bool is_focused = 3; + bool is_fullscreen = 4; + bool is_floating = 5; + bool is_suppressed = 6; + string title = 7; + bool exited = 8; + optional int32 exit_status = 9; + bool is_held = 10; + uint32 pane_x = 11; + uint32 pane_content_x = 12; + uint32 pane_y = 13; + uint32 pane_content_y = 14; + uint32 pane_rows = 15; + uint32 pane_content_rows = 16; + uint32 pane_columns = 17; + uint32 pane_content_columns = 18; + optional action.Position cursor_coordinates_in_pane = 19; + optional string terminal_command = 20; + optional string plugin_url = 21; + bool is_selectable = 22; +} + +message TabInfo { + uint32 position = 1; + string name = 2; + bool active = 3; + uint32 panes_to_hide = 4; + bool is_fullscreen_active = 5; + bool is_sync_panes_active = 6; + bool are_floating_panes_visible = 7; + repeated uint32 other_focused_clients = 8; + optional string active_swap_layout_name = 9; + bool is_swap_layout_dirty = 10; +} + +message ModeUpdatePayload { + input_mode.InputMode current_mode = 1; + repeated InputModeKeybinds keybinds = 2; + style.Style style = 3; + bool arrow_fonts_support = 4; + optional string session_name = 5; +} + +message InputModeKeybinds { + input_mode.InputMode mode = 1; + repeated KeyBind key_bind = 2; +} + +message KeyBind { + key.Key key = 1; + repeated action.Action action = 2; +} diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs new file mode 100644 index 00000000..315fa54b --- /dev/null +++ b/zellij-utils/src/plugin_api/event.rs @@ -0,0 +1,1059 @@ +pub use super::generated_api::api::{ + action::{Action as ProtobufAction, Position as ProtobufPosition}, + event::{ + event::Payload as ProtobufEventPayload, CopyDestination as ProtobufCopyDestination, + Event as ProtobufEvent, EventNameList as ProtobufEventNameList, + EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds, + KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload, + PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest, + TabInfo as ProtobufTabInfo, *, + }, + input_mode::InputMode as ProtobufInputMode, + key::Key as ProtobufKey, + style::Style as ProtobufStyle, +}; +use crate::data::{ + CopyDestination, Direction, Event, EventType, InputMode, Key, ModeInfo, Mouse, Palette, + PaletteColor, PaneInfo, PaneManifest, PluginCapabilities, Style, TabInfo, ThemeHue, +}; +use crate::errors::prelude::*; +use crate::input::actions::Action; + +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::path::PathBuf; + +impl TryFrom<ProtobufEvent> for Event { + type Error = &'static str; + fn try_from(protobuf_event: ProtobufEvent) -> Result<Self, &'static str> { + match ProtobufEventType::from_i32(protobuf_event.name) { + Some(ProtobufEventType::ModeUpdate) => match protobuf_event.payload { + Some(ProtobufEventPayload::ModeUpdatePayload(protobuf_mode_update_payload)) => { + let mode_info: ModeInfo = protobuf_mode_update_payload.try_into()?; + Ok(Event::ModeUpdate(mode_info)) + }, + _ => Err("Malformed payload for the ModeUpdate Event"), + }, + Some(ProtobufEventType::TabUpdate) => match protobuf_event.payload { + Some(ProtobufEventPayload::TabUpdatePayload(protobuf_tab_info_payload)) => { + let mut tab_infos: Vec<TabInfo> = vec![]; + for protobuf_tab_info in protobuf_tab_info_payload.tab_info { + tab_infos.push(TabInfo::try_from(protobuf_tab_info)?); + } + Ok(Event::TabUpdate(tab_infos)) + }, + _ => Err("Malformed payload for the TabUpdate Event"), + }, + Some(ProtobufEventType::PaneUpdate) => match protobuf_event.payload { + Some(ProtobufEventPayload::PaneUpdatePayload(protobuf_pane_update_payload)) => { + let mut pane_manifest: HashMap<usize, Vec<PaneInfo>> = HashMap::new(); + for protobuf_pane_manifest in protobuf_pane_update_payload.pane_manifest { + let tab_index = protobuf_pane_manifest.tab_index as usize; + let mut panes = vec![]; + for protobuf_pane_info in protobuf_pane_manifest.panes { + panes.push(protobuf_pane_info.try_into()?); + } + if pane_manifest.contains_key(&tab_index) { + return Err("Duplicate tab definition in pane manifest"); + } + pane_manifest.insert(tab_index, panes); + } + Ok(Event::PaneUpdate(PaneManifest { + panes: pane_manifest, + })) + }, + _ => Err("Malformed payload for the PaneUpdate Event"), + }, + Some(ProtobufEventType::Key) => match protobuf_event.payload { + Some(ProtobufEventPayload::KeyPayload(protobuf_key)) => { + Ok(Event::Key(protobuf_key.try_into()?)) + }, + _ => Err("Malformed payload for the Key Event"), + }, + Some(ProtobufEventType::Mouse) => match protobuf_event.payload { + Some(ProtobufEventPayload::MouseEventPayload(protobuf_mouse)) => { + Ok(Event::Mouse(protobuf_mouse.try_into()?)) + }, + _ => Err("Malformed payload for the Mouse Event"), + }, + Some(ProtobufEventType::Timer) => match protobuf_event.payload { + Some(ProtobufEventPayload::TimerPayload(seconds)) => { + Ok(Event::Timer(seconds as f64)) + }, + _ => Err("Malformed payload for the Timer Event"), + }, + Some(ProtobufEventType::CopyToClipboard) => match protobuf_event.payload { + Some(ProtobufEventPayload::CopyToClipboardPayload(copy_to_clipboard)) => { + let protobuf_copy_to_clipboard = + ProtobufCopyDestination::from_i32(copy_to_clipboard) + .ok_or("Malformed copy to clipboard payload")?; + Ok(Event::CopyToClipboard( + protobuf_copy_to_clipboard.try_into()?, + )) + }, + _ => Err("Malformed payload for the Copy To Clipboard Event"), + }, + Some(ProtobufEventType::SystemClipboardFailure) => match protobuf_event.payload { + None => Ok(Event::SystemClipboardFailure), + _ => Err("Malformed payload for the system clipboard failure Event"), + }, + Some(ProtobufEventType::InputReceived) => match protobuf_event.payload { + None => Ok(Event::InputReceived), + _ => Err("Malformed payload for the input received Event"), + }, + Some(ProtobufEventType::Visible) => match protobuf_event.payload { + Some(ProtobufEventPayload::VisiblePayload(is_visible)) => { + Ok(Event::Visible(is_visible)) + }, + _ => Err("Malformed payload for the visible Event"), + }, + Some(ProtobufEventType::CustomMessage) => match protobuf_event.payload { + Some(ProtobufEventPayload::CustomMessagePayload(custom_message_payload)) => { + Ok(Event::CustomMessage( + custom_message_payload.message_name, + custom_message_payload.payload, + )) + }, + _ => Err("Malformed payload for the custom message Event"), + }, + Some(ProtobufEventType::FileSystemCreate) => match protobuf_event.payload { + Some(ProtobufEventPayload::FileListPayload(file_list_payload)) => { + let file_paths = file_list_payload + .paths + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + Ok(Event::FileSystemCreate(file_paths)) + }, + _ => Err("Malformed payload for the file system create Event"), + }, + Some(ProtobufEventType::FileSystemRead) => match protobuf_event.payload { + Some(ProtobufEventPayload::FileListPayload(file_list_payload)) => { + let file_paths = file_list_payload + .paths + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + Ok(Event::FileSystemRead(file_paths)) + }, + _ => Err("Malformed payload for the file system read Event"), + }, + Some(ProtobufEventType::FileSystemUpdate) => match protobuf_event.payload { + Some(ProtobufEventPayload::FileListPayload(file_list_payload)) => { + let file_paths = file_list_payload + .paths + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + Ok(Event::FileSystemUpdate(file_paths)) + }, + _ => Err("Malformed payload for the file system update Event"), + }, + Some(ProtobufEventType::FileSystemDelete) => match protobuf_event.payload { + Some(ProtobufEventPayload::FileListPayload(file_list_payload)) => { + let file_paths = file_list_payload + .paths + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + Ok(Event::FileSystemDelete(file_paths)) + }, + _ => Err("Malformed payload for the file system delete Event"), + }, + None => Err("Unknown Protobuf Event"), + } + } +} + +impl TryFrom<Event> for ProtobufEvent { + type Error = &'static str; + fn try_from(event: Event) -> Result<Self, &'static str> { + match event { + Event::ModeUpdate(mode_info) => { + let protobuf_mode_update_payload = mode_info.try_into()?; + Ok(ProtobufEvent { + name: ProtobufEventType::ModeUpdate as i32, + payload: Some(event::Payload::ModeUpdatePayload( + protobuf_mode_update_payload, + )), + }) + }, + Event::TabUpdate(tab_infos) => { + let mut protobuf_tab_infos = vec![]; + for tab_info in tab_infos { + protobuf_tab_infos.push(tab_info.try_into()?); + } + let tab_update_payload = TabUpdatePayload { + tab_info: protobuf_tab_infos, + }; + Ok(ProtobufEvent { + name: ProtobufEventType::TabUpdate as i32, + payload: Some(event::Payload::TabUpdatePayload(tab_update_payload)), + }) + }, + Event::PaneUpdate(pane_manifest) => { + let mut protobuf_pane_manifests = vec![]; + for (tab_index, pane_infos) in pane_manifest.panes { + let mut protobuf_pane_infos = vec![]; + for pane_info in pane_infos { + protobuf_pane_infos.push(pane_info.try_into()?); + } + protobuf_pane_manifests.push(ProtobufPaneManifest { + tab_index: tab_index as u32, + panes: protobuf_pane_infos, + }); + } + Ok(ProtobufEvent { + name: ProtobufEventType::PaneUpdate as i32, + payload: Some(event::Payload::PaneUpdatePayload(PaneUpdatePayload { + pane_manifest: protobuf_pane_manifests, + })), + }) + }, + Event::Key(key) => Ok(ProtobufEvent { + name: ProtobufEventType::Key as i32, + payload: Some(event::Payload::KeyPayload(key.try_into()?)), + }), + Event::Mouse(mouse_event) => { + let protobuf_mouse_payload = mouse_event.try_into()?; + Ok(ProtobufEvent { + name: ProtobufEventType::Mouse as i32, + payload: Some(event::Payload::MouseEventPayload(protobuf_mouse_payload)), + }) + }, + Event::Timer(seconds) => Ok(ProtobufEvent { + name: ProtobufEventType::Timer as i32, + payload: Some(event::Payload::TimerPayload(seconds as f32)), + }), + Event::CopyToClipboard(clipboard_destination) => { + let protobuf_copy_destination: ProtobufCopyDestination = + clipboard_destination.try_into()?; + Ok(ProtobufEvent { + name: ProtobufEventType::CopyToClipboard as i32, + payload: Some(event::Payload::CopyToClipboardPayload( + protobuf_copy_destination as i32, + )), + }) + }, + Event::SystemClipboardFailure => Ok(ProtobufEvent { + name: ProtobufEventType::SystemClipboardFailure as i32, + payload: None, + }), + Event::InputReceived => Ok(ProtobufEvent { + name: ProtobufEventType::InputReceived as i32, + payload: None, + }), + Event::Visible(is_visible) => Ok(ProtobufEvent { + name: ProtobufEventType::Visible as i32, + payload: Some(event::Payload::VisiblePayload(is_visible)), + }), + Event::CustomMessage(message, payload) => Ok(ProtobufEvent { + name: ProtobufEventType::CustomMessage as i32, + payload: Some(event::Payload::CustomMessagePayload(CustomMessagePayload { + message_name: message, + payload, + })), + }), + Event::FileSystemCreate(paths) => { + let file_list_payload = FileListPayload { + paths: paths.iter().map(|p| p.display().to_string()).collect(), + }; + Ok(ProtobufEvent { + name: ProtobufEventType::FileSystemCreate as i32, + payload: Some(event::Payload::FileListPayload(file_list_payload)), + }) + }, + Event::FileSystemRead(paths) => { + let file_list_payload = FileListPayload { + paths: paths.iter().map(|p| p.display().to_string()).collect(), + }; + Ok(ProtobufEvent { + name: ProtobufEventType::FileSystemRead as i32, + payload: Some(event::Payload::FileListPayload(file_list_payload)), + }) + }, + Event::FileSystemUpdate(paths) => { + let file_list_payload = FileListPayload { + paths: paths.iter().map(|p| p.display().to_string()).collect(), + }; + Ok(ProtobufEvent { + name: ProtobufEventType::FileSystemUpdate as i32, + payload: Some(event::Payload::FileListPayload(file_list_payload)), + }) + }, + Event::FileSystemDelete(paths) => { + let file_list_payload = FileListPayload { + paths: paths.iter().map(|p| p.display().to_string()).collect(), + }; + Ok(ProtobufEvent { + name: ProtobufEventType::FileSystemDelete as i32, + payload: Some(event::Payload::FileListPayload(file_list_payload)), + }) + }, + } + } +} + +impl TryFrom<CopyDestination> for ProtobufCopyDestination { + type Error = &'static str; + fn try_from(copy_destination: CopyDestination) -> Result<Self, &'static str> { + match copy_destination { + CopyDestination::Command => Ok(ProtobufCopyDestination::Command), + CopyDestination::Primary => Ok(ProtobufCopyDestination::Primary), + CopyDestination::System => Ok(ProtobufCopyDestination::System), + } + } +} + +impl TryFrom<ProtobufCopyDestination> for CopyDestination { + type Error = &'static str; + fn try_from(protobuf_copy_destination: ProtobufCopyDestination) -> Result<Self, &'static str> { + match protobuf_copy_destination { + ProtobufCopyDestination::Command => Ok(CopyDestination::Command), + ProtobufCopyDestination::Primary => Ok(CopyDestination::Primary), + ProtobufCopyDestination::System => Ok(CopyDestination::System), + } + } +} + +impl TryFrom<MouseEventPayload> for Mouse { + type Error = &'static str; + fn try_from(mouse_event_payload: MouseEventPayload) -> Result<Self, &'static str> { + match MouseEventName::from_i32(mouse_event_payload.mouse_event_name) { + Some(MouseEventName::MouseScrollUp) => match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::LineCount(line_count)) => { + Ok(Mouse::ScrollUp(line_count as usize)) + }, + _ => Err("Malformed payload for mouse scroll up"), + }, + Some(MouseEventName::MouseScrollDown) => { + match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::LineCount(line_count)) => { + Ok(Mouse::ScrollDown(line_count as usize)) + }, + _ => Err("Malformed payload for mouse scroll down"), + } + }, + Some(MouseEventName::MouseLeftClick) => match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::Position(position)) => Ok( + Mouse::LeftClick(position.line as isize, position.column as usize), + ), + _ => Err("Malformed payload for mouse left click"), + }, + Some(MouseEventName::MouseRightClick) => { + match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::Position(position)) => Ok( + Mouse::RightClick(position.line as isize, position.column as usize), + ), + _ => Err("Malformed payload for mouse right click"), + } + }, + Some(MouseEventName::MouseHold) => match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::Position(position)) => Ok( + Mouse::Hold(position.line as isize, position.column as usize), + ), + _ => Err("Malformed payload for mouse hold"), + }, + Some(MouseEventName::MouseRelease) => match mouse_event_payload.mouse_event_payload { + Some(mouse_event_payload::MouseEventPayload::Position(position)) => Ok( + Mouse::Release(position.line as isize, position.column as usize), + ), + _ => Err("Malformed payload for mouse release"), + }, + None => Err("Malformed payload for MouseEventName"), + } + } +} + +impl TryFrom<Mouse> for MouseEventPayload { + type Error = &'static str; + fn try_from(mouse: Mouse) -> Result<Self, &'static str> { + match mouse { + Mouse::ScrollUp(number_of_lines) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseScrollUp as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::LineCount( + number_of_lines as u32, + )), + }), + Mouse::ScrollDown(number_of_lines) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseScrollDown as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::LineCount( + number_of_lines as u32, + )), + }), + Mouse::LeftClick(line, column) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseLeftClick as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::Position( + ProtobufPosition { + line: line as i64, + column: column as i64, + }, + )), + }), + Mouse::RightClick(line, column) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseRightClick as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::Position( + ProtobufPosition { + line: line as i64, + column: column as i64, + }, + )), + }), + Mouse::Hold(line, column) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseHold as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::Position( + ProtobufPosition { + line: line as i64, + column: column as i64, + }, + )), + }), + Mouse::Release(line, column) => Ok(MouseEventPayload { + mouse_event_name: MouseEventName::MouseRelease as i32, + mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::Position( + ProtobufPosition { + line: line as i64, + column: column as i64, + }, + )), + }), + } + } +} + +impl TryFrom<ProtobufPaneInfo> for PaneInfo { + type Error = &'static str; + fn try_from(protobuf_pane_info: ProtobufPaneInfo) -> Result<Self, &'static str> { + Ok(PaneInfo { + id: protobuf_pane_info.id, + is_plugin: protobuf_pane_info.is_plugin, + is_focused: protobuf_pane_info.is_focused, + is_fullscreen: protobuf_pane_info.is_fullscreen, + is_floating: protobuf_pane_info.is_floating, + is_suppressed: protobuf_pane_info.is_suppressed, + title: protobuf_pane_info.title, + exited: protobuf_pane_info.exited, + exit_status: protobuf_pane_info.exit_status, + is_held: protobuf_pane_info.is_held, + pane_x: protobuf_pane_info.pane_x as usize, + pane_content_x: protobuf_pane_info.pane_content_x as usize, + pane_y: protobuf_pane_info.pane_y as usize, + pane_content_y: protobuf_pane_info.pane_content_y as usize, + pane_rows: protobuf_pane_info.pane_rows as usize, + pane_content_rows: protobuf_pane_info.pane_content_rows as usize, + pane_columns: protobuf_pane_info.pane_columns as usize, + pane_content_columns: protobuf_pane_info.pane_content_columns as usize, + cursor_coordinates_in_pane: protobuf_pane_info + .cursor_coordinates_in_pane + .map(|position| (position.column as usize, position.line as usize)), + terminal_command: protobuf_pane_info.terminal_command, + plugin_url: protobuf_pane_info.plugin_url, + is_selectable: protobuf_pane_info.is_selectable, + }) + } +} + +impl TryFrom<PaneInfo> for ProtobufPaneInfo { + type Error = &'static str; + fn try_from(pane_info: PaneInfo) -> Result<Self, &'static str> { + Ok(ProtobufPaneInfo { + id: pane_info.id, + is_plugin: pane_info.is_plugin, + is_focused: pane_info.is_focused, + is_fullscreen: pane_info.is_fullscreen, + is_floating: pane_info.is_floating, + is_suppressed: pane_info.is_suppressed, + title: pane_info.title, + exited: pane_info.exited, + exit_status: pane_info.exit_status, + is_held: pane_info.is_held, + pane_x: pane_info.pane_x as u32, + pane_content_x: pane_info.pane_content_x as u32, + pane_y: pane_info.pane_y as u32, + pane_content_y: pane_info.pane_content_y as u32, + pane_rows: pane_info.pane_rows as u32, + pane_content_rows: pane_info.pane_content_rows as u32, + pane_columns: pane_info.pane_columns as u32, + pane_content_columns: pane_info.pane_content_columns as u32, + cursor_coordinates_in_pane: pane_info.cursor_coordinates_in_pane.map(|(x, y)| { + ProtobufPosition { + column: x as i64, + line: y as i64, + } + }), + terminal_command: pane_info.terminal_command, + plugin_url: pane_info.plugin_url, + is_selectable: pane_info.is_selectable, + }) + } +} + +impl TryFrom<ProtobufTabInfo> for TabInfo { + type Error = &'static str; + fn try_from(protobuf_tab_info: ProtobufTabInfo) -> Result<Self, &'static str> { + Ok(TabInfo { + position: protobuf_tab_info.position as usize, + name: protobuf_tab_info.name, + active: protobuf_tab_info.active, + panes_to_hide: protobuf_tab_info.panes_to_hide as usize, + is_fullscreen_active: protobuf_tab_info.is_fullscreen_active, + is_sync_panes_active: protobuf_tab_info.is_sync_panes_active, + are_floating_panes_visible: protobuf_tab_info.are_floating_panes_visible, + other_focused_clients: protobuf_tab_info + .other_focused_clients + .iter() + .map(|c| *c as u16) + .collect(), + active_swap_layout_name: protobuf_tab_info.active_swap_layout_name, + is_swap_layout_dirty: protobuf_tab_info.is_swap_layout_dirty, + }) + } +} + +impl TryFrom<TabInfo> for ProtobufTabInfo { + type Error = &'static str; + fn try_from(tab_info: TabInfo) -> Result<Self, &'static str> { + Ok(ProtobufTabInfo { + position: tab_info.position as u32, + name: tab_info.name, + active: tab_info.active, + panes_to_hide: tab_info.panes_to_hide as u32, + is_fullscreen_active: tab_info.is_fullscreen_active, + is_sync_panes_active: tab_info.is_sync_panes_active, + are_floating_panes_visible: tab_info.are_floating_panes_visible, + other_focused_clients: tab_info + .other_focused_clients + .iter() + .map(|c| *c as u32) + .collect(), + active_swap_layout_name: tab_info.active_swap_layout_name, + is_swap_layout_dirty: tab_info.is_swap_layout_dirty, + }) + } +} + +impl TryFrom<ProtobufModeUpdatePayload> for ModeInfo { + type Error = &'static str; + fn try_from( + mut protobuf_mode_update_payload: ProtobufModeUpdatePayload, + ) -> Result<Self, &'static str> { + let current_mode: InputMode = + ProtobufInputMode::from_i32(protobuf_mode_update_payload.current_mode) + .ok_or("Malformed InputMode in the ModeUpdate Event")? + .try_into()?; + let keybinds: Vec<(InputMode, Vec<(Key, Vec<Action>)>)> = protobuf_mode_update_payload + .keybinds + .iter_mut() + .filter_map(|k| { + let input_mode: InputMode = ProtobufInputMode::from_i32(k.mode) + .ok_or("Malformed InputMode in the ModeUpdate Event") + .ok()? + .try_into() + .ok()?; + let mut keybinds: Vec<(Key, Vec<Action>)> = vec![]; + for mut protobuf_keybind in k.key_bind.drain(..) { + let key: Key = protobuf_keybind.key.unwrap().try_into().ok()?; + let mut actions: Vec<Action> = vec![]; + for action in protobuf_keybind.action.drain(..) { + if let Ok(action) = action.try_into() { + actions.push(action); + } + } + keybinds.push((key, actions)); + } + Some((input_mode, keybinds)) + }) + .collect(); + let style: Style = protobuf_mode_update_payload + .style + .and_then(|m| m.try_into().ok()) + .ok_or("malformed payload for mode_info")?; + let session_name = protobuf_mode_update_payload.session_name; + let capabilities = PluginCapabilities { + arrow_fonts: protobuf_mode_update_payload.arrow_fonts_support, + }; + let mode_info = ModeInfo { + mode: current_mode, + keybinds, + style, + capabilities, + session_name, + }; + Ok(mode_info) + } +} + +impl TryFrom<ModeInfo> for ProtobufModeUpdatePayload { + type Error = &'static str; + fn try_from(mode_info: ModeInfo) -> Result<Self, &'static str> { + let current_mode: ProtobufInputMode = mode_info.mode.try_into()?; + let style: ProtobufStyle = mode_info.style.try_into()?; + let arrow_fonts_support: bool = mode_info.capabilities.arrow_fonts; + let session_name = mode_info.session_name; + let mut protobuf_input_mode_keybinds: Vec<ProtobufInputModeKeybinds> = vec![]; + for (input_mode, input_mode_keybinds) in mode_info.keybinds { + let mode: ProtobufInputMode = input_mode.try_into()?; + let mut keybinds: Vec<ProtobufKeyBind> = vec![]; + for (key, actions) in input_mode_keybinds { + let protobuf_key: ProtobufKey = key.try_into()?; + let mut protobuf_actions: Vec<ProtobufAction> = vec![]; + for action in actions { + if let Ok(protobuf_action) = action.try_into() { + protobuf_actions.push(protobuf_action); + } + } + let key_bind = ProtobufKeyBind { + key: Some(protobuf_key), + action: protobuf_actions, + }; + keybinds.push(key_bind); + } + let input_mode_keybind = ProtobufInputModeKeybinds { + mode: mode as i32, + key_bind: keybinds, + }; + protobuf_input_mode_keybinds.push(input_mode_keybind); + } + Ok(ProtobufModeUpdatePayload { + current_mode: current_mode as i32, + style: Some(style), + keybinds: protobuf_input_mode_keybinds, + arrow_fonts_support, + session_name, + }) + } +} + +impl TryFrom<ProtobufEventNameList> for HashSet<EventType> { + type Error = &'static str; + fn try_from(protobuf_event_name_list: ProtobufEventNameList) -> Result<Self, &'static str> { + let event_types: Vec<ProtobufEventType> = protobuf_event_name_list + .event_types + .iter() + .filter_map(|i| ProtobufEventType::from_i32(*i)) + .collect(); + let event_types: Vec<EventType> = event_types + .iter() + .filter_map(|e| EventType::try_from(*e).ok()) + .collect(); + Ok(event_types.into_iter().collect()) + } +} + +impl TryFrom<HashSet<EventType>> for ProtobufEventNameList { + type Error = &'static str; + fn try_from(event_types: HashSet<EventType>) -> Result<Self, &'static str> { + let protobuf_event_name_list = ProtobufEventNameList { + event_types: event_types + .iter() + .filter_map(|e| ProtobufEventType::try_from(*e).ok()) + .map(|e| e as i32) + .collect(), + }; + Ok(protobuf_event_name_list) + } +} + +impl TryFrom<ProtobufEventType> for EventType { + type Error = &'static str; + fn try_from(protobuf_event_type: ProtobufEventType) -> Result<Self, &'static str> { + Ok(match protobuf_event_type { + ProtobufEventType::ModeUpdate => EventType::ModeUpdate, + ProtobufEventType::TabUpdate => EventType::TabUpdate, + ProtobufEventType::PaneUpdate => EventType::PaneUpdate, + ProtobufEventType::Key => EventType::Key, + ProtobufEventType::Mouse => EventType::Mouse, + ProtobufEventType::Timer => EventType::Timer, + ProtobufEventType::CopyToClipboard => EventType::CopyToClipboard, + ProtobufEventType::SystemClipboardFailure => EventType::SystemClipboardFailure, + ProtobufEventType::InputReceived => EventType::InputReceived, + ProtobufEventType::Visible => EventType::Visible, + ProtobufEventType::CustomMessage => EventType::CustomMessage, + ProtobufEventType::FileSystemCreate => EventType::FileSystemCreate, + ProtobufEventType::FileSystemRead => EventType::FileSystemRead, + ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate, + ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete, + }) + } +} + +impl TryFrom<EventType> for ProtobufEventType { + type Error = &'static str; + fn try_from(event_type: EventType) -> Result<Self, &'static str> { + Ok(match event_type { + EventType::ModeUpdate => ProtobufEventType::ModeUpdate, + EventType::TabUpdate => ProtobufEventType::TabUpdate, + EventType::PaneUpdate => ProtobufEventType::PaneUpdate, + EventType::Key => ProtobufEventType::Key, + EventType::Mouse => ProtobufEventType::Mouse, + EventType::Timer => ProtobufEventType::Timer, + EventType::CopyToClipboard => ProtobufEventType::CopyToClipboard, + EventType::SystemClipboardFailure => ProtobufEventType::SystemClipboardFailure, + EventType::InputReceived => ProtobufEventType::InputReceived, + EventType::Visible => ProtobufEventType::Visible, + EventType::CustomMessage => ProtobufEventType::CustomMessage, + EventType::FileSystemCreate => ProtobufEventType::FileSystemCreate, + EventType::FileSystemRead => ProtobufEventType::FileSystemRead, + EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate, + EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete, + }) + } +} + +#[test] +fn serialize_mode_update_event() { + use prost::Message; + let mode_update_event = Event::ModeUpdate(Default::default()); + let protobuf_event: ProtobufEvent = mode_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + mode_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_mode_update_event_with_non_default_values() { + use prost::Message; + let mode_update_event = Event::ModeUpdate(ModeInfo { + mode: InputMode::Locked, + keybinds: vec![ + ( + InputMode::Locked, + vec![( + Key::Alt(crate::data::CharOrArrow::Char('b')), + vec![Action::SwitchToMode(InputMode::Normal)], + )], + ), + ( + InputMode::Tab, + vec![( + Key::Alt(crate::data::CharOrArrow::Direction(Direction::Up)), + vec![Action::SwitchToMode(InputMode::Pane)], + )], + ), + ( + InputMode::Pane, + vec![ + ( + Key::Ctrl('b'), + vec![ + Action::SwitchToMode(InputMode::Tmux), + Action::Write(vec![10]), + ], + ), + (Key::Char('a'), vec![Action::WriteChars("foo".to_owned())]), + ], + ), + ], + style: Style { + colors: Palette { + source: crate::data::PaletteSource::Default, + theme_hue: ThemeHue::Light, + fg: PaletteColor::Rgb((1, 1, 1)), + bg: PaletteColor::Rgb((200, 200, 200)), + black: PaletteColor::EightBit(1), + red: PaletteColor::EightBit(2), + green: PaletteColor::EightBit(2), + yellow: PaletteColor::EightBit(2), + blue: PaletteColor::EightBit(2), + magenta: PaletteColor::EightBit(2), + cyan: PaletteColor::EightBit(2), + white: PaletteColor::EightBit(2), + orange: PaletteColor::EightBit(2), + gray: PaletteColor::EightBit(2), + purple: PaletteColor::EightBit(2), + gold: PaletteColor::EightBit(2), + silver: PaletteColor::EightBit(2), + pink: PaletteColor::EightBit(2), + brown: PaletteColor::Rgb((222, 221, 220)), + }, + rounded_corners: true, + hide_session_name: false, + }, + capabilities: PluginCapabilities { arrow_fonts: false }, + session_name: Some("my awesome test session".to_owned()), + }); + let protobuf_event: ProtobufEvent = mode_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + mode_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_tab_update_event() { + use prost::Message; + let tab_update_event = Event::TabUpdate(Default::default()); + let protobuf_event: ProtobufEvent = tab_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + tab_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_tab_update_event_with_non_default_values() { + use prost::Message; + let tab_update_event = Event::TabUpdate(vec![ + TabInfo { + position: 0, + name: "First tab".to_owned(), + active: true, + panes_to_hide: 2, + is_fullscreen_active: true, + is_sync_panes_active: false, + are_floating_panes_visible: true, + other_focused_clients: vec![2, 3, 4], + active_swap_layout_name: Some("my cool swap layout".to_owned()), + is_swap_layout_dirty: false, + }, + TabInfo { + position: 1, + name: "Secondtab".to_owned(), + active: false, + panes_to_hide: 5, + is_fullscreen_active: false, + is_sync_panes_active: true, + are_floating_panes_visible: true, + other_focused_clients: vec![1, 5, 111], + active_swap_layout_name: None, + is_swap_layout_dirty: true, + }, + TabInfo::default(), + ]); + let protobuf_event: ProtobufEvent = tab_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + tab_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_pane_update_event() { + use prost::Message; + let pane_update_event = Event::PaneUpdate(Default::default()); + let protobuf_event: ProtobufEvent = pane_update_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + pane_update_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_key_event() { + use prost::Message; + let key_event = Event::Key(Key::Ctrl('a')); + let protobuf_event: ProtobufEvent = key_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + key_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_mouse_event() { + use prost::Message; + let mouse_event = Event::Mouse(Mouse::LeftClick(1, 1)); + let protobuf_event: ProtobufEvent = mouse_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + mouse_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_mouse_event_without_position() { + use prost::Message; + let mouse_event = Event::Mouse(Mouse::ScrollUp(17)); + let protobuf_event: ProtobufEvent = mouse_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + mouse_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_timer_event() { + use prost::Message; + let timer_event = Event::Timer(1.5); + let protobuf_event: ProtobufEvent = timer_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + timer_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_copy_to_clipboard_event() { + use prost::Message; + let copy_event = Event::CopyToClipboard(CopyDestination::Primary); + let protobuf_event: ProtobufEvent = copy_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + copy_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_clipboard_failure_event() { + use prost::Message; + let copy_event = Event::SystemClipboardFailure; + let protobuf_event: ProtobufEvent = copy_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + copy_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_input_received_event() { + use prost::Message; + let input_received_event = Event::InputReceived; + let protobuf_event: ProtobufEvent = input_received_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + input_received_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_visible_event() { + use prost::Message; + let visible_event = Event::Visible(true); + let protobuf_event: ProtobufEvent = visible_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + visible_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_custom_message_event() { + use prost::Message; + let custom_message_event = Event::CustomMessage("foo".to_owned(), "bar".to_owned()); + let protobuf_event: ProtobufEvent = custom_message_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + custom_message_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_file_system_create_event() { + use prost::Message; + let file_system_event = + Event::FileSystemCreate(vec!["/absolute/path".into(), "./relative_path".into()]); + let protobuf_event: ProtobufEvent = file_system_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + file_system_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_file_system_read_event() { + use prost::Message; + let file_system_event = + Event::FileSystemRead(vec!["/absolute/path".into(), "./relative_path".into()]); + let protobuf_event: ProtobufEvent = file_system_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + file_system_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_file_system_update_event() { + use prost::Message; + let file_system_event = + Event::FileSystemUpdate(vec!["/absolute/path".into(), "./relative_path".into()]); + let protobuf_event: ProtobufEvent = file_system_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + file_system_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} + +#[test] +fn serialize_file_system_delete_event() { + use prost::Message; + let file_system_event = + Event::FileSystemDelete(vec!["/absolute/path".into(), "./relative_path".into()]); + let protobuf_event: ProtobufEvent = file_system_event.clone().try_into().unwrap(); + let serialized_protobuf_event = protobuf_event.encode_to_vec(); + let deserialized_protobuf_event: ProtobufEvent = + Message::decode(serialized_protobuf_event.as_slice()).unwrap(); + let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap(); + assert_eq!( + file_system_event, deserialized_event, + "Event properly serialized/deserialized without change" + ); +} diff --git a/zellij-utils/src/plugin_api/file.proto b/zellij-utils/src/plugin_api/file.proto new file mode 100644 index 00000000..b2c5372b --- /dev/null +++ b/zellij-utils/src/plugin_api/file.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package api.file; + +message File { + string path = 1; + optional int32 line_number = 2; + optional string cwd = 3; +} diff --git a/zellij-utils/src/plugin_api/file.rs b/zellij-utils/src/plugin_api/file.rs new file mode 100644 index 00000000..7c06aa8e --- /dev/null +++ b/zellij-utils/src/plugin_api/file.rs @@ -0,0 +1,30 @@ +pub use super::generated_api::api::file::File as ProtobufFile; +use crate::data::FileToOpen; + +use std::convert::TryFrom; +use std::path::PathBuf; + +impl TryFrom<ProtobufFile> for FileToOpen { + type Error = &'static str; + fn try_from(protobuf_file: ProtobufFile) -> Result<Self, &'static str> { + let path = PathBuf::from(protobuf_file.path); + let line_number = protobuf_file.line_number.map(|l| l as usize); + let cwd = protobuf_file.cwd.map(|c| PathBuf::from(c)); + Ok(FileToOpen { + path, + line_number, + cwd, + }) + } +} + +impl TryFrom<FileToOpen> for ProtobufFile { + type Error = &'static str; + fn try_from(file_to_open: FileToOpen) -> Result<Self, &'static str> { + Ok(ProtobufFile { + path: file_to_open.path.display().to_string(), + line_number: file_to_open.line_number.map(|l| l as i32), + cwd: file_to_open.cwd.map(|c| c.display().to_string()), + }) + } +} diff --git a/zellij-utils/src/plugin_api/input_mode.proto b/zellij-utils/src/plugin_api/input_mode.proto new file mode 100644 index 00000000..31122495 --- /dev/null +++ b/zellij-utils/src/plugin_api/input_mode.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package api.input_mode; + +message InputModeMessage { + InputMode input_mode = 1; +} + +enum InputMode { + /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading + /// to other modes + Normal = 0; + /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled + /// except the one leading back to normal mode + Locked = 1; + /// `Resize` mode allows resizing the different existing panes. + Resize = 2; + /// `Pane` mode allows creating and closing panes, as well as moving between them. + Pane = 3; + /// `Tab` mode allows creating and closing tabs, as well as moving between them. + Tab = 4; + /// `Scroll` mode allows scrolling up and down within a pane. + Scroll = 5; + /// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane. + EnterSearch = 6; + /// `Search` mode allows for searching a term in a pane (superset of `Scroll`). + Search = 7; + /// `RenameTab` mode allows assigning a new name to a tab. + RenameTab = 8; + /// `RenamePane` mode allows assigning a new name to a pane. + RenamePane = 9; + /// `Session` mode allows detaching sessions + Session = 10; + /// `Move` mode allows moving the different existing panes within a tab + Move = 11; + /// `Prompt` mode allows interacting with active prompts. + Prompt = 12; + /// `Tmux` mode allows for basic tmux keybindings functionality + Tmux = 13; +} diff --git a/zellij-utils/src/plugin_api/input_mode.rs b/zellij-utils/src/plugin_api/input_mode.rs new file mode 100644 index 00000000..83a6a009 --- /dev/null +++ b/zellij-utils/src/plugin_api/input_mode.rs @@ -0,0 +1,69 @@ +pub use super::generated_api::api::input_mode::{ + InputMode as ProtobufInputMode, InputModeMessage as ProtobufInputModeMessage, +}; +use crate::data::InputMode; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufInputMode> for InputMode { + type Error = &'static str; + fn try_from(protobuf_input_mode: ProtobufInputMode) -> Result<Self, &'static str> { + match protobuf_input_mode { + ProtobufInputMode::Normal => Ok(InputMode::Normal), + ProtobufInputMode::Locked => Ok(InputMode::Locked), + ProtobufInputMode::Resize => Ok(InputMode::Resize), + ProtobufInputMode::Pane => Ok(InputMode::Pane), + ProtobufInputMode::Tab => Ok(InputMode::Tab), + ProtobufInputMode::Scroll => Ok(InputMode::Scroll), + ProtobufInputMode::EnterSearch => Ok(InputMode::EnterSearch), + ProtobufInputMode::Search => Ok(InputMode::Search), + ProtobufInputMode::RenameTab => Ok(InputMode::RenameTab), + ProtobufInputMode::RenamePane => Ok(InputMode::RenamePane), + ProtobufInputMode::Session => Ok(InputMode::Session), + ProtobufInputMode::Move => Ok(InputMode::Move), + ProtobufInputMode::Prompt => Ok(InputMode::Prompt), + ProtobufInputMode::Tmux => Ok(InputMode::Tmux), + } + } +} + +impl TryFrom<InputMode> for ProtobufInputMode { + type Error = &'static str; + fn try_from(input_mode: InputMode) -> Result<Self, &'static str> { + Ok(match input_mode { + InputMode::Normal => ProtobufInputMode::Normal, + InputMode::Locked => ProtobufInputMode::Locked, + InputMode::Resize => ProtobufInputMode::Resize, + InputMode::Pane => ProtobufInputMode::Pane, + InputMode::Tab => ProtobufInputMode::Tab, + InputMode::Scroll => ProtobufInputMode::Scroll, + InputMode::EnterSearch => ProtobufInputMode::EnterSearch, + InputMode::Search => ProtobufInputMode::Search, + InputMode::RenameTab => ProtobufInputMode::RenameTab, + InputMode::RenamePane => ProtobufInputMode::RenamePane, + InputMode::Session => ProtobufInputMode::Session, + InputMode::Move => ProtobufInputMode::Move, + InputMode::Prompt => ProtobufInputMode::Prompt, + InputMode::Tmux => ProtobufInputMode::Tmux, + }) + } +} + +impl TryFrom<ProtobufInputModeMessage> for InputMode { + type Error = &'static str; + fn try_from(protobuf_input_mode: ProtobufInputModeMessage) -> Result<Self, &'static str> { + ProtobufInputMode::from_i32(protobuf_input_mode.input_mode) + .and_then(|p| p.try_into().ok()) + .ok_or("Invalid input mode") + } +} + +impl TryFrom<InputMode> for ProtobufInputModeMessage { + type Error = &'static str; + fn try_from(input_mode: InputMode) -> Result<Self, &'static str> { + let protobuf_input_mode: ProtobufInputMode = input_mode.try_into()?; + Ok(ProtobufInputModeMessage { + input_mode: protobuf_input_mode as i32, + }) + } +} diff --git a/zellij-utils/src/plugin_api/key.proto b/zellij-utils/src/plugin_api/key.proto new file mode 100644 index 00000000..9a573483 --- /dev/null +++ b/zellij-utils/src/plugin_api/key.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +package api.key; + +message Key { + enum KeyModifier { + CTRL = 0; + ALT = 1; + } + + enum NamedKey { + PageDown = 0; + PageUp = 1; + LeftArrow = 2; + DownArrow = 3; + UpArrow = 4; + RightArrow = 5; + Home = 6; + End = 7; + Backspace = 8; + Delete = 9; + Insert = 10; + F1 = 11; + F2 = 12; + F3 = 13; + F4 = 14; + F5 = 15; + F6 = 16; + F7 = 17; + F8 = 18; + F9 = 19; + F10 = 20; + F11 = 21; + F12 = 22; + Tab = 23; + Esc = 24; + } + + enum Char { + a = 0; + b = 1; + c = 2; + d = 3; + e = 4; + f = 5; + g = 6; + h = 7; + i = 8; + j = 9; + k = 10; + l = 11; + m = 12; + n = 13; + o = 14; + p = 15; + q = 16; + r = 17; + s = 18; + t = 19; + u = 20; + v = 21; + w = 22; + x = 23; + y = 24; + z = 25; + zero = 26; + one = 27; + two = 28; + three = 29; + four = 30; + five = 31; + six = 32; + seven = 33; + eight = 34; + nine = 35; + } + + optional KeyModifier modifier = 1; + oneof main_key { + NamedKey key = 2; + Char char = 3; + } +} diff --git a/zellij-utils/src/plugin_api/key.rs b/zellij-utils/src/plugin_api/key.rs new file mode 100644 index 00000000..7cef4ba7 --- /dev/null +++ b/zellij-utils/src/plugin_api/key.rs @@ -0,0 +1,222 @@ +pub use super::generated_api::api::key::{ + key::{KeyModifier, MainKey, NamedKey}, + Key as ProtobufKey, +}; +use crate::data::{CharOrArrow, Direction, Key}; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufKey> for Key { + type Error = &'static str; + fn try_from(protobuf_key: ProtobufKey) -> Result<Self, &'static str> { + let key_modifier = parse_optional_modifier(&protobuf_key); + match key_modifier { + Some(KeyModifier::Ctrl) => { + let character = char_from_main_key(protobuf_key.main_key)?; + Ok(Key::Ctrl(character)) + }, + Some(KeyModifier::Alt) => { + let char_or_arrow = CharOrArrow::from_main_key(protobuf_key.main_key)?; + Ok(Key::Alt(char_or_arrow)) + }, + None => match protobuf_key.main_key.as_ref().ok_or("invalid key")? { + MainKey::Char(_key_index) => { + let character = char_from_main_key(protobuf_key.main_key)?; + Ok(Key::Char(character)) + }, + MainKey::Key(key_index) => { + let key = NamedKey::from_i32(*key_index).ok_or("invalid_key")?; + Ok(named_key_to_key(key)) + }, + }, + } + } +} + +impl TryFrom<Key> for ProtobufKey { + type Error = &'static str; + fn try_from(key: Key) -> Result<Self, &'static str> { + match key { + Key::PageDown => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::PageDown as i32)), + }), + Key::PageUp => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::PageUp as i32)), + }), + Key::Left => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::LeftArrow as i32)), + }), + Key::Down => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::DownArrow as i32)), + }), + Key::Up => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::UpArrow as i32)), + }), + Key::Right => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::RightArrow as i32)), + }), + Key::Home => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Home as i32)), + }), + Key::End => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::End as i32)), + }), + Key::Backspace => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Backspace as i32)), + }), + Key::Delete => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Delete as i32)), + }), + Key::Insert => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Insert as i32)), + }), + Key::F(index) => { + let main_key = match index { + 1 => Some(MainKey::Key(NamedKey::F1 as i32)), + 2 => Some(MainKey::Key(NamedKey::F2 as i32)), + 3 => Some(MainKey::Key(NamedKey::F3 as i32)), + 4 => Some(MainKey::Key(NamedKey::F4 as i32)), + 5 => Some(MainKey::Key(NamedKey::F5 as i32)), + 6 => Some(MainKey::Key(NamedKey::F6 as i32)), + 7 => Some(MainKey::Key(NamedKey::F7 as i32)), + 8 => Some(MainKey::Key(NamedKey::F8 as i32)), + 9 => Some(MainKey::Key(NamedKey::F9 as i32)), + 10 => Some(MainKey::Key(NamedKey::F10 as i32)), + 11 => Some(MainKey::Key(NamedKey::F11 as i32)), + 12 => Some(MainKey::Key(NamedKey::F12 as i32)), + _ => return Err("Invalid key"), + }; + Ok(ProtobufKey { + modifier: None, + main_key, + }) + }, + Key::Char(character) => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Char((character as u8) as i32)), + }), + Key::Alt(char_or_arrow) => { + let main_key = match char_or_arrow { + CharOrArrow::Char(character) => MainKey::Char((character as u8) as i32), + CharOrArrow::Direction(Direction::Left) => { + MainKey::Key(NamedKey::LeftArrow as i32) + }, + CharOrArrow::Direction(Direction::Right) => { + MainKey::Key(NamedKey::RightArrow as i32) + }, + CharOrArrow::Direction(Direction::Up) => MainKey::Key(NamedKey::UpArrow as i32), + CharOrArrow::Direction(Direction::Down) => { + MainKey::Key(NamedKey::DownArrow as i32) + }, + }; + Ok(ProtobufKey { + modifier: Some(KeyModifier::Alt as i32), + main_key: Some(main_key), + }) + }, + Key::Ctrl(character) => Ok(ProtobufKey { + modifier: Some(KeyModifier::Ctrl as i32), + main_key: Some(MainKey::Char((character as u8) as i32)), + }), + Key::BackTab => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Tab as i32)), + }), + Key::Null => { + Ok(ProtobufKey { + modifier: None, + main_key: None, // TODO: does this break deserialization? + }) + }, + Key::Esc => Ok(ProtobufKey { + modifier: None, + main_key: Some(MainKey::Key(NamedKey::Esc as i32)), + }), + } + } +} + +impl CharOrArrow { + pub fn from_main_key( + main_key: std::option::Option<MainKey>, + ) -> Result<CharOrArrow, &'static str> { + match main_key { + Some(MainKey::Char(encoded_key)) => { + Ok(CharOrArrow::Char(char_index_to_char(encoded_key))) + }, + Some(MainKey::Key(key_index)) => match NamedKey::from_i32(key_index) { + Some(NamedKey::LeftArrow) => Ok(CharOrArrow::Direction(Direction::Left)), + Some(NamedKey::RightArrow) => Ok(CharOrArrow::Direction(Direction::Right)), + Some(NamedKey::UpArrow) => Ok(CharOrArrow::Direction(Direction::Up)), + Some(NamedKey::DownArrow) => Ok(CharOrArrow::Direction(Direction::Down)), + _ => Err("Unsupported key"), + }, + _ => { + return Err("Unsupported key"); + }, + } + } +} + +fn parse_optional_modifier(m: &ProtobufKey) -> Option<KeyModifier> { + match m.modifier { + Some(modifier) => KeyModifier::from_i32(modifier), + _ => None, + } +} + +fn char_index_to_char(char_index: i32) -> char { + char_index as u8 as char +} + +fn char_from_main_key(main_key: Option<MainKey>) -> Result<char, &'static str> { + match main_key { + Some(MainKey::Char(encoded_key)) => { + return Ok(char_index_to_char(encoded_key)); + }, + _ => { + return Err("Unsupported key"); + }, + } +} + +fn named_key_to_key(named_key: NamedKey) -> Key { + match named_key { + NamedKey::PageDown => Key::PageDown, + NamedKey::PageUp => Key::PageUp, + NamedKey::LeftArrow => Key::Left, + NamedKey::DownArrow => Key::Down, + NamedKey::UpArrow => Key::Up, + NamedKey::RightArrow => Key::Right, + NamedKey::Home => Key::Home, + NamedKey::End => Key::End, + NamedKey::Backspace => Key::Backspace, + NamedKey::Delete => Key::Delete, + NamedKey::Insert => Key::Insert, + NamedKey::F1 => Key::F(1), + NamedKey::F2 => Key::F(2), + NamedKey::F3 => Key::F(3), + NamedKey::F4 => Key::F(4), + NamedKey::F5 => Key::F(5), + NamedKey::F6 => Key::F(6), + NamedKey::F7 => Key::F(7), + NamedKey::F8 => Key::F(8), + NamedKey::F9 => Key::F(9), + NamedKey::F10 => Key::F(10), + NamedKey::F11 => Key::F(11), + NamedKey::F12 => Key::F(12), + NamedKey::Tab => Key::BackTab, + NamedKey::Esc => Key::Esc, + } +} diff --git a/zellij-utils/src/plugin_api/message.proto b/zellij-utils/src/plugin_api/message.proto new file mode 100644 index 00000000..35d11150 --- /dev/null +++ b/zellij-utils/src/plugin_api/message.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package api.message; + +message Message { + string name = 1; + string payload = 2; + optional string worker_name = 3; +} diff --git a/zellij-utils/src/plugin_api/message.rs b/zellij-utils/src/plugin_api/message.rs new file mode 100644 index 00000000..365a427a --- /dev/null +++ b/zellij-utils/src/plugin_api/message.rs @@ -0,0 +1,29 @@ +pub use super::generated_api::api::message::Message as ProtobufMessage; +use crate::data::PluginMessage; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufMessage> for PluginMessage { + type Error = &'static str; + fn try_from(protobuf_message: ProtobufMessage) -> Result<Self, &'static str> { + let name = protobuf_message.name; + let payload = protobuf_message.payload; + let worker_name = protobuf_message.worker_name; + Ok(PluginMessage { + name, + payload, + worker_name, + }) + } +} + +impl TryFrom<PluginMessage> for ProtobufMessage { + type Error = &'static str; + fn try_from(plugin_message: PluginMessage) -> Result<Self, &'static str> { + Ok(ProtobufMessage { + name: plugin_message.name, + payload: plugin_message.payload, + worker_name: plugin_message.worker_name, + }) + } +} diff --git a/zellij-utils/src/plugin_api/mod.rs b/zellij-utils/src/plugin_api/mod.rs new file mode 100644 index 00000000..55ace500 --- /dev/null +++ b/zellij-utils/src/plugin_api/mod.rs @@ -0,0 +1,14 @@ +pub mod action; +pub mod command; +pub mod event; +pub mod file; +pub mod input_mode; +pub mod key; +pub mod message; +pub mod plugin_command; +pub mod plugin_ids; +pub mod resize; +pub mod style; +pub mod generated_api { + include!(concat!(env!("OUT_DIR"), "/generated_plugin_api.rs")); +} diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto new file mode 100644 index 00000000..f09ccf4b --- /dev/null +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -0,0 +1,175 @@ +syntax = "proto3"; + +import "event.proto"; +import "file.proto"; +import "command.proto"; +import "message.proto"; +import "input_mode.proto"; +import "resize.proto"; + +package api.plugin_command; + +enum CommandName { + Subscribe = 0; + Unsubscribe = 1; + SetSelectable = 2; + GetPluginIds = 3; + GetZellijVersion = 4; + OpenFile = 5; + OpenFileFloating = 6; + OpenTerminal = 7; + OpenTerminalFloating = 8; + OpenCommandPane = 9; + OpenCommandPaneFloating = 10; + SwitchTabTo = 11; + SetTimeout = 12; + ExecCmd = 13; + PostMessageTo = 14; + PostMessageToPlugin = 15; + HideSelf = 16; + ShowSelf = 17; + SwitchToMode = 18; + NewTabsWithLayout = 19; + NewTab = 20; + GoToNextTab = 21; + GoToPreviousTab = 22; + Resize = 23; + ResizeWithDirection = 24; + FocusNextPane = 25; + FocusPreviousPane = 26; + MoveFocus = 27; + MoveFocusOrTab = 28; + Detach = 29; + EditScrollback = 30; + Write = 31; + WriteChars = 32; + ToggleTab = 33; + MovePane = 34; + MovePaneWithDirection = 35; + ClearScreen = 36; + ScrollUp = 37; + ScrollDown = 38; + ScrollToTop = 39; + ScrollToBottom = 40; + PageScrollUp = 41; + PageScrollDown = 42; + ToggleFocusFullscreen = 43; + TogglePaneFrames = 44; + TogglePaneEmbedOrEject = 45; + UndoRenamePane = 46; + CloseFocus = 47; + ToggleActiveTabSync = 48; + CloseFocusedTab = 49; + UndoRenameTab = 50; + QuitZellij = 51; + PreviousSwapLayout = 52; + NextSwapLayout = 53; + GoToTabName = 54; + FocusOrCreateTab = 55; + GoToTab = 56; + StartOrReloadPlugin = 57; + CloseTerminalPane = 58; + ClosePluginPane = 59; + FocusTerminalPane = 60; + FocusPluginPane = 61; + RenameTerminalPane = 62; + RenamePluginPane = 63; + RenameTab = 64; + ReportCrash = 65; +} + +message PluginCommand { + CommandName name = 1; + oneof payload { + SubscribePayload subscribe_payload = 2; + UnsubscribePayload unsubscribe_payload = 3; + bool set_selectable_payload = 4; + OpenFilePayload open_file_payload = 5; + OpenFilePayload open_file_floating_payload = 6; + OpenFilePayload open_terminal_payload = 7; + OpenFilePayload open_terminal_floating_payload = 8; + OpenCommandPanePayload open_command_pane_payload = 9; + OpenCommandPanePayload open_command_pane_floating_payload = 10; + SwitchTabToPayload switch_tab_to_payload = 11; + SetTimeoutPayload set_timeout_payload = 12; + ExecCmdPayload exec_cmd_payload = 13; + PluginMessagePayload post_message_to_payload = 14; + PluginMessagePayload post_message_to_plugin_payload = 15; + bool show_self_payload = 16; + SwitchToModePayload switch_to_mode_payload = 17; + string new_tabs_with_layout_payload = 18; + ResizePayload resize_payload = 19; + ResizePayload resize_with_direction_payload = 20; + MovePayload move_focus_payload = 21; + MovePayload move_focus_or_tab_payload = 22; + bytes write_payload = 23; + string write_chars_payload = 24; + MovePayload move_pane_with_direction_payload = 25; + string go_to_tab_name_payload = 26; + string focus_or_create_tab_payload = 27; + int32 go_to_tab_payload = 28; + string start_or_reload_plugin_payload = 29; + int32 close_terminal_pane_payload = 30; + int32 close_plugin_pane_payload = 31; + PaneIdAndShouldFloat focus_terminal_pane_payload = 32; + PaneIdAndShouldFloat focus_plugin_pane_payload = 33; + IdAndNewName rename_terminal_pane_payload = 34; + IdAndNewName rename_plugin_pane_payload = 35; + IdAndNewName rename_tab_payload = 36; + string report_crash_payload = 37; + } +} + +message SubscribePayload { + event.EventNameList subscriptions = 1; +} + +message UnsubscribePayload { + event.EventNameList subscriptions = 1; +} + +message OpenFilePayload { + file.File file_to_open = 1; +} + +message OpenCommandPanePayload { + command.Command command_to_run = 1; +} + +message SwitchTabToPayload { + int32 tab_index = 1; +} + +message SetTimeoutPayload { + float seconds = 1; +} + +message ExecCmdPayload { + repeated string command_line = 1; +} + +message PluginMessagePayload { + api.message.Message message = 1; +} + +message SwitchToModePayload { + input_mode.InputModeMessage input_mode = 1; +} + +message ResizePayload { + resize.Resize resize = 1; +} + +message MovePayload { + resize.MoveDirection direction = 1; +} + +message PaneIdAndShouldFloat { + int32 pane_id = 1; + bool should_float = 2; +} + +message IdAndNewName { + int32 id = 1; // pane id or tab index + string new_name = 2; +} diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs new file mode 100644 index 00000000..bf359dd2 --- /dev/null +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -0,0 +1,825 @@ +pub use super::generated_api::api::{ + event::EventNameList as ProtobufEventNameList, + plugin_command::{ + plugin_command::Payload, CommandName, ExecCmdPayload, IdAndNewName, MovePayload, + OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat, + PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ResizePayload, + SetTimeoutPayload, SubscribePayload, SwitchTabToPayload, SwitchToModePayload, + UnsubscribePayload, + }, + resize::ResizeAction as ProtobufResizeAction, +}; + +use crate::data::PluginCommand; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufPluginCommand> for PluginCommand { + type Error = &'static str; + fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> { + match CommandName::from_i32(protobuf_plugin_command.name) { + Some(CommandName::Subscribe) => match protobuf_plugin_command.payload { + Some(Payload::SubscribePayload(subscribe_payload)) => { + let protobuf_event_list = subscribe_payload.subscriptions; + match protobuf_event_list { + Some(protobuf_event_list) => { + Ok(PluginCommand::Subscribe(protobuf_event_list.try_into()?)) + }, + None => Err("malformed subscription event"), + } + }, + _ => Err("Mismatched payload for Subscribe"), + }, + Some(CommandName::Unsubscribe) => match protobuf_plugin_command.payload { + Some(Payload::UnsubscribePayload(unsubscribe_payload)) => { + let protobuf_event_list = unsubscribe_payload.subscriptions; + match protobuf_event_list { + Some(protobuf_event_list) => { + Ok(PluginCommand::Unsubscribe(protobuf_event_list.try_into()?)) + }, + None => Err("malformed unsubscription event"), + } + }, + _ => Err("Mismatched payload for Unsubscribe"), + }, + Some(CommandName::SetSelectable) => match protobuf_plugin_command.payload { + Some(Payload::SetSelectablePayload(should_be_selectable)) => { + Ok(PluginCommand::SetSelectable(should_be_selectable)) + }, + _ => Err("Mismatched payload for SetSelectable"), + }, + Some(CommandName::GetPluginIds) => { + if protobuf_plugin_command.payload.is_some() { + Err("GetPluginIds should not have a payload") + } else { + Ok(PluginCommand::GetPluginIds) + } + }, + Some(CommandName::GetZellijVersion) => { + if protobuf_plugin_command.payload.is_some() { + Err("GetZellijVersion should not have a payload") + } else { + Ok(PluginCommand::GetZellijVersion) + } + }, + Some(CommandName::OpenFile) => match protobuf_plugin_command.payload { + Some(Payload::OpenFilePayload(file_to_open_payload)) => { + match file_to_open_payload.file_to_open { + Some(file_to_open) => Ok(PluginCommand::OpenFile(file_to_open.try_into()?)), + None => Err("Malformed open file payload"), + } + }, + _ => Err("Mismatched payload for OpenFile"), + }, + Some(CommandName::OpenFileFloating) => match protobuf_plugin_command.payload { + Some(Payload::OpenFileFloatingPayload(file_to_open_payload)) => { + match file_to_open_payload.file_to_open { + Some(file_to_open) => { + Ok(PluginCommand::OpenFileFloating(file_to_open.try_into()?)) + }, + None => Err("Malformed open file payload"), + } + }, + _ => Err("Mismatched payload for OpenFile"), + }, + Some(CommandName::OpenTerminal) => match protobuf_plugin_command.payload { + Some(Payload::OpenTerminalPayload(file_to_open_payload)) => { + match file_to_open_payload.file_to_open { + Some(file_to_open) => { + Ok(PluginCommand::OpenTerminal(file_to_open.try_into()?)) + }, + None => Err("Malformed open terminal payload"), + } + }, + _ => Err("Mismatched payload for OpenTerminal"), + }, + Some(CommandName::OpenTerminalFloating) => match protobuf_plugin_command.payload { + Some(Payload::OpenTerminalFloatingPayload(file_to_open_payload)) => { + match file_to_open_payload.file_to_open { + Some(file_to_open) => Ok(PluginCommand::OpenTerminalFloating( + file_to_open.try_into()?, + )), + None => Err("Malformed open terminal floating payload"), + } + }, + _ => Err("Mismatched payload for OpenTerminalFloating"), + }, + Some(CommandName::OpenCommandPane) => match protobuf_plugin_command.payload { + Some(Payload::OpenCommandPanePayload(command_to_run_payload)) => { + match command_to_run_payload.command_to_run { + Some(command_to_run) => { + Ok(PluginCommand::OpenCommandPane(command_to_run.try_into()?)) + }, + None => Err("Malformed open open command pane payload"), + } + }, + _ => Err("Mismatched payload for OpenCommandPane"), + }, + Some(CommandName::OpenCommandPaneFloating) => match protobuf_plugin_command.payload { + Some(Payload::OpenCommandPaneFloatingPayload(command_to_run_payload)) => { + match command_to_run_payload.command_to_run { + Some(command_to_run) => Ok(PluginCommand::OpenCommandPaneFloating( + command_to_run.try_into()?, + )), + None => Err("Malformed open command pane floating payload"), + } + }, + _ => Err("Mismatched payload for OpenCommandPaneFloating"), + }, + Some(CommandName::SwitchTabTo) => match protobuf_plugin_command.payload { + Some(Payload::SwitchTabToPayload(switch_to_tab_payload)) => Ok( + PluginCommand::SwitchTabTo(switch_to_tab_payload.tab_index as u32), + ), + _ => Err("Mismatched payload for SwitchToTab"), + }, + Some(CommandName::SetTimeout) => match protobuf_plugin_command.payload { + Some(Payload::SetTimeoutPayload(set_timeout_payload)) => { + Ok(PluginCommand::SetTimeout(set_timeout_payload.seconds)) + }, + _ => Err("Mismatched payload for SetTimeout"), + }, + Some(CommandName::ExecCmd) => match protobuf_plugin_command.payload { + Some(Payload::ExecCmdPayload(exec_cmd_payload)) => { + Ok(PluginCommand::ExecCmd(exec_cmd_payload.command_line)) + }, + _ => Err("Mismatched payload for ExecCmd"), + }, + Some(CommandName::PostMessageTo) => match protobuf_plugin_command.payload { + Some(Payload::PostMessageToPayload(post_message_to_payload)) => { + match post_message_to_payload.message { + Some(message) => Ok(PluginCommand::PostMessageTo(message.try_into()?)), + None => Err("Malformed post message to payload"), + } + }, + _ => Err("Mismatched payload for PostMessageTo"), + }, + Some(CommandName::PostMessageToPlugin) => match protobuf_plugin_command.payload { + Some(Payload::PostMessageToPluginPayload(post_message_to_payload)) => { + match post_message_to_payload.message { + Some(message) => { + Ok(PluginCommand::PostMessageToPlugin(message.try_into()?)) + }, + None => Err("Malformed post message to plugin payload"), + } + }, + _ => Err("Mismatched payload for PostMessageToPlugin"), + }, + Some(CommandName::HideSelf) => { + if protobuf_plugin_command.payload.is_some() { + return Err("HideSelf should not have a payload"); + } + Ok(PluginCommand::HideSelf) + }, + Some(CommandName::ShowSelf) => match protobuf_plugin_command.payload { + Some(Payload::ShowSelfPayload(should_float_if_hidden)) => { + Ok(PluginCommand::ShowSelf(should_float_if_hidden)) + }, + _ => Err("Mismatched payload for ShowSelf"), + }, + Some(CommandName::SwitchToMode) => match protobuf_plugin_command.payload { + Some(Payload::SwitchToModePayload(switch_to_mode_payload)) => { + match switch_to_mode_payload.input_mode { + Some(input_mode) => Ok(PluginCommand::SwitchToMode(input_mode.try_into()?)), + None => Err("Malformed switch to mode payload"), + } + }, + _ => Err("Mismatched payload for SwitchToMode"), + }, + Some(CommandName::NewTabsWithLayout) => match protobuf_plugin_command.payload { + Some(Payload::NewTabsWithLayoutPayload(raw_layout)) => { + Ok(PluginCommand::NewTabsWithLayout(raw_layout)) + }, + _ => Err("Mismatched payload for NewTabsWithLayout"), + }, + Some(CommandName::NewTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("NewTab should not have a payload"); + } + Ok(PluginCommand::NewTab) + }, + Some(CommandName::GoToNextTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("GoToNextTab should not have a payload"); + } + Ok(PluginCommand::GoToNextTab) + }, + Some(CommandName::GoToPreviousTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("GoToPreviousTab should not have a payload"); + } + Ok(PluginCommand::GoToPreviousTab) + }, + Some(CommandName::Resize) => match protobuf_plugin_command.payload { + Some(Payload::ResizePayload(resize_payload)) => match resize_payload.resize { + Some(resize) => Ok(PluginCommand::Resize(resize.try_into()?)), + None => Err("Malformed switch resize payload"), + }, + _ => Err("Mismatched payload for Resize"), + }, + Some(CommandName::ResizeWithDirection) => match protobuf_plugin_command.payload { + Some(Payload::ResizeWithDirectionPayload(resize_with_direction_payload)) => { + match resize_with_direction_payload.resize { + Some(resize) => Ok(PluginCommand::ResizeWithDirection(resize.try_into()?)), + None => Err("Malformed switch resize payload"), + } + }, + _ => Err("Mismatched payload for Resize"), + }, + Some(CommandName::FocusNextPane) => { + if protobuf_plugin_command.payload.is_some() { + return Err("FocusNextPane should not have a payload"); + } + Ok(PluginCommand::FocusNextPane) + }, + Some(CommandName::FocusPreviousPane) => { + if protobuf_plugin_command.payload.is_some() { + return Err("FocusPreviousPane should not have a payload"); + } + Ok(PluginCommand::FocusPreviousPane) + }, + Some(CommandName::MoveFocus) => match protobuf_plugin_command.payload { + Some(Payload::MoveFocusPayload(move_payload)) => match move_payload.direction { + Some(direction) => Ok(PluginCommand::MoveFocus(direction.try_into()?)), + None => Err("Malformed move focus payload"), + }, + _ => Err("Mismatched payload for MoveFocus"), + }, + Some(CommandName::MoveFocusOrTab) => match protobuf_plugin_command.payload { + Some(Payload::MoveFocusOrTabPayload(move_payload)) => { + match move_payload.direction { + Some(direction) => Ok(PluginCommand::MoveFocusOrTab(direction.try_into()?)), + None => Err("Malformed move focus or tab payload"), + } + }, + _ => Err("Mismatched payload for MoveFocusOrTab"), + }, + Some(CommandName::Detach) => { + if protobuf_plugin_command.payload.is_some() { + return Err("Detach should not have a payload"); + } + Ok(PluginCommand::Detach) + }, + Some(CommandName::EditScrollback) => { + if protobuf_plugin_command.payload.is_some() { + return Err("EditScrollback should not have a payload"); + } + Ok(PluginCommand::EditScrollback) + }, + Some(CommandName::Write) => match protobuf_plugin_command.payload { + Some(Payload::WritePayload(bytes)) => Ok(PluginCommand::Write(bytes)), + _ => Err("Mismatched payload for Write"), + }, + Some(CommandName::WriteChars) => match protobuf_plugin_command.payload { + Some(Payload::WriteCharsPayload(chars)) => Ok(PluginCommand::WriteChars(chars)), + _ => Err("Mismatched payload for WriteChars"), + }, + Some(CommandName::ToggleTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ToggleTab should not have a payload"); + } + Ok(PluginCommand::ToggleTab) + }, + Some(CommandName::MovePane) => { + if protobuf_plugin_command.payload.is_some() { + return Err("MovePane should not have a payload"); + } + Ok(PluginCommand::MovePane) + }, + Some(CommandName::MovePaneWithDirection) => match protobuf_plugin_command.payload { + Some(Payload::MovePaneWithDirectionPayload(move_payload)) => { + match move_payload.direction { + Some(direction) => { + Ok(PluginCommand::MovePaneWithDirection(direction.try_into()?)) + }, + None => Err("Malformed MovePaneWithDirection payload"), + } + }, + _ => Err("Mismatched payload for MovePaneWithDirection"), + }, + Some(CommandName::ClearScreen) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ClearScreen should not have a payload"); + } + Ok(PluginCommand::ClearScreen) + }, + Some(CommandName::ScrollUp) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ScrollUp should not have a payload"); + } + Ok(PluginCommand::ScrollUp) + }, + Some(CommandName::ScrollDown) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ScrollDown should not have a payload"); + } + Ok(PluginCommand::ScrollDown) + }, + Some(CommandName::ScrollToTop) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ScrollToTop should not have a payload"); + } + Ok(PluginCommand::ScrollToTop) + }, + Some(CommandName::ScrollToBottom) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ScrollToBottom should not have a payload"); + } + Ok(PluginCommand::ScrollToBottom) + }, + Some(CommandName::PageScrollUp) => { + if protobuf_plugin_command.payload.is_some() { + return Err("PageScrollUp should not have a payload"); + } + Ok(PluginCommand::PageScrollUp) + }, + Some(CommandName::PageScrollDown) => { + if protobuf_plugin_command.payload.is_some() { + return Err("PageScrollDown should not have a payload"); + } + Ok(PluginCommand::PageScrollDown) + }, + Some(CommandName::ToggleFocusFullscreen) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ToggleFocusFullscreen should not have a payload"); + } + Ok(PluginCommand::ToggleFocusFullscreen) + }, + Some(CommandName::TogglePaneFrames) => { + if protobuf_plugin_command.payload.is_some() { + return Err("TogglePaneFrames should not have a payload"); + } + Ok(PluginCommand::TogglePaneFrames) + }, + Some(CommandName::TogglePaneEmbedOrEject) => { + if protobuf_plugin_command.payload.is_some() { + return Err("TogglePaneEmbedOrEject should not have a payload"); + } + Ok(PluginCommand::TogglePaneEmbedOrEject) + }, + Some(CommandName::UndoRenamePane) => { + if protobuf_plugin_command.payload.is_some() { + return Err("UndoRenamePane should not have a payload"); + } + Ok(PluginCommand::UndoRenamePane) + }, + Some(CommandName::CloseFocus) => { + if protobuf_plugin_command.payload.is_some() { + return Err("CloseFocus should not have a payload"); + } + Ok(PluginCommand::CloseFocus) + }, + Some(CommandName::ToggleActiveTabSync) => { + if protobuf_plugin_command.payload.is_some() { + return Err("ToggleActiveTabSync should not have a payload"); + } + Ok(PluginCommand::ToggleActiveTabSync) + }, + Some(CommandName::CloseFocusedTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("CloseFocusedTab should not have a payload"); + } + Ok(PluginCommand::CloseFocusedTab) + }, + Some(CommandName::UndoRenameTab) => { + if protobuf_plugin_command.payload.is_some() { + return Err("UndoRenameTab should not have a payload"); + } + Ok(PluginCommand::UndoRenameTab) + }, + Some(CommandName::QuitZellij) => { + if protobuf_plugin_command.payload.is_some() { + return Err("QuitZellij should not have a payload"); + } + Ok(PluginCommand::QuitZellij) + }, + Some(CommandName::PreviousSwapLayout) => { + if protobuf_plugin_command.payload.is_some() { + return Err("PreviousSwapLayout should not have a payload"); + } + Ok(PluginCommand::PreviousSwapLayout) + }, + Some(CommandName::NextSwapLayout) => { + if protobuf_plugin_command.payload.is_some() { + return Err("NextSwapLayout should not have a payload"); + } + Ok(PluginCommand::NextSwapLayout) + }, + Some(CommandName::GoToTabName) => match protobuf_plugin_command.payload { + Some(Payload::GoToTabNamePayload(tab_name)) => { + Ok(PluginCommand::GoToTabName(tab_name)) + }, + _ => Err("Mismatched payload for GoToTabName"), + }, + Some(CommandName::FocusOrCreateTab) => match protobuf_plugin_command.payload { + Some(Payload::FocusOrCreateTabPayload(tab_name)) => { + Ok(PluginCommand::FocusOrCreateTab(tab_name)) + }, + _ => Err("Mismatched payload for FocusOrCreateTab"), + }, + Some(CommandName::GoToTab) => match protobuf_plugin_command.payload { + Some(Payload::GoToTabPayload(tab_index)) => { + Ok(PluginCommand::GoToTab(tab_index as u32)) + }, + _ => Err("Mismatched payload for GoToTab"), + }, + Some(CommandName::StartOrReloadPlugin) => match protobuf_plugin_command.payload { + Some(Payload::StartOrReloadPluginPayload(url)) => { + Ok(PluginCommand::StartOrReloadPlugin(url)) + }, + _ => Err("Mismatched payload for StartOrReloadPlugin"), + }, + Some(CommandName::CloseTerminalPane) => match protobuf_plugin_command.payload { + Some(Payload::CloseTerminalPanePayload(pane_id)) => { + Ok(PluginCommand::CloseTerminalPane(pane_id as u32)) + }, + _ => Err("Mismatched payload for CloseTerminalPane"), + }, + Some(CommandName::ClosePluginPane) => match protobuf_plugin_command.payload { + Some(Payload::ClosePluginPanePayload(pane_id)) => { + Ok(PluginCommand::ClosePluginPane(pane_id as u32)) + }, + _ => Err("Mismatched payload for ClosePluginPane"), + }, + Some(CommandName::FocusTerminalPane) => match protobuf_plugin_command.payload { + 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)) + }, + _ => Err("Mismatched payload for ClosePluginPane"), + }, + Some(CommandName::FocusPluginPane) => match protobuf_plugin_command.payload { + 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)) + }, + _ => Err("Mismatched payload for ClosePluginPane"), + }, + Some(CommandName::RenameTerminalPane) => match protobuf_plugin_command.payload { + Some(Payload::RenameTerminalPanePayload(payload)) => { + let pane_id = payload.id as u32; + let new_name = payload.new_name; + Ok(PluginCommand::RenameTerminalPane(pane_id, new_name)) + }, + _ => Err("Mismatched payload for RenameTerminalPane"), + }, + Some(CommandName::RenamePluginPane) => match protobuf_plugin_command.payload { + Some(Payload::RenamePluginPanePayload(payload)) => { + let pane_id = payload.id as u32; + let new_name = payload.new_name; + Ok(PluginCommand::RenamePluginPane(pane_id, new_name)) + }, + _ => Err("Mismatched payload for RenamePluginPane"), + }, + Some(CommandName::RenameTab) => match protobuf_plugin_command.payload { + Some(Payload::RenameTabPayload(payload)) => { + let tab_index = payload.id as u32; + let name = payload.new_name; + Ok(PluginCommand::RenameTab(tab_index, name)) + }, + _ => Err("Mismatched payload for RenameTab"), + }, + Some(CommandName::ReportCrash) => match protobuf_plugin_command.payload { + Some(Payload::ReportCrashPayload(payload)) => { + Ok(PluginCommand::ReportPanic(payload)) + }, + _ => Err("Mismatched payload for ReportCrash"), + }, + None => Err("Unrecognized plugin command"), + } + } +} + +impl TryFrom<PluginCommand> for ProtobufPluginCommand { + type Error = &'static str; + fn try_from(plugin_command: PluginCommand) -> Result<Self, &'static str> { + match plugin_command { + PluginCommand::Subscribe(subscriptions) => { + let subscriptions: ProtobufEventNameList = subscriptions.try_into()?; + Ok(ProtobufPluginCommand { + name: CommandName::Subscribe as i32, + payload: Some(Payload::SubscribePayload(SubscribePayload { + subscriptions: Some(subscriptions), + })), + }) + }, + PluginCommand::Unsubscribe(subscriptions) => { + let subscriptions: ProtobufEventNameList = subscriptions.try_into()?; + Ok(ProtobufPluginCommand { + name: CommandName::Unsubscribe as i32, + payload: Some(Payload::UnsubscribePayload(UnsubscribePayload { + subscriptions: Some(subscriptions), + })), + }) + }, + PluginCommand::SetSelectable(should_be_selectable) => Ok(ProtobufPluginCommand { + name: CommandName::SetSelectable as i32, + payload: Some(Payload::SetSelectablePayload(should_be_selectable)), + }), + PluginCommand::GetPluginIds => Ok(ProtobufPluginCommand { + name: CommandName::GetPluginIds as i32, + payload: None, + }), + PluginCommand::GetZellijVersion => Ok(ProtobufPluginCommand { + name: CommandName::GetZellijVersion as i32, + payload: None, + }), + PluginCommand::OpenFile(file_to_open) => Ok(ProtobufPluginCommand { + name: CommandName::OpenFile as i32, + payload: Some(Payload::OpenFilePayload(OpenFilePayload { + file_to_open: Some(file_to_open.try_into()?), + })), + }), + PluginCommand::OpenFileFloating(file_to_open) => Ok(ProtobufPluginCommand { + name: CommandName::OpenFileFloating as i32, + payload: Some(Payload::OpenFileFloatingPayload(OpenFilePayload { + file_to_open: Some(file_to_open.try_into()?), + })), + }), + PluginCommand::OpenTerminal(cwd) => Ok(ProtobufPluginCommand { + name: CommandName::OpenTerminal as i32, + payload: Some(Payload::OpenTerminalPayload(OpenFilePayload { + file_to_open: Some(cwd.try_into()?), + })), + }), + PluginCommand::OpenTerminalFloating(cwd) => Ok(ProtobufPluginCommand { + name: CommandName::OpenTerminalFloating as i32, + payload: Some(Payload::OpenTerminalFloatingPayload(OpenFilePayload { + file_to_open: Some(cwd.try_into()?), + })), + }), + PluginCommand::OpenCommandPane(command_to_run) => Ok(ProtobufPluginCommand { + name: CommandName::OpenCommandPane as i32, + payload: Some(Payload::OpenCommandPanePayload(OpenCommandPanePayload { + command_to_run: Some(command_to_run.try_into()?), + })), + }), + PluginCommand::OpenCommandPaneFloating(command_to_run) => Ok(ProtobufPluginCommand { + name: CommandName::OpenCommandPaneFloating as i32, + payload: Some(Payload::OpenCommandPaneFloatingPayload( + OpenCommandPanePayload { + command_to_run: Some(command_to_run.try_into()?), + }, + )), + }), + PluginCommand::SwitchTabTo(tab_index) => Ok(ProtobufPluginCommand { + name: CommandName::SwitchTabTo as i32, + payload: Some(Payload::SwitchTabToPayload(SwitchTabToPayload { + tab_index: tab_index as i32, + })), + }), + PluginCommand::SetTimeout(seconds) => Ok(ProtobufPluginCommand { + name: CommandName::SetTimeout as i32, + payload: Some(Payload::SetTimeoutPayload(SetTimeoutPayload { seconds })), + }), + PluginCommand::ExecCmd(command_line) => Ok(ProtobufPluginCommand { + name: CommandName::ExecCmd as i32, + payload: Some(Payload::ExecCmdPayload(ExecCmdPayload { command_line })), + }), + PluginCommand::PostMessageTo(plugin_message) => Ok(ProtobufPluginCommand { + name: CommandName::PostMessageTo as i32, + payload: Some(Payload::PostMessageToPayload(PluginMessagePayload { + message: Some(plugin_message.try_into()?), + })), + }), + PluginCommand::PostMessageToPlugin(plugin_message) => Ok(ProtobufPluginCommand { + name: CommandName::PostMessageToPlugin as i32, + payload: Some(Payload::PostMessageToPluginPayload(PluginMessagePayload { + message: Some(plugin_message.try_into()?), + })), + }), + PluginCommand::HideSelf => Ok(ProtobufPluginCommand { + name: CommandName::HideSelf as i32, + payload: None, + }), + PluginCommand::ShowSelf(should_float_if_hidden) => Ok(ProtobufPluginCommand { + name: CommandName::ShowSelf as i32, + payload: Some(Payload::ShowSelfPayload(should_float_if_hidden)), + }), + PluginCommand::SwitchToMode(input_mode) => Ok(ProtobufPluginCommand { + name: CommandName::SwitchToMode as i32, + payload: Some(Payload::SwitchToModePayload(SwitchToModePayload { + input_mode: Some(input_mode.try_into()?), + })), + }), + PluginCommand::NewTabsWithLayout(raw_layout) => Ok(ProtobufPluginCommand { + name: CommandName::NewTabsWithLayout as i32, + payload: Some(Payload::NewTabsWithLayoutPayload(raw_layout)), + }), + PluginCommand::NewTab => Ok(ProtobufPluginCommand { + name: CommandName::NewTab as i32, + payload: None, + }), + PluginCommand::GoToNextTab => Ok(ProtobufPluginCommand { + name: CommandName::GoToNextTab as i32, + payload: None, + }), + PluginCommand::GoToPreviousTab => Ok(ProtobufPluginCommand { + name: CommandName::GoToPreviousTab as i32, + payload: None, + }), + PluginCommand::Resize(resize) => Ok(ProtobufPluginCommand { + name: CommandName::Resize as i32, + payload: Some(Payload::ResizePayload(ResizePayload { + resize: Some(resize.try_into()?), + })), + }), + PluginCommand::ResizeWithDirection(resize) => Ok(ProtobufPluginCommand { + name: CommandName::ResizeWithDirection as i32, + payload: Some(Payload::ResizeWithDirectionPayload(ResizePayload { + resize: Some(resize.try_into()?), + })), + }), + PluginCommand::FocusNextPane => Ok(ProtobufPluginCommand { + name: CommandName::FocusNextPane as i32, + payload: None, + }), + PluginCommand::FocusPreviousPane => Ok(ProtobufPluginCommand { + name: CommandName::FocusPreviousPane as i32, + payload: None, + }), + PluginCommand::MoveFocus(direction) => Ok(ProtobufPluginCommand { + name: CommandName::MoveFocus as i32, + payload: Some(Payload::MoveFocusPayload(MovePayload { + direction: Some(direction.try_into()?), + })), + }), + PluginCommand::MoveFocusOrTab(direction) => Ok(ProtobufPluginCommand { + name: CommandName::MoveFocusOrTab as i32, + payload: Some(Payload::MoveFocusOrTabPayload(MovePayload { + direction: Some(direction.try_into()?), + })), + }), + PluginCommand::Detach => Ok(ProtobufPluginCommand { + name: CommandName::Detach as i32, + payload: None, + }), + PluginCommand::EditScrollback => Ok(ProtobufPluginCommand { + name: CommandName::EditScrollback as i32, + payload: None, + }), + PluginCommand::Write(bytes) => Ok(ProtobufPluginCommand { + name: CommandName::Write as i32, + payload: Some(Payload::WritePayload(bytes)), + }), + PluginCommand::WriteChars(chars) => Ok(ProtobufPluginCommand { + name: CommandName::WriteChars as i32, + payload: Some(Payload::WriteCharsPayload(chars)), + }), + PluginCommand::ToggleTab => Ok(ProtobufPluginCommand { + name: CommandName::ToggleTab as i32, + payload: None, + }), + PluginCommand::MovePane => Ok(ProtobufPluginCommand { + name: CommandName::MovePane as i32, + payload: None, + }), + PluginCommand::MovePaneWithDirection(direction) => Ok(ProtobufPluginCommand { + name: CommandName::MovePaneWithDirection as i32, + payload: Some(Payload::MovePaneWithDirectionPayload(MovePayload { + direction: Some(direction.try_into()?), + })), + }), + PluginCommand::ClearScreen => Ok(ProtobufPluginCommand { + name: CommandName::ClearScreen as i32, + payload: None, + }), + PluginCommand::ScrollUp => Ok(ProtobufPluginCommand { + name: CommandName::ScrollUp as i32, + payload: None, + }), + PluginCommand::ScrollDown => Ok(ProtobufPluginCommand { + name: CommandName::ScrollDown as i32, + payload: None, + }), + PluginCommand::ScrollToTop => Ok(ProtobufPluginCommand { + name: CommandName::ScrollToTop as i32, + payload: None, + }), + PluginCommand::ScrollToBottom => Ok(ProtobufPluginCommand { + name: CommandName::ScrollToBottom as i32, + payload: None, + }), + PluginCommand::PageScrollUp => Ok(ProtobufPluginCommand { + name: CommandName::PageScrollUp as i32, + payload: None, + }), + PluginCommand::PageScrollDown => Ok(ProtobufPluginCommand { + name: CommandName::PageScrollDown as i32, + payload: None, + }), + PluginCommand::ToggleFocusFullscreen => Ok(ProtobufPluginCommand { + name: CommandName::ToggleFocusFullscreen as i32, + payload: None, + }), + PluginCommand::TogglePaneFrames => Ok(ProtobufPluginCommand { + name: CommandName::TogglePaneFrames as i32, + payload: None, + }), + PluginCommand::TogglePaneEmbedOrEject => Ok(ProtobufPluginCommand { + name: CommandName::TogglePaneEmbedOrEject as i32, + payload: None, + }), + PluginCommand::UndoRenamePane => Ok(ProtobufPluginCommand { + name: CommandName::UndoRenamePane as i32, + payload: None, + }), + PluginCommand::CloseFocus => Ok(ProtobufPluginCommand { + name: CommandName::CloseFocus as i32, + payload: None, + }), + PluginCommand::ToggleActiveTabSync => Ok(ProtobufPluginCommand { + name: CommandName::ToggleActiveTabSync as i32, + payload: None, + }), + PluginCommand::CloseFocusedTab => Ok(ProtobufPluginCommand { + name: CommandName::CloseFocusedTab as i32, + payload: None, + }), + PluginCommand::UndoRenameTab => Ok(ProtobufPluginCommand { + name: CommandName::UndoRenameTab as i32, + payload: None, + }), + PluginCommand::QuitZellij => Ok(ProtobufPluginCommand { + name: CommandName::QuitZellij as i32, + payload: None, + }), + PluginCommand::PreviousSwapLayout => Ok(ProtobufPluginCommand { + name: CommandName::PreviousSwapLayout as i32, + payload: None, + }), + PluginCommand::NextSwapLayout => Ok(ProtobufPluginCommand { + name: CommandName::NextSwapLayout as i32, + payload: None, + }), + PluginCommand::GoToTabName(tab_name) => Ok(ProtobufPluginCommand { + name: CommandName::GoToTabName as i32, + payload: Some(Payload::GoToTabNamePayload(tab_name)), + }), + PluginCommand::FocusOrCreateTab(tab_name) => Ok(ProtobufPluginCommand { + name: CommandName::FocusOrCreateTab as i32, + payload: Some(Payload::FocusOrCreateTabPayload(tab_name)), + }), + PluginCommand::GoToTab(tab_index) => Ok(ProtobufPluginCommand { + name: CommandName::GoToTab as i32, + payload: Some(Payload::GoToTabPayload(tab_index as i32)), + }), + PluginCommand::StartOrReloadPlugin(url) => Ok(ProtobufPluginCommand { + name: CommandName::StartOrReloadPlugin as i32, + payload: Some(Payload::StartOrReloadPluginPayload(url)), + }), + PluginCommand::CloseTerminalPane(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::CloseTerminalPane as i32, + payload: Some(Payload::CloseTerminalPanePayload(pane_id as i32)), + }), + PluginCommand::ClosePluginPane(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ClosePluginPane as i32, + payload: Some(Payload::ClosePluginPanePayload(pane_id as i32)), + }), + PluginCommand::FocusTerminalPane(pane_id, should_float_if_hidden) => { + Ok(ProtobufPluginCommand { + name: CommandName::FocusTerminalPane as i32, + payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat { + pane_id: pane_id as i32, + 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 as i32, + should_float: should_float_if_hidden, + })), + }) + }, + PluginCommand::RenameTerminalPane(pane_id, new_name) => Ok(ProtobufPluginCommand { + name: CommandName::RenameTerminalPane as i32, + payload: Some(Payload::RenameTerminalPanePayload(IdAndNewName { + id: pane_id as i32, + new_name, + })), + }), + PluginCommand::RenamePluginPane(pane_id, new_name) => Ok(ProtobufPluginCommand { + name: CommandName::RenamePluginPane as i32, + payload: Some(Payload::RenamePluginPanePayload(IdAndNewName { + id: pane_id as i32, + new_name, + })), + }), + PluginCommand::RenameTab(tab_index, new_name) => Ok(ProtobufPluginCommand { + name: CommandName::RenameTab as i32, + payload: Some(Payload::RenameTabPayload(IdAndNewName { + id: tab_index as i32, + new_name, + })), + }), + PluginCommand::ReportPanic(payload) => Ok(ProtobufPluginCommand { + name: CommandName::ReportCrash as i32, + payload: Some(Payload::ReportCrashPayload(payload)), + }), + } + } +} diff --git a/zellij-utils/src/plugin_api/plugin_ids.proto b/zellij-utils/src/plugin_api/plugin_ids.proto new file mode 100644 index 00000000..2977dbe4 --- /dev/null +++ b/zellij-utils/src/plugin_api/plugin_ids.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package api.plugin_ids; + +message PluginIds { + int32 plugin_id = 1; + int32 zellij_pid = 2; +} + +message ZellijVersion { + string version = 1; +} diff --git a/zellij-utils/src/plugin_api/plugin_ids.rs b/zellij-utils/src/plugin_api/plugin_ids.rs new file mode 100644 index 00000000..51f526c6 --- /dev/null +++ b/zellij-utils/src/plugin_api/plugin_ids.rs @@ -0,0 +1,35 @@ +pub use super::generated_api::api::plugin_ids::{ + PluginIds as ProtobufPluginIds, ZellijVersion as ProtobufZellijVersion, +}; +use crate::data::PluginIds; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufPluginIds> for PluginIds { + type Error = &'static str; + fn try_from(protobuf_plugin_ids: ProtobufPluginIds) -> Result<Self, &'static str> { + Ok(PluginIds { + plugin_id: protobuf_plugin_ids.plugin_id as u32, + zellij_pid: protobuf_plugin_ids.zellij_pid as u32, + }) + } +} + +impl TryFrom<PluginIds> for ProtobufPluginIds { + type Error = &'static str; + fn try_from(plugin_ids: PluginIds) -> Result<Self, &'static str> { + Ok(ProtobufPluginIds { + plugin_id: plugin_ids.plugin_id as i32, + zellij_pid: plugin_ids.zellij_pid as i32, + }) + } +} + +impl TryFrom<&str> for ProtobufZellijVersion { + type Error = &'static str; + fn try_from(zellij_version: &str) -> Result<Self, &'static str> { + Ok(ProtobufZellijVersion { + version: zellij_version.to_owned(), + }) + } +} diff --git a/zellij-utils/src/plugin_api/resize.proto b/zellij-utils/src/plugin_api/resize.proto new file mode 100644 index 00000000..eebac79d --- /dev/null +++ b/zellij-utils/src/plugin_api/resize.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package api.resize; + +enum ResizeAction { + Increase = 0; + Decrease = 1; +} + +enum ResizeDirection { + Left = 0; + Right = 1; + Up = 2; + Down = 3; +} + +message Resize { + ResizeAction resize_action = 1; + optional ResizeDirection direction = 2; +} + +message MoveDirection { + ResizeDirection direction = 1; +} diff --git a/zellij-utils/src/plugin_api/resize.rs b/zellij-utils/src/plugin_api/resize.rs new file mode 100644 index 00000000..61031c09 --- /dev/null +++ b/zellij-utils/src/plugin_api/resize.rs @@ -0,0 +1,130 @@ +pub use super::generated_api::api::resize::{ + MoveDirection as ProtobufMoveDirection, Resize as ProtobufResize, ResizeAction, + ResizeDirection, ResizeDirection as ProtobufResizeDirection, +}; +use crate::data::{Direction, Resize, ResizeStrategy}; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufResize> for Resize { + type Error = &'static str; + fn try_from(protobuf_resize: ProtobufResize) -> Result<Self, &'static str> { + if protobuf_resize.direction.is_some() { + return Err("Resize cannot have a direction"); + } + match ResizeAction::from_i32(protobuf_resize.resize_action) { + Some(ResizeAction::Increase) => Ok(Resize::Increase), + Some(ResizeAction::Decrease) => Ok(Resize::Decrease), + None => Err("No resize action for the given index"), + } + } +} + +impl TryFrom<Resize> for ProtobufResize { + type Error = &'static str; + fn try_from(resize: Resize) -> Result<Self, &'static str> { + Ok(ProtobufResize { + resize_action: match resize { + Resize::Increase => ResizeAction::Increase as i32, + Resize::Decrease => ResizeAction::Decrease as i32, + }, + direction: None, + }) + } +} + +impl TryFrom<ProtobufResize> for ResizeStrategy { + type Error = &'static str; + fn try_from(protobuf_resize: ProtobufResize) -> Result<Self, &'static str> { + let direction = match protobuf_resize + .direction + .and_then(|r| ResizeDirection::from_i32(r)) + { + Some(ResizeDirection::Left) => Some(Direction::Left), + Some(ResizeDirection::Right) => Some(Direction::Right), + Some(ResizeDirection::Up) => Some(Direction::Up), + Some(ResizeDirection::Down) => Some(Direction::Down), + None => None, + }; + let resize = match ResizeAction::from_i32(protobuf_resize.resize_action) { + Some(ResizeAction::Increase) => Resize::Increase, + Some(ResizeAction::Decrease) => Resize::Decrease, + None => return Err("No resize action for the given index"), + }; + Ok(ResizeStrategy { + direction, + resize, + invert_on_boundaries: false, + }) + } +} + +impl TryFrom<ResizeStrategy> for ProtobufResize { + type Error = &'static str; + fn try_from(resize_strategy: ResizeStrategy) -> Result<Self, &'static str> { + Ok(ProtobufResize { + resize_action: match resize_strategy.resize { + Resize::Increase => ResizeAction::Increase as i32, + Resize::Decrease => ResizeAction::Decrease as i32, + }, + direction: match resize_strategy.direction { + Some(Direction::Left) => Some(ResizeDirection::Left as i32), + Some(Direction::Right) => Some(ResizeDirection::Right as i32), + Some(Direction::Up) => Some(ResizeDirection::Up as i32), + Some(Direction::Down) => Some(ResizeDirection::Down as i32), + None => None, + }, + }) + } +} + +impl TryFrom<ProtobufMoveDirection> for Direction { + type Error = &'static str; + fn try_from(protobuf_move_direction: ProtobufMoveDirection) -> Result<Self, &'static str> { + match ResizeDirection::from_i32(protobuf_move_direction.direction) { + Some(ResizeDirection::Left) => Ok(Direction::Left), + Some(ResizeDirection::Right) => Ok(Direction::Right), + Some(ResizeDirection::Up) => Ok(Direction::Up), + Some(ResizeDirection::Down) => Ok(Direction::Down), + None => Err("No direction for the given index"), + } + } +} + +impl TryFrom<Direction> for ProtobufMoveDirection { + type Error = &'static str; + fn try_from(direction: Direction) -> Result<Self, &'static str> { + Ok(ProtobufMoveDirection { + direction: match direction { + Direction::Left => ResizeDirection::Left as i32, + Direction::Right => ResizeDirection::Right as i32, + Direction::Up => ResizeDirection::Up as i32, + Direction::Down => ResizeDirection::Down as i32, + }, + }) + } +} + +impl TryFrom<ProtobufResizeDirection> for Direction { + type Error = &'static str; + fn try_from(protobuf_resize_direction: ProtobufResizeDirection) -> Result<Self, &'static str> { + match protobuf_resize_direction { + ProtobufResizeDirection::Left => Ok(Direction::Left), + ProtobufResizeDirection::Right => Ok(Direction::Right), + ProtobufResizeDirection::Up => Ok(Direction::Up), + ProtobufResizeDirection::Down => Ok(Direction::Down), + } + } +} + +impl TryFrom<Direction> for ProtobufResizeDirection { + type Error = &'static str; + fn try_from(direction: Direction) -> Result<Self, &'static str> { + Ok(match direction { + Direction::Left => ProtobufResizeDirection::Left, + Direction::Right => ProtobufResizeDirection::Right, + Direction::Up => ProtobufResizeDirection::Up, + Direction::Down => ProtobufResizeDirection::Down, + }) + } +} diff --git a/zellij-utils/src/plugin_api/style.proto b/zellij-utils/src/plugin_api/style.proto new file mode 100644 index 00000000..f4f3b758 --- /dev/null +++ b/zellij-utils/src/plugin_api/style.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package api.style; + +message Style { + Palette palette = 1; + bool rounded_corners = 2; + bool hide_session_name = 3; +} + +message Palette { + ThemeHue theme_hue = 1; + Color fg = 2; + Color bg = 3; + Color black = 4; + Color red = 5; + Color green = 6; + Color yellow = 7; + Color blue = 8; + Color magenta = 9; + Color cyan = 10; + Color white = 11; + Color orange = 12; + Color gray = 13; + Color purple = 14; + Color gold = 15; + Color silver = 16; + Color pink = 17; + Color brown = 18; +} + +message Color { + ColorType color_type = 1; + oneof payload { + RgbColorPayload rgb_color_payload = 2; + uint32 eight_bit_color_payload = 3; + } +} + +message RgbColorPayload { + uint32 red = 1; + uint32 green = 2; + uint32 blue = 3; +} + +enum ColorType { + Rgb = 0; + EightBit = 1; +} + +enum ThemeHue { + Dark = 0; + Light = 1; +} diff --git a/zellij-utils/src/plugin_api/style.rs b/zellij-utils/src/plugin_api/style.rs new file mode 100644 index 00000000..1e3ba677 --- /dev/null +++ b/zellij-utils/src/plugin_api/style.rs @@ -0,0 +1,213 @@ +use super::generated_api::api::style::{ + color::Payload as ProtobufColorPayload, Color as ProtobufColor, ColorType as ProtobufColorType, + Palette as ProtobufPalette, RgbColorPayload as ProtobufRgbColorPayload, Style as ProtobufStyle, + ThemeHue as ProtobufThemeHue, +}; +use crate::data::{Palette, PaletteColor, Style, ThemeHue}; +use crate::errors::prelude::*; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufStyle> for Style { + type Error = &'static str; + fn try_from(protobuf_style: ProtobufStyle) -> Result<Self, &'static str> { + Ok(Style { + colors: protobuf_style + .palette + .ok_or("malformed style payload")? + .try_into()?, + rounded_corners: protobuf_style.rounded_corners, + hide_session_name: protobuf_style.hide_session_name, + }) + } +} + +impl TryFrom<Style> for ProtobufStyle { + type Error = &'static str; + fn try_from(style: Style) -> Result<Self, &'static str> { + Ok(ProtobufStyle { + palette: Some(style.colors.try_into()?), + rounded_corners: style.rounded_corners, + hide_session_name: style.hide_session_name, + }) + } +} + +impl TryFrom<ProtobufPalette> for Palette { + type Error = &'static str; + fn try_from(protobuf_palette: ProtobufPalette) -> Result<Self, &'static str> { + Ok(Palette { + theme_hue: ProtobufThemeHue::from_i32(protobuf_palette.theme_hue) + .ok_or("malformed theme_hue payload for Palette")? + .try_into()?, + fg: protobuf_palette + .fg + .ok_or("malformed palette payload")? + .try_into()?, + bg: protobuf_palette + .bg + .ok_or("malformed palette payload")? + .try_into()?, + black: protobuf_palette + .black + .ok_or("malformed palette payload")? + .try_into()?, + red: protobuf_palette + .red + .ok_or("malformed palette payload")? + .try_into()?, + green: protobuf_palette + .green + .ok_or("malformed palette payload")? + .try_into()?, + yellow: protobuf_palette + .yellow + .ok_or("malformed palette payload")? + .try_into()?, + blue: protobuf_palette + .blue + .ok_or("malformed palette payload")? + .try_into()?, + magenta: protobuf_palette + .magenta + .ok_or("malformed palette payload")? + .try_into()?, + cyan: protobuf_palette + .cyan + .ok_or("malformed palette payload")? + .try_into()?, + white: protobuf_palette + .white + .ok_or("malformed palette payload")? + .try_into()?, + orange: protobuf_palette + .orange + .ok_or("malformed palette payload")? + .try_into()?, + gray: protobuf_palette + .gray + .ok_or("malformed palette payload")? + .try_into()?, + purple: protobuf_palette + .purple + .ok_or("malformed palette payload")? + .try_into()?, + gold: protobuf_palette + .gold + .ok_or("malformed palette payload")? + .try_into()?, + silver: protobuf_palette + .silver + .ok_or("malformed palette payload")? + .try_into()?, + pink: protobuf_palette + .pink + .ok_or("malformed palette payload")? + .try_into()?, + brown: protobuf_palette + .brown + .ok_or("malformed palette payload")? + .try_into()?, + ..Default::default() + }) + } +} + +impl TryFrom<Palette> for ProtobufPalette { + type Error = &'static str; + fn try_from(palette: Palette) -> Result<Self, &'static str> { + let theme_hue: ProtobufThemeHue = palette + .theme_hue + .try_into() + .map_err(|_| "malformed payload for palette")?; + Ok(ProtobufPalette { + theme_hue: theme_hue as i32, + fg: Some(palette.fg.try_into()?), + bg: Some(palette.bg.try_into()?), + black: Some(palette.black.try_into()?), + red: Some(palette.red.try_into()?), + green: Some(palette.green.try_into()?), + yellow: Some(palette.yellow.try_into()?), + blue: Some(palette.blue.try_into()?), + magenta: Some(palette.magenta.try_into()?), + cyan: Some(palette.cyan.try_into()?), + white: Some(palette.white.try_into()?), + orange: Some(palette.orange.try_into()?), + gray: Some(palette.gray.try_into()?), + purple: Some(palette.purple.try_into()?), + gold: Some(palette.gold.try_into()?), + silver: Some(palette.silver.try_into()?), + pink: Some(palette.pink.try_into()?), + brown: Some(palette.brown.try_into()?), + ..Default::default() + }) + } +} + +impl TryFrom<ProtobufColor> for PaletteColor { + type Error = &'static str; + fn try_from(protobuf_color: ProtobufColor) -> Result<Self, &'static str> { + match ProtobufColorType::from_i32(protobuf_color.color_type) { + Some(ProtobufColorType::Rgb) => match protobuf_color.payload { + Some(ProtobufColorPayload::RgbColorPayload(rgb_color_payload)) => { + Ok(PaletteColor::Rgb(( + rgb_color_payload.red as u8, + rgb_color_payload.green as u8, + rgb_color_payload.blue as u8, + ))) + }, + _ => Err("malformed payload for Rgb color"), + }, + Some(ProtobufColorType::EightBit) => match protobuf_color.payload { + Some(ProtobufColorPayload::EightBitColorPayload(eight_bit_payload)) => { + Ok(PaletteColor::EightBit(eight_bit_payload as u8)) + }, + _ => Err("malformed payload for 8bit color"), + }, + None => Err("malformed payload for Color"), + } + } +} + +impl TryFrom<PaletteColor> for ProtobufColor { + type Error = &'static str; + fn try_from(color: PaletteColor) -> Result<Self, &'static str> { + match color { + PaletteColor::Rgb((red, green, blue)) => { + let red = red as u32; + let green = green as u32; + let blue = blue as u32; + Ok(ProtobufColor { + color_type: ProtobufColorType::Rgb as i32, + payload: Some(ProtobufColorPayload::RgbColorPayload( + ProtobufRgbColorPayload { red, green, blue }, + )), + }) + }, + PaletteColor::EightBit(color) => Ok(ProtobufColor { + color_type: ProtobufColorType::EightBit as i32, + payload: Some(ProtobufColorPayload::EightBitColorPayload(color as u32)), + }), + } + } +} + +impl TryFrom<ThemeHue> for ProtobufThemeHue { + type Error = &'static str; + fn try_from(theme_hue: ThemeHue) -> Result<Self, &'static str> { + match theme_hue { + ThemeHue::Light => Ok(ProtobufThemeHue::Light), + ThemeHue::Dark => Ok(ProtobufThemeHue::Dark), + } + } +} + +impl TryFrom<ProtobufThemeHue> for ThemeHue { + type Error = &'static str; + fn try_from(protobuf_theme_hue: ProtobufThemeHue) -> Result<Self, &'static str> { + match protobuf_theme_hue { + ProtobufThemeHue::Light => Ok(ThemeHue::Light), + ProtobufThemeHue::Dark => Ok(ThemeHue::Dark), + } + } +} |
