feat(plugins): use protocol buffers for serializing across the wasm boundary (#2686)

* work

* almost done with command protobuffers

* done translating command data structures

* mid transferring of every command to protobuff command

* transferred plugin_command.rs, now moving on to shim.rs

* plugin command working with protobufs

* protobuffers in update

* protobuf event tests

* various TODOs and comments

* fix zellij-tile

* clean up prost deps

* remove version mismatch error

* fix panic

* some cleanups

* clean up event protobuffers

* clean up command protobuffers

* clean up various protobufs

* refactor protobufs

* update comments

* some transformation fixes

* use protobufs for workers

* style(fmt): rustfmt

* style(fmt): rustfmt

* chore(build): add protoc

* chore(build): authenticate protoc
This commit is contained in:
Aram Drevekenin 2023-08-09 22:26:00 +02:00 committed by GitHub
parent c3e140cb4b
commit 1bedfc9002
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 6045 additions and 1072 deletions

View file

@ -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

View file

@ -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:

View file

@ -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

97
Cargo.lock generated
View file

@ -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",

View file

@ -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(),
});
},
_ => {},
}

View file

@ -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<String> = 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<String> = 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);

View file

@ -41,10 +41,11 @@ impl Search {
match serde_json::from_str::<MessageToSearch>(&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) {

View file

@ -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();
}

View file

@ -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()
}),
}
}
}

View file

@ -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<Mutex<HashMap<PathBuf, Module>>>,
plugin_path: PathBuf,
@ -645,10 +536,8 @@ impl<'a> PluginLoader<'a> {
&mut self,
module: Module,
) -> Result<(Instance, PluginEnv, Arc<Mutex<Subscriptions>>)> {
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)?;

View file

@ -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::<anyError, _>(|e| {
match e.downcast::<serde_json::Error>() {
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(())
}
}

View file

@ -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",
(
[],
[],

View file

@ -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\"}",
(
[],
[],

View file

@ -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,
),

View file

@ -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<u8>)>,
) -> 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::<anyError, _>(|e| match e.downcast::<serde_json::Error>() {
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,

File diff suppressed because it is too large Load diff

View file

@ -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<u8> = $crate::shim::object_from_stdin().unwrap();
let protobuf_configuration: ProtobufPluginConfiguration =
ProtobufPluginConfiguration::decode(protobuf_bytes.as_slice()).unwrap();
let plugin_configuration: BTreeMap<String, String> =
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<u8> = $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<u8> = $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);

View file

@ -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<EventType> = 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<EventType> = 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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) {
object_to_stdout(&(
path.as_ref(),
args.iter().map(|a| a.as_ref()).collect::<Vec<&str>>(),
));
unsafe { host_open_command_pane() };
// pub fn open_command_pane<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) {
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<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) {
object_to_stdout(&(
path.as_ref(),
args.iter().map(|a| a.as_ref()).collect::<Vec<&str>>(),
));
unsafe { host_open_command_pane_floating() };
// pub fn open_command_pane_floating<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) {
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!("<NO PAYLOAD>")
};
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<u8>) {
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<S: AsRef<str>>(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<S: AsRef<str>>(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<S: AsRef<str>>(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<S: AsRef<str>>(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<S: AsRef<str>>(tab_position: i32, new_name: S) {
object_to_stdout(&(tab_position, new_name.as_ref()));
unsafe { host_rename_tab() };
pub fn rename_tab<S: AsRef<str>>(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<T: DeserializeOwned>() -> Result<T> {
serde_json::from_str(&json).with_context(err_context)
}
#[doc(hidden)]
pub fn bytes_from_stdin() -> Result<Vec<u8>> {
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<S: AsRef<str>>(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<S: AsRef<str>>(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();
}

View file

@ -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):

21
zellij-utils/build.rs Normal file
View file

@ -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();
}

View file

@ -2,9 +2,9 @@ use crate::input::actions::Action;
use crate::input::config::ConversionError;
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString};
@ -810,3 +810,144 @@ pub enum CopyDestination {
Primary,
System,
}
#[derive(Debug, Default, Clone)]
pub struct FileToOpen {
pub path: PathBuf,
pub line_number: Option<usize>,
pub cwd: Option<PathBuf>,
}
impl FileToOpen {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
FileToOpen {
path: path.as_ref().to_path_buf(),
..Default::default()
}
}
pub fn with_line_number(mut self, line_number: usize) -> Self {
self.line_number = Some(line_number);
self
}
pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
self.cwd = Some(cwd);
self
}
}
#[derive(Debug, Default, Clone)]
pub struct CommandToRun {
pub path: PathBuf,
pub args: Vec<String>,
pub cwd: Option<PathBuf>,
}
impl CommandToRun {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
CommandToRun {
path: path.as_ref().to_path_buf(),
..Default::default()
}
}
pub fn new_with_args<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) -> Self {
CommandToRun {
path: path.as_ref().to_path_buf(),
args: args.into_iter().map(|a| a.as_ref().to_owned()).collect(),
..Default::default()
}
}
}
#[derive(Debug, Default, Clone)]
pub struct PluginMessage {
pub name: String,
pub payload: String,
pub worker_name: Option<String>,
}
impl PluginMessage {
pub fn new_to_worker(worker_name: &str, message: &str, payload: &str) -> Self {
PluginMessage {
name: message.to_owned(),
payload: payload.to_owned(),
worker_name: Some(worker_name.to_owned()),
}
}
pub fn new_to_plugin(message: &str, payload: &str) -> Self {
PluginMessage {
name: message.to_owned(),
payload: payload.to_owned(),
worker_name: None,
}
}
}
#[derive(Debug, Clone)]
pub enum PluginCommand {
Subscribe(HashSet<EventType>),
Unsubscribe(HashSet<EventType>),
SetSelectable(bool),
GetPluginIds,
GetZellijVersion,
OpenFile(FileToOpen),
OpenFileFloating(FileToOpen),
OpenTerminal(FileToOpen), // only used for the path as cwd
OpenTerminalFloating(FileToOpen), // only used for the path as cwd
OpenCommandPane(CommandToRun),
OpenCommandPaneFloating(CommandToRun),
SwitchTabTo(u32), // tab index
SetTimeout(f32), // seconds
ExecCmd(Vec<String>),
PostMessageTo(PluginMessage),
PostMessageToPlugin(PluginMessage),
HideSelf,
ShowSelf(bool), // bool - should float if hidden
SwitchToMode(InputMode),
NewTabsWithLayout(String), // raw kdl layout
NewTab,
GoToNextTab,
GoToPreviousTab,
Resize(Resize),
ResizeWithDirection(ResizeStrategy),
FocusNextPane,
FocusPreviousPane,
MoveFocus(Direction),
MoveFocusOrTab(Direction),
Detach,
EditScrollback,
Write(Vec<u8>), // bytes
WriteChars(String),
ToggleTab,
MovePane,
MovePaneWithDirection(Direction),
ClearScreen,
ScrollUp,
ScrollDown,
ScrollToTop,
ScrollToBottom,
PageScrollUp,
PageScrollDown,
ToggleFocusFullscreen,
TogglePaneFrames,
TogglePaneEmbedOrEject,
UndoRenamePane,
CloseFocus,
ToggleActiveTabSync,
CloseFocusedTab,
UndoRenameTab,
QuitZellij,
PreviousSwapLayout,
NextSwapLayout,
GoToTabName(String),
FocusOrCreateTab(String),
GoToTab(u32), // tab index
StartOrReloadPlugin(String), // plugin url (eg. file:/path/to/plugin.wasm)
CloseTerminalPane(u32), // terminal pane id
ClosePluginPane(u32), // plugin pane id
FocusTerminalPane(u32, bool), // terminal pane id, should_float_if_hidden
FocusPluginPane(u32, bool), // plugin pane id, should_float_if_hidden
RenameTerminalPane(u32, String), // terminal pane id, new name
RenamePluginPane(u32, String), // plugin pane id, new name
RenameTab(u32, String), // tab index, new name
ReportPanic(String), // stringified panic
}

View file

@ -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;

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
syntax = "proto3";
package api.command;
message Command {
string path = 1;
repeated string args = 2;
optional string cwd = 3;
}

View file

@ -0,0 +1,26 @@
pub use super::generated_api::api::command::Command as ProtobufCommand;
use crate::data::CommandToRun;
use std::convert::TryFrom;
use std::path::PathBuf;
impl TryFrom<ProtobufCommand> for CommandToRun {
type Error = &'static str;
fn try_from(protobuf_command: ProtobufCommand) -> Result<Self, &'static str> {
let path = PathBuf::from(protobuf_command.path);
let args = protobuf_command.args;
let cwd = protobuf_command.cwd.map(|c| PathBuf::from(c));
Ok(CommandToRun { path, args, cwd })
}
}
impl TryFrom<CommandToRun> for ProtobufCommand {
type Error = &'static str;
fn try_from(command_to_run: CommandToRun) -> Result<Self, &'static str> {
Ok(ProtobufCommand {
path: command_to_run.path.display().to_string(),
args: command_to_run.args,
cwd: command_to_run.cwd.map(|c| c.display().to_string()),
})
}
}

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
syntax = "proto3";
package api.file;
message File {
string path = 1;
optional int32 line_number = 2;
optional string cwd = 3;
}

View file

@ -0,0 +1,30 @@
pub use super::generated_api::api::file::File as ProtobufFile;
use crate::data::FileToOpen;
use std::convert::TryFrom;
use std::path::PathBuf;
impl TryFrom<ProtobufFile> for FileToOpen {
type Error = &'static str;
fn try_from(protobuf_file: ProtobufFile) -> Result<Self, &'static str> {
let path = PathBuf::from(protobuf_file.path);
let line_number = protobuf_file.line_number.map(|l| l as usize);
let cwd = protobuf_file.cwd.map(|c| PathBuf::from(c));
Ok(FileToOpen {
path,
line_number,
cwd,
})
}
}
impl TryFrom<FileToOpen> for ProtobufFile {
type Error = &'static str;
fn try_from(file_to_open: FileToOpen) -> Result<Self, &'static str> {
Ok(ProtobufFile {
path: file_to_open.path.display().to_string(),
line_number: file_to_open.line_number.map(|l| l as i32),
cwd: file_to_open.cwd.map(|c| c.display().to_string()),
})
}
}

View file

@ -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;
}

View file

@ -0,0 +1,69 @@
pub use super::generated_api::api::input_mode::{
InputMode as ProtobufInputMode, InputModeMessage as ProtobufInputModeMessage,
};
use crate::data::InputMode;
use std::convert::TryFrom;
impl TryFrom<ProtobufInputMode> for InputMode {
type Error = &'static str;
fn try_from(protobuf_input_mode: ProtobufInputMode) -> Result<Self, &'static str> {
match protobuf_input_mode {
ProtobufInputMode::Normal => Ok(InputMode::Normal),
ProtobufInputMode::Locked => Ok(InputMode::Locked),
ProtobufInputMode::Resize => Ok(InputMode::Resize),
ProtobufInputMode::Pane => Ok(InputMode::Pane),
ProtobufInputMode::Tab => Ok(InputMode::Tab),
ProtobufInputMode::Scroll => Ok(InputMode::Scroll),
ProtobufInputMode::EnterSearch => Ok(InputMode::EnterSearch),
ProtobufInputMode::Search => Ok(InputMode::Search),
ProtobufInputMode::RenameTab => Ok(InputMode::RenameTab),
ProtobufInputMode::RenamePane => Ok(InputMode::RenamePane),
ProtobufInputMode::Session => Ok(InputMode::Session),
ProtobufInputMode::Move => Ok(InputMode::Move),
ProtobufInputMode::Prompt => Ok(InputMode::Prompt),
ProtobufInputMode::Tmux => Ok(InputMode::Tmux),
}
}
}
impl TryFrom<InputMode> for ProtobufInputMode {
type Error = &'static str;
fn try_from(input_mode: InputMode) -> Result<Self, &'static str> {
Ok(match input_mode {
InputMode::Normal => ProtobufInputMode::Normal,
InputMode::Locked => ProtobufInputMode::Locked,
InputMode::Resize => ProtobufInputMode::Resize,
InputMode::Pane => ProtobufInputMode::Pane,
InputMode::Tab => ProtobufInputMode::Tab,
InputMode::Scroll => ProtobufInputMode::Scroll,
InputMode::EnterSearch => ProtobufInputMode::EnterSearch,
InputMode::Search => ProtobufInputMode::Search,
InputMode::RenameTab => ProtobufInputMode::RenameTab,
InputMode::RenamePane => ProtobufInputMode::RenamePane,
InputMode::Session => ProtobufInputMode::Session,
InputMode::Move => ProtobufInputMode::Move,
InputMode::Prompt => ProtobufInputMode::Prompt,
InputMode::Tmux => ProtobufInputMode::Tmux,
})
}
}
impl TryFrom<ProtobufInputModeMessage> for InputMode {
type Error = &'static str;
fn try_from(protobuf_input_mode: ProtobufInputModeMessage) -> Result<Self, &'static str> {
ProtobufInputMode::from_i32(protobuf_input_mode.input_mode)
.and_then(|p| p.try_into().ok())
.ok_or("Invalid input mode")
}
}
impl TryFrom<InputMode> for ProtobufInputModeMessage {
type Error = &'static str;
fn try_from(input_mode: InputMode) -> Result<Self, &'static str> {
let protobuf_input_mode: ProtobufInputMode = input_mode.try_into()?;
Ok(ProtobufInputModeMessage {
input_mode: protobuf_input_mode as i32,
})
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,222 @@
pub use super::generated_api::api::key::{
key::{KeyModifier, MainKey, NamedKey},
Key as ProtobufKey,
};
use crate::data::{CharOrArrow, Direction, Key};
use std::convert::TryFrom;
impl TryFrom<ProtobufKey> for Key {
type Error = &'static str;
fn try_from(protobuf_key: ProtobufKey) -> Result<Self, &'static str> {
let key_modifier = parse_optional_modifier(&protobuf_key);
match key_modifier {
Some(KeyModifier::Ctrl) => {
let character = char_from_main_key(protobuf_key.main_key)?;
Ok(Key::Ctrl(character))
},
Some(KeyModifier::Alt) => {
let char_or_arrow = CharOrArrow::from_main_key(protobuf_key.main_key)?;
Ok(Key::Alt(char_or_arrow))
},
None => match protobuf_key.main_key.as_ref().ok_or("invalid key")? {
MainKey::Char(_key_index) => {
let character = char_from_main_key(protobuf_key.main_key)?;
Ok(Key::Char(character))
},
MainKey::Key(key_index) => {
let key = NamedKey::from_i32(*key_index).ok_or("invalid_key")?;
Ok(named_key_to_key(key))
},
},
}
}
}
impl TryFrom<Key> for ProtobufKey {
type Error = &'static str;
fn try_from(key: Key) -> Result<Self, &'static str> {
match key {
Key::PageDown => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::PageDown as i32)),
}),
Key::PageUp => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::PageUp as i32)),
}),
Key::Left => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::LeftArrow as i32)),
}),
Key::Down => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::DownArrow as i32)),
}),
Key::Up => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::UpArrow as i32)),
}),
Key::Right => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::RightArrow as i32)),
}),
Key::Home => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Home as i32)),
}),
Key::End => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::End as i32)),
}),
Key::Backspace => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Backspace as i32)),
}),
Key::Delete => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Delete as i32)),
}),
Key::Insert => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Insert as i32)),
}),
Key::F(index) => {
let main_key = match index {
1 => Some(MainKey::Key(NamedKey::F1 as i32)),
2 => Some(MainKey::Key(NamedKey::F2 as i32)),
3 => Some(MainKey::Key(NamedKey::F3 as i32)),
4 => Some(MainKey::Key(NamedKey::F4 as i32)),
5 => Some(MainKey::Key(NamedKey::F5 as i32)),
6 => Some(MainKey::Key(NamedKey::F6 as i32)),
7 => Some(MainKey::Key(NamedKey::F7 as i32)),
8 => Some(MainKey::Key(NamedKey::F8 as i32)),
9 => Some(MainKey::Key(NamedKey::F9 as i32)),
10 => Some(MainKey::Key(NamedKey::F10 as i32)),
11 => Some(MainKey::Key(NamedKey::F11 as i32)),
12 => Some(MainKey::Key(NamedKey::F12 as i32)),
_ => return Err("Invalid key"),
};
Ok(ProtobufKey {
modifier: None,
main_key,
})
},
Key::Char(character) => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Char((character as u8) as i32)),
}),
Key::Alt(char_or_arrow) => {
let main_key = match char_or_arrow {
CharOrArrow::Char(character) => MainKey::Char((character as u8) as i32),
CharOrArrow::Direction(Direction::Left) => {
MainKey::Key(NamedKey::LeftArrow as i32)
},
CharOrArrow::Direction(Direction::Right) => {
MainKey::Key(NamedKey::RightArrow as i32)
},
CharOrArrow::Direction(Direction::Up) => MainKey::Key(NamedKey::UpArrow as i32),
CharOrArrow::Direction(Direction::Down) => {
MainKey::Key(NamedKey::DownArrow as i32)
},
};
Ok(ProtobufKey {
modifier: Some(KeyModifier::Alt as i32),
main_key: Some(main_key),
})
},
Key::Ctrl(character) => Ok(ProtobufKey {
modifier: Some(KeyModifier::Ctrl as i32),
main_key: Some(MainKey::Char((character as u8) as i32)),
}),
Key::BackTab => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Tab as i32)),
}),
Key::Null => {
Ok(ProtobufKey {
modifier: None,
main_key: None, // TODO: does this break deserialization?
})
},
Key::Esc => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Esc as i32)),
}),
}
}
}
impl CharOrArrow {
pub fn from_main_key(
main_key: std::option::Option<MainKey>,
) -> Result<CharOrArrow, &'static str> {
match main_key {
Some(MainKey::Char(encoded_key)) => {
Ok(CharOrArrow::Char(char_index_to_char(encoded_key)))
},
Some(MainKey::Key(key_index)) => match NamedKey::from_i32(key_index) {
Some(NamedKey::LeftArrow) => Ok(CharOrArrow::Direction(Direction::Left)),
Some(NamedKey::RightArrow) => Ok(CharOrArrow::Direction(Direction::Right)),
Some(NamedKey::UpArrow) => Ok(CharOrArrow::Direction(Direction::Up)),
Some(NamedKey::DownArrow) => Ok(CharOrArrow::Direction(Direction::Down)),
_ => Err("Unsupported key"),
},
_ => {
return Err("Unsupported key");
},
}
}
}
fn parse_optional_modifier(m: &ProtobufKey) -> Option<KeyModifier> {
match m.modifier {
Some(modifier) => KeyModifier::from_i32(modifier),
_ => None,
}
}
fn char_index_to_char(char_index: i32) -> char {
char_index as u8 as char
}
fn char_from_main_key(main_key: Option<MainKey>) -> Result<char, &'static str> {
match main_key {
Some(MainKey::Char(encoded_key)) => {
return Ok(char_index_to_char(encoded_key));
},
_ => {
return Err("Unsupported key");
},
}
}
fn named_key_to_key(named_key: NamedKey) -> Key {
match named_key {
NamedKey::PageDown => Key::PageDown,
NamedKey::PageUp => Key::PageUp,
NamedKey::LeftArrow => Key::Left,
NamedKey::DownArrow => Key::Down,
NamedKey::UpArrow => Key::Up,
NamedKey::RightArrow => Key::Right,
NamedKey::Home => Key::Home,
NamedKey::End => Key::End,
NamedKey::Backspace => Key::Backspace,
NamedKey::Delete => Key::Delete,
NamedKey::Insert => Key::Insert,
NamedKey::F1 => Key::F(1),
NamedKey::F2 => Key::F(2),
NamedKey::F3 => Key::F(3),
NamedKey::F4 => Key::F(4),
NamedKey::F5 => Key::F(5),
NamedKey::F6 => Key::F(6),
NamedKey::F7 => Key::F(7),
NamedKey::F8 => Key::F(8),
NamedKey::F9 => Key::F(9),
NamedKey::F10 => Key::F(10),
NamedKey::F11 => Key::F(11),
NamedKey::F12 => Key::F(12),
NamedKey::Tab => Key::BackTab,
NamedKey::Esc => Key::Esc,
}
}

View file

@ -0,0 +1,9 @@
syntax = "proto3";
package api.message;
message Message {
string name = 1;
string payload = 2;
optional string worker_name = 3;
}

View file

@ -0,0 +1,29 @@
pub use super::generated_api::api::message::Message as ProtobufMessage;
use crate::data::PluginMessage;
use std::convert::TryFrom;
impl TryFrom<ProtobufMessage> for PluginMessage {
type Error = &'static str;
fn try_from(protobuf_message: ProtobufMessage) -> Result<Self, &'static str> {
let name = protobuf_message.name;
let payload = protobuf_message.payload;
let worker_name = protobuf_message.worker_name;
Ok(PluginMessage {
name,
payload,
worker_name,
})
}
}
impl TryFrom<PluginMessage> for ProtobufMessage {
type Error = &'static str;
fn try_from(plugin_message: PluginMessage) -> Result<Self, &'static str> {
Ok(ProtobufMessage {
name: plugin_message.name,
payload: plugin_message.payload,
worker_name: plugin_message.worker_name,
})
}
}

View file

@ -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"));
}

View file

@ -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;
}

View file

@ -0,0 +1,825 @@
pub use super::generated_api::api::{
event::EventNameList as ProtobufEventNameList,
plugin_command::{
plugin_command::Payload, CommandName, ExecCmdPayload, IdAndNewName, MovePayload,
OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ResizePayload,
SetTimeoutPayload, SubscribePayload, SwitchTabToPayload, SwitchToModePayload,
UnsubscribePayload,
},
resize::ResizeAction as ProtobufResizeAction,
};
use crate::data::PluginCommand;
use std::convert::TryFrom;
impl TryFrom<ProtobufPluginCommand> for PluginCommand {
type Error = &'static str;
fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> {
match CommandName::from_i32(protobuf_plugin_command.name) {
Some(CommandName::Subscribe) => match protobuf_plugin_command.payload {
Some(Payload::SubscribePayload(subscribe_payload)) => {
let protobuf_event_list = subscribe_payload.subscriptions;
match protobuf_event_list {
Some(protobuf_event_list) => {
Ok(PluginCommand::Subscribe(protobuf_event_list.try_into()?))
},
None => Err("malformed subscription event"),
}
},
_ => Err("Mismatched payload for Subscribe"),
},
Some(CommandName::Unsubscribe) => match protobuf_plugin_command.payload {
Some(Payload::UnsubscribePayload(unsubscribe_payload)) => {
let protobuf_event_list = unsubscribe_payload.subscriptions;
match protobuf_event_list {
Some(protobuf_event_list) => {
Ok(PluginCommand::Unsubscribe(protobuf_event_list.try_into()?))
},
None => Err("malformed unsubscription event"),
}
},
_ => Err("Mismatched payload for Unsubscribe"),
},
Some(CommandName::SetSelectable) => match protobuf_plugin_command.payload {
Some(Payload::SetSelectablePayload(should_be_selectable)) => {
Ok(PluginCommand::SetSelectable(should_be_selectable))
},
_ => Err("Mismatched payload for SetSelectable"),
},
Some(CommandName::GetPluginIds) => {
if protobuf_plugin_command.payload.is_some() {
Err("GetPluginIds should not have a payload")
} else {
Ok(PluginCommand::GetPluginIds)
}
},
Some(CommandName::GetZellijVersion) => {
if protobuf_plugin_command.payload.is_some() {
Err("GetZellijVersion should not have a payload")
} else {
Ok(PluginCommand::GetZellijVersion)
}
},
Some(CommandName::OpenFile) => match protobuf_plugin_command.payload {
Some(Payload::OpenFilePayload(file_to_open_payload)) => {
match file_to_open_payload.file_to_open {
Some(file_to_open) => Ok(PluginCommand::OpenFile(file_to_open.try_into()?)),
None => Err("Malformed open file payload"),
}
},
_ => Err("Mismatched payload for OpenFile"),
},
Some(CommandName::OpenFileFloating) => match protobuf_plugin_command.payload {
Some(Payload::OpenFileFloatingPayload(file_to_open_payload)) => {
match file_to_open_payload.file_to_open {
Some(file_to_open) => {
Ok(PluginCommand::OpenFileFloating(file_to_open.try_into()?))
},
None => Err("Malformed open file payload"),
}
},
_ => Err("Mismatched payload for OpenFile"),
},
Some(CommandName::OpenTerminal) => match protobuf_plugin_command.payload {
Some(Payload::OpenTerminalPayload(file_to_open_payload)) => {
match file_to_open_payload.file_to_open {
Some(file_to_open) => {
Ok(PluginCommand::OpenTerminal(file_to_open.try_into()?))
},
None => Err("Malformed open terminal payload"),
}
},
_ => Err("Mismatched payload for OpenTerminal"),
},
Some(CommandName::OpenTerminalFloating) => match protobuf_plugin_command.payload {
Some(Payload::OpenTerminalFloatingPayload(file_to_open_payload)) => {
match file_to_open_payload.file_to_open {
Some(file_to_open) => Ok(PluginCommand::OpenTerminalFloating(
file_to_open.try_into()?,
)),
None => Err("Malformed open terminal floating payload"),
}
},
_ => Err("Mismatched payload for OpenTerminalFloating"),
},
Some(CommandName::OpenCommandPane) => match protobuf_plugin_command.payload {
Some(Payload::OpenCommandPanePayload(command_to_run_payload)) => {
match command_to_run_payload.command_to_run {
Some(command_to_run) => {
Ok(PluginCommand::OpenCommandPane(command_to_run.try_into()?))
},
None => Err("Malformed open open command pane payload"),
}
},
_ => Err("Mismatched payload for OpenCommandPane"),
},
Some(CommandName::OpenCommandPaneFloating) => match protobuf_plugin_command.payload {
Some(Payload::OpenCommandPaneFloatingPayload(command_to_run_payload)) => {
match command_to_run_payload.command_to_run {
Some(command_to_run) => Ok(PluginCommand::OpenCommandPaneFloating(
command_to_run.try_into()?,
)),
None => Err("Malformed open command pane floating payload"),
}
},
_ => Err("Mismatched payload for OpenCommandPaneFloating"),
},
Some(CommandName::SwitchTabTo) => match protobuf_plugin_command.payload {
Some(Payload::SwitchTabToPayload(switch_to_tab_payload)) => Ok(
PluginCommand::SwitchTabTo(switch_to_tab_payload.tab_index as u32),
),
_ => Err("Mismatched payload for SwitchToTab"),
},
Some(CommandName::SetTimeout) => match protobuf_plugin_command.payload {
Some(Payload::SetTimeoutPayload(set_timeout_payload)) => {
Ok(PluginCommand::SetTimeout(set_timeout_payload.seconds))
},
_ => Err("Mismatched payload for SetTimeout"),
},
Some(CommandName::ExecCmd) => match protobuf_plugin_command.payload {
Some(Payload::ExecCmdPayload(exec_cmd_payload)) => {
Ok(PluginCommand::ExecCmd(exec_cmd_payload.command_line))
},
_ => Err("Mismatched payload for ExecCmd"),
},
Some(CommandName::PostMessageTo) => match protobuf_plugin_command.payload {
Some(Payload::PostMessageToPayload(post_message_to_payload)) => {
match post_message_to_payload.message {
Some(message) => Ok(PluginCommand::PostMessageTo(message.try_into()?)),
None => Err("Malformed post message to payload"),
}
},
_ => Err("Mismatched payload for PostMessageTo"),
},
Some(CommandName::PostMessageToPlugin) => match protobuf_plugin_command.payload {
Some(Payload::PostMessageToPluginPayload(post_message_to_payload)) => {
match post_message_to_payload.message {
Some(message) => {
Ok(PluginCommand::PostMessageToPlugin(message.try_into()?))
},
None => Err("Malformed post message to plugin payload"),
}
},
_ => Err("Mismatched payload for PostMessageToPlugin"),
},
Some(CommandName::HideSelf) => {
if protobuf_plugin_command.payload.is_some() {
return Err("HideSelf should not have a payload");
}
Ok(PluginCommand::HideSelf)
},
Some(CommandName::ShowSelf) => match protobuf_plugin_command.payload {
Some(Payload::ShowSelfPayload(should_float_if_hidden)) => {
Ok(PluginCommand::ShowSelf(should_float_if_hidden))
},
_ => Err("Mismatched payload for ShowSelf"),
},
Some(CommandName::SwitchToMode) => match protobuf_plugin_command.payload {
Some(Payload::SwitchToModePayload(switch_to_mode_payload)) => {
match switch_to_mode_payload.input_mode {
Some(input_mode) => Ok(PluginCommand::SwitchToMode(input_mode.try_into()?)),
None => Err("Malformed switch to mode payload"),
}
},
_ => Err("Mismatched payload for SwitchToMode"),
},
Some(CommandName::NewTabsWithLayout) => match protobuf_plugin_command.payload {
Some(Payload::NewTabsWithLayoutPayload(raw_layout)) => {
Ok(PluginCommand::NewTabsWithLayout(raw_layout))
},
_ => Err("Mismatched payload for NewTabsWithLayout"),
},
Some(CommandName::NewTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("NewTab should not have a payload");
}
Ok(PluginCommand::NewTab)
},
Some(CommandName::GoToNextTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("GoToNextTab should not have a payload");
}
Ok(PluginCommand::GoToNextTab)
},
Some(CommandName::GoToPreviousTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("GoToPreviousTab should not have a payload");
}
Ok(PluginCommand::GoToPreviousTab)
},
Some(CommandName::Resize) => match protobuf_plugin_command.payload {
Some(Payload::ResizePayload(resize_payload)) => match resize_payload.resize {
Some(resize) => Ok(PluginCommand::Resize(resize.try_into()?)),
None => Err("Malformed switch resize payload"),
},
_ => Err("Mismatched payload for Resize"),
},
Some(CommandName::ResizeWithDirection) => match protobuf_plugin_command.payload {
Some(Payload::ResizeWithDirectionPayload(resize_with_direction_payload)) => {
match resize_with_direction_payload.resize {
Some(resize) => Ok(PluginCommand::ResizeWithDirection(resize.try_into()?)),
None => Err("Malformed switch resize payload"),
}
},
_ => Err("Mismatched payload for Resize"),
},
Some(CommandName::FocusNextPane) => {
if protobuf_plugin_command.payload.is_some() {
return Err("FocusNextPane should not have a payload");
}
Ok(PluginCommand::FocusNextPane)
},
Some(CommandName::FocusPreviousPane) => {
if protobuf_plugin_command.payload.is_some() {
return Err("FocusPreviousPane should not have a payload");
}
Ok(PluginCommand::FocusPreviousPane)
},
Some(CommandName::MoveFocus) => match protobuf_plugin_command.payload {
Some(Payload::MoveFocusPayload(move_payload)) => match move_payload.direction {
Some(direction) => Ok(PluginCommand::MoveFocus(direction.try_into()?)),
None => Err("Malformed move focus payload"),
},
_ => Err("Mismatched payload for MoveFocus"),
},
Some(CommandName::MoveFocusOrTab) => match protobuf_plugin_command.payload {
Some(Payload::MoveFocusOrTabPayload(move_payload)) => {
match move_payload.direction {
Some(direction) => Ok(PluginCommand::MoveFocusOrTab(direction.try_into()?)),
None => Err("Malformed move focus or tab payload"),
}
},
_ => Err("Mismatched payload for MoveFocusOrTab"),
},
Some(CommandName::Detach) => {
if protobuf_plugin_command.payload.is_some() {
return Err("Detach should not have a payload");
}
Ok(PluginCommand::Detach)
},
Some(CommandName::EditScrollback) => {
if protobuf_plugin_command.payload.is_some() {
return Err("EditScrollback should not have a payload");
}
Ok(PluginCommand::EditScrollback)
},
Some(CommandName::Write) => match protobuf_plugin_command.payload {
Some(Payload::WritePayload(bytes)) => Ok(PluginCommand::Write(bytes)),
_ => Err("Mismatched payload for Write"),
},
Some(CommandName::WriteChars) => match protobuf_plugin_command.payload {
Some(Payload::WriteCharsPayload(chars)) => Ok(PluginCommand::WriteChars(chars)),
_ => Err("Mismatched payload for WriteChars"),
},
Some(CommandName::ToggleTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ToggleTab should not have a payload");
}
Ok(PluginCommand::ToggleTab)
},
Some(CommandName::MovePane) => {
if protobuf_plugin_command.payload.is_some() {
return Err("MovePane should not have a payload");
}
Ok(PluginCommand::MovePane)
},
Some(CommandName::MovePaneWithDirection) => match protobuf_plugin_command.payload {
Some(Payload::MovePaneWithDirectionPayload(move_payload)) => {
match move_payload.direction {
Some(direction) => {
Ok(PluginCommand::MovePaneWithDirection(direction.try_into()?))
},
None => Err("Malformed MovePaneWithDirection payload"),
}
},
_ => Err("Mismatched payload for MovePaneWithDirection"),
},
Some(CommandName::ClearScreen) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ClearScreen should not have a payload");
}
Ok(PluginCommand::ClearScreen)
},
Some(CommandName::ScrollUp) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ScrollUp should not have a payload");
}
Ok(PluginCommand::ScrollUp)
},
Some(CommandName::ScrollDown) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ScrollDown should not have a payload");
}
Ok(PluginCommand::ScrollDown)
},
Some(CommandName::ScrollToTop) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ScrollToTop should not have a payload");
}
Ok(PluginCommand::ScrollToTop)
},
Some(CommandName::ScrollToBottom) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ScrollToBottom should not have a payload");
}
Ok(PluginCommand::ScrollToBottom)
},
Some(CommandName::PageScrollUp) => {
if protobuf_plugin_command.payload.is_some() {
return Err("PageScrollUp should not have a payload");
}
Ok(PluginCommand::PageScrollUp)
},
Some(CommandName::PageScrollDown) => {
if protobuf_plugin_command.payload.is_some() {
return Err("PageScrollDown should not have a payload");
}
Ok(PluginCommand::PageScrollDown)
},
Some(CommandName::ToggleFocusFullscreen) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ToggleFocusFullscreen should not have a payload");
}
Ok(PluginCommand::ToggleFocusFullscreen)
},
Some(CommandName::TogglePaneFrames) => {
if protobuf_plugin_command.payload.is_some() {
return Err("TogglePaneFrames should not have a payload");
}
Ok(PluginCommand::TogglePaneFrames)
},
Some(CommandName::TogglePaneEmbedOrEject) => {
if protobuf_plugin_command.payload.is_some() {
return Err("TogglePaneEmbedOrEject should not have a payload");
}
Ok(PluginCommand::TogglePaneEmbedOrEject)
},
Some(CommandName::UndoRenamePane) => {
if protobuf_plugin_command.payload.is_some() {
return Err("UndoRenamePane should not have a payload");
}
Ok(PluginCommand::UndoRenamePane)
},
Some(CommandName::CloseFocus) => {
if protobuf_plugin_command.payload.is_some() {
return Err("CloseFocus should not have a payload");
}
Ok(PluginCommand::CloseFocus)
},
Some(CommandName::ToggleActiveTabSync) => {
if protobuf_plugin_command.payload.is_some() {
return Err("ToggleActiveTabSync should not have a payload");
}
Ok(PluginCommand::ToggleActiveTabSync)
},
Some(CommandName::CloseFocusedTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("CloseFocusedTab should not have a payload");
}
Ok(PluginCommand::CloseFocusedTab)
},
Some(CommandName::UndoRenameTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("UndoRenameTab should not have a payload");
}
Ok(PluginCommand::UndoRenameTab)
},
Some(CommandName::QuitZellij) => {
if protobuf_plugin_command.payload.is_some() {
return Err("QuitZellij should not have a payload");
}
Ok(PluginCommand::QuitZellij)
},
Some(CommandName::PreviousSwapLayout) => {
if protobuf_plugin_command.payload.is_some() {
return Err("PreviousSwapLayout should not have a payload");
}
Ok(PluginCommand::PreviousSwapLayout)
},
Some(CommandName::NextSwapLayout) => {
if protobuf_plugin_command.payload.is_some() {
return Err("NextSwapLayout should not have a payload");
}
Ok(PluginCommand::NextSwapLayout)
},
Some(CommandName::GoToTabName) => match protobuf_plugin_command.payload {
Some(Payload::GoToTabNamePayload(tab_name)) => {
Ok(PluginCommand::GoToTabName(tab_name))
},
_ => Err("Mismatched payload for GoToTabName"),
},
Some(CommandName::FocusOrCreateTab) => match protobuf_plugin_command.payload {
Some(Payload::FocusOrCreateTabPayload(tab_name)) => {
Ok(PluginCommand::FocusOrCreateTab(tab_name))
},
_ => Err("Mismatched payload for FocusOrCreateTab"),
},
Some(CommandName::GoToTab) => match protobuf_plugin_command.payload {
Some(Payload::GoToTabPayload(tab_index)) => {
Ok(PluginCommand::GoToTab(tab_index as u32))
},
_ => Err("Mismatched payload for GoToTab"),
},
Some(CommandName::StartOrReloadPlugin) => match protobuf_plugin_command.payload {
Some(Payload::StartOrReloadPluginPayload(url)) => {
Ok(PluginCommand::StartOrReloadPlugin(url))
},
_ => Err("Mismatched payload for StartOrReloadPlugin"),
},
Some(CommandName::CloseTerminalPane) => match protobuf_plugin_command.payload {
Some(Payload::CloseTerminalPanePayload(pane_id)) => {
Ok(PluginCommand::CloseTerminalPane(pane_id as u32))
},
_ => Err("Mismatched payload for CloseTerminalPane"),
},
Some(CommandName::ClosePluginPane) => match protobuf_plugin_command.payload {
Some(Payload::ClosePluginPanePayload(pane_id)) => {
Ok(PluginCommand::ClosePluginPane(pane_id as u32))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
Some(CommandName::FocusTerminalPane) => match protobuf_plugin_command.payload {
Some(Payload::FocusTerminalPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
Ok(PluginCommand::FocusTerminalPane(pane_id, should_float))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
Some(CommandName::FocusPluginPane) => match protobuf_plugin_command.payload {
Some(Payload::FocusPluginPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
Ok(PluginCommand::FocusPluginPane(pane_id, should_float))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
Some(CommandName::RenameTerminalPane) => match protobuf_plugin_command.payload {
Some(Payload::RenameTerminalPanePayload(payload)) => {
let pane_id = payload.id as u32;
let new_name = payload.new_name;
Ok(PluginCommand::RenameTerminalPane(pane_id, new_name))
},
_ => Err("Mismatched payload for RenameTerminalPane"),
},
Some(CommandName::RenamePluginPane) => match protobuf_plugin_command.payload {
Some(Payload::RenamePluginPanePayload(payload)) => {
let pane_id = payload.id as u32;
let new_name = payload.new_name;
Ok(PluginCommand::RenamePluginPane(pane_id, new_name))
},
_ => Err("Mismatched payload for RenamePluginPane"),
},
Some(CommandName::RenameTab) => match protobuf_plugin_command.payload {
Some(Payload::RenameTabPayload(payload)) => {
let tab_index = payload.id as u32;
let name = payload.new_name;
Ok(PluginCommand::RenameTab(tab_index, name))
},
_ => Err("Mismatched payload for RenameTab"),
},
Some(CommandName::ReportCrash) => match protobuf_plugin_command.payload {
Some(Payload::ReportCrashPayload(payload)) => {
Ok(PluginCommand::ReportPanic(payload))
},
_ => Err("Mismatched payload for ReportCrash"),
},
None => Err("Unrecognized plugin command"),
}
}
}
impl TryFrom<PluginCommand> for ProtobufPluginCommand {
type Error = &'static str;
fn try_from(plugin_command: PluginCommand) -> Result<Self, &'static str> {
match plugin_command {
PluginCommand::Subscribe(subscriptions) => {
let subscriptions: ProtobufEventNameList = subscriptions.try_into()?;
Ok(ProtobufPluginCommand {
name: CommandName::Subscribe as i32,
payload: Some(Payload::SubscribePayload(SubscribePayload {
subscriptions: Some(subscriptions),
})),
})
},
PluginCommand::Unsubscribe(subscriptions) => {
let subscriptions: ProtobufEventNameList = subscriptions.try_into()?;
Ok(ProtobufPluginCommand {
name: CommandName::Unsubscribe as i32,
payload: Some(Payload::UnsubscribePayload(UnsubscribePayload {
subscriptions: Some(subscriptions),
})),
})
},
PluginCommand::SetSelectable(should_be_selectable) => Ok(ProtobufPluginCommand {
name: CommandName::SetSelectable as i32,
payload: Some(Payload::SetSelectablePayload(should_be_selectable)),
}),
PluginCommand::GetPluginIds => Ok(ProtobufPluginCommand {
name: CommandName::GetPluginIds as i32,
payload: None,
}),
PluginCommand::GetZellijVersion => Ok(ProtobufPluginCommand {
name: CommandName::GetZellijVersion as i32,
payload: None,
}),
PluginCommand::OpenFile(file_to_open) => Ok(ProtobufPluginCommand {
name: CommandName::OpenFile as i32,
payload: Some(Payload::OpenFilePayload(OpenFilePayload {
file_to_open: Some(file_to_open.try_into()?),
})),
}),
PluginCommand::OpenFileFloating(file_to_open) => Ok(ProtobufPluginCommand {
name: CommandName::OpenFileFloating as i32,
payload: Some(Payload::OpenFileFloatingPayload(OpenFilePayload {
file_to_open: Some(file_to_open.try_into()?),
})),
}),
PluginCommand::OpenTerminal(cwd) => Ok(ProtobufPluginCommand {
name: CommandName::OpenTerminal as i32,
payload: Some(Payload::OpenTerminalPayload(OpenFilePayload {
file_to_open: Some(cwd.try_into()?),
})),
}),
PluginCommand::OpenTerminalFloating(cwd) => Ok(ProtobufPluginCommand {
name: CommandName::OpenTerminalFloating as i32,
payload: Some(Payload::OpenTerminalFloatingPayload(OpenFilePayload {
file_to_open: Some(cwd.try_into()?),
})),
}),
PluginCommand::OpenCommandPane(command_to_run) => Ok(ProtobufPluginCommand {
name: CommandName::OpenCommandPane as i32,
payload: Some(Payload::OpenCommandPanePayload(OpenCommandPanePayload {
command_to_run: Some(command_to_run.try_into()?),
})),
}),
PluginCommand::OpenCommandPaneFloating(command_to_run) => Ok(ProtobufPluginCommand {
name: CommandName::OpenCommandPaneFloating as i32,
payload: Some(Payload::OpenCommandPaneFloatingPayload(
OpenCommandPanePayload {
command_to_run: Some(command_to_run.try_into()?),
},
)),
}),
PluginCommand::SwitchTabTo(tab_index) => Ok(ProtobufPluginCommand {
name: CommandName::SwitchTabTo as i32,
payload: Some(Payload::SwitchTabToPayload(SwitchTabToPayload {
tab_index: tab_index as i32,
})),
}),
PluginCommand::SetTimeout(seconds) => Ok(ProtobufPluginCommand {
name: CommandName::SetTimeout as i32,
payload: Some(Payload::SetTimeoutPayload(SetTimeoutPayload { seconds })),
}),
PluginCommand::ExecCmd(command_line) => Ok(ProtobufPluginCommand {
name: CommandName::ExecCmd as i32,
payload: Some(Payload::ExecCmdPayload(ExecCmdPayload { command_line })),
}),
PluginCommand::PostMessageTo(plugin_message) => Ok(ProtobufPluginCommand {
name: CommandName::PostMessageTo as i32,
payload: Some(Payload::PostMessageToPayload(PluginMessagePayload {
message: Some(plugin_message.try_into()?),
})),
}),
PluginCommand::PostMessageToPlugin(plugin_message) => Ok(ProtobufPluginCommand {
name: CommandName::PostMessageToPlugin as i32,
payload: Some(Payload::PostMessageToPluginPayload(PluginMessagePayload {
message: Some(plugin_message.try_into()?),
})),
}),
PluginCommand::HideSelf => Ok(ProtobufPluginCommand {
name: CommandName::HideSelf as i32,
payload: None,
}),
PluginCommand::ShowSelf(should_float_if_hidden) => Ok(ProtobufPluginCommand {
name: CommandName::ShowSelf as i32,
payload: Some(Payload::ShowSelfPayload(should_float_if_hidden)),
}),
PluginCommand::SwitchToMode(input_mode) => Ok(ProtobufPluginCommand {
name: CommandName::SwitchToMode as i32,
payload: Some(Payload::SwitchToModePayload(SwitchToModePayload {
input_mode: Some(input_mode.try_into()?),
})),
}),
PluginCommand::NewTabsWithLayout(raw_layout) => Ok(ProtobufPluginCommand {
name: CommandName::NewTabsWithLayout as i32,
payload: Some(Payload::NewTabsWithLayoutPayload(raw_layout)),
}),
PluginCommand::NewTab => Ok(ProtobufPluginCommand {
name: CommandName::NewTab as i32,
payload: None,
}),
PluginCommand::GoToNextTab => Ok(ProtobufPluginCommand {
name: CommandName::GoToNextTab as i32,
payload: None,
}),
PluginCommand::GoToPreviousTab => Ok(ProtobufPluginCommand {
name: CommandName::GoToPreviousTab as i32,
payload: None,
}),
PluginCommand::Resize(resize) => Ok(ProtobufPluginCommand {
name: CommandName::Resize as i32,
payload: Some(Payload::ResizePayload(ResizePayload {
resize: Some(resize.try_into()?),
})),
}),
PluginCommand::ResizeWithDirection(resize) => Ok(ProtobufPluginCommand {
name: CommandName::ResizeWithDirection as i32,
payload: Some(Payload::ResizeWithDirectionPayload(ResizePayload {
resize: Some(resize.try_into()?),
})),
}),
PluginCommand::FocusNextPane => Ok(ProtobufPluginCommand {
name: CommandName::FocusNextPane as i32,
payload: None,
}),
PluginCommand::FocusPreviousPane => Ok(ProtobufPluginCommand {
name: CommandName::FocusPreviousPane as i32,
payload: None,
}),
PluginCommand::MoveFocus(direction) => Ok(ProtobufPluginCommand {
name: CommandName::MoveFocus as i32,
payload: Some(Payload::MoveFocusPayload(MovePayload {
direction: Some(direction.try_into()?),
})),
}),
PluginCommand::MoveFocusOrTab(direction) => Ok(ProtobufPluginCommand {
name: CommandName::MoveFocusOrTab as i32,
payload: Some(Payload::MoveFocusOrTabPayload(MovePayload {
direction: Some(direction.try_into()?),
})),
}),
PluginCommand::Detach => Ok(ProtobufPluginCommand {
name: CommandName::Detach as i32,
payload: None,
}),
PluginCommand::EditScrollback => Ok(ProtobufPluginCommand {
name: CommandName::EditScrollback as i32,
payload: None,
}),
PluginCommand::Write(bytes) => Ok(ProtobufPluginCommand {
name: CommandName::Write as i32,
payload: Some(Payload::WritePayload(bytes)),
}),
PluginCommand::WriteChars(chars) => Ok(ProtobufPluginCommand {
name: CommandName::WriteChars as i32,
payload: Some(Payload::WriteCharsPayload(chars)),
}),
PluginCommand::ToggleTab => Ok(ProtobufPluginCommand {
name: CommandName::ToggleTab as i32,
payload: None,
}),
PluginCommand::MovePane => Ok(ProtobufPluginCommand {
name: CommandName::MovePane as i32,
payload: None,
}),
PluginCommand::MovePaneWithDirection(direction) => Ok(ProtobufPluginCommand {
name: CommandName::MovePaneWithDirection as i32,
payload: Some(Payload::MovePaneWithDirectionPayload(MovePayload {
direction: Some(direction.try_into()?),
})),
}),
PluginCommand::ClearScreen => Ok(ProtobufPluginCommand {
name: CommandName::ClearScreen as i32,
payload: None,
}),
PluginCommand::ScrollUp => Ok(ProtobufPluginCommand {
name: CommandName::ScrollUp as i32,
payload: None,
}),
PluginCommand::ScrollDown => Ok(ProtobufPluginCommand {
name: CommandName::ScrollDown as i32,
payload: None,
}),
PluginCommand::ScrollToTop => Ok(ProtobufPluginCommand {
name: CommandName::ScrollToTop as i32,
payload: None,
}),
PluginCommand::ScrollToBottom => Ok(ProtobufPluginCommand {
name: CommandName::ScrollToBottom as i32,
payload: None,
}),
PluginCommand::PageScrollUp => Ok(ProtobufPluginCommand {
name: CommandName::PageScrollUp as i32,
payload: None,
}),
PluginCommand::PageScrollDown => Ok(ProtobufPluginCommand {
name: CommandName::PageScrollDown as i32,
payload: None,
}),
PluginCommand::ToggleFocusFullscreen => Ok(ProtobufPluginCommand {
name: CommandName::ToggleFocusFullscreen as i32,
payload: None,
}),
PluginCommand::TogglePaneFrames => Ok(ProtobufPluginCommand {
name: CommandName::TogglePaneFrames as i32,
payload: None,
}),
PluginCommand::TogglePaneEmbedOrEject => Ok(ProtobufPluginCommand {
name: CommandName::TogglePaneEmbedOrEject as i32,
payload: None,
}),
PluginCommand::UndoRenamePane => Ok(ProtobufPluginCommand {
name: CommandName::UndoRenamePane as i32,
payload: None,
}),
PluginCommand::CloseFocus => Ok(ProtobufPluginCommand {
name: CommandName::CloseFocus as i32,
payload: None,
}),
PluginCommand::ToggleActiveTabSync => Ok(ProtobufPluginCommand {
name: CommandName::ToggleActiveTabSync as i32,
payload: None,
}),
PluginCommand::CloseFocusedTab => Ok(ProtobufPluginCommand {
name: CommandName::CloseFocusedTab as i32,
payload: None,
}),
PluginCommand::UndoRenameTab => Ok(ProtobufPluginCommand {
name: CommandName::UndoRenameTab as i32,
payload: None,
}),
PluginCommand::QuitZellij => Ok(ProtobufPluginCommand {
name: CommandName::QuitZellij as i32,
payload: None,
}),
PluginCommand::PreviousSwapLayout => Ok(ProtobufPluginCommand {
name: CommandName::PreviousSwapLayout as i32,
payload: None,
}),
PluginCommand::NextSwapLayout => Ok(ProtobufPluginCommand {
name: CommandName::NextSwapLayout as i32,
payload: None,
}),
PluginCommand::GoToTabName(tab_name) => Ok(ProtobufPluginCommand {
name: CommandName::GoToTabName as i32,
payload: Some(Payload::GoToTabNamePayload(tab_name)),
}),
PluginCommand::FocusOrCreateTab(tab_name) => Ok(ProtobufPluginCommand {
name: CommandName::FocusOrCreateTab as i32,
payload: Some(Payload::FocusOrCreateTabPayload(tab_name)),
}),
PluginCommand::GoToTab(tab_index) => Ok(ProtobufPluginCommand {
name: CommandName::GoToTab as i32,
payload: Some(Payload::GoToTabPayload(tab_index as i32)),
}),
PluginCommand::StartOrReloadPlugin(url) => Ok(ProtobufPluginCommand {
name: CommandName::StartOrReloadPlugin as i32,
payload: Some(Payload::StartOrReloadPluginPayload(url)),
}),
PluginCommand::CloseTerminalPane(pane_id) => Ok(ProtobufPluginCommand {
name: CommandName::CloseTerminalPane as i32,
payload: Some(Payload::CloseTerminalPanePayload(pane_id as i32)),
}),
PluginCommand::ClosePluginPane(pane_id) => Ok(ProtobufPluginCommand {
name: CommandName::ClosePluginPane as i32,
payload: Some(Payload::ClosePluginPanePayload(pane_id as i32)),
}),
PluginCommand::FocusTerminalPane(pane_id, should_float_if_hidden) => {
Ok(ProtobufPluginCommand {
name: CommandName::FocusTerminalPane as i32,
payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat {
pane_id: pane_id as i32,
should_float: should_float_if_hidden,
})),
})
},
PluginCommand::FocusPluginPane(pane_id, should_float_if_hidden) => {
Ok(ProtobufPluginCommand {
name: CommandName::FocusPluginPane as i32,
payload: Some(Payload::FocusPluginPanePayload(PaneIdAndShouldFloat {
pane_id: pane_id as i32,
should_float: should_float_if_hidden,
})),
})
},
PluginCommand::RenameTerminalPane(pane_id, new_name) => Ok(ProtobufPluginCommand {
name: CommandName::RenameTerminalPane as i32,
payload: Some(Payload::RenameTerminalPanePayload(IdAndNewName {
id: pane_id as i32,
new_name,
})),
}),
PluginCommand::RenamePluginPane(pane_id, new_name) => Ok(ProtobufPluginCommand {
name: CommandName::RenamePluginPane as i32,
payload: Some(Payload::RenamePluginPanePayload(IdAndNewName {
id: pane_id as i32,
new_name,
})),
}),
PluginCommand::RenameTab(tab_index, new_name) => Ok(ProtobufPluginCommand {
name: CommandName::RenameTab as i32,
payload: Some(Payload::RenameTabPayload(IdAndNewName {
id: tab_index as i32,
new_name,
})),
}),
PluginCommand::ReportPanic(payload) => Ok(ProtobufPluginCommand {
name: CommandName::ReportCrash as i32,
payload: Some(Payload::ReportCrashPayload(payload)),
}),
}
}
}

View file

@ -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;
}

View file

@ -0,0 +1,35 @@
pub use super::generated_api::api::plugin_ids::{
PluginIds as ProtobufPluginIds, ZellijVersion as ProtobufZellijVersion,
};
use crate::data::PluginIds;
use std::convert::TryFrom;
impl TryFrom<ProtobufPluginIds> for PluginIds {
type Error = &'static str;
fn try_from(protobuf_plugin_ids: ProtobufPluginIds) -> Result<Self, &'static str> {
Ok(PluginIds {
plugin_id: protobuf_plugin_ids.plugin_id as u32,
zellij_pid: protobuf_plugin_ids.zellij_pid as u32,
})
}
}
impl TryFrom<PluginIds> for ProtobufPluginIds {
type Error = &'static str;
fn try_from(plugin_ids: PluginIds) -> Result<Self, &'static str> {
Ok(ProtobufPluginIds {
plugin_id: plugin_ids.plugin_id as i32,
zellij_pid: plugin_ids.zellij_pid as i32,
})
}
}
impl TryFrom<&str> for ProtobufZellijVersion {
type Error = &'static str;
fn try_from(zellij_version: &str) -> Result<Self, &'static str> {
Ok(ProtobufZellijVersion {
version: zellij_version.to_owned(),
})
}
}

View file

@ -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;
}

View file

@ -0,0 +1,130 @@
pub use super::generated_api::api::resize::{
MoveDirection as ProtobufMoveDirection, Resize as ProtobufResize, ResizeAction,
ResizeDirection, ResizeDirection as ProtobufResizeDirection,
};
use crate::data::{Direction, Resize, ResizeStrategy};
use std::convert::TryFrom;
impl TryFrom<ProtobufResize> for Resize {
type Error = &'static str;
fn try_from(protobuf_resize: ProtobufResize) -> Result<Self, &'static str> {
if protobuf_resize.direction.is_some() {
return Err("Resize cannot have a direction");
}
match ResizeAction::from_i32(protobuf_resize.resize_action) {
Some(ResizeAction::Increase) => Ok(Resize::Increase),
Some(ResizeAction::Decrease) => Ok(Resize::Decrease),
None => Err("No resize action for the given index"),
}
}
}
impl TryFrom<Resize> for ProtobufResize {
type Error = &'static str;
fn try_from(resize: Resize) -> Result<Self, &'static str> {
Ok(ProtobufResize {
resize_action: match resize {
Resize::Increase => ResizeAction::Increase as i32,
Resize::Decrease => ResizeAction::Decrease as i32,
},
direction: None,
})
}
}
impl TryFrom<ProtobufResize> for ResizeStrategy {
type Error = &'static str;
fn try_from(protobuf_resize: ProtobufResize) -> Result<Self, &'static str> {
let direction = match protobuf_resize
.direction
.and_then(|r| ResizeDirection::from_i32(r))
{
Some(ResizeDirection::Left) => Some(Direction::Left),
Some(ResizeDirection::Right) => Some(Direction::Right),
Some(ResizeDirection::Up) => Some(Direction::Up),
Some(ResizeDirection::Down) => Some(Direction::Down),
None => None,
};
let resize = match ResizeAction::from_i32(protobuf_resize.resize_action) {
Some(ResizeAction::Increase) => Resize::Increase,
Some(ResizeAction::Decrease) => Resize::Decrease,
None => return Err("No resize action for the given index"),
};
Ok(ResizeStrategy {
direction,
resize,
invert_on_boundaries: false,
})
}
}
impl TryFrom<ResizeStrategy> for ProtobufResize {
type Error = &'static str;
fn try_from(resize_strategy: ResizeStrategy) -> Result<Self, &'static str> {
Ok(ProtobufResize {
resize_action: match resize_strategy.resize {
Resize::Increase => ResizeAction::Increase as i32,
Resize::Decrease => ResizeAction::Decrease as i32,
},
direction: match resize_strategy.direction {
Some(Direction::Left) => Some(ResizeDirection::Left as i32),
Some(Direction::Right) => Some(ResizeDirection::Right as i32),
Some(Direction::Up) => Some(ResizeDirection::Up as i32),
Some(Direction::Down) => Some(ResizeDirection::Down as i32),
None => None,
},
})
}
}
impl TryFrom<ProtobufMoveDirection> for Direction {
type Error = &'static str;
fn try_from(protobuf_move_direction: ProtobufMoveDirection) -> Result<Self, &'static str> {
match ResizeDirection::from_i32(protobuf_move_direction.direction) {
Some(ResizeDirection::Left) => Ok(Direction::Left),
Some(ResizeDirection::Right) => Ok(Direction::Right),
Some(ResizeDirection::Up) => Ok(Direction::Up),
Some(ResizeDirection::Down) => Ok(Direction::Down),
None => Err("No direction for the given index"),
}
}
}
impl TryFrom<Direction> for ProtobufMoveDirection {
type Error = &'static str;
fn try_from(direction: Direction) -> Result<Self, &'static str> {
Ok(ProtobufMoveDirection {
direction: match direction {
Direction::Left => ResizeDirection::Left as i32,
Direction::Right => ResizeDirection::Right as i32,
Direction::Up => ResizeDirection::Up as i32,
Direction::Down => ResizeDirection::Down as i32,
},
})
}
}
impl TryFrom<ProtobufResizeDirection> for Direction {
type Error = &'static str;
fn try_from(protobuf_resize_direction: ProtobufResizeDirection) -> Result<Self, &'static str> {
match protobuf_resize_direction {
ProtobufResizeDirection::Left => Ok(Direction::Left),
ProtobufResizeDirection::Right => Ok(Direction::Right),
ProtobufResizeDirection::Up => Ok(Direction::Up),
ProtobufResizeDirection::Down => Ok(Direction::Down),
}
}
}
impl TryFrom<Direction> for ProtobufResizeDirection {
type Error = &'static str;
fn try_from(direction: Direction) -> Result<Self, &'static str> {
Ok(match direction {
Direction::Left => ProtobufResizeDirection::Left,
Direction::Right => ProtobufResizeDirection::Right,
Direction::Up => ProtobufResizeDirection::Up,
Direction::Down => ProtobufResizeDirection::Down,
})
}
}

View file

@ -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;
}

View file

@ -0,0 +1,213 @@
use super::generated_api::api::style::{
color::Payload as ProtobufColorPayload, Color as ProtobufColor, ColorType as ProtobufColorType,
Palette as ProtobufPalette, RgbColorPayload as ProtobufRgbColorPayload, Style as ProtobufStyle,
ThemeHue as ProtobufThemeHue,
};
use crate::data::{Palette, PaletteColor, Style, ThemeHue};
use crate::errors::prelude::*;
use std::convert::TryFrom;
impl TryFrom<ProtobufStyle> for Style {
type Error = &'static str;
fn try_from(protobuf_style: ProtobufStyle) -> Result<Self, &'static str> {
Ok(Style {
colors: protobuf_style
.palette
.ok_or("malformed style payload")?
.try_into()?,
rounded_corners: protobuf_style.rounded_corners,
hide_session_name: protobuf_style.hide_session_name,
})
}
}
impl TryFrom<Style> for ProtobufStyle {
type Error = &'static str;
fn try_from(style: Style) -> Result<Self, &'static str> {
Ok(ProtobufStyle {
palette: Some(style.colors.try_into()?),
rounded_corners: style.rounded_corners,
hide_session_name: style.hide_session_name,
})
}
}
impl TryFrom<ProtobufPalette> for Palette {
type Error = &'static str;
fn try_from(protobuf_palette: ProtobufPalette) -> Result<Self, &'static str> {
Ok(Palette {
theme_hue: ProtobufThemeHue::from_i32(protobuf_palette.theme_hue)
.ok_or("malformed theme_hue payload for Palette")?
.try_into()?,
fg: protobuf_palette
.fg
.ok_or("malformed palette payload")?
.try_into()?,
bg: protobuf_palette
.bg
.ok_or("malformed palette payload")?
.try_into()?,
black: protobuf_palette
.black
.ok_or("malformed palette payload")?
.try_into()?,
red: protobuf_palette
.red
.ok_or("malformed palette payload")?
.try_into()?,
green: protobuf_palette
.green
.ok_or("malformed palette payload")?
.try_into()?,
yellow: protobuf_palette
.yellow
.ok_or("malformed palette payload")?
.try_into()?,
blue: protobuf_palette
.blue
.ok_or("malformed palette payload")?
.try_into()?,
magenta: protobuf_palette
.magenta
.ok_or("malformed palette payload")?
.try_into()?,
cyan: protobuf_palette
.cyan
.ok_or("malformed palette payload")?
.try_into()?,
white: protobuf_palette
.white
.ok_or("malformed palette payload")?
.try_into()?,
orange: protobuf_palette
.orange
.ok_or("malformed palette payload")?
.try_into()?,
gray: protobuf_palette
.gray
.ok_or("malformed palette payload")?
.try_into()?,
purple: protobuf_palette
.purple
.ok_or("malformed palette payload")?
.try_into()?,
gold: protobuf_palette
.gold
.ok_or("malformed palette payload")?
.try_into()?,
silver: protobuf_palette
.silver
.ok_or("malformed palette payload")?
.try_into()?,
pink: protobuf_palette
.pink
.ok_or("malformed palette payload")?
.try_into()?,
brown: protobuf_palette
.brown
.ok_or("malformed palette payload")?
.try_into()?,
..Default::default()
})
}
}
impl TryFrom<Palette> for ProtobufPalette {
type Error = &'static str;
fn try_from(palette: Palette) -> Result<Self, &'static str> {
let theme_hue: ProtobufThemeHue = palette
.theme_hue
.try_into()
.map_err(|_| "malformed payload for palette")?;
Ok(ProtobufPalette {
theme_hue: theme_hue as i32,
fg: Some(palette.fg.try_into()?),
bg: Some(palette.bg.try_into()?),
black: Some(palette.black.try_into()?),
red: Some(palette.red.try_into()?),
green: Some(palette.green.try_into()?),
yellow: Some(palette.yellow.try_into()?),
blue: Some(palette.blue.try_into()?),
magenta: Some(palette.magenta.try_into()?),
cyan: Some(palette.cyan.try_into()?),
white: Some(palette.white.try_into()?),
orange: Some(palette.orange.try_into()?),
gray: Some(palette.gray.try_into()?),
purple: Some(palette.purple.try_into()?),
gold: Some(palette.gold.try_into()?),
silver: Some(palette.silver.try_into()?),
pink: Some(palette.pink.try_into()?),
brown: Some(palette.brown.try_into()?),
..Default::default()
})
}
}
impl TryFrom<ProtobufColor> for PaletteColor {
type Error = &'static str;
fn try_from(protobuf_color: ProtobufColor) -> Result<Self, &'static str> {
match ProtobufColorType::from_i32(protobuf_color.color_type) {
Some(ProtobufColorType::Rgb) => match protobuf_color.payload {
Some(ProtobufColorPayload::RgbColorPayload(rgb_color_payload)) => {
Ok(PaletteColor::Rgb((
rgb_color_payload.red as u8,
rgb_color_payload.green as u8,
rgb_color_payload.blue as u8,
)))
},
_ => Err("malformed payload for Rgb color"),
},
Some(ProtobufColorType::EightBit) => match protobuf_color.payload {
Some(ProtobufColorPayload::EightBitColorPayload(eight_bit_payload)) => {
Ok(PaletteColor::EightBit(eight_bit_payload as u8))
},
_ => Err("malformed payload for 8bit color"),
},
None => Err("malformed payload for Color"),
}
}
}
impl TryFrom<PaletteColor> for ProtobufColor {
type Error = &'static str;
fn try_from(color: PaletteColor) -> Result<Self, &'static str> {
match color {
PaletteColor::Rgb((red, green, blue)) => {
let red = red as u32;
let green = green as u32;
let blue = blue as u32;
Ok(ProtobufColor {
color_type: ProtobufColorType::Rgb as i32,
payload: Some(ProtobufColorPayload::RgbColorPayload(
ProtobufRgbColorPayload { red, green, blue },
)),
})
},
PaletteColor::EightBit(color) => Ok(ProtobufColor {
color_type: ProtobufColorType::EightBit as i32,
payload: Some(ProtobufColorPayload::EightBitColorPayload(color as u32)),
}),
}
}
}
impl TryFrom<ThemeHue> for ProtobufThemeHue {
type Error = &'static str;
fn try_from(theme_hue: ThemeHue) -> Result<Self, &'static str> {
match theme_hue {
ThemeHue::Light => Ok(ProtobufThemeHue::Light),
ThemeHue::Dark => Ok(ProtobufThemeHue::Dark),
}
}
}
impl TryFrom<ProtobufThemeHue> for ThemeHue {
type Error = &'static str;
fn try_from(protobuf_theme_hue: ProtobufThemeHue) -> Result<Self, &'static str> {
match protobuf_theme_hue {
ProtobufThemeHue::Light => Ok(ThemeHue::Light),
ProtobufThemeHue::Dark => Ok(ThemeHue::Dark),
}
}
}