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 | |
parent | c3e140cb4b3c0897329bf07ee7f51e9fd402b3df (diff) |
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')
29 files changed, 4949 insertions, 2 deletions
diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 65398c532..3e5e2dfd8 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -42,6 +42,7 @@ shellexpand = "3.0.0" uuid = { version = "0.8.2", features = ["serde", "v4"] } async-channel = "1.8.0" include_dir = "0.7.3" +prost = "0.11.9" #[cfg(not(target_family = "wasm"))] [target.'cfg(not(target_family = "wasm"))'.dependencies] @@ -55,6 +56,8 @@ notify-debouncer-full = "0.1.0" [dev-dependencies] insta = { version = "1.6.0", features = ["backtrace"] } +[build-dependencies] +prost-build = "0.11.9" [features] # If this feature is NOT set (default): diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm Binary files differindex bd878f2a8..f38c500fc 100755 --- a/zellij-utils/assets/plugins/compact-bar.wasm +++ b/zellij-utils/assets/plugins/compact-bar.wasm diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm Binary files differindex 33bf1a44c..1a2919f6f 100755 --- a/zellij-utils/assets/plugins/tab-bar.wasm +++ b/zellij-utils/assets/plugins/tab-bar.wasm diff --git a/zellij-utils/build.rs b/zellij-utils/build.rs new file mode 100644 index 000000000..92f89d27c --- /dev/null +++ b/zellij-utils/build.rs @@ -0,0 +1,21 @@ +use prost_build; +use std::fs; + +fn main() { + let mut prost_build = prost_build::Config::new(); + prost_build.include_file("generated_plugin_api.rs"); + let mut proto_files = vec![]; + for entry in fs::read_dir("src/plugin_api").unwrap() { + let entry_path = entry.unwrap().path(); + if entry_path.is_file() { + if let Some(extension) = entry_path.extension() { + if extension == "proto" { + proto_files.push(entry_path.display().to_string()) + } + } + } + } + prost_build + .compile_protos(&proto_files, &["src/plugin_api"]) + .unwrap(); +} diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index a12821c42..9c1c8e2f6 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -2,9 +2,9 @@ use crate::input::actions::Action; use crate::input::config::ConversionError; use clap::ArgEnum; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString}; @@ -810,3 +810,144 @@ pub enum CopyDestination { Primary, System, } + +#[derive(Debug, Default, Clone)] +pub struct FileToOpen { + pub path: PathBuf, + pub line_number: Option<usize>, + pub cwd: Option<PathBuf>, +} + +impl FileToOpen { + pub fn new<P: AsRef<Path>>(path: P) -> Self { + FileToOpen { + path: path.as_ref().to_path_buf(), + ..Default::default() + } + } + pub fn with_line_number(mut self, line_number: usize) -> Self { + self.line_number = Some(line_number); + self + } + pub fn with_cwd(mut self, cwd: PathBuf) -> Self { + self.cwd = Some(cwd); + self + } +} + +#[derive(Debug, Default, Clone)] +pub struct CommandToRun { + pub path: PathBuf, + pub args: Vec<String>, + pub cwd: Option<PathBuf>, +} + +impl CommandToRun { + pub fn new<P: AsRef<Path>>(path: P) -> Self { + CommandToRun { + path: path.as_ref().to_path_buf(), + ..Default::default() + } + } + pub fn new_with_args<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) -> Self { + CommandToRun { + path: path.as_ref().to_path_buf(), + args: args.into_iter().map(|a| a.as_ref().to_owned()).collect(), + ..Default::default() + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct PluginMessage { + pub name: String, + pub payload: String, + pub worker_name: Option<String>, +} + +impl PluginMessage { + pub fn new_to_worker(worker_name: &str, message: &str, payload: &str) -> Self { + PluginMessage { + name: message.to_owned(), + payload: payload.to_owned(), + worker_name: Some(worker_name.to_owned()), + } + } + pub fn new_to_plugin(message: &str, payload: &str) -> Self { + PluginMessage { + name: message.to_owned(), + payload: payload.to_owned(), + worker_name: None, + } + } +} + +#[derive(Debug, Clone)] +pub enum PluginCommand { + Subscribe(HashSet<EventType>), + Unsubscribe(HashSet<EventType>), + SetSelectable(bool), + GetPluginIds, + GetZellijVersion, + OpenFile(FileToOpen), + OpenFileFloating(FileToOpen), + OpenTerminal(FileToOpen), // only used for the path as cwd + OpenTerminalFloating(FileToOpen), // only used for the path as cwd + OpenCommandPane(CommandToRun), + OpenCommandPaneFloating(CommandToRun), + SwitchTabTo(u32), // tab index + SetTimeout(f32), // seconds + ExecCmd(Vec<String>), + PostMessageTo(PluginMessage), + PostMessageToPlugin(PluginMessage), + HideSelf, + ShowSelf(bool), // bool - should float if hidden + SwitchToMode(InputMode), + NewTabsWithLayout(String), // raw kdl layout + NewTab, + GoToNextTab, + GoToPreviousTab, + Resize(Resize), + ResizeWithDirection(ResizeStrategy), + FocusNextPane, + FocusPreviousPane, + MoveFocus(Direction), + MoveFocusOrTab(Direction), + Detach, + EditScrollback, + Write(Vec<u8>), // bytes + WriteChars(String), + ToggleTab, + MovePane, + MovePaneWithDirection(Direction), + ClearScreen, + ScrollUp, + ScrollDown, + ScrollToTop, + ScrollToBottom, + PageScrollUp, + PageScrollDown, + ToggleFocusFullscreen, + TogglePaneFrames, + TogglePaneEmbedOrEject, + UndoRenamePane, + CloseFocus, + ToggleActiveTabSync, + CloseFocusedTab, + UndoRenameTab, + QuitZellij, + PreviousSwapLayout, + NextSwapLayout, + GoToTabName(String), + FocusOrCreateTab(String), + GoToTab(u32), // tab index + StartOrReloadPlugin(String), // plugin url (eg. file:/path/to/plugin.wasm) + CloseTerminalPane(u32), // terminal pane id + ClosePluginPane(u32), // plugin pane id + FocusTerminalPane(u32, bool), // terminal pane id, should_float_if_hidden + FocusPluginPane(u32, bool), // plugin pane id, should_float_if_hidden + RenameTerminalPane(u32, String), // terminal pane id, new name + RenamePluginPane(u32, String), // plugin pane id, new name + RenameTab(u32, String), // tab index, new name + ReportPanic(String), // stringified panic +} diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs index b07fe8bdc..c9b3581d0 100644 --- a/zellij-utils/src/lib.rs +++ b/zellij-utils/src/lib.rs @@ -6,6 +6,7 @@ pub mod errors; pub mod input; pub mod kdl; pub mod pane_size; +pub mod plugin_api; pub mod position; pub mod setup; pub mod shared; @@ -23,3 +24,5 @@ pub use ::{ anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte, }; + +pub use ::prost; diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto new file mode 100644 index 000000000..05fa5c6ed --- /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 000000000..1a964fa9b --- /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, + |