From 1bedfc90021558cb201695444107afe5bddd2c17 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 9 Aug 2023 22:26:00 +0200 Subject: [PATCH] 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 --- .github/workflows/e2e.yml | 4 + .github/workflows/release.yml | 5 + .github/workflows/rust.yml | 13 + Cargo.lock | 97 ++ .../fixture-plugin-for-tests/src/main.rs | 61 +- default-plugins/strider/src/main.rs | 80 +- default-plugins/strider/src/search/mod.rs | 28 +- .../strider/src/search/search_state.rs | 46 +- default-plugins/strider/src/state.rs | 5 +- zellij-server/src/plugins/plugin_loader.rs | 131 +- zellij-server/src/plugins/plugin_worker.rs | 31 +- ..._tests__go_to_tab_name_plugin_command.snap | 4 +- ..._tests__send_configuration_to_plugins.snap | 4 +- ...gin_tests__write_chars_plugin_command.snap | 4 +- zellij-server/src/plugins/wasm_bridge.rs | 29 +- zellij-server/src/plugins/zellij_exports.rs | 1045 ++++++------- zellij-tile/src/lib.rs | 50 +- zellij-tile/src/shim.rs | 529 ++++--- zellij-utils/Cargo.toml | 3 + zellij-utils/assets/plugins/compact-bar.wasm | Bin 526958 -> 526730 bytes zellij-utils/assets/plugins/tab-bar.wasm | Bin 497627 -> 497583 bytes zellij-utils/build.rs | 21 + zellij-utils/src/data.rs | 145 +- zellij-utils/src/lib.rs | 3 + zellij-utils/src/plugin_api/action.proto | 246 ++++ zellij-utils/src/plugin_api/action.rs | 1304 +++++++++++++++++ zellij-utils/src/plugin_api/command.proto | 9 + zellij-utils/src/plugin_api/command.rs | 26 + zellij-utils/src/plugin_api/event.proto | 162 ++ zellij-utils/src/plugin_api/event.rs | 1059 +++++++++++++ zellij-utils/src/plugin_api/file.proto | 9 + zellij-utils/src/plugin_api/file.rs | 30 + zellij-utils/src/plugin_api/input_mode.proto | 40 + zellij-utils/src/plugin_api/input_mode.rs | 69 + zellij-utils/src/plugin_api/key.proto | 83 ++ zellij-utils/src/plugin_api/key.rs | 222 +++ zellij-utils/src/plugin_api/message.proto | 9 + zellij-utils/src/plugin_api/message.rs | 29 + zellij-utils/src/plugin_api/mod.rs | 14 + .../src/plugin_api/plugin_command.proto | 175 +++ zellij-utils/src/plugin_api/plugin_command.rs | 825 +++++++++++ zellij-utils/src/plugin_api/plugin_ids.proto | 12 + zellij-utils/src/plugin_api/plugin_ids.rs | 35 + zellij-utils/src/plugin_api/resize.proto | 24 + zellij-utils/src/plugin_api/resize.rs | 130 ++ zellij-utils/src/plugin_api/style.proto | 54 + zellij-utils/src/plugin_api/style.rs | 213 +++ 47 files changed, 6045 insertions(+), 1072 deletions(-) create mode 100644 zellij-utils/build.rs create mode 100644 zellij-utils/src/plugin_api/action.proto create mode 100644 zellij-utils/src/plugin_api/action.rs create mode 100644 zellij-utils/src/plugin_api/command.proto create mode 100644 zellij-utils/src/plugin_api/command.rs create mode 100644 zellij-utils/src/plugin_api/event.proto create mode 100644 zellij-utils/src/plugin_api/event.rs create mode 100644 zellij-utils/src/plugin_api/file.proto create mode 100644 zellij-utils/src/plugin_api/file.rs create mode 100644 zellij-utils/src/plugin_api/input_mode.proto create mode 100644 zellij-utils/src/plugin_api/input_mode.rs create mode 100644 zellij-utils/src/plugin_api/key.proto create mode 100644 zellij-utils/src/plugin_api/key.rs create mode 100644 zellij-utils/src/plugin_api/message.proto create mode 100644 zellij-utils/src/plugin_api/message.rs create mode 100644 zellij-utils/src/plugin_api/mod.rs create mode 100644 zellij-utils/src/plugin_api/plugin_command.proto create mode 100644 zellij-utils/src/plugin_api/plugin_command.rs create mode 100644 zellij-utils/src/plugin_api/plugin_ids.proto create mode 100644 zellij-utils/src/plugin_api/plugin_ids.rs create mode 100644 zellij-utils/src/plugin_api/resize.proto create mode 100644 zellij-utils/src/plugin_api/resize.rs create mode 100644 zellij-utils/src/plugin_api/style.proto create mode 100644 zellij-utils/src/plugin_api/style.rs diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 76e38d3c..2d842326 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -30,6 +30,10 @@ jobs: options: -v ${{ github.workspace }}/target:/usr/src/zellij --name ssh steps: - uses: actions/checkout@v3 + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Add WASM target run: rustup target add wasm32-wasi - name: Install musl-tools diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 315968f6..8f8537da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cc0ee2ab..19b95fd7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,6 +22,11 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup toolchain run: rustup show - uses: Swatinem/rust-cache@v2 @@ -40,6 +45,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Setup toolchain run: rustup show - uses: Swatinem/rust-cache@v2 @@ -63,6 +72,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Setup toolchain run: rustup show - uses: Swatinem/rust-cache@v2 diff --git a/Cargo.lock b/Cargo.lock index 2e143932..918d8a87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cache-padded" version = "1.2.0" @@ -1491,6 +1497,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.2" @@ -1836,6 +1851,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "names" version = "0.14.0" @@ -2152,6 +2173,16 @@ dependencies = [ "sha-1", ] +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "phf" version = "0.8.0" @@ -2297,6 +2328,16 @@ dependencies = [ "getopts", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.96", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2336,6 +2377,60 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.0", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.96", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.96", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -4518,6 +4613,8 @@ dependencies = [ "notify-debouncer-full", "once_cell", "percent-encoding", + "prost", + "prost-build", "regex", "rmp-serde", "serde", diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index 074aa1a4..fe8b034b 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -22,13 +22,14 @@ impl<'de> ZellijWorker<'de> for TestWorker { fn on_message(&mut self, message: String, payload: String) { if message == "ping" { self.number_of_messages_received += 1; - post_message_to_plugin( - "pong".into(), - format!( + post_message_to_plugin(PluginMessage { + worker_name: None, + name: "pong".into(), + payload: format!( "{}, received {} messages", payload, self.number_of_messages_received ), - ); + }); } } } @@ -143,22 +144,30 @@ impl ZellijPlugin for State { start_or_reload_plugin(plugin_url) }, Key::Ctrl('g') => { - open_file(std::path::PathBuf::from("/path/to/my/file.rs").as_path()); + open_file(FileToOpen { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + ..Default::default() + }); }, Key::Ctrl('h') => { - open_file_floating(std::path::PathBuf::from("/path/to/my/file.rs").as_path()); + open_file_floating(FileToOpen { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + ..Default::default() + }); }, Key::Ctrl('i') => { - open_file_with_line( - std::path::PathBuf::from("/path/to/my/file.rs").as_path(), - 42, - ); + open_file(FileToOpen { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + line_number: Some(42), + ..Default::default() + }); }, Key::Ctrl('j') => { - open_file_with_line_floating( - std::path::PathBuf::from("/path/to/my/file.rs").as_path(), - 42, - ); + open_file_floating(FileToOpen { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + line_number: Some(42), + ..Default::default() + }); }, Key::Ctrl('k') => { open_terminal(std::path::PathBuf::from("/path/to/my/file.rs").as_path()); @@ -169,16 +178,18 @@ impl ZellijPlugin for State { ); }, Key::Ctrl('m') => { - open_command_pane( - std::path::PathBuf::from("/path/to/my/file.rs").as_path(), - vec!["arg1".to_owned(), "arg2".to_owned()], - ); + open_command_pane(CommandToRun { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + args: vec!["arg1".to_owned(), "arg2".to_owned()], + ..Default::default() + }); }, Key::Ctrl('n') => { - open_command_pane_floating( - std::path::PathBuf::from("/path/to/my/file.rs").as_path(), - vec!["arg1".to_owned(), "arg2".to_owned()], - ); + open_command_pane_floating(CommandToRun { + path: std::path::PathBuf::from("/path/to/my/file.rs"), + args: vec!["arg1".to_owned(), "arg2".to_owned()], + ..Default::default() + }); }, Key::Ctrl('o') => { switch_tab_to(1); @@ -225,7 +236,11 @@ impl ZellijPlugin for State { }, Event::SystemClipboardFailure => { // this is just to trigger the worker message - post_message_to("test", "ping", "gimme_back_my_payload"); + post_message_to(PluginMessage { + worker_name: Some("test".into()), + name: "ping".into(), + payload: "gimme_back_my_payload".into(), + }); }, _ => {}, } diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs index 35ff92ea..e620930c 100644 --- a/default-plugins/strider/src/main.rs +++ b/default-plugins/strider/src/main.rs @@ -31,16 +31,16 @@ impl ZellijPlugin for State { EventType::FileSystemUpdate, EventType::FileSystemDelete, ]); - post_message_to( - "file_name_search", - &serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), - "", - ); - post_message_to( - "file_contents_search", - &serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), - "", - ); + post_message_to(PluginMessage { + worker_name: Some("file_name_search".into()), + name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), + payload: "".into(), + }); + post_message_to(PluginMessage { + worker_name: Some("file_contents_search".into()), + name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), + payload: "".into(), + }); self.search_state.loading = true; set_timeout(0.5); // for displaying loading animation } @@ -191,48 +191,48 @@ impl ZellijPlugin for State { .iter() .map(|p| p.to_string_lossy().to_string()) .collect(); - post_message_to( - "file_name_search", - &serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); - post_message_to( - "file_contents_search", - &serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); + post_message_to(PluginMessage { + worker_name: Some("file_name_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); + post_message_to(PluginMessage { + worker_name: Some("file_contents_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); }, Event::FileSystemUpdate(paths) => { let paths: Vec = paths .iter() .map(|p| p.to_string_lossy().to_string()) .collect(); - post_message_to( - "file_name_search", - &serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); - post_message_to( - "file_contents_search", - &serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); + post_message_to(PluginMessage { + worker_name: Some("file_name_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); + post_message_to(PluginMessage { + worker_name: Some("file_contents_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); }, Event::FileSystemDelete(paths) => { let paths: Vec = paths .iter() .map(|p| p.to_string_lossy().to_string()) .collect(); - post_message_to( - "file_name_search", - &serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); - post_message_to( - "file_contents_search", - &serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), - &serde_json::to_string(&paths).unwrap(), - ); + post_message_to(PluginMessage { + worker_name: Some("file_name_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); + post_message_to(PluginMessage { + worker_name: Some("file_contents_search".into()), + name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), + payload: serde_json::to_string(&paths).unwrap(), + }); }, _ => { dbg!("Unknown event {:?}", event); diff --git a/default-plugins/strider/src/search/mod.rs b/default-plugins/strider/src/search/mod.rs index 33c8a60b..6ffa60cb 100644 --- a/default-plugins/strider/src/search/mod.rs +++ b/default-plugins/strider/src/search/mod.rs @@ -41,10 +41,11 @@ impl Search { match serde_json::from_str::(&message) { Ok(MessageToSearch::ScanFolder) => { self.scan_hd(); - post_message_to_plugin( - serde_json::to_string(&MessageToPlugin::DoneScanningFolder).unwrap(), - "".to_owned(), - ); + post_message_to_plugin(PluginMessage { + worker_name: None, + name: serde_json::to_string(&MessageToPlugin::DoneScanningFolder).unwrap(), + payload: "".to_owned(), + }); }, Ok(MessageToSearch::Search) => { if let Some(current_search_term) = self.read_search_term_from_hd_cache() { @@ -115,16 +116,19 @@ impl Search { } } if let Some(file_names_search_results) = file_names_search_results { - post_message_to_plugin( - serde_json::to_string(&MessageToPlugin::UpdateFileNameSearchResults).unwrap(), - serde_json::to_string(&file_names_search_results).unwrap(), - ); + post_message_to_plugin(PluginMessage { + name: serde_json::to_string(&MessageToPlugin::UpdateFileNameSearchResults).unwrap(), + payload: serde_json::to_string(&file_names_search_results).unwrap(), + ..Default::default() + }); } if let Some(file_contents_search_results) = file_contents_search_results { - post_message_to_plugin( - serde_json::to_string(&MessageToPlugin::UpdateFileContentsSearchResults).unwrap(), - serde_json::to_string(&file_contents_search_results).unwrap(), - ); + post_message_to_plugin(PluginMessage { + name: serde_json::to_string(&MessageToPlugin::UpdateFileContentsSearchResults) + .unwrap(), + payload: serde_json::to_string(&file_contents_search_results).unwrap(), + ..Default::default() + }); } } pub fn rescan_files(&mut self, paths: String) { diff --git a/default-plugins/strider/src/search/search_state.rs b/default-plugins/strider/src/search/search_state.rs index 865a98e5..834a74fe 100644 --- a/default-plugins/strider/src/search/search_state.rs +++ b/default-plugins/strider/src/search/search_state.rs @@ -3,8 +3,8 @@ use crate::search::{MessageToSearch, ResultsOfSearch}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use zellij_tile::prelude::{ - hide_self, open_file, open_file_floating, open_file_with_line, open_file_with_line_floating, - open_terminal, open_terminal_floating, post_message_to, Key, + hide_self, open_file, open_file_floating, open_terminal, open_terminal_floating, + post_message_to, FileToOpen, Key, PluginMessage, }; pub const CURRENT_SEARCH_TERM: &str = "/data/current_search_term"; @@ -88,18 +88,32 @@ impl SearchState { match self.selected_search_result_entry() { Some(SearchResult::File { path, .. }) => { if self.should_open_floating { - open_file_floating(&PathBuf::from(path)) + open_file_floating(FileToOpen { + path: PathBuf::from(path), + ..Default::default() + }); } else { - open_file(&PathBuf::from(path)); + open_file(FileToOpen { + path: PathBuf::from(path), + ..Default::default() + }); } }, Some(SearchResult::LineInFile { path, line_number, .. }) => { if self.should_open_floating { - open_file_with_line_floating(&PathBuf::from(path), line_number); + open_file_floating(FileToOpen { + path: PathBuf::from(path), + line_number: Some(line_number), + ..Default::default() + }); } else { - open_file_with_line(&PathBuf::from(path), line_number); + open_file(FileToOpen { + path: PathBuf::from(path), + line_number: Some(line_number), + ..Default::default() + }); } }, None => eprintln!("Search results not found"), @@ -153,16 +167,16 @@ impl SearchState { match std::fs::write(CURRENT_SEARCH_TERM, &self.search_term) { Ok(_) => { if !self.search_term.is_empty() { - post_message_to( - "file_name_search", - &serde_json::to_string(&MessageToSearch::Search).unwrap(), - "", - ); - post_message_to( - "file_contents_search", - &serde_json::to_string(&MessageToSearch::Search).unwrap(), - "", - ); + post_message_to(PluginMessage { + worker_name: Some("file_name_search".into()), + name: serde_json::to_string(&MessageToSearch::Search).unwrap(), + payload: "".into(), + }); + post_message_to(PluginMessage { + worker_name: Some("file_contents_search".into()), + name: serde_json::to_string(&MessageToSearch::Search).unwrap(), + payload: "".into(), + }); self.file_name_search_results.clear(); self.file_contents_search_results.clear(); } diff --git a/default-plugins/strider/src/state.rs b/default-plugins/strider/src/state.rs index 8dd09653..67e9b891 100644 --- a/default-plugins/strider/src/state.rs +++ b/default-plugins/strider/src/state.rs @@ -66,7 +66,10 @@ impl State { self.path = p; refresh_directory(self); }, - FsEntry::File(p, _) => open_file(p.strip_prefix(ROOT).unwrap()), + FsEntry::File(p, _) => open_file(FileToOpen { + path: p.strip_prefix(ROOT).unwrap().into(), + ..Default::default() + }), } } } diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 73957cf7..92a4c048 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -1,27 +1,28 @@ use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions}; use crate::plugins::plugin_worker::{plugin_worker, RunningWorker}; -use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object, zellij_exports}; +use crate::plugins::zellij_exports::{wasi_write_object, zellij_exports}; use crate::plugins::PluginId; use highway::{HighwayHash, PortableHash}; use log::info; -use semver::Version; use std::{ collections::{HashMap, HashSet}, - fmt, fs, + fs, path::PathBuf, sync::{Arc, Mutex}, }; use url::Url; use wasmer::{ChainableNamedResolver, Instance, Module, Store}; use wasmer_wasi::{Pipe, WasiState}; +use zellij_utils::prost::Message; use crate::{ logging_pipe::LoggingPipe, screen::ScreenInstruction, thread_bus::ThreadSenders, ui::loading_indication::LoadingIndication, ClientId, }; +use zellij_utils::plugin_api::action::ProtobufPluginConfiguration; use zellij_utils::{ - consts::{VERSION, ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR}, + consts::{ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR}, data::PluginCapabilities, errors::prelude::*, input::command::TerminalAction, @@ -43,116 +44,6 @@ macro_rules! display_loading_stage { }}; } -/// Custom error for plugin version mismatch. -/// -/// This is thrown when, during starting a plugin, it is detected that the plugin version doesn't -/// match the zellij version. This is treated as a fatal error and leads to instantaneous -/// termination. -#[derive(Debug)] -pub struct VersionMismatchError { - zellij_version: String, - plugin_version: String, - plugin_path: PathBuf, - // true for builtin plugins - builtin: bool, -} - -impl std::error::Error for VersionMismatchError {} - -impl VersionMismatchError { - pub fn new( - zellij_version: &str, - plugin_version: &str, - plugin_path: &PathBuf, - builtin: bool, - ) -> Self { - VersionMismatchError { - zellij_version: zellij_version.to_owned(), - plugin_version: plugin_version.to_owned(), - plugin_path: plugin_path.to_owned(), - builtin, - } - } -} - -impl fmt::Display for VersionMismatchError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let first_line = if self.builtin { - "It seems your version of zellij was built with outdated core plugins." - } else { - "If you're seeing this error a plugin version doesn't match the current -zellij version." - }; - - write!( - f, - "{} -Detected versions: - -- Plugin version: {} -- Zellij version: {} -- Offending plugin: {} - -If you're a user: - Please contact the distributor of your zellij version and report this error - to them. - -If you're a developer: - Please run zellij with updated plugins. The easiest way to achieve this - is to build zellij with `cargo xtask install`. Also refer to the docs: - https://github.com/zellij-org/zellij/blob/main/CONTRIBUTING.md#building -", - first_line, - self.plugin_version.trim_end(), - self.zellij_version.trim_end(), - self.plugin_path.display() - ) - } -} - -// Returns `Ok` if the plugin version matches the zellij version. -// Returns an `Err` otherwise. -fn assert_plugin_version(instance: &Instance, plugin_env: &PluginEnv) -> Result<()> { - let err_context = || { - format!( - "failed to determine plugin version for plugin {}", - plugin_env.plugin.path.display() - ) - }; - - let plugin_version_func = match instance.exports.get_function("plugin_version") { - Ok(val) => val, - Err(_) => { - return Err(anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - ))) - }, - }; - - let plugin_version = plugin_version_func - .call(&[]) - .map_err(anyError::new) - .and_then(|_| wasi_read_string(&plugin_env.wasi_env)) - .and_then(|string| Version::parse(&string).context("failed to parse plugin version")) - .with_context(err_context)?; - let zellij_version = Version::parse(VERSION) - .context("failed to parse zellij version") - .with_context(err_context)?; - if plugin_version != zellij_version { - return Err(anyError::new(VersionMismatchError::new( - VERSION, - &plugin_version.to_string(), - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - ))); - } - - Ok(()) -} - pub struct PluginLoader<'a> { plugin_cache: Arc>>, plugin_path: PathBuf, @@ -645,10 +536,8 @@ impl<'a> PluginLoader<'a> { &mut self, module: Module, ) -> Result<(Instance, PluginEnv, Arc>)> { - let err_context = || format!("Failed to create environment for plugin"); let (instance, plugin_env, subscriptions) = self.create_plugin_instance_env_and_subscriptions(&module)?; - assert_plugin_version(&instance, &plugin_env).with_context(err_context)?; // Only do an insert when everything went well! let cloned_plugin = self.plugin.clone(); self.plugin_cache @@ -724,9 +613,17 @@ impl<'a> PluginLoader<'a> { } start_function.call(&[]).with_context(err_context)?; + let protobuf_plugin_configuration: ProtobufPluginConfiguration = self + .plugin + .userspace_configuration + .clone() + .try_into() + .map_err(|e| anyhow!("Failed to serialize user configuration: {:?}", e))?; + let protobuf_bytes = protobuf_plugin_configuration.encode_to_vec(); wasi_write_object( &plugin_env.wasi_env, - &self.plugin.userspace_configuration.inner(), + &protobuf_bytes, + // &self.plugin.userspace_configuration.inner(), ) .with_context(err_context)?; load_function.call(&[]).with_context(err_context)?; diff --git a/zellij-server/src/plugins/plugin_worker.rs b/zellij-server/src/plugins/plugin_worker.rs index bc7303c7..9aae0bab 100644 --- a/zellij-server/src/plugins/plugin_worker.rs +++ b/zellij-server/src/plugins/plugin_worker.rs @@ -1,4 +1,3 @@ -use crate::plugins::plugin_loader::VersionMismatchError; use crate::plugins::plugin_map::PluginEnv; use crate::plugins::zellij_exports::wasi_write_object; use wasmer::Instance; @@ -6,7 +5,9 @@ use wasmer::Instance; use zellij_utils::async_channel::{unbounded, Receiver, Sender}; use zellij_utils::async_std::task; use zellij_utils::errors::prelude::*; -use zellij_utils::{consts::VERSION, input::plugins::PluginConfig}; +use zellij_utils::input::plugins::PluginConfig; +use zellij_utils::plugin_api::message::ProtobufMessage; +use zellij_utils::prost::Message; pub struct RunningWorker { pub instance: Instance, @@ -31,29 +32,19 @@ impl RunningWorker { } pub fn send_message(&self, message: String, payload: String) -> Result<()> { let err_context = || format!("Failed to send message to worker"); - + let protobuf_message = ProtobufMessage { + name: message, + payload, + ..Default::default() + }; + let protobuf_bytes = protobuf_message.encode_to_vec(); let work_function = self .instance .exports .get_function(&self.name) .with_context(err_context)?; - wasi_write_object(&self.plugin_env.wasi_env, &(message, payload)) - .with_context(err_context)?; - work_function.call(&[]).or_else::(|e| { - match e.downcast::() { - Ok(_) => panic!( - "{}", - anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &self.plugin_config.path, - self.plugin_config.is_builtin(), - )) - ), - Err(e) => Err(e).with_context(err_context), - } - })?; - + wasi_write_object(&self.plugin_env.wasi_env, &protobuf_bytes).with_context(err_context)?; + work_function.call(&[]).with_context(err_context)?; Ok(()) } } diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap index 7934131f..a66449c6 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap @@ -1,11 +1,11 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 2334 +assertion_line: 2709 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( GoToTabName( - "my tab name\n\r", + "my tab name", ( [], [], diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap index 01cb4b6b..cfc94db2 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap @@ -1,11 +1,11 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 4220 +assertion_line: 4225 expression: "format!(\"{:#?}\", go_to_tab_event)" --- Some( GoToTabName( - "{\"fake_config_key_1\": \"fake_config_value_1\", \"fake_config_key_2\": \"fake_config_value_2\"}\n\r", + "{\"fake_config_key_1\": \"fake_config_value_1\", \"fake_config_key_2\": \"fake_config_value_2\"}", ( [], [], diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap index 504134d0..8c881def 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 1171 +assertion_line: 1449 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( @@ -9,8 +9,6 @@ Some( 102, 111, 111, - 10, - 13, ], 1, ), diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 5ae23be4..d6fcf1fc 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -1,5 +1,5 @@ use super::{PluginId, PluginInstruction}; -use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError}; +use crate::plugins::plugin_loader::PluginLoader; use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions}; use crate::plugins::plugin_worker::MessageToWorker; use crate::plugins::watch_filesystem::watch_filesystem; @@ -14,13 +14,15 @@ use std::{ use wasmer::{Instance, Module, Store, Value}; use zellij_utils::async_std::task::{self, JoinHandle}; use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; +use zellij_utils::plugin_api::event::ProtobufEvent; + +use zellij_utils::prost::Message; use crate::{ background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders, ui::loading_indication::LoadingIndication, ClientId, }; use zellij_utils::{ - consts::VERSION, data::{Event, EventType, PluginCapabilities}, errors::prelude::*, input::{ @@ -737,26 +739,17 @@ pub fn apply_event_to_plugin( plugin_bytes: &mut Vec<(PluginId, ClientId, Vec)>, ) -> Result<()> { let err_context = || format!("Failed to apply event to plugin {plugin_id}"); + let protobuf_event: ProtobufEvent = event + .clone() + .try_into() + .map_err(|e| anyhow!("Failed to convert to protobuf: {:?}", e))?; let update = instance .exports .get_function("update") .with_context(err_context)?; - wasi_write_object(&plugin_env.wasi_env, &event).with_context(err_context)?; - let update_return = - update - .call(&[]) - .or_else::(|e| match e.downcast::() { - Ok(_) => panic!( - "{}", - anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - )) - ), - Err(e) => Err(e).with_context(err_context), - })?; + wasi_write_object(&plugin_env.wasi_env, &protobuf_event.encode_to_vec()) + .with_context(err_context)?; + let update_return = update.call(&[]).with_context(err_context)?; let should_render = match update_return.get(0) { Some(Value::I32(n)) => *n == 1, _ => false, diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index c7306847..275de13e 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -3,7 +3,7 @@ use crate::plugins::plugin_map::{PluginEnv, Subscriptions}; use crate::plugins::wasm_bridge::handle_plugin_crash; use crate::route::route_action; use log::{debug, warn}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; use std::{ collections::{BTreeMap, HashSet}, path::PathBuf, @@ -21,7 +21,10 @@ use crate::{panes::PaneId, screen::ScreenInstruction}; use zellij_utils::{ consts::VERSION, - data::{Direction, Event, EventType, InputMode, PluginIds, Resize}, + data::{ + CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds, + PluginMessage, Resize, ResizeStrategy, + }, errors::prelude::*, input::{ actions::Action, @@ -29,6 +32,11 @@ use zellij_utils::{ layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}, plugins::PluginType, }, + plugin_api::{ + plugin_command::ProtobufPluginCommand, + plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion}, + }, + prost::Message, serde, }; @@ -53,87 +61,13 @@ pub fn zellij_exports( plugin_env: &PluginEnv, subscriptions: &Arc>, ) -> ImportObject { - macro_rules! zellij_export { - ($($host_function:ident),+ $(,)?) => { - imports! { - "zellij" => { - $(stringify!($host_function) => - Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), $host_function),)+ - } - } + imports! { + "zellij" => { + "host_run_plugin_command" => { + Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), host_run_plugin_command) + } } } - - zellij_export! { - host_subscribe, - host_unsubscribe, - host_set_selectable, - host_get_plugin_ids, - host_get_zellij_version, - host_open_file, - host_open_file_floating, - host_open_file_with_line, - host_open_file_with_line_floating, - host_open_terminal, - host_open_terminal_floating, - host_open_command_pane, - host_open_command_pane_floating, - host_switch_tab_to, - host_set_timeout, - host_exec_cmd, - host_report_panic, - host_post_message_to, - host_post_message_to_plugin, - host_hide_self, - host_show_self, - host_switch_to_mode, - host_new_tabs_with_layout, - host_new_tab, - host_go_to_next_tab, - host_go_to_previous_tab, - host_resize, - host_resize_with_direction, - host_focus_next_pane, - host_focus_previous_pane, - host_move_focus, - host_move_focus_or_tab, - host_detach, - host_edit_scrollback, - host_write, - host_write_chars, - host_toggle_tab, - host_move_pane, - host_move_pane_with_direction, - host_clear_screen, - host_scroll_up, - host_scroll_down, - host_scroll_to_top, - host_scroll_to_bottom, - host_page_scroll_up, - host_page_scroll_down, - host_toggle_focus_fullscreen, - host_toggle_pane_frames, - host_toggle_pane_embed_or_eject, - host_undo_rename_pane, - host_close_focus, - host_toggle_active_tab_sync, - host_close_focused_tab, - host_undo_rename_tab, - host_quit_zellij, - host_previous_swap_layout, - host_next_swap_layout, - host_go_to_tab_name, - host_focus_or_create_tab, - host_go_to_tab, - host_start_or_reload_plugin, - host_close_terminal_pane, - host_close_plugin_pane, - host_focus_terminal_pane, - host_focus_plugin_pane, - host_rename_terminal_pane, - host_rename_plugin_pane, - host_rename_tab, - } } #[derive(WasmerEnv, Clone)] @@ -151,42 +85,151 @@ impl ForeignFunctionEnv { } } -fn host_subscribe(env: &ForeignFunctionEnv) { - wasi_read_object::>(&env.plugin_env.wasi_env) - .and_then(|new| { - env.subscriptions.lock().to_anyhow()?.extend(new.clone()); - Ok(new) - }) - .and_then(|new| { - env.plugin_env - .senders - .send_to_plugin(PluginInstruction::PluginSubscribedToEvents( - env.plugin_env.plugin_id, - env.plugin_env.client_id, - new, - )) - }) - .with_context(|| format!("failed to subscribe for plugin {}", env.plugin_env.name())) - .fatal(); -} - -fn host_unsubscribe(env: &ForeignFunctionEnv) { - wasi_read_object::>(&env.plugin_env.wasi_env) - .and_then(|old| { - env.subscriptions - .lock() - .to_anyhow()? - .retain(|k| !old.contains(k)); +fn host_run_plugin_command(env: &ForeignFunctionEnv) { + wasi_read_bytes(&env.plugin_env.wasi_env) + .and_then(|bytes| { + let command: ProtobufPluginCommand = ProtobufPluginCommand::decode(bytes.as_slice())?; + let command: PluginCommand = command + .try_into() + .map_err(|e| anyhow!("failed to convert serialized command: {}", e))?; + match command { + PluginCommand::Subscribe(event_list) => subscribe(env, event_list)?, + PluginCommand::Unsubscribe(event_list) => unsubscribe(env, event_list)?, + PluginCommand::SetSelectable(selectable) => set_selectable(env, selectable), + PluginCommand::GetPluginIds => get_plugin_ids(env), + PluginCommand::GetZellijVersion => get_zellij_version(env), + PluginCommand::OpenFile(file_to_open) => open_file(env, file_to_open), + PluginCommand::OpenFileFloating(file_to_open) => { + open_file_floating(env, file_to_open) + }, + PluginCommand::OpenTerminal(cwd) => open_terminal(env, cwd.path.try_into()?), + PluginCommand::OpenTerminalFloating(cwd) => { + open_terminal_floating(env, cwd.path.try_into()?) + }, + PluginCommand::OpenCommandPane(command_to_run) => { + open_command_pane(env, command_to_run) + }, + PluginCommand::OpenCommandPaneFloating(command_to_run) => { + open_command_pane_floating(env, command_to_run) + }, + PluginCommand::SwitchTabTo(tab_index) => switch_tab_to(env, tab_index), + PluginCommand::SetTimeout(seconds) => set_timeout(env, seconds), + PluginCommand::ExecCmd(command_line) => exec_cmd(env, command_line), + PluginCommand::PostMessageTo(plugin_message) => { + post_message_to(env, plugin_message)? + }, + PluginCommand::PostMessageToPlugin(plugin_message) => { + post_message_to_plugin(env, plugin_message)? + }, + PluginCommand::HideSelf => hide_self(env)?, + PluginCommand::ShowSelf(should_float_if_hidden) => { + show_self(env, should_float_if_hidden) + }, + PluginCommand::SwitchToMode(input_mode) => { + switch_to_mode(env, input_mode.try_into()?) + }, + PluginCommand::NewTabsWithLayout(raw_layout) => { + new_tabs_with_layout(env, &raw_layout)? + }, + PluginCommand::NewTab => new_tab(env), + PluginCommand::GoToNextTab => go_to_next_tab(env), + PluginCommand::GoToPreviousTab => go_to_previous_tab(env), + PluginCommand::Resize(resize_payload) => resize(env, resize_payload), + PluginCommand::ResizeWithDirection(resize_strategy) => { + resize_with_direction(env, resize_strategy) + }, + PluginCommand::FocusNextPane => focus_next_pane(env), + PluginCommand::FocusPreviousPane => focus_previous_pane(env), + PluginCommand::MoveFocus(direction) => move_focus(env, direction), + PluginCommand::MoveFocusOrTab(direction) => move_focus_or_tab(env, direction), + PluginCommand::Detach => detach(env), + PluginCommand::EditScrollback => edit_scrollback(env), + PluginCommand::Write(bytes) => write(env, bytes), + PluginCommand::WriteChars(chars) => write_chars(env, chars), + PluginCommand::ToggleTab => toggle_tab(env), + PluginCommand::MovePane => move_pane(env), + PluginCommand::MovePaneWithDirection(direction) => { + move_pane_with_direction(env, direction) + }, + PluginCommand::ClearScreen => clear_screen(env), + PluginCommand::ScrollUp => scroll_up(env), + PluginCommand::ScrollDown => scroll_down(env), + PluginCommand::ScrollToTop => scroll_to_top(env), + PluginCommand::ScrollToBottom => scroll_to_bottom(env), + PluginCommand::PageScrollUp => page_scroll_up(env), + PluginCommand::PageScrollDown => page_scroll_down(env), + PluginCommand::ToggleFocusFullscreen => toggle_focus_fullscreen(env), + PluginCommand::TogglePaneFrames => toggle_pane_frames(env), + PluginCommand::TogglePaneEmbedOrEject => toggle_pane_embed_or_eject(env), + PluginCommand::UndoRenamePane => undo_rename_pane(env), + PluginCommand::CloseFocus => close_focus(env), + PluginCommand::ToggleActiveTabSync => toggle_active_tab_sync(env), + PluginCommand::CloseFocusedTab => close_focused_tab(env), + PluginCommand::UndoRenameTab => undo_rename_tab(env), + PluginCommand::QuitZellij => quit_zellij(env), + PluginCommand::PreviousSwapLayout => previous_swap_layout(env), + PluginCommand::NextSwapLayout => next_swap_layout(env), + PluginCommand::GoToTabName(tab_name) => go_to_tab_name(env, tab_name), + PluginCommand::FocusOrCreateTab(tab_name) => focus_or_create_tab(env, tab_name), + PluginCommand::GoToTab(tab_index) => go_to_tab(env, tab_index), + PluginCommand::StartOrReloadPlugin(plugin_url) => { + start_or_reload_plugin(env, &plugin_url)? + }, + PluginCommand::CloseTerminalPane(terminal_pane_id) => { + close_terminal_pane(env, terminal_pane_id) + }, + PluginCommand::ClosePluginPane(plugin_pane_id) => { + close_plugin_pane(env, plugin_pane_id) + }, + PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden) => { + focus_terminal_pane(env, terminal_pane_id, should_float_if_hidden) + }, + PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden) => { + focus_plugin_pane(env, plugin_pane_id, should_float_if_hidden) + }, + PluginCommand::RenameTerminalPane(terminal_pane_id, new_name) => { + rename_terminal_pane(env, terminal_pane_id, &new_name) + }, + PluginCommand::RenamePluginPane(plugin_pane_id, new_name) => { + rename_plugin_pane(env, plugin_pane_id, &new_name) + }, + PluginCommand::RenameTab(tab_index, new_name) => { + rename_tab(env, tab_index, &new_name) + }, + PluginCommand::ReportPanic(crash_payload) => report_panic(env, &crash_payload), + } Ok(()) }) - .with_context(|| format!("failed to unsubscribe for plugin {}", env.plugin_env.name())) - .fatal(); + .with_context(|| format!("failed to run plugin command {}", env.plugin_env.name())) + .non_fatal(); } -fn host_set_selectable(env: &ForeignFunctionEnv, selectable: i32) { +fn subscribe(env: &ForeignFunctionEnv, event_list: HashSet) -> Result<()> { + env.subscriptions + .lock() + .to_anyhow()? + .extend(event_list.clone()); + env.plugin_env + .senders + .send_to_plugin(PluginInstruction::PluginSubscribedToEvents( + env.plugin_env.plugin_id, + env.plugin_env.client_id, + event_list, + )) +} + +fn unsubscribe(env: &ForeignFunctionEnv, event_list: HashSet) -> Result<()> { + env.subscriptions + .lock() + .to_anyhow()? + .retain(|k| !event_list.contains(k)); + Ok(()) +} + +fn set_selectable(env: &ForeignFunctionEnv, selectable: bool) { match env.plugin_env.plugin.run { PluginType::Pane(Some(tab_index)) => { - let selectable = selectable != 0; + // let selectable = selectable != 0; env.plugin_env .senders .send_to_screen(ScreenInstruction::SetSelectable( @@ -205,19 +248,24 @@ fn host_set_selectable(env: &ForeignFunctionEnv, selectable: i32) { }, _ => { debug!( - "{} - Calling method 'host_set_selectable' does nothing for headless plugins", + "{} - Calling method 'set_selectable' does nothing for headless plugins", env.plugin_env.plugin.location ) }, } } -fn host_get_plugin_ids(env: &ForeignFunctionEnv) { +fn get_plugin_ids(env: &ForeignFunctionEnv) { let ids = PluginIds { plugin_id: env.plugin_env.plugin_id, zellij_pid: process::id(), }; - wasi_write_object(&env.plugin_env.wasi_env, &ids) + ProtobufPluginIds::try_from(ids) + .map_err(|e| anyhow!("Failed to serialized plugin ids: {}", e)) + .and_then(|serialized| { + wasi_write_object(&env.plugin_env.wasi_env, &serialized.encode_to_vec())?; + Ok(()) + }) .with_context(|| { format!( "failed to query plugin IDs from host for plugin {}", @@ -227,199 +275,124 @@ fn host_get_plugin_ids(env: &ForeignFunctionEnv) { .non_fatal(); } -fn host_get_zellij_version(env: &ForeignFunctionEnv) { - wasi_write_object(&env.plugin_env.wasi_env, VERSION) - .with_context(|| { - format!( - "failed to request zellij version from host for plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn get_zellij_version(env: &ForeignFunctionEnv) { + let protobuf_zellij_version = ProtobufZellijVersion { + version: VERSION.to_owned(), + }; + wasi_write_object( + &env.plugin_env.wasi_env, + &protobuf_zellij_version.encode_to_vec(), + ) + .with_context(|| { + format!( + "failed to request zellij version from host for plugin {}", + env.plugin_env.name() + ) + }) + .non_fatal(); } -fn host_open_file(env: &ForeignFunctionEnv) { - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|path| { - let error_msg = || { - format!( - "failed to open floating file in plugin {}", - env.plugin_env.name() - ) - }; - let floating = false; - let action = Action::EditFile(path, None, None, None, floating); // TODO: add cwd - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_file(env: &ForeignFunctionEnv, file_to_open: FileToOpen) { + let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); + let floating = false; + let action = Action::EditFile( + file_to_open.path, + file_to_open.line_number, + file_to_open.cwd, + None, + floating, + ); + apply_action!(action, error_msg, env); } -fn host_open_file_floating(env: &ForeignFunctionEnv) { - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|path| { - let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); - let floating = true; - let action = Action::EditFile(path, None, None, None, floating); // TODO: add cwd - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_file_floating(env: &ForeignFunctionEnv, file_to_open: FileToOpen) { + let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); + let floating = true; + let action = Action::EditFile( + file_to_open.path, + file_to_open.line_number, + file_to_open.cwd, + None, + floating, + ); + apply_action!(action, error_msg, env); } -fn host_open_file_with_line(env: &ForeignFunctionEnv) { - wasi_read_object::<(PathBuf, usize)>(&env.plugin_env.wasi_env) - .and_then(|(path, line)| { - let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); - let floating = false; - let action = Action::EditFile(path, Some(line), None, None, floating); // TODO: add cwd - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_terminal(env: &ForeignFunctionEnv, cwd: PathBuf) { + let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); + let mut default_shell = env + .plugin_env + .default_shell + .clone() + .unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default())); + default_shell.change_cwd(cwd); + let run_command_action: Option = match default_shell { + TerminalAction::RunCommand(run_command) => Some(run_command.into()), + _ => None, + }; + let action = Action::NewTiledPane(None, run_command_action, None); + apply_action!(action, error_msg, env); } -fn host_open_file_with_line_floating(env: &ForeignFunctionEnv) { - wasi_read_object::<(PathBuf, usize)>(&env.plugin_env.wasi_env) - .and_then(|(path, line)| { - let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); - let floating = true; - let action = Action::EditFile(path, Some(line), None, None, floating); // TODO: add cwd - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_terminal_floating(env: &ForeignFunctionEnv, cwd: PathBuf) { + let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); + let mut default_shell = env + .plugin_env + .default_shell + .clone() + .unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default())); + default_shell.change_cwd(cwd); + let run_command_action: Option = match default_shell { + TerminalAction::RunCommand(run_command) => Some(run_command.into()), + _ => None, + }; + let action = Action::NewFloatingPane(run_command_action, None); + apply_action!(action, error_msg, env); } -fn host_open_terminal(env: &ForeignFunctionEnv) { - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|path| { - let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); - let mut default_shell = env - .plugin_env - .default_shell - .clone() - .unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default())); - default_shell.change_cwd(path); - let run_command_action: Option = match default_shell { - TerminalAction::RunCommand(run_command) => Some(run_command.into()), - _ => None, - }; - let action = Action::NewTiledPane(None, run_command_action, None); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_command_pane(env: &ForeignFunctionEnv, command_to_run: CommandToRun) { + let error_msg = || format!("failed to open command in plugin {}", env.plugin_env.name()); + let command = command_to_run.path; + let cwd = command_to_run.cwd; + let args = command_to_run.args; + let direction = None; + let hold_on_close = true; + let hold_on_start = false; + let name = None; + let run_command_action = RunCommandAction { + command, + args, + cwd, + direction, + hold_on_close, + hold_on_start, + }; + let action = Action::NewTiledPane(direction, Some(run_command_action), name); + apply_action!(action, error_msg, env); } -fn host_open_terminal_floating(env: &ForeignFunctionEnv) { - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|path| { - let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); - let mut default_shell = env - .plugin_env - .default_shell - .clone() - .unwrap_or_else(|| TerminalAction::RunCommand(RunCommand::default())); - default_shell.change_cwd(path); - let run_command_action: Option = match default_shell { - TerminalAction::RunCommand(run_command) => Some(run_command.into()), - _ => None, - }; - let action = Action::NewFloatingPane(run_command_action, None); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| { - format!( - "failed to open file on host from plugin {}", - env.plugin_env.name() - ) - }) - .non_fatal(); +fn open_command_pane_floating(env: &ForeignFunctionEnv, command_to_run: CommandToRun) { + let error_msg = || format!("failed to open command in plugin {}", env.plugin_env.name()); + let command = command_to_run.path; + let cwd = command_to_run.cwd; + let args = command_to_run.args; + let direction = None; + let hold_on_close = true; + let hold_on_start = false; + let name = None; + let run_command_action = RunCommandAction { + command, + args, + cwd, + direction, + hold_on_close, + hold_on_start, + }; + let action = Action::NewFloatingPane(Some(run_command_action), name); + apply_action!(action, error_msg, env); } -fn host_open_command_pane(env: &ForeignFunctionEnv) { - let error_msg = || format!("failed to run command in plugin {}", env.plugin_env.name()); - wasi_read_object::<(PathBuf, Vec)>(&env.plugin_env.wasi_env) - .and_then(|(command, args)| { - let cwd = None; - let direction = None; - let hold_on_close = true; - let hold_on_start = false; - let name = None; - let run_command_action = RunCommandAction { - command, - args, - cwd, - direction, - hold_on_close, - hold_on_start, - }; - let action = Action::NewTiledPane(direction, Some(run_command_action), name); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .non_fatal(); -} - -fn host_open_command_pane_floating(env: &ForeignFunctionEnv) { - let error_msg = || format!("failed to run command in plugin {}", env.plugin_env.name()); - wasi_read_object::<(PathBuf, Vec)>(&env.plugin_env.wasi_env) - .and_then(|(command, args)| { - let cwd = None; - let direction = None; - let hold_on_close = true; - let hold_on_start = false; - let name = None; - let run_command_action = RunCommandAction { - command, - args, - cwd, - direction, - hold_on_close, - hold_on_start, - }; - let action = Action::NewFloatingPane(Some(run_command_action), name); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .non_fatal(); -} - -fn host_switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) { +fn switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) { env.plugin_env .senders .send_to_screen(ScreenInstruction::GoToTab( @@ -428,14 +401,14 @@ fn host_switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) { )) .with_context(|| { format!( - "failed to switch host to tab {tab_idx} from plugin {}", + "failed to switch to tab {tab_idx} from plugin {}", env.plugin_env.name() ) }) .non_fatal(); } -fn host_set_timeout(env: &ForeignFunctionEnv, secs: f64) { +fn set_timeout(env: &ForeignFunctionEnv, secs: f32) { // There is a fancy, high-performance way to do this with zero additional threads: // If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the // next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()` @@ -452,7 +425,7 @@ fn host_set_timeout(env: &ForeignFunctionEnv, secs: f64) { // TODO: we should really use an async task for this thread::spawn(move || { let start_time = Instant::now(); - thread::sleep(Duration::from_secs_f64(secs)); + thread::sleep(Duration::from_secs_f32(secs)); // FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the // time it takes an event to actually reach the plugin after it's sent to the `wasm` thread. let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); @@ -478,67 +451,62 @@ fn host_set_timeout(env: &ForeignFunctionEnv, secs: f64) { }); } -fn host_exec_cmd(env: &ForeignFunctionEnv) { +fn exec_cmd(env: &ForeignFunctionEnv, mut command_line: Vec) { let err_context = || { format!( "failed to execute command on host for plugin '{}'", env.plugin_env.name() ) }; - - let mut cmdline: Vec = wasi_read_object(&env.plugin_env.wasi_env) - .with_context(err_context) - .fatal(); - let command = cmdline.remove(0); + let command = command_line.remove(0); // Bail out if we're forbidden to run command if !env.plugin_env.plugin._allow_exec_host_cmd { warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.", - cmd = command, args = cmdline.join(" ")); + cmd = command, args = command_line.join(" ")); return; } // Here, we don't wait the command to finish process::Command::new(command) - .args(cmdline) + .args(command_line) .spawn() .with_context(err_context) .non_fatal(); } -fn host_post_message_to(env: &ForeignFunctionEnv) { - wasi_read_object::<(String, String, String)>(&env.plugin_env.wasi_env) - .and_then(|(worker_name, message, payload)| { - env.plugin_env - .senders - .send_to_plugin(PluginInstruction::PostMessagesToPluginWorker( - env.plugin_env.plugin_id, - env.plugin_env.client_id, - worker_name, - vec![(message, payload)], - )) - }) - .with_context(|| format!("failed to post message to worker {}", env.plugin_env.name())) - .fatal(); +fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> { + let worker_name = plugin_message + .worker_name + .ok_or(anyhow!("Worker name not specified in message to worker"))?; + env.plugin_env + .senders + .send_to_plugin(PluginInstruction::PostMessagesToPluginWorker( + env.plugin_env.plugin_id, + env.plugin_env.client_id, + worker_name, + vec![(plugin_message.name, plugin_message.payload)], + )) } -fn host_post_message_to_plugin(env: &ForeignFunctionEnv) { - wasi_read_object::<(String, String)>(&env.plugin_env.wasi_env) - .and_then(|(message, payload)| { - env.plugin_env - .senders - .send_to_plugin(PluginInstruction::PostMessageToPlugin( - env.plugin_env.plugin_id, - env.plugin_env.client_id, - message, - payload, - )) - }) - .with_context(|| format!("failed to post message to plugin {}", env.plugin_env.name())) - .fatal(); +fn post_message_to_plugin(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> { + if let Some(worker_name) = plugin_message.worker_name { + return Err(anyhow!( + "Worker name (\"{}\") should not be specified in message to plugin", + worker_name + )); + } + env.plugin_env + .senders + .send_to_plugin(PluginInstruction::PostMessageToPlugin( + env.plugin_env.plugin_id, + env.plugin_env.client_id, + plugin_message.name, + plugin_message.payload, + )) } -fn host_hide_self(env: &ForeignFunctionEnv) { +fn hide_self(env: &ForeignFunctionEnv) -> Result<()> { env.plugin_env .senders .send_to_screen(ScreenInstruction::SuppressPane( @@ -546,261 +514,206 @@ fn host_hide_self(env: &ForeignFunctionEnv) { env.plugin_env.client_id, )) .with_context(|| format!("failed to hide self")) - .fatal(); } -fn host_show_self(env: &ForeignFunctionEnv, should_float_if_hidden: i32) { - let should_float_if_hidden = should_float_if_hidden != 0; +fn show_self(env: &ForeignFunctionEnv, should_float_if_hidden: bool) { let action = Action::FocusPluginPaneWithId(env.plugin_env.plugin_id, should_float_if_hidden); let error_msg = || format!("Failed to show self for plugin"); apply_action!(action, error_msg, env); } -fn host_switch_to_mode(env: &ForeignFunctionEnv) { - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|input_mode| { - let action = Action::SwitchToMode(input_mode); - let error_msg = || { - format!( - "failed to switch to mode in plugin {}", - env.plugin_env.name() - ) - }; - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(|| format!("failed to subscribe for plugin {}", env.plugin_env.name())) - .fatal(); +fn switch_to_mode(env: &ForeignFunctionEnv, input_mode: InputMode) { + let action = Action::SwitchToMode(input_mode); + let error_msg = || { + format!( + "failed to switch to mode in plugin {}", + env.plugin_env.name() + ) + }; + apply_action!(action, error_msg, env); } -fn host_new_tabs_with_layout(env: &ForeignFunctionEnv) { - wasi_read_string(&env.plugin_env.wasi_env) - .and_then(|raw_layout| { - Layout::from_str( - &raw_layout, - format!("Layout from plugin: {}", env.plugin_env.name()), - None, - None, - ) - .map_err(|e| anyhow!("Failed to parse layout: {:?}", e)) - }) // TODO: cwd? - .and_then(|layout| { - let mut tabs_to_open = vec![]; - let tabs = layout.tabs(); - if tabs.is_empty() { - let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); - let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); - let action = Action::NewTab( - layout.template.as_ref().map(|t| t.0.clone()), - layout.template.map(|t| t.1).unwrap_or_default(), - swap_tiled_layouts, - swap_floating_layouts, - None, - ); - tabs_to_open.push(action); - } else { - for (tab_name, tiled_pane_layout, floating_pane_layout) in layout.tabs() { - let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); - let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); - let action = Action::NewTab( - Some(tiled_pane_layout), - floating_pane_layout, - swap_tiled_layouts, - swap_floating_layouts, - tab_name, - ); - tabs_to_open.push(action); - } - } - for action in tabs_to_open { - let error_msg = || format!("Failed to create layout tab"); - apply_action!(action, error_msg, env); - } - Ok(()) - }) - .non_fatal(); +fn new_tabs_with_layout(env: &ForeignFunctionEnv, raw_layout: &str) -> Result<()> { + // TODO: cwd + let layout = Layout::from_str( + &raw_layout, + format!("Layout from plugin: {}", env.plugin_env.name()), + None, + None, + ) + .map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?; + let mut tabs_to_open = vec![]; + let tabs = layout.tabs(); + if tabs.is_empty() { + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + let action = Action::NewTab( + layout.template.as_ref().map(|t| t.0.clone()), + layout.template.map(|t| t.1).unwrap_or_default(), + swap_tiled_layouts, + swap_floating_layouts, + None, + ); + tabs_to_open.push(action); + } else { + for (tab_name, tiled_pane_layout, floating_pane_layout) in layout.tabs() { + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + let action = Action::NewTab( + Some(tiled_pane_layout), + floating_pane_layout, + swap_tiled_layouts, + swap_floating_layouts, + tab_name, + ); + tabs_to_open.push(action); + } + } + for action in tabs_to_open { + let error_msg = || format!("Failed to create layout tab"); + apply_action!(action, error_msg, env); + } + Ok(()) } -fn host_new_tab(env: &ForeignFunctionEnv) { +fn new_tab(env: &ForeignFunctionEnv) { let action = Action::NewTab(None, vec![], None, None, None); let error_msg = || format!("Failed to open new tab"); apply_action!(action, error_msg, env); } -fn host_go_to_next_tab(env: &ForeignFunctionEnv) { +fn go_to_next_tab(env: &ForeignFunctionEnv) { let action = Action::GoToNextTab; let error_msg = || format!("Failed to go to next tab"); apply_action!(action, error_msg, env); } -fn host_go_to_previous_tab(env: &ForeignFunctionEnv) { +fn go_to_previous_tab(env: &ForeignFunctionEnv) { let action = Action::GoToPreviousTab; let error_msg = || format!("Failed to go to previous tab"); apply_action!(action, error_msg, env); } -fn host_resize(env: &ForeignFunctionEnv) { +fn resize(env: &ForeignFunctionEnv, resize: Resize) { let error_msg = || format!("failed to resize in plugin {}", env.plugin_env.name()); - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|resize| { - let action = Action::Resize(resize, None); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let action = Action::Resize(resize, None); + apply_action!(action, error_msg, env); } -fn host_resize_with_direction(env: &ForeignFunctionEnv) { +fn resize_with_direction(env: &ForeignFunctionEnv, resize: ResizeStrategy) { let error_msg = || format!("failed to resize in plugin {}", env.plugin_env.name()); - wasi_read_object::<(Resize, Direction)>(&env.plugin_env.wasi_env) - .and_then(|(resize, direction)| { - let action = Action::Resize(resize, Some(direction)); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let action = Action::Resize(resize.resize, resize.direction); + apply_action!(action, error_msg, env); } -fn host_focus_next_pane(env: &ForeignFunctionEnv) { +fn focus_next_pane(env: &ForeignFunctionEnv) { let action = Action::FocusNextPane; let error_msg = || format!("Failed to focus next pane"); apply_action!(action, error_msg, env); } -fn host_focus_previous_pane(env: &ForeignFunctionEnv) { +fn focus_previous_pane(env: &ForeignFunctionEnv) { let action = Action::FocusPreviousPane; let error_msg = || format!("Failed to focus previous pane"); apply_action!(action, error_msg, env); } -fn host_move_focus(env: &ForeignFunctionEnv) { +fn move_focus(env: &ForeignFunctionEnv, direction: Direction) { let error_msg = || format!("failed to move focus in plugin {}", env.plugin_env.name()); - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|direction| { - let action = Action::MoveFocus(direction); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let action = Action::MoveFocus(direction); + apply_action!(action, error_msg, env); } -fn host_move_focus_or_tab(env: &ForeignFunctionEnv) { +fn move_focus_or_tab(env: &ForeignFunctionEnv, direction: Direction) { let error_msg = || format!("failed to move focus in plugin {}", env.plugin_env.name()); - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|direction| { - let action = Action::MoveFocusOrTab(direction); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let action = Action::MoveFocusOrTab(direction); + apply_action!(action, error_msg, env); } -fn host_detach(env: &ForeignFunctionEnv) { +fn detach(env: &ForeignFunctionEnv) { let action = Action::Detach; let error_msg = || format!("Failed to detach"); apply_action!(action, error_msg, env); } -fn host_edit_scrollback(env: &ForeignFunctionEnv) { +fn edit_scrollback(env: &ForeignFunctionEnv) { let action = Action::EditScrollback; let error_msg = || format!("Failed to edit scrollback"); apply_action!(action, error_msg, env); } -fn host_write(env: &ForeignFunctionEnv) { +fn write(env: &ForeignFunctionEnv, bytes: Vec) { let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name()); - wasi_read_object::>(&env.plugin_env.wasi_env) - .and_then(|bytes| { - let action = Action::Write(bytes); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); -} - -fn host_write_chars(env: &ForeignFunctionEnv) { - let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name()); - wasi_read_string(&env.plugin_env.wasi_env) - .and_then(|chars_to_write| { - let action = Action::WriteChars(chars_to_write); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); -} - -fn host_toggle_tab(env: &ForeignFunctionEnv) { - let action = Action::ToggleTab; - let error_msg = || format!("Failed to toggle tab"); + let action = Action::Write(bytes); apply_action!(action, error_msg, env); } -fn host_move_pane(env: &ForeignFunctionEnv) { +fn write_chars(env: &ForeignFunctionEnv, chars_to_write: String) { + let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name()); + let action = Action::WriteChars(chars_to_write); + apply_action!(action, error_msg, env); +} + +fn toggle_tab(env: &ForeignFunctionEnv) { + let error_msg = || format!("Failed to toggle tab"); + let action = Action::ToggleTab; + apply_action!(action, error_msg, env); +} + +fn move_pane(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to move pane in plugin {}", env.plugin_env.name()); let action = Action::MovePane(None); apply_action!(action, error_msg, env); } -fn host_move_pane_with_direction(env: &ForeignFunctionEnv) { +fn move_pane_with_direction(env: &ForeignFunctionEnv, direction: Direction) { let error_msg = || format!("failed to move pane in plugin {}", env.plugin_env.name()); - wasi_read_object::(&env.plugin_env.wasi_env) - .and_then(|direction| { - let action = Action::MovePane(Some(direction)); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let action = Action::MovePane(Some(direction)); + apply_action!(action, error_msg, env); } -fn host_clear_screen(env: &ForeignFunctionEnv) { +fn clear_screen(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to clear screen in plugin {}", env.plugin_env.name()); let action = Action::ClearScreen; apply_action!(action, error_msg, env); } -fn host_scroll_up(env: &ForeignFunctionEnv) { +fn scroll_up(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll up in plugin {}", env.plugin_env.name()); let action = Action::ScrollUp; apply_action!(action, error_msg, env); } -fn host_scroll_down(env: &ForeignFunctionEnv) { +fn scroll_down(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll down in plugin {}", env.plugin_env.name()); let action = Action::ScrollDown; apply_action!(action, error_msg, env); } -fn host_scroll_to_top(env: &ForeignFunctionEnv) { +fn scroll_to_top(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name()); let action = Action::ScrollToTop; apply_action!(action, error_msg, env); } -fn host_scroll_to_bottom(env: &ForeignFunctionEnv) { +fn scroll_to_bottom(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name()); let action = Action::ScrollToBottom; apply_action!(action, error_msg, env); } -fn host_page_scroll_up(env: &ForeignFunctionEnv) { +fn page_scroll_up(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name()); let action = Action::PageScrollUp; apply_action!(action, error_msg, env); } -fn host_page_scroll_down(env: &ForeignFunctionEnv) { +fn page_scroll_down(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to scroll in plugin {}", env.plugin_env.name()); let action = Action::PageScrollDown; apply_action!(action, error_msg, env); } -fn host_toggle_focus_fullscreen(env: &ForeignFunctionEnv) { +fn toggle_focus_fullscreen(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to toggle full screen in plugin {}", @@ -811,7 +724,7 @@ fn host_toggle_focus_fullscreen(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_toggle_pane_frames(env: &ForeignFunctionEnv) { +fn toggle_pane_frames(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to toggle full screen in plugin {}", @@ -822,7 +735,7 @@ fn host_toggle_pane_frames(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_toggle_pane_embed_or_eject(env: &ForeignFunctionEnv) { +fn toggle_pane_embed_or_eject(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to toggle pane embed or eject in plugin {}", @@ -833,7 +746,7 @@ fn host_toggle_pane_embed_or_eject(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_undo_rename_pane(env: &ForeignFunctionEnv) { +fn undo_rename_pane(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to undo rename pane in plugin {}", @@ -844,7 +757,7 @@ fn host_undo_rename_pane(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_close_focus(env: &ForeignFunctionEnv) { +fn close_focus(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to close focused pane in plugin {}", @@ -855,7 +768,7 @@ fn host_close_focus(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_toggle_active_tab_sync(env: &ForeignFunctionEnv) { +fn toggle_active_tab_sync(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to toggle active tab sync in plugin {}", @@ -866,7 +779,7 @@ fn host_toggle_active_tab_sync(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_close_focused_tab(env: &ForeignFunctionEnv) { +fn close_focused_tab(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to close active tab in plugin {}", @@ -877,7 +790,7 @@ fn host_close_focused_tab(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_undo_rename_tab(env: &ForeignFunctionEnv) { +fn undo_rename_tab(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to undo rename tab in plugin {}", @@ -888,13 +801,13 @@ fn host_undo_rename_tab(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_quit_zellij(env: &ForeignFunctionEnv) { +fn quit_zellij(env: &ForeignFunctionEnv) { let error_msg = || format!("failed to quit zellij in plugin {}", env.plugin_env.name()); let action = Action::Quit; apply_action!(action, error_msg, env); } -fn host_previous_swap_layout(env: &ForeignFunctionEnv) { +fn previous_swap_layout(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to switch swap layout in plugin {}", @@ -905,7 +818,7 @@ fn host_previous_swap_layout(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_next_swap_layout(env: &ForeignFunctionEnv) { +fn next_swap_layout(env: &ForeignFunctionEnv) { let error_msg = || { format!( "failed to switch swap layout in plugin {}", @@ -916,49 +829,37 @@ fn host_next_swap_layout(env: &ForeignFunctionEnv) { apply_action!(action, error_msg, env); } -fn host_go_to_tab_name(env: &ForeignFunctionEnv) { +fn go_to_tab_name(env: &ForeignFunctionEnv, tab_name: String) { let error_msg = || format!("failed to change tab in plugin {}", env.plugin_env.name()); - wasi_read_string(&env.plugin_env.wasi_env) - .and_then(|tab_name| { - let create = false; - let action = Action::GoToTabName(tab_name, create); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let create = false; + let action = Action::GoToTabName(tab_name, create); + apply_action!(action, error_msg, env); } -fn host_focus_or_create_tab(env: &ForeignFunctionEnv) { +fn focus_or_create_tab(env: &ForeignFunctionEnv, tab_name: String) { let error_msg = || { format!( "failed to change or create tab in plugin {}", env.plugin_env.name() ) }; - wasi_read_string(&env.plugin_env.wasi_env) - .and_then(|tab_name| { - let create = true; - let action = Action::GoToTabName(tab_name, create); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let create = true; + let action = Action::GoToTabName(tab_name, create); + apply_action!(action, error_msg, env); } -fn host_go_to_tab(env: &ForeignFunctionEnv, tab_index: i32) { +fn go_to_tab(env: &ForeignFunctionEnv, tab_index: u32) { let error_msg = || { format!( "failed to change tab focus in plugin {}", env.plugin_env.name() ) }; - let action = Action::GoToTab(tab_index as u32); + let action = Action::GoToTab(tab_index); apply_action!(action, error_msg, env); } -fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) { +fn start_or_reload_plugin(env: &ForeignFunctionEnv, url: &str) -> Result<()> { let error_msg = || { format!( "failed to start or reload plugin in plugin {}", @@ -966,106 +867,74 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) { ) }; let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - wasi_read_string(&env.plugin_env.wasi_env) - .and_then(|url| Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e))) - .and_then(|url| { - RunPluginLocation::parse(url.as_str(), Some(cwd)) - .map_err(|e| anyhow!("Failed to parse plugin location: {}", e)) - }) - .and_then(|run_plugin_location| { - let run_plugin = RunPlugin { - location: run_plugin_location, - _allow_exec_host_cmd: false, - configuration: PluginUserConfiguration::new(BTreeMap::new()), // TODO: allow passing configuration - }; - let action = Action::StartOrReloadPlugin(run_plugin); - apply_action!(action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let url = Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e))?; + let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(cwd)) + .map_err(|e| anyhow!("Failed to parse plugin location: {}", e))?; + let run_plugin = RunPlugin { + location: run_plugin_location, + _allow_exec_host_cmd: false, + configuration: PluginUserConfiguration::new(BTreeMap::new()), // TODO: allow passing configuration + }; + let action = Action::StartOrReloadPlugin(run_plugin); + apply_action!(action, error_msg, env); + Ok(()) } -fn host_close_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: i32) { +fn close_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: u32) { let error_msg = || { format!( "failed to change tab focus in plugin {}", env.plugin_env.name() ) }; - let action = Action::CloseTerminalPane(terminal_pane_id as u32); + let action = Action::CloseTerminalPane(terminal_pane_id); apply_action!(action, error_msg, env); } -fn host_close_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: i32) { +fn close_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32) { let error_msg = || { format!( "failed to change tab focus in plugin {}", env.plugin_env.name() ) }; - let action = Action::ClosePluginPane(plugin_pane_id as u32); + let action = Action::ClosePluginPane(plugin_pane_id); apply_action!(action, error_msg, env); } -fn host_focus_terminal_pane( +fn focus_terminal_pane( env: &ForeignFunctionEnv, - terminal_pane_id: i32, - should_float_if_hidden: i32, + terminal_pane_id: u32, + should_float_if_hidden: bool, ) { - let should_float_if_hidden = should_float_if_hidden != 0; - let action = Action::FocusTerminalPaneWithId(terminal_pane_id as u32, should_float_if_hidden); + let action = Action::FocusTerminalPaneWithId(terminal_pane_id, should_float_if_hidden); let error_msg = || format!("Failed to focus terminal pane"); apply_action!(action, error_msg, env); } -fn host_focus_plugin_pane( - env: &ForeignFunctionEnv, - plugin_pane_id: i32, - should_float_if_hidden: i32, -) { - let should_float_if_hidden = should_float_if_hidden != 0; - let action = Action::FocusPluginPaneWithId(plugin_pane_id as u32, should_float_if_hidden); +fn focus_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32, should_float_if_hidden: bool) { + let action = Action::FocusPluginPaneWithId(plugin_pane_id, should_float_if_hidden); let error_msg = || format!("Failed to focus plugin pane"); apply_action!(action, error_msg, env); } -fn host_rename_terminal_pane(env: &ForeignFunctionEnv) { +fn rename_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: u32, new_name: &str) { let error_msg = || format!("Failed to rename terminal pane"); - wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env) - .and_then(|(terminal_pane_id, new_name)| { - let rename_pane_action = - Action::RenameTerminalPane(terminal_pane_id, new_name.as_bytes().to_vec()); - apply_action!(rename_pane_action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let rename_pane_action = + Action::RenameTerminalPane(terminal_pane_id, new_name.as_bytes().to_vec()); + apply_action!(rename_pane_action, error_msg, env); } -fn host_rename_plugin_pane(env: &ForeignFunctionEnv) { +fn rename_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: u32, new_name: &str) { let error_msg = || format!("Failed to rename plugin pane"); - wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env) - .and_then(|(plugin_pane_id, new_name)| { - let rename_pane_action = - Action::RenamePluginPane(plugin_pane_id, new_name.as_bytes().to_vec()); - apply_action!(rename_pane_action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let rename_pane_action = Action::RenamePluginPane(plugin_pane_id, new_name.as_bytes().to_vec()); + apply_action!(rename_pane_action, error_msg, env); } -fn host_rename_tab(env: &ForeignFunctionEnv) { +fn rename_tab(env: &ForeignFunctionEnv, tab_index: u32, new_name: &str) { let error_msg = || format!("Failed to rename tab"); - wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env) - .and_then(|(tab_index, new_name)| { - let rename_tab_action = Action::RenameTab(tab_index, new_name.as_bytes().to_vec()); - apply_action!(rename_tab_action, error_msg, env); - Ok(()) - }) - .with_context(error_msg) - .fatal(); + let rename_tab_action = Action::RenameTab(tab_index, new_name.as_bytes().to_vec()); + apply_action!(rename_tab_action, error_msg, env); } // Custom panic handler for plugins. @@ -1073,19 +942,11 @@ fn host_rename_tab(env: &ForeignFunctionEnv) { // This is called when a panic occurs in a plugin. Since most panics will likely originate in the // code trying to deserialize an `Event` upon a plugin state update, we read some panic message, // formatted as string from the plugin. -fn host_report_panic(env: &ForeignFunctionEnv) { - let msg = wasi_read_string(&env.plugin_env.wasi_env) - .with_context(|| { - format!( - "failed to report panic for plugin '{}'", - env.plugin_env.name() - ) - }) - .fatal(); - log::error!("PANIC IN PLUGIN! {}", msg); +fn report_panic(env: &ForeignFunctionEnv, msg: &str) { + log::error!("PANIC IN PLUGIN!\n\r{}", msg); handle_plugin_crash( env.plugin_env.plugin_id, - msg, + msg.to_owned(), env.plugin_env.senders.clone(), ); } @@ -1135,7 +996,7 @@ pub fn wasi_write_object(wasi_env: &WasiEnv, object: &(impl Serialize + ?Sized)) .with_context(|| format!("failed to serialize object for WASI env '{wasi_env:?}'")) } -pub fn wasi_read_object(wasi_env: &WasiEnv) -> Result { +pub fn wasi_read_bytes(wasi_env: &WasiEnv) -> Result> { wasi_read_string(wasi_env) .and_then(|string| serde_json::from_str(&string).map_err(anyError::new)) .with_context(|| format!("failed to deserialize object from WASI env '{wasi_env:?}'")) diff --git a/zellij-tile/src/lib.rs b/zellij-tile/src/lib.rs index 7bcaf473..67b4a551 100644 --- a/zellij-tile/src/lib.rs +++ b/zellij-tile/src/lib.rs @@ -22,6 +22,8 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use zellij_utils::data::Event; +// use zellij_tile::shim::plugin_api::event::ProtobufEvent; + /// This trait should be implemented - once per plugin - on a struct (normally representing the /// plugin state). This struct should then be registered with the /// [`register_plugin!`](register_plugin) macro. @@ -104,22 +106,31 @@ macro_rules! register_plugin { #[no_mangle] fn load() { STATE.with(|state| { - let configuration = $crate::shim::object_from_stdin() - .context($crate::PLUGIN_MISMATCH) - .to_stdout() - .unwrap(); - state.borrow_mut().load(configuration); + use std::collections::BTreeMap; + use std::convert::TryInto; + use zellij_tile::shim::plugin_api::action::ProtobufPluginConfiguration; + use zellij_tile::shim::prost::Message; + let protobuf_bytes: Vec = $crate::shim::object_from_stdin().unwrap(); + let protobuf_configuration: ProtobufPluginConfiguration = + ProtobufPluginConfiguration::decode(protobuf_bytes.as_slice()).unwrap(); + let plugin_configuration: BTreeMap = + BTreeMap::try_from(&protobuf_configuration).unwrap(); + state.borrow_mut().load(plugin_configuration); }); } #[no_mangle] pub fn update() -> bool { + let err_context = "Failed to deserialize event"; + use std::convert::TryInto; + use zellij_tile::shim::plugin_api::event::ProtobufEvent; + use zellij_tile::shim::prost::Message; STATE.with(|state| { - let object = $crate::shim::object_from_stdin() - .context($crate::PLUGIN_MISMATCH) - .to_stdout() - .unwrap(); - state.borrow_mut().update(object) + let protobuf_bytes: Vec = $crate::shim::object_from_stdin().unwrap(); + let protobuf_event: ProtobufEvent = + ProtobufEvent::decode(protobuf_bytes.as_slice()).unwrap(); + let event = protobuf_event.try_into().unwrap(); + state.borrow_mut().update(event) }) } @@ -168,18 +179,15 @@ macro_rules! register_worker { } #[no_mangle] pub fn $worker_name() { - + use zellij_tile::shim::plugin_api::message::ProtobufMessage; + use zellij_tile::shim::prost::Message; let worker_display_name = std::stringify!($worker_name); - - // read message from STDIN - let (message, payload): (String, String) = $crate::shim::object_from_stdin() - .unwrap_or_else(|e| { - eprintln!( - "Failed to deserialize message to worker \"{}\": {:?}", - worker_display_name, e - ); - Default::default() - }); + let protobuf_bytes: Vec = $crate::shim::object_from_stdin() + .unwrap(); + let protobuf_message: ProtobufMessage = ProtobufMessage::decode(protobuf_bytes.as_slice()) + .unwrap(); + let message = protobuf_message.name; + let payload = protobuf_message.payload; $worker_static_name.with(|worker_instance| { let mut worker_instance = worker_instance.borrow_mut(); worker_instance.on_message(message, payload); diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index c12a6152..c3a772f9 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -1,375 +1,559 @@ use serde::{de::DeserializeOwned, Serialize}; +use std::collections::HashSet; use std::{io, path::Path}; use zellij_utils::data::*; use zellij_utils::errors::prelude::*; +pub use zellij_utils::plugin_api; +use zellij_utils::plugin_api::plugin_command::ProtobufPluginCommand; +use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion}; + +pub use zellij_utils::prost::{self, *}; // Subscription Handling /// Subscribe to a list of [`Event`]s represented by their [`EventType`]s that will then trigger the `update` method pub fn subscribe(event_types: &[EventType]) { - object_to_stdout(&event_types); - unsafe { host_subscribe() }; + let event_types: HashSet = event_types.iter().cloned().collect(); + let plugin_command = PluginCommand::Subscribe(event_types); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Unsubscribe to a list of [`Event`]s represented by their [`EventType`]s. pub fn unsubscribe(event_types: &[EventType]) { - object_to_stdout(&event_types); - unsafe { host_unsubscribe() }; + let event_types: HashSet = event_types.iter().cloned().collect(); + let plugin_command = PluginCommand::Unsubscribe(event_types); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } // Plugin Settings /// Sets the plugin as selectable or unselectable to the user. Unselectable plugins might be desired when they do not accept user input. pub fn set_selectable(selectable: bool) { - unsafe { host_set_selectable(selectable as i32) }; + let plugin_command = PluginCommand::SetSelectable(selectable); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } // Query Functions /// Returns the unique Zellij pane ID for the plugin as well as the Zellij process id. pub fn get_plugin_ids() -> PluginIds { - unsafe { host_get_plugin_ids() }; - object_from_stdin().unwrap() + let plugin_command = PluginCommand::GetPluginIds; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; + let protobuf_plugin_ids = + ProtobufPluginIds::decode(bytes_from_stdin().unwrap().as_slice()).unwrap(); + PluginIds::try_from(protobuf_plugin_ids).unwrap() } /// Returns the version of the running Zellij instance - can be useful to check plugin compatibility pub fn get_zellij_version() -> String { - unsafe { host_get_zellij_version() }; - object_from_stdin().unwrap() + let plugin_command = PluginCommand::GetZellijVersion; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; + let protobuf_zellij_version = + ProtobufZellijVersion::decode(bytes_from_stdin().unwrap().as_slice()).unwrap(); + protobuf_zellij_version.version } // Host Functions /// Open a file in the user's default `$EDITOR` in a new pane -pub fn open_file>(path: P) { - object_to_stdout(&path.as_ref()); - unsafe { host_open_file() }; +pub fn open_file(file_to_open: FileToOpen) { + let plugin_command = PluginCommand::OpenFile(file_to_open); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a file in the user's default `$EDITOR` in a new floating pane -pub fn open_file_floating>(path: P) { - object_to_stdout(&path.as_ref()); - unsafe { host_open_file_floating() }; -} - -/// Open a file to a specific line in the user's default `$EDITOR` (if it supports it, most do) in a new pane -pub fn open_file_with_line>(path: P, line: usize) { - object_to_stdout(&(path.as_ref(), line)); - unsafe { host_open_file_with_line() }; -} - -/// Open a file to a specific line in the user's default `$EDITOR` (if it supports it, most do) in a new floating pane -pub fn open_file_with_line_floating>(path: P, line: usize) { - object_to_stdout(&(path.as_ref(), line)); - unsafe { host_open_file_with_line_floating() }; +pub fn open_file_floating(file_to_open: FileToOpen) { + let plugin_command = PluginCommand::OpenFileFloating(file_to_open); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a new terminal pane to the specified location on the host filesystem pub fn open_terminal>(path: P) { - object_to_stdout(&path.as_ref()); - unsafe { host_open_terminal() }; + let file_to_open = FileToOpen::new(path.as_ref().to_path_buf()); + let plugin_command = PluginCommand::OpenTerminal(file_to_open); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a new floating terminal pane to the specified location on the host filesystem pub fn open_terminal_floating>(path: P) { - object_to_stdout(&path.as_ref()); - unsafe { host_open_terminal_floating() }; + let file_to_open = FileToOpen::new(path.as_ref().to_path_buf()); + let plugin_command = PluginCommand::OpenTerminalFloating(file_to_open); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a new command pane with the specified command and args (this sort of pane allows the user to control the command, re-run it and see its exit status through the Zellij UI). -pub fn open_command_pane, A: AsRef>(path: P, args: Vec) { - object_to_stdout(&( - path.as_ref(), - args.iter().map(|a| a.as_ref()).collect::>(), - )); - unsafe { host_open_command_pane() }; +// pub fn open_command_pane, A: AsRef>(path: P, args: Vec) { +pub fn open_command_pane(command_to_run: CommandToRun) { + let plugin_command = PluginCommand::OpenCommandPane(command_to_run); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a new floating command pane with the specified command and args (this sort of pane allows the user to control the command, re-run it and see its exit status through the Zellij UI). -pub fn open_command_pane_floating, A: AsRef>(path: P, args: Vec) { - object_to_stdout(&( - path.as_ref(), - args.iter().map(|a| a.as_ref()).collect::>(), - )); - unsafe { host_open_command_pane_floating() }; +// pub fn open_command_pane_floating, A: AsRef>(path: P, args: Vec) { +pub fn open_command_pane_floating(command_to_run: CommandToRun) { + let plugin_command = PluginCommand::OpenCommandPaneFloating(command_to_run); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change the focused tab to the specified index (corresponding with the default tab names, to starting at `1`, `0` will be considered as `1`). pub fn switch_tab_to(tab_idx: u32) { - unsafe { host_switch_tab_to(tab_idx) }; + let plugin_command = PluginCommand::SwitchTabTo(tab_idx); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Set a timeout in seconds (or fractions thereof) after which the plugins [update](./plugin-api-events#update) method will be called with the [`Timer`](./plugin-api-events.md#timer) event. pub fn set_timeout(secs: f64) { - unsafe { host_set_timeout(secs) }; + let plugin_command = PluginCommand::SetTimeout(secs as f32); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } #[doc(hidden)] pub fn exec_cmd(cmd: &[&str]) { - object_to_stdout(&cmd); - unsafe { host_exec_cmd() }; + let plugin_command = + PluginCommand::ExecCmd(cmd.iter().cloned().map(|s| s.to_owned()).collect()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Hide the plugin pane (suppress it) from the UI pub fn hide_self() { - unsafe { host_hide_self() }; + let plugin_command = PluginCommand::HideSelf; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Show the plugin pane (unsuppress it if it is suppressed), focus it and switch to its tab pub fn show_self(should_float_if_hidden: bool) { - unsafe { host_show_self(should_float_if_hidden as i32) }; + let plugin_command = PluginCommand::ShowSelf(should_float_if_hidden); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Switch to the specified Input Mode (eg. `Normal`, `Tab`, `Pane`) pub fn switch_to_input_mode(mode: &InputMode) { - object_to_stdout(&mode); - unsafe { host_switch_to_mode() }; + let plugin_command = PluginCommand::SwitchToMode(*mode); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Provide a stringified [`layout`](https://zellij.dev/documentation/layouts.html) to be applied to the current session. If the layout has multiple tabs, they will all be opened. pub fn new_tabs_with_layout(layout: &str) { - println!("{}", layout); - unsafe { host_new_tabs_with_layout() } + let plugin_command = PluginCommand::NewTabsWithLayout(layout.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Open a new tab with the default layout pub fn new_tab() { - unsafe { host_new_tab() } + let plugin_command = PluginCommand::NewTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus to the next tab or loop back to the first pub fn go_to_next_tab() { - unsafe { host_go_to_next_tab() } + let plugin_command = PluginCommand::GoToNextTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus to the previous tab or loop back to the last pub fn go_to_previous_tab() { - unsafe { host_go_to_previous_tab() } + let plugin_command = PluginCommand::GoToPreviousTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } pub fn report_panic(info: &std::panic::PanicInfo) { - println!(""); - println!("A panic occured in a plugin"); - println!("{:#?}", info); - unsafe { host_report_panic() }; + let panic_payload = if let Some(s) = info.payload().downcast_ref::<&str>() { + format!("{}", s) + } else { + format!("") + }; + let panic_stringified = format!("{}\n\r{:#?}", panic_payload, info).replace("\n", "\r\n"); + let plugin_command = PluginCommand::ReportPanic(panic_stringified); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Either Increase or Decrease the size of the focused pane pub fn resize_focused_pane(resize: Resize) { - object_to_stdout(&resize); - unsafe { host_resize() }; + let plugin_command = PluginCommand::Resize(resize); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Either Increase or Decrease the size of the focused pane in a specified direction (eg. `Left`, `Right`, `Up`, `Down`). pub fn resize_focused_pane_with_direction(resize: Resize, direction: Direction) { - object_to_stdout(&(resize, direction)); - unsafe { host_resize_with_direction() }; + let resize_strategy = ResizeStrategy { + resize, + direction: Some(direction), + invert_on_boundaries: false, + }; + let plugin_command = PluginCommand::ResizeWithDirection(resize_strategy); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus tot he next pane in chronological order pub fn focus_next_pane() { - unsafe { host_focus_next_pane() }; + let plugin_command = PluginCommand::FocusNextPane; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus to the previous pane in chronological order pub fn focus_previous_pane() { - unsafe { host_focus_previous_pane() }; + let plugin_command = PluginCommand::FocusPreviousPane; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change the focused pane in the specified direction pub fn move_focus(direction: Direction) { - object_to_stdout(&direction); - unsafe { host_move_focus() }; + let plugin_command = PluginCommand::MoveFocus(direction); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change the focused pane in the specified direction, if the pane is on the edge of the screen, the next tab is focused (next if right edge, previous if left edge). pub fn move_focus_or_tab(direction: Direction) { - object_to_stdout(&direction); - unsafe { host_move_focus_or_tab() }; + let plugin_command = PluginCommand::MoveFocusOrTab(direction); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Detach the user from the active session pub fn detach() { - unsafe { host_detach() }; + let plugin_command = PluginCommand::Detach; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Edit the scrollback of the focused pane in the user's default `$EDITOR` pub fn edit_scrollback() { - unsafe { host_edit_scrollback() }; + let plugin_command = PluginCommand::EditScrollback; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Write bytes to the `STDIN` of the focused pane pub fn write(bytes: Vec) { - object_to_stdout(&bytes); - unsafe { host_write() }; + let plugin_command = PluginCommand::Write(bytes); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Write characters to the `STDIN` of the focused pane pub fn write_chars(chars: &str) { - println!("{}", chars); - unsafe { host_write_chars() }; + let plugin_command = PluginCommand::WriteChars(chars.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Focused the previously focused tab (regardless of the tab position) pub fn toggle_tab() { - unsafe { host_toggle_tab() }; + let plugin_command = PluginCommand::ToggleTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Switch the position of the focused pane with a different pane pub fn move_pane() { - unsafe { host_move_pane() }; + let plugin_command = PluginCommand::MovePane; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Switch the position of the focused pane with a different pane in the specified direction (eg. `Down`, `Up`, `Left`, `Right`). pub fn move_pane_with_direction(direction: Direction) { - object_to_stdout(&direction); - unsafe { host_move_pane_with_direction() }; + let plugin_command = PluginCommand::MovePaneWithDirection(direction); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Clear the scroll buffer of the focused pane pub fn clear_screen() { - unsafe { host_clear_screen() }; + let plugin_command = PluginCommand::ClearScreen; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane up 1 line pub fn scroll_up() { - unsafe { host_scroll_up() }; + let plugin_command = PluginCommand::ScrollUp; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane down 1 line pub fn scroll_down() { - unsafe { host_scroll_down() }; + let plugin_command = PluginCommand::ScrollDown; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane all the way to the top of the scrollbuffer pub fn scroll_to_top() { - unsafe { host_scroll_to_top() }; + let plugin_command = PluginCommand::ScrollToTop; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane all the way to the bottom of the scrollbuffer pub fn scroll_to_bottom() { - unsafe { host_scroll_to_bottom() }; + let plugin_command = PluginCommand::ScrollToBottom; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane up one page pub fn page_scroll_up() { - unsafe { host_page_scroll_up() }; + let plugin_command = PluginCommand::PageScrollUp; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Scroll the focused pane down one page pub fn page_scroll_down() { - unsafe { host_page_scroll_down() }; + let plugin_command = PluginCommand::PageScrollDown; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Toggle the focused pane to be fullscreen or normal sized pub fn toggle_focus_fullscreen() { - unsafe { host_toggle_focus_fullscreen() }; + let plugin_command = PluginCommand::ToggleFocusFullscreen; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Toggle the UI pane frames on or off pub fn toggle_pane_frames() { - unsafe { host_toggle_pane_frames() }; + let plugin_command = PluginCommand::TogglePaneFrames; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Embed the currently focused pane (make it stop floating) or turn it to a float pane if it is not pub fn toggle_pane_embed_or_eject() { - unsafe { host_toggle_pane_embed_or_eject() }; + let plugin_command = PluginCommand::TogglePaneEmbedOrEject; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } pub fn undo_rename_pane() { - unsafe { host_undo_rename_pane() }; + let plugin_command = PluginCommand::UndoRenamePane; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Close the focused pane pub fn close_focus() { - unsafe { host_close_focus() }; + let plugin_command = PluginCommand::CloseFocus; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Turn the `STDIN` synchronization of the current tab on or off pub fn toggle_active_tab_sync() { - unsafe { host_toggle_active_tab_sync() }; + let plugin_command = PluginCommand::ToggleActiveTabSync; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Close the focused tab pub fn close_focused_tab() { - unsafe { host_close_focused_tab() }; + let plugin_command = PluginCommand::CloseFocusedTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } pub fn undo_rename_tab() { - unsafe { host_undo_rename_tab() }; + let plugin_command = PluginCommand::UndoRenameTab; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Compeltely quit Zellij for this and all other connected clients pub fn quit_zellij() { - unsafe { host_quit_zellij() }; + let plugin_command = PluginCommand::QuitZellij; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change to the previous [swap layout](https://zellij.dev/documentation/swap-layouts.html) pub fn previous_swap_layout() { - unsafe { host_previous_swap_layout() }; + let plugin_command = PluginCommand::PreviousSwapLayout; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change to the next [swap layout](https://zellij.dev/documentation/swap-layouts.html) pub fn next_swap_layout() { - unsafe { host_next_swap_layout() }; + let plugin_command = PluginCommand::NextSwapLayout; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus to the tab with the specified name pub fn go_to_tab_name(tab_name: &str) { - println!("{}", tab_name); - unsafe { host_go_to_tab_name() }; + let plugin_command = PluginCommand::GoToTabName(tab_name.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Change focus to the tab with the specified name or create it if it does not exist pub fn focus_or_create_tab(tab_name: &str) { - print!("{}", tab_name); - unsafe { host_focus_or_create_tab() }; + let plugin_command = PluginCommand::FocusOrCreateTab(tab_name.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } -pub fn go_to_tab(tab_index: i32) { - unsafe { host_go_to_tab(tab_index) }; +pub fn go_to_tab(tab_index: u32) { + let plugin_command = PluginCommand::GoToTab(tab_index); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } pub fn start_or_reload_plugin(url: &str) { - println!("{}", url); - unsafe { host_start_or_reload_plugin() }; + let plugin_command = PluginCommand::StartOrReloadPlugin(url.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Closes a terminal pane with the specified id -pub fn close_terminal_pane(terminal_pane_id: i32) { - unsafe { host_close_terminal_pane(terminal_pane_id) }; +pub fn close_terminal_pane(terminal_pane_id: u32) { + let plugin_command = PluginCommand::CloseTerminalPane(terminal_pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Closes a plugin pane with the specified id -pub fn close_plugin_pane(plugin_pane_id: i32) { - unsafe { host_close_plugin_pane(plugin_pane_id) }; +pub fn close_plugin_pane(plugin_pane_id: u32) { + let plugin_command = PluginCommand::ClosePluginPane(plugin_pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Changes the focus to the terminal pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled). -pub fn focus_terminal_pane(terminal_pane_id: i32, should_float_if_hidden: bool) { - unsafe { host_focus_terminal_pane(terminal_pane_id, should_float_if_hidden as i32) }; +pub fn focus_terminal_pane(terminal_pane_id: u32, should_float_if_hidden: bool) { + let plugin_command = PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Changes the focus to the plugin pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled). -pub fn focus_plugin_pane(plugin_pane_id: i32, should_float_if_hidden: bool) { - unsafe { host_focus_plugin_pane(plugin_pane_id, should_float_if_hidden as i32) }; +pub fn focus_plugin_pane(plugin_pane_id: u32, should_float_if_hidden: bool) { + let plugin_command = PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Changes the name (the title that appears in the UI) of the terminal pane with the specified id. -pub fn rename_terminal_pane>(terminal_pane_id: i32, new_name: S) { - object_to_stdout(&(terminal_pane_id, new_name.as_ref())); - unsafe { host_rename_terminal_pane() }; +pub fn rename_terminal_pane>(terminal_pane_id: u32, new_name: S) +where + S: ToString, +{ + let plugin_command = PluginCommand::RenameTerminalPane(terminal_pane_id, new_name.to_string()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Changes the name (the title that appears in the UI) of the plugin pane with the specified id. -pub fn rename_plugin_pane>(plugin_pane_id: i32, new_name: S) { - object_to_stdout(&(plugin_pane_id, new_name.as_ref())); - unsafe { host_rename_plugin_pane() }; +pub fn rename_plugin_pane>(plugin_pane_id: u32, new_name: S) +where + S: ToString, +{ + let plugin_command = PluginCommand::RenamePluginPane(plugin_pane_id, new_name.to_string()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Changes the name (the title that appears in the UI) of the tab with the specified position. -pub fn rename_tab>(tab_position: i32, new_name: S) { - object_to_stdout(&(tab_position, new_name.as_ref())); - unsafe { host_rename_tab() }; +pub fn rename_tab>(tab_position: u32, new_name: S) +where + S: ToString, +{ + let plugin_command = PluginCommand::RenameTab(tab_position, new_name.to_string()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } // Utility Functions @@ -408,6 +592,14 @@ pub fn object_from_stdin() -> Result { serde_json::from_str(&json).with_context(err_context) } +#[doc(hidden)] +pub fn bytes_from_stdin() -> Result> { + let err_context = || "failed to deserialize bytes from stdin".to_string(); + let mut json = String::new(); + io::stdin().read_line(&mut json).with_context(err_context)?; + serde_json::from_str(&json).with_context(err_context) +} + #[doc(hidden)] pub fn object_to_stdout(object: &impl Serialize) { // TODO: no crashy @@ -415,91 +607,22 @@ pub fn object_to_stdout(object: &impl Serialize) { } /// Post a message to a worker of this plugin, for more information please see [Plugin Workers](https://zellij.dev/documentation/plugin-api-workers.md) -pub fn post_message_to>(worker_name: S, message: S, payload: S) { - match serde_json::to_string(&(worker_name.as_ref(), message.as_ref(), payload.as_ref())) { - Ok(serialized) => println!("{}", serialized), - Err(e) => eprintln!("Failed to serialize message: {:?}", e), - } - unsafe { host_post_message_to() }; +pub fn post_message_to(plugin_message: PluginMessage) { + let plugin_command = PluginCommand::PostMessageTo(plugin_message); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } /// Post a message to this plugin, for more information please see [Plugin Workers](https://zellij.dev/documentation/plugin-api-workers.md) -pub fn post_message_to_plugin>(message: S, payload: S) { - match serde_json::to_string(&(message.as_ref(), payload.as_ref())) { - Ok(serialized) => println!("{}", serialized), - Err(e) => eprintln!("Failed to serialize message: {:?}", e), - } - unsafe { host_post_message_to_plugin() }; +pub fn post_message_to_plugin(plugin_message: PluginMessage) { + let plugin_command = PluginCommand::PostMessageToPlugin(plugin_message); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; } #[link(wasm_import_module = "zellij")] extern "C" { - fn host_subscribe(); - fn host_unsubscribe(); - fn host_set_selectable(selectable: i32); - fn host_get_plugin_ids(); - fn host_get_zellij_version(); - fn host_open_file(); - fn host_open_file_floating(); - fn host_open_file_with_line(); - fn host_open_file_with_line_floating(); - fn host_open_terminal(); - fn host_open_terminal_floating(); - fn host_open_command_pane(); - fn host_open_command_pane_floating(); - fn host_switch_tab_to(tab_idx: u32); - fn host_set_timeout(secs: f64); - fn host_exec_cmd(); - fn host_report_panic(); - fn host_post_message_to(); - fn host_post_message_to_plugin(); - fn host_hide_self(); - fn host_show_self(should_float_if_hidden: i32); - fn host_switch_to_mode(); - fn host_new_tabs_with_layout(); - fn host_new_tab(); - fn host_go_to_next_tab(); - fn host_go_to_previous_tab(); - fn host_resize(); - fn host_resize_with_direction(); - fn host_focus_next_pane(); - fn host_focus_previous_pane(); - fn host_move_focus(); - fn host_move_focus_or_tab(); - fn host_detach(); - fn host_edit_scrollback(); - fn host_write(); - fn host_write_chars(); - fn host_toggle_tab(); - fn host_move_pane(); - fn host_move_pane_with_direction(); - fn host_clear_screen(); - fn host_scroll_up(); - fn host_scroll_down(); - fn host_scroll_to_top(); - fn host_scroll_to_bottom(); - fn host_page_scroll_up(); - fn host_page_scroll_down(); - fn host_toggle_focus_fullscreen(); - fn host_toggle_pane_frames(); - fn host_toggle_pane_embed_or_eject(); - fn host_undo_rename_pane(); - fn host_close_focus(); - fn host_toggle_active_tab_sync(); - fn host_close_focused_tab(); - fn host_undo_rename_tab(); - fn host_quit_zellij(); - fn host_previous_swap_layout(); - fn host_next_swap_layout(); - fn host_go_to_tab_name(); - fn host_focus_or_create_tab(); - fn host_go_to_tab(tab_index: i32); - fn host_start_or_reload_plugin(); - fn host_close_terminal_pane(terminal_pane: i32); - fn host_close_plugin_pane(plugin_pane: i32); - fn host_focus_terminal_pane(terminal_pane: i32, should_float_if_hidden: i32); - fn host_focus_plugin_pane(plugin_pane: i32, should_float_if_hidden: i32); - fn host_rename_terminal_pane(); - fn host_rename_plugin_pane(); - fn host_rename_tab(); + fn host_run_plugin_command(); } diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 65398c53..3e5e2dfd 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 index bd878f2a83fa7d7f69f1a77ab9046731c8ceef5a..f38c500fcb01a2172b61102aacd437694a3d8b6e 100755 GIT binary patch delta 106928 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 v�s%dq#%RB_+i^Br(M{a4enx%h~GUwovuO4}K~5z8hR&Pz@jP)YE?f?+pj5 delta 105893 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 CB{ju=4wmyrTTXLb$zqGMK948>&x`r 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 }*> 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 z delta 98322 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, + pub cwd: Option, +} + +impl FileToOpen { + pub fn new>(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, + pub cwd: Option, +} + +impl CommandToRun { + pub fn new>(path: P) -> Self { + CommandToRun { + path: path.as_ref().to_path_buf(), + ..Default::default() + } + } + pub fn new_with_args, A: AsRef>(path: P, args: Vec) -> 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, +} + +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), + Unsubscribe(HashSet), + 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), + 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), // 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 b07fe8bd..c9b3581d 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 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 for Action { + type Error = &'static str; + fn try_from(protobuf_action: ProtobufAction) -> Result { + 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 = 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 = 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 = payload.line_number.map(|l| l as usize); + let cwd: Option = payload.cwd.map(|p| PathBuf::from(p)); + let direction: Option = 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 = 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 for ProtobufAction { + type Error = &'static str; + fn try_from(action: Action) -> Result { + 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 = 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 for SearchOption { + type Error = &'static str; + fn try_from(protobuf_search_option: ProtobufSearchOption) -> Result { + match protobuf_search_option { + ProtobufSearchOption::CaseSensitivity => Ok(SearchOption::CaseSensitivity), + ProtobufSearchOption::WholeWord => Ok(SearchOption::WholeWord), + ProtobufSearchOption::Wrap => Ok(SearchOption::Wrap), + } + } +} + +impl TryFrom for ProtobufSearchOption { + type Error = &'static str; + fn try_from(search_option: SearchOption) -> Result { + match search_option { + SearchOption::CaseSensitivity => Ok(ProtobufSearchOption::CaseSensitivity), + SearchOption::WholeWord => Ok(ProtobufSearchOption::WholeWord), + SearchOption::Wrap => Ok(ProtobufSearchOption::Wrap), + } + } +} + +impl TryFrom for SearchDirection { + type Error = &'static str; + fn try_from(protobuf_search_direction: ProtobufSearchDirection) -> Result { + match protobuf_search_direction { + ProtobufSearchDirection::Up => Ok(SearchDirection::Up), + ProtobufSearchDirection::Down => Ok(SearchDirection::Down), + } + } +} + +impl TryFrom for ProtobufSearchDirection { + type Error = &'static str; + fn try_from(search_direction: SearchDirection) -> Result { + match search_direction { + SearchDirection::Up => Ok(ProtobufSearchDirection::Up), + SearchDirection::Down => Ok(ProtobufSearchDirection::Down), + } + } +} + +impl TryFrom for RunCommandAction { + type Error = &'static str; + fn try_from( + protobuf_run_command_action: ProtobufRunCommandAction, + ) -> Result { + let command = PathBuf::from(protobuf_run_command_action.command); + let args: Vec = protobuf_run_command_action.args; + let cwd: Option = protobuf_run_command_action.cwd.map(|c| PathBuf::from(c)); + let direction: Option = 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 for ProtobufRunCommandAction { + type Error = &'static str; + fn try_from(run_command_action: RunCommandAction) -> Result { + let command = run_command_action.command.display().to_string(); + let args: Vec = 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 for Position { + type Error = &'static str; + fn try_from(protobuf_position: ProtobufPosition) -> Result { + Ok(Position::new( + protobuf_position.line as i32, + protobuf_position.column as u16, + )) + } +} + +impl TryFrom for ProtobufPosition { + type Error = &'static str; + fn try_from(position: Position) -> Result { + Ok(ProtobufPosition { + line: position.line.0 as i64, + column: position.column.0 as i64, + }) + } +} + +impl TryFrom for PluginUserConfiguration { + type Error = &'static str; + fn try_from(plugin_configuration: ProtobufPluginConfiguration) -> Result { + 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 for ProtobufPluginConfiguration { + type Error = &'static str; + fn try_from(plugin_configuration: PluginUserConfiguration) -> Result { + 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 { + type Error = &'static str; + fn try_from(plugin_configuration: &ProtobufPluginConfiguration) -> Result { + 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 for CommandToRun { + type Error = &'static str; + fn try_from(protobuf_command: ProtobufCommand) -> Result { + 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 for ProtobufCommand { + type Error = &'static str; + fn try_from(command_to_run: CommandToRun) -> Result { + 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 for Event { + type Error = &'static str; + fn try_from(protobuf_event: ProtobufEvent) -> Result { + 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 = 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> = 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 for ProtobufEvent { + type Error = &'static str; + fn try_from(event: Event) -> Result { + 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 for ProtobufCopyDestination { + type Error = &'static str; + fn try_from(copy_destination: CopyDestination) -> Result { + match copy_destination { + CopyDestination::Command => Ok(ProtobufCopyDestination::Command), + CopyDestination::Primary => Ok(ProtobufCopyDestination::Primary), + CopyDestination::System => Ok(ProtobufCopyDestination::System), + } + } +} + +impl TryFrom for CopyDestination { + type Error = &'static str; + fn try_from(protobuf_copy_destination: ProtobufCopyDestination) -> Result { + match protobuf_copy_destination { + ProtobufCopyDestination::Command => Ok(CopyDestination::Command), + ProtobufCopyDestination::Primary => Ok(CopyDestination::Primary), + ProtobufCopyDestination::System => Ok(CopyDestination::System), + } + } +} + +impl TryFrom for Mouse { + type Error = &'static str; + fn try_from(mouse_event_payload: MouseEventPayload) -> Result { + 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 for MouseEventPayload { + type Error = &'static str; + fn try_from(mouse: Mouse) -> Result { + 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 for PaneInfo { + type Error = &'static str; + fn try_from(protobuf_pane_info: ProtobufPaneInfo) -> Result { + 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 for ProtobufPaneInfo { + type Error = &'static str; + fn try_from(pane_info: PaneInfo) -> Result { + 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 for TabInfo { + type Error = &'static str; + fn try_from(protobuf_tab_info: ProtobufTabInfo) -> Result { + 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 for ProtobufTabInfo { + type Error = &'static str; + fn try_from(tab_info: TabInfo) -> Result { + 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 for ModeInfo { + type Error = &'static str; + fn try_from( + mut protobuf_mode_update_payload: ProtobufModeUpdatePayload, + ) -> Result { + 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)>)> = 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)> = vec![]; + for mut protobuf_keybind in k.key_bind.drain(..) { + let key: Key = protobuf_keybind.key.unwrap().try_into().ok()?; + let mut actions: Vec = 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 for ProtobufModeUpdatePayload { + type Error = &'static str; + fn try_from(mode_info: ModeInfo) -> Result { + 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 = vec![]; + for (input_mode, input_mode_keybinds) in mode_info.keybinds { + let mode: ProtobufInputMode = input_mode.try_into()?; + let mut keybinds: Vec = vec![]; + for (key, actions) in input_mode_keybinds { + let protobuf_key: ProtobufKey = key.try_into()?; + let mut protobuf_actions: Vec = 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 for HashSet { + type Error = &'static str; + fn try_from(protobuf_event_name_list: ProtobufEventNameList) -> Result { + let event_types: Vec = protobuf_event_name_list + .event_types + .iter() + .filter_map(|i| ProtobufEventType::from_i32(*i)) + .collect(); + let event_types: Vec = event_types + .iter() + .filter_map(|e| EventType::try_from(*e).ok()) + .collect(); + Ok(event_types.into_iter().collect()) + } +} + +impl TryFrom> for ProtobufEventNameList { + type Error = &'static str; + fn try_from(event_types: HashSet) -> Result { + 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 for EventType { + type Error = &'static str; + fn try_from(protobuf_event_type: ProtobufEventType) -> Result { + 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 for ProtobufEventType { + type Error = &'static str; + fn try_from(event_type: EventType) -> Result { + 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 for FileToOpen { + type Error = &'static str; + fn try_from(protobuf_file: ProtobufFile) -> Result { + 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 for ProtobufFile { + type Error = &'static str; + fn try_from(file_to_open: FileToOpen) -> Result { + 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 for InputMode { + type Error = &'static str; + fn try_from(protobuf_input_mode: ProtobufInputMode) -> Result { + 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 for ProtobufInputMode { + type Error = &'static str; + fn try_from(input_mode: InputMode) -> Result { + 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 for InputMode { + type Error = &'static str; + fn try_from(protobuf_input_mode: ProtobufInputModeMessage) -> Result { + ProtobufInputMode::from_i32(protobuf_input_mode.input_mode) + .and_then(|p| p.try_into().ok()) + .ok_or("Invalid input mode") + } +} + +impl TryFrom for ProtobufInputModeMessage { + type Error = &'static str; + fn try_from(input_mode: InputMode) -> Result { + 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 for Key { + type Error = &'static str; + fn try_from(protobuf_key: ProtobufKey) -> Result { + 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 for ProtobufKey { + type Error = &'static str; + fn try_from(key: Key) -> Result { + 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, + ) -> Result { + 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 { + 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) -> Result { + 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 for PluginMessage { + type Error = &'static str; + fn try_from(protobuf_message: ProtobufMessage) -> Result { + 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 for ProtobufMessage { + type Error = &'static str; + fn try_from(plugin_message: PluginMessage) -> Result { + 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 for PluginCommand { + type Error = &'static str; + fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result { + 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 for ProtobufPluginCommand { + type Error = &'static str; + fn try_from(plugin_command: PluginCommand) -> Result { + 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 for PluginIds { + type Error = &'static str; + fn try_from(protobuf_plugin_ids: ProtobufPluginIds) -> Result { + Ok(PluginIds { + plugin_id: protobuf_plugin_ids.plugin_id as u32, + zellij_pid: protobuf_plugin_ids.zellij_pid as u32, + }) + } +} + +impl TryFrom for ProtobufPluginIds { + type Error = &'static str; + fn try_from(plugin_ids: PluginIds) -> Result { + 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 { + 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 for Resize { + type Error = &'static str; + fn try_from(protobuf_resize: ProtobufResize) -> Result { + 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 for ProtobufResize { + type Error = &'static str; + fn try_from(resize: Resize) -> Result { + Ok(ProtobufResize { + resize_action: match resize { + Resize::Increase => ResizeAction::Increase as i32, + Resize::Decrease => ResizeAction::Decrease as i32, + }, + direction: None, + }) + } +} + +impl TryFrom for ResizeStrategy { + type Error = &'static str; + fn try_from(protobuf_resize: ProtobufResize) -> Result { + 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 for ProtobufResize { + type Error = &'static str; + fn try_from(resize_strategy: ResizeStrategy) -> Result { + 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 for Direction { + type Error = &'static str; + fn try_from(protobuf_move_direction: ProtobufMoveDirection) -> Result { + 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 for ProtobufMoveDirection { + type Error = &'static str; + fn try_from(direction: Direction) -> Result { + 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 for Direction { + type Error = &'static str; + fn try_from(protobuf_resize_direction: ProtobufResizeDirection) -> Result { + 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 for ProtobufResizeDirection { + type Error = &'static str; + fn try_from(direction: Direction) -> Result { + 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 for Style { + type Error = &'static str; + fn try_from(protobuf_style: ProtobufStyle) -> Result { + 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