summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-05-16 12:47:18 +0200
committerGitHub <noreply@github.com>2023-05-16 12:47:18 +0200
commit5fe4d60c220c872c1e77a7ddf24fec6686c28e95 (patch)
treeb6abec3006de7cd2eaecee149005d55ee4671a6b
parent4b7d7c34b72e67168a2ec53b27776047ccddf522 (diff)
feat(plugins): Plugin workers and strider (#2449)
* mvp of strider fuzzy find * improve search ui * various refactoringz * moar refactoring * even more refactoring * tests and more refactoring * refactor: remove unused stuff * style(fmt): rustfmt * debug ci * debug ci * correct path for plugin system tests * fix plugin system ci tests * remove debugging statements from test * fix plugin worker persistence * add test for plugin worker persistence * style(fmt): rustfmt * final cleanups * remove outdated comments
-rw-r--r--Cargo.lock66
-rw-r--r--Cargo.toml1
-rw-r--r--default-plugins/fixture-plugin-for-tests/.cargo/config.toml2
-rw-r--r--default-plugins/fixture-plugin-for-tests/Cargo.toml11
l---------default-plugins/fixture-plugin-for-tests/LICENSE.md1
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs78
-rw-r--r--default-plugins/strider/Cargo.toml6
-rw-r--r--default-plugins/strider/src/main.rs113
-rw-r--r--default-plugins/strider/src/search.rs415
-rw-r--r--default-plugins/strider/src/state.rs88
-rw-r--r--xtask/src/ci.rs23
-rw-r--r--xtask/src/main.rs1
-rw-r--r--zellij-server/Cargo.toml2
-rw-r--r--zellij-server/src/logging_pipe.rs5
-rw-r--r--zellij-server/src/plugins/mod.rs63
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs221
-rw-r--r--zellij-server/src/plugins/plugin_map.rs226
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs320
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__load_new_plugin_from_hd.snap12
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__plugin_workers.snap12
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__plugin_workers_persist_state.snap12
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs270
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs57
-rw-r--r--zellij-tile/src/lib.rs68
-rw-r--r--zellij-tile/src/shim.rs26
-rw-r--r--zellij-utils/Cargo.toml1
-rwxr-xr-xzellij-utils/assets/plugins/compact-bar.wasmbin490119 -> 791665 bytes
-rwxr-xr-xzellij-utils/assets/plugins/fixture-plugin-for-tests.wasmbin0 -> 732287 bytes
-rwxr-xr-xzellij-utils/assets/plugins/status-bar.wasmbin621069 -> 920797 bytes
-rwxr-xr-xzellij-utils/assets/plugins/strider.wasmbin503181 -> 916361 bytes
-rwxr-xr-xzellij-utils/assets/plugins/tab-bar.wasmbin458835 -> 762202 bytes
-rw-r--r--zellij-utils/src/consts.rs5
-rw-r--r--zellij-utils/src/data.rs4
-rw-r--r--zellij-utils/src/errors.rs3
34 files changed, 1926 insertions, 186 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 74374912a..33c762962 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -776,11 +776,12 @@ checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
[[package]]
name = "dialoguer"
-version = "0.10.1"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8c8ae48e400addc32a8710c8d62d55cb84249a7d58ac4cd959daecfbaddc545"
+checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87"
dependencies = [
"console",
+ "shell-words",
"tempfile",
"zeroize",
]
@@ -1020,6 +1021,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
+name = "fixture-plugin-for-tests"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "zellij-tile",
+]
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1072,6 +1082,15 @@ dependencies = [
]
[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
+[[package]]
name = "generational-arena"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2444,6 +2463,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2597,6 +2625,12 @@ dependencies = [
]
[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
name = "shellexpand"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2798,8 +2832,14 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
name = "strider"
version = "0.2.0"
dependencies = [
+ "ansi_term",
"colored",
+ "fuzzy-matcher",
"pretty-bytes",
+ "serde",
+ "serde_json",
+ "unicode-width",
+ "walkdir",
"zellij-tile",
]
@@ -3067,6 +3107,16 @@ dependencies = [
]
[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if 1.0.0",
+ "once_cell",
+]
+
+[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3372,6 +3422,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4212,6 +4272,7 @@ dependencies = [
"sixel-image",
"sixel-tokenizer",
"sysinfo",
+ "tempfile",
"typetag",
"unicode-width",
"url",
@@ -4278,6 +4339,7 @@ dependencies = [
"thiserror",
"unicode-width",
"url",
+ "uuid",
"vte 0.11.0",
]
diff --git a/Cargo.toml b/Cargo.toml
index 1c16269f2..1ce85c77f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,7 @@ members = [
"default-plugins/status-bar",
"default-plugins/strider",
"default-plugins/tab-bar",
+ "default-plugins/fixture-plugin-for-tests",
"zellij-client",
"zellij-server",
"zellij-utils",
diff --git a/default-plugins/fixture-plugin-for-tests/.cargo/config.toml b/default-plugins/fixture-plugin-for-tests/.cargo/config.toml
new file mode 100644
index 000000000..bc255e30b
--- /dev/null
+++ b/default-plugins/fixture-plugin-for-tests/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi" \ No newline at end of file
diff --git a/default-plugins/fixture-plugin-for-tests/Cargo.toml b/default-plugins/fixture-plugin-for-tests/Cargo.toml
new file mode 100644
index 000000000..39211c340
--- /dev/null
+++ b/default-plugins/fixture-plugin-for-tests/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "fixture-plugin-for-tests"
+version = "0.1.0"
+authors = ["Aram Drevekenin <aram@poor.dev>"]
+edition = "2021"
+license = "MIT"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+zellij-tile = { path = "../../zellij-tile" }
diff --git a/default-plugins/fixture-plugin-for-tests/LICENSE.md b/default-plugins/fixture-plugin-for-tests/LICENSE.md
new file mode 120000
index 000000000..f0608a63a
--- /dev/null
+++ b/default-plugins/fixture-plugin-for-tests/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
new file mode 100644
index 000000000..124b2dc75
--- /dev/null
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -0,0 +1,78 @@
+use serde::{Deserialize, Serialize};
+use zellij_tile::prelude::*;
+
+// This is a fixture plugin used only for tests in Zellij
+// it is not (and should not!) be included in the mainline executable
+// it's included here for convenience so that it will be built by the CI
+
+#[derive(Default)]
+struct State {
+ received_events: Vec<Event>,
+ received_payload: Option<String>,
+}
+
+#[derive(Default, Serialize, Deserialize)]
+struct TestWorker {
+ number_of_messages_received: usize,
+}
+
+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!(
+ "{}, received {} messages",
+ payload, self.number_of_messages_received
+ ),
+ );
+ }
+ }
+}
+
+register_plugin!(State);
+register_worker!(TestWorker, test_worker);
+
+impl ZellijPlugin for State {
+ fn load(&mut self) {
+ subscribe(&[
+ EventType::InputReceived,
+ EventType::SystemClipboardFailure,
+ EventType::CustomMessage,
+ ]);
+ }
+
+ fn update(&mut self, event: Event) -> bool {
+ match &event {
+ Event::CustomMessage(message, payload) => {
+ if message == "pong" {
+ self.received_payload = Some(payload.clone());
+ }
+ },
+ Event::SystemClipboardFailure => {
+ // this is just to trigger the worker message
+ post_message_to(
+ "test",
+ "ping".to_owned(),
+ "gimme_back_my_payload".to_owned(),
+ );
+ },
+ _ => {},
+ }
+ let should_render = true;
+ self.received_events.push(event);
+ should_render
+ }
+
+ fn render(&mut self, rows: usize, cols: usize) {
+ if let Some(payload) = self.received_payload.as_ref() {
+ println!("Payload from worker: {:?}", payload);
+ } else {
+ println!(
+ "Rows: {:?}, Cols: {:?}, Received events: {:?}",
+ rows, cols, self.received_events
+ );
+ }
+ }
+}
diff --git a/default-plugins/strider/Cargo.toml b/default-plugins/strider/Cargo.toml
index f7801bfaf..d45a8ff21 100644
--- a/default-plugins/strider/Cargo.toml
+++ b/default-plugins/strider/Cargo.toml
@@ -10,3 +10,9 @@ license = "MIT"
colored = "2.0.0"
zellij-tile = { path = "../../zellij-tile" }
pretty-bytes = "0.2.2"
+walkdir = "2.3.3"
+fuzzy-matcher = "0.3.7"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+unicode-width = "0.1.8"
+ansi_term = "0.12.1"
diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs
index 6868c6706..4f299c508 100644
--- a/default-plugins/strider/src/main.rs
+++ b/default-plugins/strider/src/main.rs
@@ -1,16 +1,28 @@
+mod search;
mod state;
use colored::*;
-use state::{refresh_directory, FsEntry, State};
+use search::{ResultsOfSearch, SearchWorker};
+use serde_json;
+use state::{refresh_directory, FsEntry, State, CURRENT_SEARCH_TERM};
use std::{cmp::min, time::Instant};
use zellij_tile::prelude::*;
register_plugin!(State);
+register_worker!(SearchWorker, search_worker);
impl ZellijPlugin for State {
fn load(&mut self) {
refresh_directory(self);
- subscribe(&[EventType::Key, EventType::Mouse]);
+ self.loading = true;
+ subscribe(&[
+ EventType::Key,
+ EventType::Mouse,
+ EventType::CustomMessage,
+ EventType::Timer,
+ ]);
+ post_message_to("search", String::from("scan_folder"), String::new());
+ set_timeout(0.5); // for displaying loading animation
}
fn update(&mut self, event: Event) -> bool {
@@ -22,26 +34,101 @@ impl ZellijPlugin for State {
};
self.ev_history.push_back((event.clone(), Instant::now()));
match event {
+ Event::Timer(_elapsed) => {
+ should_render = true;
+ if self.loading {
+ set_timeout(0.5);
+ if self.loading_animation_offset == u8::MAX {
+ self.loading_animation_offset = 0;
+ } else {
+ self.loading_animation_offset =
+ self.loading_animation_offset.saturating_add(1);
+ }
+ }
+ },
+ Event::CustomMessage(message, payload) => match message.as_str() {
+ "update_search_results" => {
+ if let Ok(mut results_of_search) =
+ serde_json::from_str::<ResultsOfSearch>(&payload)
+ {
+ if Some(results_of_search.search_term) == self.search_term {
+ self.search_results =
+ results_of_search.search_results.drain(..).collect();
+ should_render = true;
+ }
+ }
+ },
+ "done_scanning_folder" => {
+ self.loading = false;
+ should_render = true;
+ },
+ _ => {},
+ },
Event::Key(key) => match key {
+ // modes:
+ // 1. typing_search_term
+ // 2. exploring_search_results
+ // 3. normal
+ Key::Esc | Key::Char('\n') if self.typing_search_term() => {
+ self.accept_search_term();
+ },
+ _ if self.typing_search_term() => {
+ self.append_to_search_term(key);
+ if let Some(search_term) = self.search_term.as_ref() {
+ std::fs::write(CURRENT_SEARCH_TERM, search_term.as_bytes()).unwrap();
+ post_message_to(
+ "search",
+ String::from("search"),
+ String::from(&self.search_term.clone().unwrap()),
+ );
+ }
+ should_render = true;
+ },
+ Key::Esc if self.exploring_search_results() => {
+ self.stop_exploring_search_results();
+ should_render = true;
+ },
+ Key::Char('/') => {
+ self.start_typing_search_term();
+ should_render = true;
+ },
+ Key::Esc => {
+ self.stop_typing_search_term();
+ should_render = true;
+ },
Key::Up | Key::Char('k') => {
- let currently_selected = self.selected();
- *self.selected_mut() = self.selected().saturating_sub(1);
- if currently_selected != self.selected() {
+ if self.exploring_search_results() {
+ self.move_search_selection_up();
should_render = true;
+ } else {
+ let currently_selected = self.selected();
+ *self.selected_mut() = self.selected().saturating_sub(1);
+ if currently_selected != self.selected() {
+ should_render = true;
+ }
}
},
Key::Down | Key::Char('j') => {
- let currently_selected = self.selected();
- let next = self.selected().saturating_add(1);
- *self.selected_mut() = min(self.files.len().saturating_sub(1), next);
- if currently_selected != self.selected() {
+ if self.exploring_search_results() {
+ self.move_search_selection_down();
should_render = true;
+ } else {
+ let currently_selected = self.selected();
+ let next = self.selected().saturating_add(1);
+ *self.selected_mut() = min(self.files.len().saturating_sub(1), next);
+ if currently_selected != self.selected() {
+ should_render = true;
+ }
}
},
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
+ if self.exploring_search_results() {
+ self.open_search_result();
+ } else {
+ self.traverse_dir_or_open_file();
+ self.ev_history.clear();
+ }
should_render = true;
- self.traverse_dir_or_open_file();
- self.ev_history.clear();
},
Key::Left | Key::Char('h') => {
if self.path.components().count() > 2 {
@@ -111,6 +198,10 @@ impl ZellijPlugin for State {
}
fn render(&mut self, rows: usize, cols: usize) {
+ if self.typing_search_term() || self.exploring_search_results() {
+ return self.render_search(rows, cols);
+ }
+
for i in 0..rows {
if self.selected() < self.scroll() {
*self.scroll_mut() = self.selected();
diff --git a/default-plugins/strider/src/search.rs b/default-plugins/strider/src/search.rs
new file mode 100644
index 000000000..299882eac
--- /dev/null
+++ b/default-plugins/strider/src/search.rs
@@ -0,0 +1,415 @@
+use crate::state::{State, CURRENT_SEARCH_TERM, ROOT};
+
+use unicode_width::UnicodeWidthStr;
+use zellij_tile::prelude::*;
+
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+use serde::{Deserialize, Serialize};
+use walkdir::WalkDir;
+
+use std::io::{self, BufRead};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum SearchResult {
+ File {
+ path: String,
+ score: i64,
+ indices: Vec<usize>,
+ },
+ LineInFile {
+ path: String,
+ line: String,
+ line_number: usize,
+ score: i64,
+ indices: Vec<usize>,
+ },
+}
+
+impl SearchResult {
+ pub fn new_file_name(score: i64, indices: Vec<usize>, path: String) -> Self {
+ SearchResult::File {
+ path,
+ score,
+ indices,
+ }
+ }
+ pub fn new_file_line(
+ score: i64,
+ indices: Vec<usize>,
+ path: String,
+ line: String,
+ line_number: usize,
+ ) -> Self {
+ SearchResult::LineInFile {
+ path,
+ score,
+ indices,
+ line,
+ line_number,
+ }
+ }
+ pub fn score(&self) -> i64 {
+ match self {
+ SearchResult::File { score, .. } => *score,
+ SearchResult::LineInFile { score, .. } => *score,
+ }
+ }
+ pub fn rendered_height(&self) -> usize {
+ match self {
+ SearchResult::File { .. } => 1,
+ SearchResult::LineInFile { .. } => 2,
+ }
+ }
+ pub fn render(&self, max_width: usize, is_selected: bool) -> String {
+ let green_code = 154;
+ let orange_code = 166;
+ let bold_code = "\u{1b}[1m";
+ let green_foreground = format!("\u{1b}[38;5;{}m", green_code);
+ let orange_foreground = format!("\u{1b}[38;5;{}m", orange_code);
+ let reset_code = "\u{1b}[m";
+ let max_width = max_width.saturating_sub(3); // for the UI left line separator
+ match self {
+ SearchResult::File { path, indices, .. } => {
+ if is_selected {
+ let line = self.render_line_with_indices(
+ path,
+