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 bd878f2a..f38c500f 100755 Binary files a/zellij-utils/assets/plugins/compact-bar.wasm and b/zellij-utils/assets/plugins/compact-bar.wasm differ diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm index 33bf1a44..1a2919f6 100755 Binary files a/zellij-utils/assets/plugins/tab-bar.wasm and b/zellij-utils/assets/plugins/tab-bar.wasm differ diff --git a/zellij-utils/build.rs b/zellij-utils/build.rs new file mode 100644 index 00000000..92f89d27 --- /dev/null +++ b/zellij-utils/build.rs @@ -0,0 +1,21 @@ +use prost_build; +use std::fs; + +fn main() { + let mut prost_build = prost_build::Config::new(); + prost_build.include_file("generated_plugin_api.rs"); + let mut proto_files = vec![]; + for entry in fs::read_dir("src/plugin_api").unwrap() { + let entry_path = entry.unwrap().path(); + if entry_path.is_file() { + if let Some(extension) = entry_path.extension() { + if extension == "proto" { + proto_files.push(entry_path.display().to_string()) + } + } + } + } + prost_build + .compile_protos(&proto_files, &["src/plugin_api"]) + .unwrap(); +} diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index a12821c4..9c1c8e2f 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -2,9 +2,9 @@ use crate::input::actions::Action; use crate::input::config::ConversionError; use clap::ArgEnum; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString}; @@ -810,3 +810,144 @@ pub enum CopyDestination { Primary, System, } + +#[derive(Debug, Default, Clone)] +pub struct FileToOpen { + pub path: PathBuf, + pub line_number: Option, + 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