aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-06-07 12:43:35 +0200
committerGitHub <noreply@github.com>2023-06-07 12:43:35 +0200
commitc11d75f9157873fc99fe0d40933de8ec5fbb4f6b (patch)
treefc55dc7f9132395585613dd9cce94c98c8c76600
parentdocs(changelog): fix cwd of newtab action (diff)
downloadsl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.tar.gz
sl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.tar.xz
sl0ppij-c11d75f9157873fc99fe0d40933de8ec5fbb4f6b.zip
feat(wasm-plugin-system): major overhaul and some goodies (#2510)
* strider resiliency * worker channel prototype * finalized ui * show hide plugin * fs events to plugins * tests for events and new screen instructions * various refactoringz * report plugin errors instead of crashing zellij * fix plugin loading with workers * refactor: move watch filesystem * some fixes and refactoring * refactor(panes): combine pane insertion logic * refactor(screen): launch or focus * refactor(pty): consolidate default shell fetching * refactor: various cleanups * initial refactoring * more initial refactoring * refactor(strider): search * style(fmt): rustfmt * style(pty): cleanup * style(clippy): ok clippy * style(fmt): rustfmt
-rw-r--r--Cargo.lock120
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs6
-rw-r--r--default-plugins/strider/Cargo.toml1
-rw-r--r--default-plugins/strider/src/main.rs191
-rw-r--r--default-plugins/strider/src/search.rs415
-rw-r--r--default-plugins/strider/src/search/controls_line.rs353
-rw-r--r--default-plugins/strider/src/search/mod.rs329
-rw-r--r--default-plugins/strider/src/search/search_results.rs308
-rw-r--r--default-plugins/strider/src/search/search_state.rs241
-rw-r--r--default-plugins/strider/src/search/selection_controls_area.rs61
-rw-r--r--default-plugins/strider/src/search/ui.rs120
-rw-r--r--default-plugins/strider/src/state.rs80
-rw-r--r--zellij-server/src/lib.rs9
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs17
-rw-r--r--zellij-server/src/panes/plugin_pane.rs2
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs20
-rw-r--r--zellij-server/src/plugins/mod.rs13
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs43
-rw-r--r--zellij-server/src/plugins/plugin_map.rs70
-rw-r--r--zellij-server/src/plugins/plugin_worker.rs89
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs82
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap12
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs150
-rw-r--r--zellij-server/src/plugins/watch_filesystem.rs63
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs123
-rw-r--r--zellij-server/src/pty.rs14
-rw-r--r--zellij-server/src/route.rs10
-rw-r--r--zellij-server/src/screen.rs77
-rw-r--r--zellij-server/src/tab/mod.rs399
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs457
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs26
-rw-r--r--zellij-server/src/ui/loading_indication.rs16
-rw-r--r--zellij-server/src/unit/screen_tests.rs156
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap26
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap6
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap26
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap25
-rw-r--r--zellij-tile/src/lib.rs65
-rw-r--r--zellij-tile/src/shim.rs29
-rw-r--r--zellij-utils/Cargo.toml2
-rwxr-xr-xzellij-utils/assets/plugins/compact-bar.wasmbin791665 -> 796418 bytes
-rwxr-xr-xzellij-utils/assets/plugins/fixture-plugin-for-tests.wasmbin732287 -> 725203 bytes
-rwxr-xr-xzellij-utils/assets/plugins/status-bar.wasmbin920797 -> 924326 bytes
-rwxr-xr-xzellij-utils/assets/plugins/strider.wasmbin916361 -> 968345 bytes
-rwxr-xr-xzellij-utils/assets/plugins/tab-bar.wasmbin762202 -> 766944 bytes
-rw-r--r--zellij-utils/src/cli.rs5
-rw-r--r--zellij-utils/src/data.rs5
-rw-r--r--zellij-utils/src/errors.rs2
-rw-r--r--zellij-utils/src/input/actions.rs12
-rw-r--r--zellij-utils/src/input/command.rs13
-rw-r--r--zellij-utils/src/kdl/mod.rs23
-rw-r--r--zellij-utils/src/lib.rs4
52 files changed, 3066 insertions, 1250 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 33c76296..b410d17f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -75,11 +75,11 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "async-channel"
-version = "1.6.1"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
- "concurrent-queue",
+ "concurrent-queue 2.2.0",
"event-listener",
"futures-core",
]
@@ -91,7 +91,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
- "concurrent-queue",
+ "concurrent-queue 1.2.2",
"fastrand",
"futures-lite",
"once_cell",
@@ -119,7 +119,7 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
dependencies = [
- "concurrent-queue",
+ "concurrent-queue 1.2.2",
"futures-lite",
"libc",
"log",
@@ -502,6 +502,15 @@ dependencies = [
]
[[package]]
+name = "concurrent-queue"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "console"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -679,12 +688,11 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.8"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if 1.0.0",
- "lazy_static",
]
[[package]]
@@ -1009,6 +1017,18 @@ dependencies = [
]
[[package]]
+name = "filetime"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "finl_unicode"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1046,6 +1066,15 @@ dependencies = [
]
[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1278,6 +1307,26 @@ dependencies = [
]
[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "insta"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1392,6 +1441,26 @@ dependencies = [
]
[[package]]
+name = "kqueue"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1664,6 +1733,18 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1733,6 +1814,24 @@ dependencies = [
]
[[package]]
+name = "notify"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio 0.8.6",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2838,6 +2937,7 @@ dependencies = [
"pretty-bytes",
"serde",
"serde_json",
+ "strip-ansi-escapes",
"unicode-width",
"walkdir",
"zellij-tile",
@@ -4243,7 +4343,7 @@ version = "0.37.0"
dependencies = [
"insta",
"log",
- "mio",
+ "mio 0.7.14",
"serde",
"serde_json",
"serde_yaml",
@@ -4306,6 +4406,7 @@ name = "zellij-utils"
version = "0.37.0"
dependencies = [
"anyhow",
+ "async-channel",
"async-std",
"backtrace",
"clap",
@@ -4323,6 +4424,7 @@ dependencies = [
"log4rs",
"miette",
"nix 0.23.1",
+ "notify",
"once_cell",
"percent-encoding",
"regex",
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index 124b2dc7..7e4139f5 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -32,7 +32,7 @@ impl<'de> ZellijWorker<'de> for TestWorker {
}
register_plugin!(State);
-register_worker!(TestWorker, test_worker);
+register_worker!(TestWorker, test_worker, TEST_WORKER);
impl ZellijPlugin for State {
fn load(&mut self) {
@@ -40,6 +40,10 @@ impl ZellijPlugin for State {
EventType::InputReceived,
EventType::SystemClipboardFailure,
EventType::CustomMessage,
+ EventType::FileSystemCreate,
+ EventType::FileSystemRead,
+ EventType::FileSystemUpdate,
+ EventType::FileSystemDelete,
]);
}
diff --git a/default-plugins/strider/Cargo.toml b/default-plugins/strider/Cargo.toml
index d45a8ff2..65b5d23e 100644
--- a/default-plugins/strider/Cargo.toml
+++ b/default-plugins/strider/Cargo.toml
@@ -16,3 +16,4 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
unicode-width = "0.1.8"
ansi_term = "0.12.1"
+strip-ansi-escapes = "0.1.1"
diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs
index 4f299c50..966b53a4 100644
--- a/default-plugins/strider/src/main.rs
+++ b/default-plugins/strider/src/main.rs
@@ -2,26 +2,45 @@ mod search;
mod state;
use colored::*;
-use search::{ResultsOfSearch, SearchWorker};
+use search::{FileContentsWorker, FileNameWorker, MessageToSearch, ResultsOfSearch};
+use serde::{Deserialize, Serialize};
use serde_json;
-use state::{refresh_directory, FsEntry, State, CURRENT_SEARCH_TERM};
+use state::{refresh_directory, FsEntry, State};
use std::{cmp::min, time::Instant};
use zellij_tile::prelude::*;
register_plugin!(State);
-register_worker!(SearchWorker, search_worker);
+register_worker!(FileNameWorker, file_name_search_worker, FILE_NAME_WORKER);
+register_worker!(
+ FileContentsWorker,
+ file_contents_search_worker,
+ FILE_CONTENTS_WORKER
+);
impl ZellijPlugin for State {
fn load(&mut self) {
refresh_directory(self);
- self.loading = true;
+ self.search_state.loading = true;
subscribe(&[
EventType::Key,
EventType::Mouse,
EventType::CustomMessage,
EventType::Timer,
+ EventType::FileSystemCreate,
+ EventType::FileSystemUpdate,
+ EventType::FileSystemDelete,
]);
- post_message_to("search", String::from("scan_folder"), String::new());
+ post_message_to(
+ "file_name_search",
+ serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(),
+ "".to_owned(),
+ );
+ post_message_to(
+ "file_contents_search",
+ serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(),
+ "".to_owned(),
+ );
+ self.search_state.loading = true;
set_timeout(0.5); // for displaying loading animation
}
@@ -35,57 +54,43 @@ impl ZellijPlugin for State {
self.ev_history.push_back((event.clone(), Instant::now()));
match event {
Event::Timer(_elapsed) => {
- should_render = true;
- if self.loading {
+ if self.search_state.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);
- }
+ self.search_state.progress_animation();
+ should_render = true;
}
},
- Event::CustomMessage(message, payload) => match message.as_str() {
- "update_search_results" => {
- if let Ok(mut results_of_search) =
- serde_json::from_str::<ResultsOfSearch>(&payload)
+ Event::CustomMessage(message, payload) => match serde_json::from_str(&message) {
+ Ok(MessageToPlugin::UpdateFileNameSearchResults) => {
+ if let Ok(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;
- }
+ self.search_state
+ .update_file_name_search_results(results_of_search);
+ should_render = true;
+ }
+ },
+ Ok(MessageToPlugin::UpdateFileContentsSearchResults) => {
+ if let Ok(results_of_search) = serde_json::from_str::<ResultsOfSearch>(&payload)
+ {
+ self.search_state
+ .update_file_contents_search_results(results_of_search);
+ should_render = true;
}
},
- "done_scanning_folder" => {
- self.loading = false;
+ Ok(MessageToPlugin::DoneScanningFolder) => {
+ self.search_state.loading = false;
should_render = true;
},
- _ => {},
+ Err(e) => eprintln!("Failed to deserialize custom message: {:?}", e),
},
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()),
- );
- }
+ Key::Esc if self.typing_search_term() => {
+ self.stop_typing_search_term();
+ self.search_state.handle_key(key);
should_render = true;
},
- Key::Esc if self.exploring_search_results() => {
- self.stop_exploring_search_results();
+ _ if self.typing_search_term() => {
+ self.search_state.handle_key(key);
should_render = true;
},
Key::Char('/') => {
@@ -94,40 +99,27 @@ impl ZellijPlugin for State {
},
Key::Esc => {
self.stop_typing_search_term();
+ hide_self();
should_render = true;
},
Key::Up | Key::Char('k') => {
- if self.exploring_search_results() {
- self.move_search_selection_up();
+ let currently_selected = self.selected();
+ *self.selected_mut() = self.selected().saturating_sub(1);
+ if currently_selected != self.selected() {
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') => {
- if self.exploring_search_results() {
- self.move_search_selection_down();
+ 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;
- } 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();
- }
+ self.traverse_dir_or_open_file();
+ self.ev_history.clear();
should_render = true;
},
Key::Left | Key::Char('h') => {
@@ -190,6 +182,54 @@ impl ZellijPlugin for State {
},
_ => {},
},
+ Event::FileSystemCreate(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::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(),
+ );
+ },
+ 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(),
+ );
+ },
+ 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(),
+ );
+ },
_ => {
dbg!("Unknown event {:?}", event);
},
@@ -198,8 +238,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);
+ if self.typing_search_term() {
+ self.search_state.change_size(rows, cols);
+ print!("{}", self.search_state);
+ return;
}
for i in 0..rows {
@@ -221,9 +263,9 @@ impl ZellijPlugin for State {
if i == self.selected() {
if is_last_row {
- print!("{}", path.reversed());
+ print!("{}", path.clone().reversed());
} else {
- println!("{}", path.reversed());
+ println!("{}", path.clone().reversed());
}
} else {
if is_last_row {
@@ -238,3 +280,10 @@ impl ZellijPlugin for State {
}
}
}
+
+#[derive(Serialize, Deserialize)]
+pub enum MessageToPlugin {
+ UpdateFileNameSearchResults,
+ UpdateFileContentsSearchResults,
+ DoneScanningFolder,
+}
diff --git a/default-plugins/strider/src/search.rs b/default-plugins/strider/src/search.rs
deleted file mode 100644
index 299882ea..00000000
--- a/default-plugins/strider/src/search.rs
+++ /dev/null
@@ -1,415 +0,0 @@
-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,
- indices,
- max_width,
- None,
- Some(green_code),
- true,
- );
- format!("{} | {}{}", green_foreground, reset_code, line)
- } else {
- let line =
- self.render_line_with_indices(path, indices, max_width, None, None, true);
- format!(" | {}", line)
- }
- },
- SearchResult::LineInFile {
- path,
- line,
- line_number,
- indices,
- ..
- } => {
- if is_selected {
- let first_line = self.render_line_with_indices(
- path,
- &vec![],
- max_width,
- None,
- Some(green_code),
- true,
- );
- let line_indication_text = format!("{}-> {}", bold_code, line_number);
- let line_indication = format!(
- "{}{}{}",
- orange_foreground, line_indication_text, reset_code
- ); // TODO: also truncate
- let second_line = self.render_line_with_indices(
- line,
- indices,
- max_width.saturating_sub(line_indication_text.width()),
- None,
- Some(orange_code),
- false,
- );
- format!(
- " {}│{} {}\n {}│{} {} {}",
- green_foreground,
- reset_code,
- first_line,
- green_foreground,
- reset_code,
- line_indication,
- second_line
- )
- } else {
- let first_line =
- self.render_line_with_indices(path, &vec![], max_width, None, None, true); // TODO:
- let line_indication_text = format!("{}-> {}", bold_code, line_number);
- let second_line = self.render_line_with_indices(
- line,
- indices,
- max_width.saturating_sub(line_indication_text.width()),
- None,
- None,
- false,
- );
- format!(
- " │ {}\n │ {} {}",
- first_line, line_indication_text, second_line
- )
- }
- },
- }
- }
- fn render_line_with_indices(
- &self,
- line_to_render: &String,
- indices: &Vec<usize>,
- max_width: usize,
- background_color: Option<usize>,
- foreground_color: Option<usize>,
- is_bold: bool,
- ) -> String {
- // TODO: get these from Zellij
- let reset_code = "\u{1b}[m";
- let underline_code = "\u{1b}[4m";
- let foreground_color = foreground_color
- .map(|c| format!("\u{1b}[38;5;{}m", c))
- .unwrap_or_else(|| format!(""));
- let background_color = background_color
- .map(|c| format!("\u{1b}[48;5;{}m", c))
- .unwrap_or_else(|| format!(""));
- let bold = if is_bold { "\u{1b}[1m" } else { "" };
- let non_index_character_style = format!("{}{}{}", background_color, foreground_color, bold);
- let index_character_style = format!(
- "{}{}{}{}",
- background_color, foreground_color, bold, underline_code
- );
-
- let mut truncate_start_position = None;
- let mut truncate_end_position = None;
- if line_to_render.width() > max_width {
- let length_of_each_half = max_width.saturating_sub(4) / 2;
- truncate_start_position = Some(length_of_each_half);
- truncate_end_position =
- Some(line_to_render.width().saturating_sub(length_of_each_half));
- }
- let mut first_half = format!("{}", reset_code);
- let mut second_half = format!("{}", reset_code);
- for (i, character) in line_to_render.chars().enumerate() {
- if (truncate_start_position.is_none() && truncate_end_position.is_none())
- || Some(i) < truncate_start_position
- {
- if indices.contains(&i) {
- first_half.push_str(&index_character_style);
- first_half.push(character);
- first_half.push_str(reset_code);
- } else {
- first_half.push_str(&non_index_character_style);
- first_half.push(character);
- first_half.push_str(reset_code);
- }
- } else if Some(i) > truncate_end_position {
- if indices.contains(&i) {
- second_half.push_str(&index_character_style);
- second_half.push(character);
- second_half.push_str(reset_code);
- } else {
- second_half.push_str(&non_index_character_style);
- second_half.push(character);
- second_half.push_str(reset_code);
- }
- }
- }
- if let Some(_truncate_start_position) = truncate_start_position {
- format!(
- "{}{}{}[..]{}{}{}",
- first_half, reset_code, foreground_color, reset_code, second_half, reset_code
- )
- } else {
- format!("{}{}", first_half, reset_code)
- }
- }
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct ResultsOfSearch {
- pub search_term: String,
- pub search_results: Vec<SearchResult>,
-}
-
-impl ResultsOfSearch {
- pub fn new(search_term: String, search_results: Vec<SearchResult>) -> Self {
- ResultsOfSearch {
- search_term,
- search_results,
- }
- }
- pub fn limit_search_results(mut self, max_results: usize) -> Self {
- self.search_results
- .sort_by(|a, b| b.score().cmp(&a.score()));
- self.search_results = if self.search_results.len() > max_results {
- self.search_results.drain(..max_results).collect()
- } else {
- self.search_results.drain(..).collect()
- };
- self
- }
-}
-
-#[derive(Default, Serialize, Deserialize)]
-pub struct SearchWorker {
- pub search_paths: Vec<String>,
- pub search_file_contents: Vec<(String, usize, String)>, // file_name, line_number, line
- skip_hidden_files: bool,
-}
-
-impl<'de> ZellijWorker<'de> for SearchWorker {
- // TODO: handle out of order messages, likely when rendering
- fn on_message(&mut self, message: String, payload: String) {
- match message.as_str() {
- // TODO: deserialize to type
- "scan_folder" => {
- self.populate_search_paths();
- post_message_to_plugin("done_scanning_folder".into(), "".into());
- },
- "search" => {
- let search_term = payload;
- let (search_term, matches) = self.search(search_term);
- let search_results =
- ResultsOfSearch::new(search_term, matches).limit_search_results(100);
- post_message_to_plugin(
- "update_search_results".into(),
- serde_json::to_string(&search_results).unwrap(),
- );
- },
- "skip_hidden_files" => match serde_json::from_str::<bool>(&payload) {
- Ok(should_skip_hidden_files) => {
- self.skip_hidden_files = should_skip_hidden_files;
- },
- Err(e) => {
- eprintln!("Failed to deserialize payload: {:?}", e);
- },
- },
- _ => {},
- }
- }
-}
-
-impl SearchWorker {
- fn search(&mut self, search_term: String) -> (String, Vec<SearchResult>) {
- if self.search_paths.is_empty() {
- self.populate_search_paths();
- }
- let mut matches = vec![];
- let mut matcher = SkimMatcherV2::default().use_cache(true).element_limit(100); // TODO: no hard
- // coded limit!
- self.search_file_names(&search_term, &mut matcher, &mut matches);
- self.search_file_contents(&search_term, &mut matcher, &mut matches);
-
- // if the search term changed before we finished, let's search again!
- if let Ok(current_search_term) = std::fs::read(CURRENT_SEARCH_TERM) {
- let current_search_term = String::from_utf8_lossy(&current_search_term); // TODO: not lossy, search can be lots of stuff
- if current_search_term != search_term {
- return self.search(current_search_term.into());
- }
- }
- (search_term, matches)
- }
- fn populate_search_paths(&mut self) {
- for entry in WalkDir::new(ROOT).into_iter().filter_map(|e| e.ok()) {
- if self.skip_hidden_files
- && entry
- .file_name()
- .to_str()
- .map(|s| s.starts_with('.'))
- .unwrap_or(false)
- {
- continue;
- }
- let file_path = entry.path().display().to_string();
-
- if entry.metadata().unwrap().is_file() {
- if let Ok(file) = std::fs::File::open(&file_path) {
- let lines = io::BufReader::new(file).lines();
- for (index, line) in lines.enumerate() {
- match line {
- Ok(line) => {
- self.search_file_contents.push((
- file_path.clone(),
- index + 1,
- line,
- ));
- },
- Err(_) => {
- break; // probably a binary file, skip it
- },
- }
- }
- }
- }
-
- self.search_paths.push(file_path);
- }
- }
- fn search_file_names(
- &self,
- search_term: &str,
- matcher: &mut SkimMatcherV2,
- matches: &mut Vec<SearchResult>,
- ) {
- for entry in &self.search_paths {
- if let Some((score, indices)) = matcher.fuzzy_indices(&entry, &search_term) {
- matches.push(SearchResult::new_file_name(
- score,
- indices,
- entry.to_owned(),
- ));
- }
- }
- }
- fn search_file_contents(
- &self,
- search_term: &str,
- matcher: &mut SkimMatcherV2,
- matches: &mut Vec<SearchResult>,
- ) {
- for (file_name, line_number, line_entry) in &self.search_file_contents {
- if let Some((score, indices)) = matcher.fuzzy_indices(&line_entry, &search_term) {
- matches.push(SearchResult::new_file_line(
- score,
- indices,
- file_name.clone(),
- line_entry.clone(),
- *line_number,
- ));
- }
- }
- }
-}
-
-impl State {
- pub fn render_search(&mut self, rows: usize, cols: usize) {
- if let Some(search_term) = self.search_term.as_ref() {
- let mut to_render = String::new();
- to_render.push_str(&format!(
- " \u{1b}[38;5;51;1mSEARCH:\u{1b}[m {}\n",
- search_term
- ));
- let mut rows_left_to_render = rows.saturating_sub(3);
- if self.loading && self.search_results.is_empty() {
- to_render.push_str(&self.render_loading());
- }
- for (i, result) in self
- .search_results
- .iter()
- .enumerate()
- .take(rows.saturating_sub(3))
- {
- let result_height = result.rendered_height();
- if result_height + 1 > rows_left_to_render {
- break;
- }
- rows_left_to_render -= result_height;
- rows_left_to_render -= 1; // space between
- let is_selected = i == self.selected_search_result;
- let rendered_result = result.render(cols, is_selected);
- to_render.push_str(&format!("\n{}\n", rendered_result));
- }
- print!("{}", to_render);
- }
- }
- pub fn render_loading(&self) -> String {
- let mut rendered = String::from("Scanning folder");
- let dot_count = self.loading_animation_offset % 4;
- for _ in 0..dot_count {
- rendered.push('.');
- }
- rendered
- }
-}
diff --git a/default-plugins/strider/src/search/controls_line.rs b/default-plugins/strider/src/search/controls_line.rs
new file mode 100644
index 00000000..a75715ef
--- /dev/null
+++ b/default-plugins/strider/src/search/controls_line.rs
@@ -0,0 +1,353 @@
+use crate::search::search_state::SearchType;
+use crate::search::ui::{
+ arrow, bold, color_line_to_end, dot, styled_text, BLACK, GRAY_DARK, GRAY_LIGHT, RED, WHITE,
+};
+
+#[derive(Default)]
+pub struct ControlsLine {
+ controls: Vec<Control>,
+ scanning_indication: Option<Vec<&'static str>>,
+ animation_offset: u8,
+}
+
+impl ControlsLine {
+ pub fn new(controls: Vec<Control>, scanning_indication: Option<Vec<&'static str>>) -> Self {
+ ControlsLine {
+ controls,
+ scanning_indication,
+ ..Default::default()
+ }
+ }
+ pub fn with_animation_offset(mut self, animation_offset: u8) -> Self {
+ self.animation_offset = animation_offset;
+ self
+ }
+ pub fn render(&self, max_width: usize, show_controls: bool) -> String {
+ if show_controls {
+ self.render_controls(max_width)
+ } else {
+ self.render_empty_line(max_width)
+ }
+ }
+ pub fn render_controls(&self, max_width: usize) -> String {
+ let loading_animation =
+ LoadingAnimation::new(&self.scanning_indication, self.animation_offset);
+ let full_length = loading_animation.full_len()
+ + self.controls.iter().map(|c| c.full_len()).sum::<usize>();
+ let mid_length =
+ loading_animation.mid_len() + self.controls.iter().map(|c| c.mid_len()).sum::<usize>();
+ let short_length = loading_animation.short_len()
+ + self.controls.iter().map(|c| c.short_len()).sum::<usize>();
+ if max_width >= full_length {
+ let mut to_render = String::new();
+ for control in &self.controls {
+ to_render.push_str(&control.render_full_length());
+ }
+ to_render.push_str(&self.render_padding(max_width.saturating_sub(full_length)));
+ to_render.push_str(&loading_animation.render_full_length());
+ to_render
+ } else if max_width >= mid_length {
+ let mut to_render = String::new();
+ for control in &self.controls {
+ to_render.push_str(&control.render_mid_length());
+ }
+ to_render.push_str(&self.render_padding(max_width.saturating_sub(mid_length)));
+ to_render.push_str(&loading_animation.render_mid_length());
+ to_render
+ } else if max_width >= short_length {
+ let mut to_render = String::new();
+ for control in &self.controls {
+ to_render.push_str(&control.render_short_length());
+ }
+ to_render.push_str(&self.render_padding(max_width.saturating_sub(short_length)));
+ to_render.push_str(&loading_animation.render_short_length());
+ to_render
+ } else {
+ format!("")
+ }
+ }
+ pub fn render_empty_line(&self, max_width: usize) -> String {
+ let loading_animation =
+ LoadingAnimation::new(&self.scanning_indication, self.animation_offset);
+ let mut to_render = String::new();
+ if max_width >= loading_animation.full_len() {
+ to_render.push_str(
+ &self.render_padding(max_width.saturating_sub(loading_animation.full_len())),
+ );
+ to_render.push_str(&loading_animation.render_full_length());
+ } else if max_width >= loading_animation.mid_len() {
+ to_render.push_str(
+ &self.render_padding(max_width.saturating_sub(loading_animation.mid_len())),
+ );
+ to_render.push_str(&loading_animation.render_mid_length());
+ } else if max_width >= loading_animation.short_len() {
+ to_render.push_str(
+ &self.render_padding(max_width.saturating_sub(loading_animation.short_len())),
+ );
+ to_render.push_str(&loading_animation.render_short_length());
+ }
+ to_render
+ }
+ fn render_padding(&self, padding: usize) -> String {
+ // TODO: color whole line
+ format!("{}\u{1b}[{}C", color_line_to_end(GRAY_LIGHT), padding)
+ }
+}
+
+pub struct Control {
+ key: &'static str,
+ options: Vec<&'static str>,
+ option_index: (usize, usize), // eg. 1 out of 2 (1, 2)
+ keycode_background_color: u8,
+ keycode_foreground_color: u8,
+ control_text_background_color: u8,
+ control_text_foreground_color: u8,
+ active_dot_color: u8,
+}
+
+impl Default for Control {
+ fn default() -> Self {
+ Control {
+ key: "",
+ options: vec![],
+ option_index: (0, 0),
+ keycode_background_color: GRAY_LIGHT,
+ keycode_foreground_color: WHITE,
+ control_text_background_color: GRAY_DARK,
+ control_text_foreground_color: BLACK,
+ active_dot_color: RED,
+ }
+ }
+}
+
+impl Control {
+ pub fn new(
+ key: &'static str,
+ options: Vec<&'static str>,
+ option_index: (usize, usize),
+ ) -> Self {
+ Control {
+ key,
+ options,
+ option_index,
+ ..Default::default()
+ }
+ }
+ pub fn new_floating_control(key: &'static str, should_open_floating: bool) -> Self {
+ if should_open_floating {
+ Control::new(key, vec!["OPEN FLOATING", "FLOATING", "F"], (2, 2))
+ } else {
+ Control::new(key, vec!["OPEN TILED", "TILED", "T"], (1, 2))
+ }
+ }
+ pub fn new_filter_control(key: &'static str, search_filter: &SearchType) -> Self {
+ match search_filter {
+ SearchType::NamesAndContents => Control::new(
+ key,
+ vec!["FILE NAMES AND CONTENTS", "NAMES + CONTENTS", "N+C"],
+ (1, 3),
+ ),
+ SearchType::Names => Control::new(key, vec!["FILE NAMES", "NAMES", "N"], (2, 3)),
+ SearchType::Contents => {
+ Control::new(key, vec!["FILE CONTENTS", "CONTENTS", "C"], (3, 3))
+ },
+ }
+ }
+ pub fn short_len(&self) -> usize {
+ let short_text = self
+ .options
+ .get(2)
+ .or_else(|| self.options.get(1))
+ .or_else(|| self.options.get(0))
+ .unwrap_or(&"");
+ short_text.chars().count() + self.key.chars().count() + self.option_index.1 + 7
+ // 7 for all the spaces and decorations
+ }
+ pub fn mid_len(&self) -> usize {
+ let mid_text = self
+ .options
+ .get(1)
+ .or_else(|| self.options.get(0))
+ .unwrap_or(&"");
+ mid_text.chars().count() + self.key.chars().count() + self.option_index.1 + 7
+ // 7 for all the spaces and decorations
+ }
+ pub fn full_len(&self) -> usize {
+ let full_text = self.options.get(0).unwrap_or(&"");
+ full_text.chars().count() + self.key.chars().count() + self.option_index.1 + 7
+ // 7 for all the spaces and decorations
+ }
+ pub fn render_short_length(&self) -> String {
+ let short_text = self
+ .options
+ .get(2)
+ .or_else(|| self.options.get(1))
+ .or_else(|| self.options.get(0))
+ .unwrap_or(&"");
+ self.render(short_text)
+ }
+ pub fn render_mid_length(&self) -> String {
+ let mid_text = self
+ .options
+ .get(1)
+ .or_else(|| self.options.get(0))
+ .unwrap_or(&"");
+ self.render(mid_text)
+ }
+ pub fn render_full_length(&self) -> String {
+ let full_text = self.options.get(0).unwrap_or(&"");
+ self.render(full_text)
+ }
+ fn render(&self, text: &str) -> String {
+ format!(
+ "{}{}{}{}{}{}",
+ self.render_keycode(&format!(" {} ", self.key)),
+ arrow(
+ self.keycode_background_color,
+ self.control_text_background_color
+ ),
+ self.render_selection_dots(),
+ self.render_control_text(&format!("{} ", text)),
+ arrow(
+ self.control_text_background_color,
+ self.keycode_background_color
+ ),
+ color_line_to_end(self.keycode_background_color),
+ )
+ }
+ fn render_keycode(&self, text: &str) -> String {
+ styled_text(
+ self.keycode_foreground_color,
+ self.keycode_background_color,
+ &bold(text),
+ )
+ }
+ fn render_control_text(&self, text: &str) -> String {
+ styled_text(
+ self.control_text_foreground_color,
+ self.control_text_background_color,
+ &bold(text),
+ )
+ }
+ fn render_selection_dots(&self) -> String {
+ let mut selection_dots = String::from(" ");
+ for i in 1..=self.option_index.1 {
+ if i == self.option_index.0 {
+ selection_dots.push_str(&dot(
+ self.active_dot_color,
+ self.control_text_background_color,
+ ));
+ } else {
+ selection_dots.push_str(&dot(
+ self.control_text_foreground_color,
+ self.control_text_background_color,
+ ));
+ }
+ }
+ selection_dots.push_str(" ");
+ selection_dots
+ }
+}
+
+struct LoadingAnimation {
+ scanning_indication: Option<Vec<&'static str>>,
+ animation_offset: u8,
+ background_color: u8,
+ foreground_color: u8,
+}
+impl LoadingAnimation {
+ pub fn new(scanning_indication: &Option<Vec<&'static str>>, animation_offset: u8) -> Self {
+ LoadingAnimation {
+ scanning_indication: scanning_indication.clone(),
+ animation_offset,
+ background_color: GRAY_LIGHT,
+ foreground_color: WHITE,
+ }
+ }
+ pub fn full_len(&self) -> usize {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| scanning_indication.get(0))
+ .map(|s| s.chars().count() + 3) // 3 for animation dots
+ .unwrap_or(0)
+ }
+ pub fn mid_len(&self) -> usize {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| {
+ scanning_indication
+ .get(1)
+ .or_else(|| scanning_indication.get(0))
+ })
+ .map(|s| s.chars().count() + 3) // 3 for animation dots
+ .unwrap_or(0)
+ }
+ pub fn short_len(&self) -> usize {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| {
+ scanning_indication
+ .get(2)
+ .or_else(|| scanning_indication.get(1))
+ .or_else(|| scanning_indication.get(0))
+ })
+ .map(|s| s.chars().count() + 3) // 3 for animation dots
+ .unwrap_or(0)
+ }
+ pub fn render_full_length(&self) -> String {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| scanning_indication.get(0))
+ .map(|s| {
+ styled_text(
+ self.foreground_color,
+ self.background_color,
+ &bold(&(s.to_string() + &self.animation_dots())),
+ )
+ })
+ .unwrap_or_else(String::new)
+ }
+ pub fn render_mid_length(&self) -> String {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| {
+ scanning_indication
+ .get(1)
+ .or_else(|| scanning_indication.get(0))
+ })
+ .map(|s| {
+ styled_text(
+ self.background_color,
+ self.foreground_color,
+ &bold(&(s.to_string() + &self.animation_dots())),
+ )
+ })
+ .unwrap_or_else(String::new)
+ }
+ pub fn render_short_length(&self) -> String {
+ self.scanning_indication
+ .as_ref()
+ .and_then(|scanning_indication| {
+ scanning_indication
+ .get(2)
+ .or_else(|| scanning_indication.get(1))
+ .or_else(|| scanning_indication.get(0))
+ })
+ .map(|s| {
+ styled_text(
+ self.background_color,
+ self.foreground_color,
+ &bold(&(s.to_string() + &self.animation_dots())),
+ )
+ })
+ .unwrap_or_else(String::new)
+ }
+ fn animation_dots(&self) -> String {
+ let mut to_render = String::from("");
+ let dot_count = self.animation_offset % 4;
+ for _ in 0..dot_count {
+ to_render.push('.');
+ }
+ to_render
+ }
+}
diff --git a/default-plugins/strider/src/search/mod.rs b/default-plugins/strider/src/search/mod.rs
new file mode 100644
index 00000000..5aba9c4d
--- /dev/null
+++ b/default-plugins/strider/src/search/mod.rs
@@ -0,0 +1,329 @@
+pub mod controls_line;
+pub mod search_results;
+pub mod search_state;
+pub mod selection_controls_area;
+pub mod ui;
+
+use crate::state::{CURRENT_SEARCH_TERM, ROOT};
+use crate::MessageToPlugin;
+use search_state::SearchType;
+use std::collections::{BTreeMap, BTreeSet, HashMap};
+use std::path::{Path, PathBuf};
+
+use unicode_width::UnicodeWidthStr;
+use zellij_tile::prelude::*;
+
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+use search_results::SearchResult;
+use serde::{Deserialize, Serialize};
+use walkdir::WalkDir;
+
+use std::io::{self, BufRead};
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct Search {
+ search_type: SearchType,
+ file_names: BTreeSet<String>,
+ file_contents: BTreeMap<(String, usize), String>, // file_name, line_number, line
+ cached_file_name_results: HashMap<String, Vec<SearchResult>>,
+ cached_file_contents_results: HashMap<String, Vec<SearchResult>>,
+}
+
+impl Search {
+ pub fn new(search_type: SearchType) -> Self {
+ Search {
+ search_type,
+ ..Default::default()
+ }
+ }
+ fn on_message(&mut self, message: String, payload: String) {
+ 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(),
+ );
+ },
+ Ok(MessageToSearch::Search) => {
+ if let Some(current_search_term) = self.read_search_term_from_hd_cache() {
+ self.search(current_search_term);
+ }
+ },
+ Ok(MessageToSearch::FileSystemCreate) => {
+ self.rescan_files(payload);
+ },
+ Ok(MessageToSearch::FileSystemUpdate) => {
+ self.rescan_files(payload);
+ },
+ Ok(MessageToSearch::FileSystemDelete) => {
+ self.delete_files(payload);
+ },
+ Err(e) => eprintln!("Failed to deserialize worker message {:?}", e),
+ }
+ }
+ pub fn scan_hd(&mut self) {
+ for entry in WalkDir::new(ROOT).into_iter().filter_map(|e| e.ok()) {
+ self.add_file_entry(entry.path(), entry.metadata().ok());
+ }
+ }
+ pub fn search(&mut self, search_term: String) {
+ let search_results_limit = 100; // artificial limit to prevent probably unwanted chaos
+ // let mut matcher = SkimMatcherV2::default().use_cache(true).element_limit(search_results_limit);
+ let mut file_names_search_results = None;
+ let mut file_contents_search_results = None;
+ if let SearchType::Names | SearchType::NamesAndContents = self.search_type {
+ let file_names_matches = match self.cached_file_name_results.get(&search_term) {
+ Some(cached_results) => cached_results.clone(),
+ None => {
+ let mut matcher = SkimMatcherV2::default().use_cache(true);
+ let results = self.search_file_names(&search_term, &mut matcher);
+ self.cached_file_name_results
+ .insert(search_term.clone(), results.clone());
+ results
+ },
+ };
+ file_names_search_results = Some(
+ ResultsOfSearch::new(search_term.clone(), file_names_matches)
+ .limit_search_results(search_results_limit),
+ );
+ };
+ if let SearchType::Contents | SearchType::NamesAndContents = self.search_type {
+ let file_contents_matches = match self.cached_file_contents_results.get(&search_term) {
+ Some(cached_results) => cached_results.clone(),
+ None => {
+ let mut matcher = SkimMatcherV2::default().use_cache(true);
+ let results = self.search_file_contents(&search_term, &mut matcher);
+ self.cached_file_contents_results
+ .insert(search_term.clone(), results.clone());
+ results
+ },
+ };
+ file_contents_search_results = Some(
+ ResultsOfSearch::new(search_term.clone(), file_contents_matches)
+ .limit_search_results(search_results_limit),
+ );
+ };
+
+ // if the search term changed before we finished, let's search again!
+ if let Some(current_search_term) = self.read_search_term_from_hd_cache() {
+ if current_search_term != search_term {
+ return self.search(current_search_term.into());
+ }
+ }
+ 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(),
+ );
+ }
+ 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(),
+ );
+ }
+ }
+ pub fn rescan_files(&mut self, paths: String) {
+ match serde_json::from_str::<Vec<PathBuf>>(&paths) {
+ Ok(paths) => {
+ for path in paths {
+ self.add_file_entry(&path, path.metadata().ok());
+ }
+ self.cached_file_name_results.clear();
+ self.cached_file_contents_results.clear();
+ },
+ Err(e) => eprintln!("Failed to deserialize paths: {:?}", e),
+ }
+ }
+ pub fn delete_files(&mut self, paths: String) {
+ match serde_json::from_str::<Vec<PathBuf>>(&paths) {
+ Ok(paths) => {
+ self.remove_existing_entries(&paths);
+ self.cached_file_name_results.clear();
+ self.cached_file_contents_results.clear();
+ },
+ Err(e) => eprintln!("Failed to deserialize paths: {:?}", e),
+ }
+ }
+ fn add_file_entry(&mut self, file_name: &Path, file_metadata: Option<std::fs::Metadata>) {
+ let file_path = file_name.display().to_string();
+ let file_path_stripped_prefix = self.strip_file_prefix(&file_name);
+
+ self.file_names.insert(file_path_stripped_prefix.clone());
+ if let SearchType::NamesAndContents | SearchType::Contents = self.search_type {
+ if file_metadata.map(|f| f.is_file()).unwrap_or(false) {
+ if let Ok(file) = std::fs::File::open(&file_path) {
+ let lines = io::BufReader::new(file).lines();
+ for (index, line) in lines.enumerate() {
+ match line {
+ Ok(line) => {
+ self.file_contents.insert(
+ (
+ // String::from_utf8_lossy(&strip_ansi_escapes::strip(file_path_stripped_prefix.clone()).unwrap()).to_string(),
+ file_path_stripped_prefix.clone(),
+ index + 1,
+ ),
+ String::from_utf8_lossy(
+ &strip_ansi_escapes::strip(line).unwrap(),
+ )
+ .to_string(),
+ );
+ },
+ Err(_) => {
+ break; // probably a binary file, skip it
+ },
+ }
+ }
+ }
+ }
+ }
+ }
+ fn search_file_names(
+ &self,
+ search_term: &str,
+ matcher: &mut SkimMatcherV2,
+ ) -> Vec<SearchResult> {
+ let mut matches = vec![];
+ for entry in &self.file_names {
+ if let Some((score, indices)) = matcher.fuzzy_indices(&entry, &search_term) {
+ matches.push(SearchResult::new_file_name(
+ score,
+ indices,
+ entry.to_owned(),
+ ));
+ }
+ }
+ matches
+ }
+ fn search_file_contents(
+ &self,
+ search_term: &str,
+ matcher: &mut SkimMatcherV2,
+ ) -> Vec<SearchResult> {
+ let mut matches = vec![];
+ for ((file_name, line_number), line_entry) in &self.file_contents {
+ if line_entry.contains("struct") {
+ if line_entry.len() < 400 {
+ eprintln!("matching against: {:?}", line_entry)
+ } else {
+ eprintln!("matching again line that has struct but is very long")
+ }
+ }
+ if let Some((score, indices)) = matcher.fuzzy_indices(&line_entry, &search_term) {
+ if line_entry.contains("struct") {
+ eprintln!("score: {:?}", score)
+ }
+ matches.push(SearchResult::new_file_line(
+ score,
+ indices,
+ file_name.clone(),
+ line_entry.clone(),
+ *line_number,
+ ));
+ } else {
+ if line_entry.contains("struct") {
+ eprintln!("no score!")
+ }
+ }
+ }
+ matches
+ }
+ fn strip_file_prefix(&self, file_name: &Path) -> String {
+ let mut file_path_stripped_prefix = file_name.display().to_string().split_off(ROOT.width());
+ if file_path_stripped_prefix.starts_with('/') {
+ file_path_stripped_prefix.remove(0);
+ }
+ file_path_stripped_prefix
+ }
+ fn read_search_term_from_hd_cache(&self) -> Option<String> {
+ match std::fs::read(CURRENT_SEARCH_TERM) {
+ Ok(current_search_term) => {
+ Some(String::from_utf8_lossy(&current_search_term).to_string())
+ },
+ _ => None,
+ }
+ }
+ fn remove_existing_entries(&mut self, paths: &Vec<PathBuf>) {
+ let file_path_stripped_prefixes: Vec<String> =
+ paths.iter().map(|p| self.strip_file_prefix(&p)).collect();
+ self.file_names
+ .retain(|file_name| !file_path_stripped_prefixes.contains(file_name));
+ self.file_contents.retain(|(file_name, _line_in_file), _| {
+ !file_path_stripped_prefixes.contains(file_name)
+ });
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub enum MessageToSearch {
+ ScanFolder,
+ Search,
+ FileSystemCreate,
+ FileSystemUpdate,
+ FileSystemDelete,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct FileNameWorker {
+ search: Search,
+}
+
+impl Default for FileNameWorker {
+ fn default() -> Self {
+ FileNameWorker {
+ search: Search::new(SearchType::Names),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct FileContentsWorker {
+ search: Search,
+}
+
+impl Default for FileContentsWorker {
+ fn default() -> Self {
+ FileContentsWorker {
+ search: Search::new(SearchType::Contents),
+ }
+ }
+}
+
+impl<'de> ZellijWorker<'de> for FileNameWorker {
+ fn on_message(&mut self, message: String, payload: String) {
+ self.search.on_message(message, payload);
+ }
+}
+
+impl<'de> ZellijWorker<'de> for FileContentsWorker {
+ fn on_message(&mut self, message: String, payload: String) {
+ self.search.on_message(message, payload);
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ResultsOfSearch {
+ pub search_term: String,
+ pub search_results: Vec<SearchResult>,
+}
+
+impl ResultsOfSearch {
+ pub fn new(search_term: String, search_results: Vec<SearchResult>) -> Self {
+ ResultsOfSearch {
+ search_term,
+ search_results,
+ }
+ }
+ pub fn limit_search_results(mut self, max_results: usize) -> Self {
+ self.search_results
+ .sort_by(|a, b| b.score().cmp(&a.score()));
+ self.search_results = if self.search_results.len() > max_results {
+ self.search_results.drain(..max_results).collect()
+ } else {
+ self.search_results.drain(..).collect()
+ };
+ self
+ }
+}
diff --git a/default-plugins/strider/src/search/search_results.rs b/default-plugins/strider/src/search/search_results.rs
new file mode 100644
index 00000000..b76200ae
--- /dev/null
+++ b/default-plugins/strider/src/search/search_results.rs
@@ -0,0 +1,308 @@
+use crate::search::ui::{
+ bold, styled_text, styled_text_background, styled_text_foreground, underline, GRAY_LIGHT,
+ GREEN, ORANGE,
+};
+use serde::{Deserialize, Serialize};
+use unicode_width::UnicodeWidthStr;
+
+#[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 is_same_entry(&self, other: &Self) -> bool {
+ match (&self, other) {
+ (
+ SearchResult::File { path: my_path, .. },
+ SearchResult::File {
+ path: other_path, ..
+ },
+ ) => my_path == other_path,
+ (
+ SearchResult::LineInFile {
+ path: my_path,
+ line_number: my_line_number,
+ ..
+ },
+ SearchResult::LineInFile {
+ path: other_path,
+ line_number: other_line_number,
+ ..
+ },
+ ) => my_path == other_path && my_line_number == other_line_number,
+ _ => false,
+ }
+ }
+ pub fn render(
+ &self,
+ max_width: usize,
+ is_selected: bool,
+ is_below_search_result: bool,
+ ) -> String {
+ let max_width = max_width.saturating_sub(4); // for the UI left line separator
+ match self {
+ SearchResult::File { path, indices, .. } => self.render_file_result(
+ path,
+ indices,
+ is_selected,
+ is_below_search_result,
+ max_width,
+ ),
+ SearchResult::LineInFile {
+ path,
+ line,
+ line_number,
+ indices,
+ ..
+ } => self.render_line_in_file_result(
+ path,
+ line,
+ *line_number,
+ indices,
+ is_selected,
+ is_below_search_result,
+ max_width,
+ ),
+ }
+ }
+ fn render_file_result(
+ &self,
+ path: &String,
+ indices: &Vec<usize>,
+ is_selected: bool,
+ is_below_search_result: bool,
+ max_width: usize,
+ ) -> String {
+ if is_selected {
+ let line = self.render_line_with_indices(
+ path,
+ indices,
+ max_width.saturating_sub(3),
+ Some(GREEN),
+ );
+ let selection_arrow = styled_text_foreground(ORANGE, "┌>");
+ format!("{} {}", selection_arrow, line)
+ } else {
+ let line_prefix = if is_below_search_result { "│ " } else { " " };
+ let line =
+ self.render_line_with_indices(path, indices, max_width.saturating_sub(3), None);
+ format!("{} {}", line_prefix, line)
+ }
+ }
+ fn render_line_in_file_result(
+ &self,
+ path: &String,
+ line: &String,
+ line_number: usize,
+ indices: &Vec<usize>,
+ is_selected: bool,
+ is_below_search_result: bool,
+ max_width: usize,
+ ) -> String {
+ let line_number_prefix_text = format!("└ {} ", line_number);
+ let max_width_of_line_in_file = max_width
+ .saturating_sub(3)
+ .saturating_sub(line_number_prefix_text.width());
+ if is_selected {
+ let file_name_line = self.render_line_with_indices(
+ path,
+ &vec![],
+ max_width.saturating_sub(3),
+ Some(GREEN),
+ );
+ let line_in_file = self.render_line_with_indices(
+ line,
+ indices,
+ max_width_of_line_in_file,
+ Some(GREEN),
+ );
+ let line_number_prefix = styled_text_foreground(GREEN, &bold(&line_number_prefix_text));
+ format!(
+ "{} {}\n│ {}{}",
+ styled_text_foreground(ORANGE, "┌>"),
+ file_name_line,
+ line_number_prefix,
+ line_in_file
+ )
+ } else {
+ let file_name_line =
+ self.render_line_with_indices(path, &vec![], max_width.saturating_sub(3), None);
+ let line_in_file =
+ self.render_line_with_indices(line, indices, max_width_of_line_in_file, None);
+ let line_number_prefix = bold(&line_number_prefix_text);
+ let line_prefix = if is_below_search_result { "│ " } else { " " };
+ format!(
+ "{} {}\n{} {}{}",
+ line_prefix, file_name_line, line_prefix, line_number_prefix, line_in_file
+ )
+ }
+ }
+ fn render_line_with_indices(
+ &self,
+ line_to_render: &String,
+ indices: &Vec<usize>,
+ max_width: usize,
+ foreground_color: Option<u8>,
+ ) -> String {
+ let non_index_character_style = |c: &str| match foreground_color {
+ Some(foreground_color) => styled_text_foreground(foreground_color, &bold(c)),
+ None => bold(c),
+ };
+ let index_character_style = |c: &str| match foreground_color {
+ Some(foreground_color) => {
+ styled_text(foreground_color, GRAY_LIGHT, &bold(&underline(c)))
+ },
+ None => styled_text_background(GRAY_LIGHT, &bold(&underline(c))),
+ };
+
+ let truncate_positions =
+ self.truncate_line_with_indices(line_to_render, indices, max_width);
+ let truncate_start_position = truncate_positions.map(|p| p.0).unwrap_or(0);
+ let truncate_end_position = truncate_positions
+ .map(|p| p.1)
+ .unwrap_or(line_to_render.chars().count());
+ let mut visible_portion = String::new();
+ for (i, character) in line_to_render.chars().enumerate() {
+ if i >= truncate_start_position && i <= truncate_end_position {
+ if indices.contains(&i) {
+ visible_portion.push_str(&index_character_style(&character.to_string()));
+ } else {
+ visible_portion.push_str(&non_index_character_style(&character.to_string()));
+ }
+ }
+ }
+ if truncate_positions.is_some() {
+ let left_truncate_sign = if truncate_start_position == 0 {
+ ""
+ } else {
+ ".."
+ };
+ let right_truncate_sign = if truncate_end_position == line_to_render.chars().count() {
+ ""
+ } else {
+ ".."
+ };
+ format!(
+ "{}{}{}",
+ non_index_character_style(left_truncate_sign),
+ visible_portion,
+ non_index_character_style(right_truncate_sign)
+ )
+ } else {
+ visible_portion
+ }
+ }
+ fn truncate_line_with_indices(
+ &self,
+ line_to_render: &String,
+ indices: &Vec<usize>,
+ max_width: usize,
+ ) -> Option<(usize, usize)> {
+ let first_index = indices.get(0).copied().unwrap_or(0);
+ let last_index = indices
+ .last()
+ .copied()
+ .unwrap_or_else(|| std::cmp::min(line_to_render.chars().count(), max_width));
+ if line_to_render.width() <= max_width {
+ // there's enough room, no need to truncate
+ None
+ } else if last_index.saturating_sub(first_index) < max_width {
+ // truncate around the indices
+ let mut width_remaining = max_width
+ .saturating_sub(1)
+ .saturating_sub(last_index.saturating_sub(first_index));
+
+ let mut string_start_position = first_index;
+ let mut string_end_position = last_index;
+
+ let mut i = 0;
+ loop {
+ if i >= width_remaining {
+ break;
+ }
+ if string_start_position > 0 && string_end_position < line_to_render.chars().count()
+ {
+ let take_from_start = i % 2 == 0;
+ if take_from_start {
+ string_start_position -= 1;
+ if string_start_position == 0 {
+ width_remaining += 2; // no need for truncating dots
+ }
+ } else {
+ string_end_position += 1;
+ if string_end_position == line_to_render.chars().count() {
+ width_remaining += 2; // no need for truncating dots
+ }
+ }
+ } else if string_end_position < line_to_render.chars().count() {
+ string_end_position += 1;
+ if string_end_position == line_to_render.chars().count() {
+ width_remaining += 2; // no need for truncating dots
+ }
+ } else if string_start_position > 0 {
+ string_start_position -= 1;
+ if string_start_position == 0 {
+ width_remaining += 2; // no need for truncating dots
+ }
+ } else {
+ break;
+ }
+ i += 1;
+ }
+ Some((string_start_position, string_end_position))
+ } else if !indices.is_empty() {
+ // no room for all indices, remove the last one and try again
+ let mut new_indices = indices.clone();
+ new_indices.pop();
+ self.truncate_line_with_indices(line_to_render, &new_indices, max_width)
+ } else {
+ Some((first_index, last_index))
+ }
+ }
+}
diff --git a/default-plugins/strider/src/search/search_state.rs b/default-plugins/strider/src/search/search_state.rs
new file mode 100644
index 00000000..5766d1c2
--- /dev/null
+++ b/default-plugins/strider/src/search/search_state.rs
@@ -0,0 +1,241 @@
+use crate::search::search_results::SearchResult;
+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,
+};
+
+pub const CURRENT_SEARCH_TERM: &str = "/data/current_search_term";
+
+#[derive(Default)]
+pub struct SearchState {
+ pub search_term: String,
+ pub file_name_search_results: Vec<SearchResult>,
+ pub file_contents_search_results: Vec<SearchResult>,
+ pub loading: bool,
+ pub loading_animation_offset: u8,
+ pub selected_search_result: usize,
+ pub should_open_floating: bool,
+ pub search_filter: SearchType,
+ pub display_rows: usize,
+ pub display_columns: usize,
+ pub displayed_search_results: (usize, Vec<SearchResult>), // usize is selected index
+}
+
+impl SearchState {
+ pub fn handle_key(&mut self, key: Key) {
+ match key {
+ Key::Down => self.move_search_selection_down(),
+ Key::Up => self.move_search_selection_up(),
+ Key::Char('\n') => self.open_search_result_in_editor(),
+ Key::BackTab => self.open_search_result_in_terminal(),
+ Key::Ctrl('f') => {
+ self.should_open_floating = !self.should_open_floating;
+ },
+ Key::Ctrl('r') => self.toggle_search_filter(),
+ Key::Esc => {
+ hide_self();
+ self.clear_state();
+ },
+ _ => self.append_to_search_term(key),
+ }
+ }
+ pub fn update_file_name_search_results(&mut self, mut results_of_search: ResultsOfSearch) {
+ if self.search_term == results_of_search.search_term {
+ self.file_name_search_results = results_of_search.search_results.drain(..).collect();
+ self.update_displayed_search_results();
+ }
+ }
+ pub fn update_file_contents_search_results(&mut self, mut results_of_search: ResultsOfSearch) {
+ if self.search_term == results_of_search.search_term {
+ self.file_contents_search_results =
+ results_of_search.search_results.drain(..).collect();
+ self.update_displayed_search_results();
+ }
+ }
+ pub fn change_size(&mut self, rows: usize, cols: usize) {
+ self.display_rows = rows;
+ self.display_columns = cols;
+ }
+ pub fn progress_animation(&mut self) {
+ if self.loading_animation_offset == u8::MAX {
+ self.loading_animation_offset = 0;
+ } else {
+ self.loading_animation_offset = self.loading_animation_offset.saturating_add(1);
+ }
+ }
+ pub fn number_of_lines_in_displayed_search_results(&self) -> usize {
+ self.displayed_search_results
+ .1
+ .iter()
+ .map(|l| l.rendered_height())
+ .sum()
+ }
+ fn move_search_selection_down(&mut self) {
+ if self.displayed_search_results.0 < self.max_search_selection_index() {
+ self.displayed_search_results.0 += 1;
+ }
+ }
+ fn move_search_selection_up(&mut self) {
+ self.displayed_search_results.0 = self.displayed_search_results.0.saturating_sub(1);
+ }
+ fn open_search_result_in_editor(&mut self) {
+ match self.selected_search_result_entry() {
+ Some(SearchResult::File { path, .. }) => {
+ if self.should_open_floating {
+ open_file_floating(&PathBuf::from(path))
+ } else {
+ open_file(&PathBuf::from(path));
+ }
+ },
+ Some(SearchResult::LineInFile {
+ path, line_number, ..
+ }) => {
+ if self.should_open_floating {
+ open_file_with_line_floating(&PathBuf::from(path), line_number);
+ } else {
+ open_file_with_line(&PathBuf::from(path), line_number);
+ }
+ },
+ None => eprintln!("Search results not found"),
+ }
+ }
+ fn open_search_result_in_terminal(&mut self) {
+ let dir_path_of_result = |path: &str| -> PathBuf {
+ let file_path = PathBuf::from(path);
+ let mut dir_path = file_path.components();
+ dir_path.next_back(); // remove file name to stay with just the folder
+ dir_path.as_path().into()
+ };
+ let selected_search_result_entry = self.selected_search_result_entry();
+ if let Some(SearchResult::File { path, .. }) | Some(SearchResult::LineInFile { path, .. }) =
+ selected_search_result_entry
+ {
+ let dir_path = dir_path_of_result(&path);
+ if self.should_open_floating {
+ open_terminal_floating(&dir_path);
+ } else {
+ open_terminal(&dir_path);
+ }
+ }
+ }
+ fn toggle_search_filter(&mut self) {
+ self.search_filter.progress();
+ self.send_search_query();
+ }
+ fn clear_state(&mut self) {
+ self.file_name_search_results.clear();
+ self.file_contents_search_results.clear();
+ self.displayed_search_results = (0, vec![]);
+ self.search_term.clear();
+ }
+ fn append_to_search_term(&mut self, key: Key) {
+ match key {
+ Key::Char(character) => {
+ self.search_term.push(character);
+ },
+ Key::Backspace => {
+ self.search_term.pop();
+ if self.search_term.len() == 0 {
+ self.clear_state();
+ }
+ },
+ _ => {},
+ }
+ self.send_search_query();
+ }
+ fn send_search_query(&mut self) {
+ 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(),
+ "".to_owned(),
+ );
+ post_message_to(
+ "file_contents_search",
+ serde_json::to_string(&MessageToSearch::Search).unwrap(),
+ "".to_owned(),
+ );
+ self.file_name_search_results.clear();
+ self.file_contents_search_results.clear();
+ }
+ },
+ Err(e) => eprintln!("Failed to write search term to HD, aborting search: {}", e),
+ }
+ }
+ fn max_search_selection_index(&self) -> usize {
+ self.displayed_search_results.1.len().saturating_sub(1)
+ }
+ fn update_displayed_search_results(&mut self) {
+ if self.search_term.is_empty() {
+ self.clear_state();
+ return;
+ }
+ let mut search_results_of_interest = match self.search_filter {
+ SearchType::NamesAndContents => {
+ let mut all_search_results = self.file_name_search_results.clone();
+ all_search_results.append(&mut self.file_contents_search_results.clone());
+ all_search_results.sort_by(|a, b| b.score().cmp(&a.score()));
+ all_search_results
+ },
+ SearchType::Names => self.file_name_search_results.clone(),
+ SearchType::Contents => self.file_contents_search_results.clone(),
+ };
+ let mut height_taken_up_by_results = 0;
+ let mut displayed_search_results = vec![];
+ for search_result in search_results_of_interest.drain(..) {
+ if height_taken_up_by_results + search_result.rendered_height()
+ > self.rows_for_results()
+ {
+ break;
+ }
+ height_taken_up_by_results += search_result.rendered_height();
+ displayed_search_results.push(search_result);
+ }
+ let new_index = self
+ .selected_search_result_entry()
+ .and_then(|currently_selected_search_result| {
+ displayed_search_results
+ .iter()
+ .position(|r| r.is_same_entry(&currently_selected_search_result))
+ })
+ .unwrap_or(0);
+ self.displayed_search_results = (new_index, displayed_search_results);
+ }
+ fn selected_search_result_entry(&self) -> Option<SearchResult> {
+ self.displayed_search_results
+ .1
+ .get(self.displayed_search_results.0)
+ .cloned()
+ }
+ pub fn rows_for_results(&self) -> usize {
+ self.display_rows.saturating_sub(3) // search line and 2 controls lines
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub enum SearchType {
+ NamesAndContents,
+ Names,
+ Contents,
+}
+
+impl SearchType {
+ pub fn progress(&mut self) {
+ match &self {
+ &SearchType::NamesAndContents => *self = SearchType::Names,
+ &SearchType::Names => *self = SearchType::Contents,
+ &SearchType::Contents => *self = SearchType::NamesAndContents,
+ }
+ }
+}
+
+impl Default for SearchType {
+ fn default() -> Self {
+ SearchType::NamesAndContents
+ }
+}
diff --git a/default-plugins/strider/src/search/selection_controls_area.rs b/default-plugins/strider/src/search/selection_controls_area.rs
new file mode 100644
index 00000000..0805e1e4
--- /dev/null
+++ b/default-plugins/strider/src/search/selection_controls_area.rs
@@ -0,0 +1,61 @@
+use crate::search::ui::{bold, styled_text_foreground, ORANGE};
+
+pub struct SelectionControlsArea {
+ display_lines: usize,
+ display_columns: usize,
+}
+
+impl SelectionControlsArea {
+ pub fn new(display_lines: usize, display_columns: usize) -> Self {
+ SelectionControlsArea {
+ display_lines,
+ display_columns,
+ }
+ }
+ pub fn render(&self, result_count: usize) -> String {
+ let mut to_render = String::new();
+ let padding = self.display_lines.saturating_sub(result_count);
+ for _ in 0..padding {
+ to_render.push_str(&self.render_padding_line());
+ }
+ let selection_controls = self.render_selection_controls();
+ to_render.push_str(&selection_controls);
+ to_render
+ }
+ pub fn render_empty_lines(&self) -> String {
+ let mut to_render = String::new();
+ for _ in 0..self.display_lines {
+ to_render.push_str("\n");
+ }
+ to_render
+ }
+ fn render_padding_line(&self) -> String {
+ format!("│\n")
+ }
+ fn render_selection_controls(&self) -> String {
+ if self.display_columns >= self.full_selection_controls_len() {
+ self.render_full_selection_controls()
+ } else {
+ self.render_truncated_selection_controls()
+ }
+ }
+ fn full_selection_controls_len(&self) -> usize {
+ 62
+ }
+ fn render_full_selection_controls(&self) -> String {
+ let arrow_tail = "└ ";
+ let enter = styled_text_foreground(ORANGE, &bold("<ENTER>"));
+ let enter_tip = bold(" - open in editor. ");
+ let tab = styled_text_foreground(ORANGE, &bold("<TAB>"));
+ let tab_tip = bold(" - open terminal at location.");
+ format!("{}{}{}{}{}", arrow_tail, enter, enter_tip, tab, tab_tip)
+ }
+ fn render_truncated_selection_controls(&self) -> String {
+ let arrow_tail = "└ ";
+ let enter = styled_text_foreground(ORANGE, &bold("<ENTER>"));
+ let enter_tip = bold(" - edit. ");
+ let tab = styled_text_foreground(ORANGE, &bold("<TAB>"));
+ let tab_tip = bold(" - terminal.");
+ format!("{}{}{}{}{}", arrow_tail, enter, enter_tip, tab, tab_tip)
+ }
+}
diff --git a/default-plugins/strider/src/search/ui.rs b/default-plugins/strider/src/search/ui.rs
new file mode 100644
index 00000000..5ee5a3e8
--- /dev/null
+++ b/default-plugins/strider/src/search/ui.rs
@@ -0,0 +1,120 @@
+use crate::search::controls_line::{Control, ControlsLine};
+use crate::search::search_state::SearchState;
+use crate::search::selection_controls_area::SelectionControlsArea;
+use std::fmt::{Display, Formatter, Result};
+
+pub const CYAN: u8 = 51;
+pub const GRAY_LIGHT: u8 = 238;
+pub const GRAY_DARK: u8 = 245;
+pub const WHITE: u8 = 15;
+pub const BLACK: u8 = 16;
+pub const RED: u8 = 124;
+pub const GREEN: u8 = 154;
+pub const ORANGE: u8 = 166;
+
+impl Display for SearchState {
+ fn fmt(&self, f: &mut Formatter) -> Result {
+ write!(f, "{}", self.render_search_line())?;
+ write!(f, "{}", self.render_search_results())?;
+ write!(f, "{}", self.render_selection_control_area())?;
+ write!(f, "{}", self.render_controls_line())?;
+ Ok(())
+ }
+}
+
+impl SearchState {
+ pub fn render_search_line(&self) -> String {
+ format!(
+ "{}{}\n",
+ styled_text_foreground(CYAN, &bold("SEARCH: ")),
+ self.search_term
+ )
+ }
+ pub fn render_search_results(&self) -> String {
+ let mut space_for_results = self.display_rows.saturating_sub(3); // title and both controls lines
+ let mut to_render = String::new();
+ for (i, search_result) in self.displayed_search_results.1.iter().enumerate() {
+ let result_height = search_result.rendered_height();
+ if space_for_results < result_height {
+ break;
+ }
+ space_for_results -= result_height;
+ let index_of_selected_result = self.displayed_search_results.0;
+ let is_selected = i == index_of_selected_result;
+ let is_below_search_result = i > index_of_selected_result;
+ let rendered_result =
+ search_result.render(self.display_columns, is_selected, is_below_search_result);
+ to_render.push_str(&format!("{}", rendered_result));
+ to_render.push('\n')
+ }
+ to_render
+ }
+ pub fn render_selection_control_area(&self) -> String {
+ let rows_for_results = self.rows_for_results();
+ if !self.displayed_search_results.1.is_empty() {
+ format!(
+ "{}\n",
+ SelectionControlsArea::new(rows_for_results, self.display_columns)
+ .render(self.number_of_lines_in_displayed_search_results())
+ )
+ } else {
+ format!(
+ "{}\n",
+ SelectionControlsArea::new(rows_for_results, self.display_columns)
+ .render_empty_lines()
+ )
+ }
+ }
+ pub fn render_controls_line(&self) -> String {
+ let has_results = !self.displayed_search_results.1.is_empty();
+ let tiled_floating_control =
+ Control::new_floating_control("Ctrl f", self.should_open_floating);
+ let names_contents_control = Control::new_filter_control("Ctrl r", &self.search_filter);
+ if self.loading {
+ ControlsLine::new(
+ vec![tiled_floating_control, names_contents_control],
+ Some(vec!["Scanning folder", "Scanning", "S"]),
+ )
+ .with_animation_offset(self.loading_animation_offset)
+ .render(self.display_columns, has_results)
+ } else {
+ ControlsLine::new(vec![tiled_floating_control, names_contents_control], None)
+ .render(self.display_columns, has_results)
+ }
+ }
+}
+
+pub fn bold(text: &str) -> String {
+ format!("\u{1b}[1m{}\u{1b}[m", text)
+}
+
+pub fn underline(text: &str) -> String {
+ format!("\u{1b}[4m{}\u{1b}[m", text)
+}
+
+pub fn styled_text(foreground_color: u8, background_color: u8, text: &str) -> String {
+ format!(
+ "\u{1b}[38;5;{};48;5;{}m{}\u{1b}[m",
+ foreground_color, background_color, text
+ )
+}
+
+pub fn styled_text_foreground(foreground_color: u8, text: &str) -> String {
+ format!("\u{1b}[38;5;{}m{}\u{1b}[m", foreground_color, text)
+}
+
+pub fn styled_text_background(background_color: u8, text: &str) -> String {
+ format!("\u{1b}[48;5;{}m{}\u{1b}[m", background_color, text)
+}
+
+pub fn color_line_to_end(background_color: u8) -> String {
+ format!("\u{1b}[48;5;{}m\u{1b}[0K", background_color)
+}
+
+pub fn arrow(foreground: u8, background: u8) -> String {
+ format!("\u{1b}[38;5;{}m\u{1b}[48;5;{}m", foreground, background)
+}
+
+pub fn dot(foreground: u8, background: u8) -> String {
+ format!("\u{1b}[38;5;{};48;5;{}m•", foreground, background)
+}
diff --git a/default-plugins/strider/src/state.rs b/default-plugins/strider/src/state.rs
index 7ced4932..8dd09653 100644
--- a/default-plugins/strider/src/state.rs
+++ b/default-plugins/strider/src/state.rs
@@ -1,4 +1,6 @@
-use crate::search::SearchResult;
+use crate::search::search_results::SearchResult;
+use crate::search::search_state::SearchState;
+use crate::search::search_state::SearchType;
use pretty_bytes::converter as pb;
use std::{
collections::{HashMap, VecDeque},
@@ -18,65 +20,29 @@ pub struct State {
pub cursor_hist: HashMap<PathBuf, (usize, usize)>,
pub hide_hidden_files: bool,
pub ev_history: VecDeque<(Event, Instant)>, // stores last event, can be expanded in future
+ pub search_state: SearchState,
pub search_paths: Vec<String>,
pub search_term: Option<String>,
- pub search_results: Vec<SearchResult>,
+ pub file_name_search_results: Vec<SearchResult>,
+ pub file_contents_search_results: Vec<SearchResult>,
pub loading: bool,
pub loading_animation_offset: u8,
pub typing_search_term: bool,
- pub exploring_search_results: bool,
pub selected_search_result: usize,
+ pub processed_search_index: usize,
+ pub should_open_floating: bool,
+ pub search_filter: SearchType,
}
impl State {
- pub fn append_to_search_term(&mut self, key: Key) {
- match key {
- Key::Char(character) => {
- if let Some(search_term) = self.search_term.as_mut() {
- search_term.push(character);
- }
- },
- Key::Backspace => {
- if let Some(search_term) = self.search_term.as_mut() {
- search_term.pop();
- if search_term.len() == 0 {
- self.search_term = None;
- self.typing_search_term = false;
- }
- }
- },
- _ => {},
- }
- }
- pub fn accept_search_term(&mut self) {
- self.typing_search_term = false;
- self.exploring_search_results = true;
- }
pub fn typing_search_term(&self) -> bool {
self.typing_search_term
}
- pub fn exploring_search_results(&self) -> bool {
- self.exploring_search_results
- }
- pub fn stop_exploring_search_results(&mut self) {
- self.exploring_search_results = false;
- }
pub fn start_typing_search_term(&mut self) {
- if self.search_term.is_none() {
- self.search_term = Some(String::new());
- }
self.typing_search_term = true;
}
pub fn stop_typing_search_term(&mut self) {
- self.typing_search_term = true;
- }
- pub fn move_search_selection_up(&mut self) {
- self.selected_search_result = self.selected_search_result.saturating_sub(1);
- }
- pub fn move_search_selection_down(&mut self) {
- if self.selected_search_result < self.search_results.len() {
- self.selected_search_result = self.selected_search_result.saturating_add(1);
- }
+ self.typing_search_term = false;
}
pub fn selected_mut(&mut self) -> &mut usize {
&mut self.cursor_hist.entry(self.path.clone()).or_default().0
@@ -104,32 +70,6 @@ impl State {
}
}
}
- pub fn open_search_result(&mut self) {
- match self.search_results.get(self.selected_search_result) {
- Some(SearchResult::File {
- path,
- score,
- indices,
- }) => {
- let file_path = PathBuf::from(path);
- open_file(file_path.strip_prefix(ROOT).unwrap());
- },
- Some(SearchResult::LineInFile {
- path,
- score,
- indices,
- line,
- line_number,
- }) => {
- let file_path = PathBuf::from(path);
- open_file_with_line(file_path.strip_prefix(ROOT).unwrap(), *line_number);
- // open_file_with_line(&file_path, *line_number); // TODO: no!!
- },
- None => {
- eprintln!("Search result not found");
- },
- }
- }
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 166a60c5..4e491142 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -32,7 +32,7 @@ use wasmer::Store;
use crate::{
os_input_output::ServerOsApi,
plugins::{plugin_thread_main, PluginInstruction},
- pty::{pty_thread_main, Pty, PtyInstruction},
+ pty::{get_default_shell, pty_thread_main, Pty, PtyInstruction},
screen::{screen_thread_main, ScreenInstruction},
thread_bus::{Bus, ThreadSenders},
};
@@ -705,6 +705,10 @@ fn init_session(
..Default::default()
})
});
+ let path_to_default_shell = config_options
+ .default_shell
+ .clone()
+ .unwrap_or_else(|| get_default_shell());
let pty_thread = thread::Builder::new()
.name("pty".to_string())
@@ -757,6 +761,7 @@ fn init_session(
})
.unwrap();
+ let zellij_cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let plugin_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
@@ -780,6 +785,8 @@ fn init_session(
data_dir,
plugins.unwrap_or_default(),
layout,
+ path_to_default_shell,
+ zellij_cwd,
)
.fatal()
}
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index a85cf80f..071f6e7c 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -25,7 +25,7 @@ use zellij_utils::{
data::{ModeInfo, Style},
errors::prelude::*,
input::command::RunCommand,
- input::layout::FloatingPaneLayout,
+ input::layout::{FloatingPaneLayout, Run, RunPlugin},
pane_size::{Dimension, Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
@@ -870,4 +870,19 @@ impl FloatingPanes {
self.focus_pane_for_all_clients(active_pane_id);
}
}
+ pub fn get_plugin_pane_id(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ }
+ pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> {
+ if self.panes.get(&pane_id).is_some() {
+ self.focus_pane(pane_id, client_id);
+ Ok(())
+ } else {
+ Err(anyhow!("Pane not found"))
+ }
+ }
}
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 7915069c..28fdb3d1 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -537,7 +537,7 @@ impl Pane for PluginPane {
self.pane_title = title;
}
fn update_loading_indication(&mut self, loading_indication: LoadingIndication) {
- if self.loading_indication.ended {
+ if self.loading_indication.ended && !loading_indication.is_error() {
return;
}
self.loading_indication.merge(loading_indication);
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 08a633b0..3734214a 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -20,7 +20,10 @@ use stacked_panes::StackedPanes;
use zellij_utils::{
data::{Direction, ModeInfo, ResizeStrategy, Style},
errors::prelude::*,
- input::{command::RunCommand, layout::SplitDirection},
+ input::{
+ command::RunCommand,
+ layout::{Run, RunPlugin, SplitDirection},
+ },
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
@@ -529,6 +532,14 @@ impl TiledPanes {
}
self.reset_boundaries();
}
+ pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> {
+ if self.panes.get(&pane_id).is_some() {
+ self.focus_pane(pane_id, client_id);
+ Ok(())
+ } else {
+ Err(anyhow!("Pane not found"))
+ }
+ }
pub fn focus_pane_at_position(&mut self, position_and_size: PaneGeom, client_id: ClientId) {
if let Some(pane_id) = self
.panes
@@ -1691,6 +1702,13 @@ impl TiledPanes {
fn reset_boundaries(&mut self) {
self.client_id_to_boundaries.clear();
}
+ pub fn get_plugin_pane_id(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ }
}
#[allow(clippy::borrowed_box)]
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 9384b3f6..7b322b05 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -1,6 +1,8 @@
mod plugin_loader;
mod plugin_map;
+mod plugin_worker;
mod wasm_bridge;
+mod watch_filesystem;
mod zellij_exports;
use log::info;
use std::{collections::HashMap, fs, path::PathBuf};
@@ -104,13 +106,22 @@ pub(crate) fn plugin_thread_main(
data_dir: PathBuf,
plugins: PluginsConfig,
layout: Box<Layout>,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
info!("Wasm main thread starts");
let plugin_dir = data_dir.join("plugins/");
let plugin_global_data_dir = plugin_dir.join("data");
- let mut wasm_bridge = WasmBridge::new(plugins, bus.senders.clone(), store, plugin_dir);
+ let mut wasm_bridge = WasmBridge::new(
+ plugins,
+ bus.senders.clone(),
+ store,
+ plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
+ );
loop {
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs
index 91eb6c93..4d77bd9e 100644
--- a/zellij-server/src/plugins/plugin_loader.rs
+++ b/zellij-server/src/plugins/plugin_loader.rs
@@ -1,6 +1,5 @@
-use crate::plugins::plugin_map::{
- PluginEnv, PluginMap, RunningPlugin, RunningWorker, Subscriptions,
-};
+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, zellij_exports};
use crate::plugins::PluginId;
use highway::{HighwayHash, PortableHash};
@@ -164,6 +163,8 @@ pub struct PluginLoader<'a> {
plugin_own_data_dir: PathBuf,
size: Size,
wasm_blob_on_hd: Option<(Vec<u8>, PathBuf)>,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
}
impl<'a> PluginLoader<'a> {
@@ -176,6 +177,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to reload plugin {plugin_id} from memory");
let mut connected_clients: Vec<ClientId> =
@@ -194,6 +197,8 @@ impl<'a> PluginLoader<'a> {
first_client_id,
&store,
&plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.load_module_from_memory()
@@ -227,6 +232,8 @@ impl<'a> PluginLoader<'a> {
size: Size,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}");
let mut plugin_loader = PluginLoader::new(
@@ -240,6 +247,8 @@ impl<'a> PluginLoader<'a> {
&plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.load_module_from_memory()
@@ -273,6 +282,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let mut new_plugins = HashSet::new();
for plugin_id in plugin_map.lock().unwrap().plugin_ids() {
@@ -288,6 +299,8 @@ impl<'a> PluginLoader<'a> {
existing_client_id,
&store,
&plugin_dir,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
)?;
plugin_loader
.load_module_from_memory()
@@ -314,6 +327,8 @@ impl<'a> PluginLoader<'a> {
plugin_map: Arc<Mutex<PluginMap>>,
connected_clients: Arc<Mutex<Vec<ClientId>>>,
loading_indication: &mut LoadingIndication,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<()> {
let err_context = || format!("failed to reload plugin id {plugin_id}");
@@ -333,6 +348,8 @@ impl<'a> PluginLoader<'a> {
first_client_id,
&store,
&plugin_dir,
+ path_to_default_shell,
+ zellij_cwd,
)?;
plugin_loader
.compile_module()
@@ -363,6 +380,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir: &'a PathBuf,
tab_index: usize,
size: Size,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
@@ -383,6 +402,8 @@ impl<'a> PluginLoader<'a> {
plugin_own_data_dir,
size,
wasm_blob_on_hd: None,
+ path_to_default_shell,
+ zellij_cwd,
})
}
pub fn new_from_existing_plugin_attributes(
@@ -394,6 +415,8 @@ impl<'a> PluginLoader<'a> {
client_id: ClientId,
store: &Store,
plugin_dir: &'a PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let (running_plugin, _subscriptions, _workers) = {
@@ -421,6 +444,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)
}
pub fn new_from_different_client_id(
@@ -432,6 +457,8 @@ impl<'a> PluginLoader<'a> {
client_id: ClientId,
store: &Store,
plugin_dir: &'a PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let running_plugin = {
@@ -460,6 +487,8 @@ impl<'a> PluginLoader<'a> {
plugin_dir,
tab_index,
size,
+ path_to_default_shell,
+ zellij_cwd,
)
}
pub fn load_module_from_memory(&mut self) -> Result<Module> {
@@ -625,7 +654,8 @@ impl<'a> PluginLoader<'a> {
let worker =
RunningWorker::new(instance, &function_name, plugin_config, plugin_env);
- workers.insert(function_name.into(), Arc::new(Mutex::new(worker)));
+ let worker_sender = plugin_worker(worker);
+ workers.insert(function_name.into(), worker_sender);
}
}
start_function.call(&[]).with_context(err_context)?;
@@ -689,6 +719,8 @@ impl<'a> PluginLoader<'a> {
*client_id,
&self.store,
&self.plugin_dir,
+ self.path_to_default_shell.clone(),
+ self.zellij_cwd.clone(),
)?;
plugin_loader_for_client
.load_module_from_memory()
@@ -746,7 +778,7 @@ impl<'a> PluginLoader<'a> {
};
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
- .map_dir("/host", ".")
+ .map_dir("/host", self.zellij_cwd.clone())
.and_then(|wasi| wasi.map_dir("/data", &self.plugin_own_data_dir))
.and_then(|wasi| wasi.map_dir("/tmp", ZELLIJ_TMP_DIR.as_path()))
.and_then(|wasi| {
@@ -771,6 +803,7 @@ impl<'a> PluginLoader<'a> {
wasi_env,
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
tab_index: self.tab_index,
+ path_to_default_shell: self.path_to_default_shell.clone(),
};
let subscriptions = Arc::new(Mutex::new(HashSet::new()));
diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs
index 0c3df931..040c9f9f 100644
--- a/zellij-server/src/plugins/plugin_map.rs
+++ b/zellij-server/src/plugins/plugin_map.rs
@@ -1,5 +1,4 @@
-use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
-use crate::plugins::zellij_exports::wasi_write_object;
+use crate::plugins::plugin_worker::MessageToWorker;
use crate::plugins::PluginId;
use std::{
collections::{HashMap, HashSet},
@@ -11,10 +10,10 @@ use wasmer_wasi::WasiEnv;
use crate::{thread_bus::ThreadSenders, ClientId};
+use zellij_utils::async_channel::Sender;
use zellij_utils::errors::prelude::*;
use zellij_utils::{
- consts::VERSION, data::EventType, input::layout::RunPluginLocation,
- input::plugins::PluginConfig,
+ data::EventType, input::layout::RunPluginLocation, input::plugins::PluginConfig,
};
// the idea here is to provide atomicity when adding/removing plugins from the map (eg. when a new
@@ -29,7 +28,7 @@ pub struct PluginMap {
(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
),
>,
}
@@ -41,7 +40,7 @@ impl PluginMap {
) -> Vec<(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
)> {
let mut removed = vec![];
let ids_in_plugin_map: Vec<(PluginId, ClientId)> =
@@ -62,7 +61,7 @@ impl PluginMap {
) -> Option<(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
- HashMap<String, Arc<Mutex<RunningWorker>>>,
+ HashMap<String, Sender<MessageToWorker>>,
)> {
self.plugin_assets.remove(&(plugin_id, client_id))
}
@@ -132,12 +131,12 @@ impl PluginMap {
.and_then(|(_, (running_plugin, _, _))| Some(running_plugin.clone())),
}
}
- pub fn clone_worker(
+ pub fn worker_sender(
&self,
plugin_id: PluginId,
client_id: ClientId,
worker_name: &str,
- ) -> Option<Arc<Mutex<RunningWorker>>> {
+ ) -> Option<Sender<MessageToWorker>> {
self.plugin_assets
.iter()
.find(|((p_id, c_id), _)| p_id == &plugin_id && c_id == &client_id)
@@ -174,7 +173,7 @@ impl PluginMap {
client_id: ClientId,
running_plugin: Arc<Mutex<RunningPlugin>>,
subscriptions: Arc<Mutex<Subscriptions>>,
- running_workers: HashMap<String, Arc<Mutex<RunningWorker>>>,
+ running_workers: HashMap<String, Sender<MessageToWorker>>,
) {
self.plugin_assets.insert(
(plugin_id, client_id),
@@ -195,6 +194,7 @@ pub struct PluginEnv {
pub client_id: ClientId,
#[allow(dead_code)]
pub plugin_own_data_dir: PathBuf,
+ pub path_to_default_shell: PathBuf,
}
impl PluginEnv {
@@ -256,53 +256,3 @@ impl RunningPlugin {
}
}
}
-
-pub struct RunningWorker {
- pub instance: Instance,
- pub name: String,
- pub plugin_config: PluginConfig,
- pub plugin_env: PluginEnv,
-}
-
-impl RunningWorker {
- pub fn new(
- instance: Instance,
- name: &str,
- plugin_config: PluginConfig,
- plugin_env: PluginEnv,
- ) -> Self {
- RunningWorker {
- instance,
- name: name.into(),
- plugin_config,
- plugin_env,
- }
- }
- pub fn send_message(&self, message: String, payload: String) -> Result<()> {
- let err_context = || format!("Failed to send message to worker");
-
- 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),
- }
- })?;
-
- Ok(())
- }
-}
diff --git a/zellij-server/src/plugins/plugin_worker.rs b/zellij-server/src/plugins/plugin_worker.rs
new file mode 100644
index 00000000..bc7303c7
--- /dev/null
+++ b/zellij-server/src/plugins/plugin_worker.rs
@@ -0,0 +1,89 @@
+use crate::plugins::plugin_loader::VersionMismatchError;
+use crate::plugins::plugin_map::PluginEnv;
+use crate::plugins::zellij_exports::wasi_write_object;
+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};
+
+pub struct RunningWorker {
+ pub instance: Instance,
+ pub name: String,
+ pub plugin_config: PluginConfig,
+ pub plugin_env: PluginEnv,
+}
+
+impl RunningWorker {
+ pub fn new(
+ instance: Instance,
+ name: &str,
+ plugin_config: PluginConfig,
+ plugin_env: PluginEnv,
+ ) -> Self {
+ RunningWorker {
+ instance,
+ name: name.into(),
+ plugin_config,
+ plugin_env,
+ }
+ }
+ pub fn send_message(&self, message: String, payload: String) -> Result<()> {
+ let err_context = || format!("Failed to send message to worker");
+
+ 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),
+ }
+ })?;
+
+ Ok(())
+ }
+}
+
+pub enum MessageToWorker {
+ Message(String, String), // message, payload
+ Exit,
+}
+
+pub fn plugin_worker(worker: RunningWorker) -> Sender<MessageToWorker> {
+ let (sender, receiver): (Sender<MessageToWorker>, Receiver<MessageToWorker>) = unbounded();
+ task::spawn({
+ async move {
+ loop {
+ match receiver.recv().await {
+ Ok(MessageToWorker::Message(message, payload)) => {
+ if let Err(e) = worker.send_message(message, payload) {
+ log::error!("Failed to send message to worker: {:?}", e);
+ }
+ },
+ Ok(MessageToWorker::Exit) => {
+ break;
+ },
+ Err(e) => {
+ log::error!("Failed to receive worker message on channel: {:?}", e);
+ break;
+ },
+ }
+ }
+ }
+ });
+ sender
+}
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index d1e79a5f..d41a477b 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -50,11 +50,14 @@ macro_rules! log_actions_in_thread {
};
}
-fn create_plugin_thread() -> (
+fn create_plugin_thread(
+ zellij_cwd: Option<PathBuf>,
+) -> (
SenderWithContext<PluginInstruction>,
Receiver<(ScreenInstruction, ErrorContext)>,
Box<dyn FnMut()>,
) {
+ let zellij_cwd = zellij_cwd.unwrap_or_else(|| PathBuf::from("."));
let (to_server, _server_receiver): ChannelWithContext<ServerInstruction> =
channels::bounded(50);
let to_server = SenderWithContext::new(to_server);
@@ -88,6 +91,7 @@ fn create_plugin_thread() -> (
.should_silently_fail();
let store = Store::new(&wasmer::Universal::new(wasmer::Singlepass::default()).engine());
let data_dir = PathBuf::from(tempdir().unwrap().path());
+ let default_shell = PathBuf::from(".");
let _plugin_thread = std::thread::Builder::new()
.name("plugin_thread".to_string())
.spawn(move || {
@@ -98,6 +102,8 @@ fn create_plugin_thread() -> (
data_dir,
PluginsConfig::default(),
Box::new(Layout::default()),
+ default_shell,
+ zellij_cwd,
)
.expect("TEST")
})
@@ -134,7 +140,7 @@ pub fn load_new_plugin_from_hd() {
// message (this is what the fixture plugin does)
// we then listen on our mock screen receiver to make sure we got a PluginBytes instruction
// that contains said render, and assert against it
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -192,7 +198,7 @@ pub fn load_new_plugin_from_hd() {
#[test]
#[ignore]
pub fn plugin_workers() {
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -253,7 +259,7 @@ pub fn plugin_workers() {
#[test]
#[ignore]
pub fn plugin_workers_persist_state() {
- let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread();
+ let (plugin_thread_sender, screen_receiver, mut teardown) = create_plugin_thread(None);
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPlugin {
@@ -318,3 +324,71 @@ pub fn plugin_workers_persist_state() {
});
assert_snapshot!(format!("{:#?}", plugin_bytes_event));
}
+
+#[test]
+#[ignore]
+pub fn can_subscribe_to_hd_events() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let screen_thread = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::PluginBytes,
+ screen_receiver,
+ 3
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::InputReceived,
+ )])); // will be cached and sent to the plugin once it's loaded
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ std::fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(PathBuf::from(temp_folder.path()).join("test1"))
+ .unwrap();
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let plugin_bytes_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::PluginBytes(plugin_bytes) = i {
+ for (plugin_id, client_id, plugin_bytes) in plugin_bytes {
+ let plugin_bytes = String::from_utf8_lossy(plugin_bytes).to_string();
+ if plugin_bytes.contains("FileSystem") {
+ return Some((*plugin_id, *client_id, plugin_bytes));
+ }
+ }
+ }
+ None
+ });
+ assert_snapshot!(format!("{:#?}", plugin_bytes_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap
new file mode 100644
index 00000000..57e31f15
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__can_subscribe_to_hd_events.snap
@@ -0,0 +1,12 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 387
+expression: "format!(\"{:#?}\", plugin_bytes_event)"
+---
+Some(
+ (
+ 0,
+ 1,
+ "Rows: 20, Cols: 121, Received events: [InputReceived, FileSystemRead([\"/host/test1\"])]\n\r",
+ ),
+)
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 0015552c..1ede55e2 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -1,24 +1,24 @@
use super::{PluginId, PluginInstruction};
use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
-use crate::plugins::plugin_map::{
- AtomicEvent, PluginEnv, PluginMap, RunningPlugin, RunningWorker, Subscriptions,
-};
+use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions};
+use crate::plugins::plugin_worker::MessageToWorker;
+use crate::plugins::watch_filesystem::watch_filesystem;
use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
use log::info;
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
str::FromStr,
- sync::{Arc, Mutex, TryLockError},
+ sync::{Arc, Mutex},
};
use wasmer::{Instance, Module, Store, Value};
use zellij_utils::async_std::task::{self, JoinHandle};
+use zellij_utils::notify::{RecommendedWatcher, Watcher};
use crate::{
background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders,
ui::loading_indication::LoadingIndication, ClientId,
};
-
use zellij_utils::{
consts::VERSION,
data::{Event, EventType},
@@ -30,8 +30,6 @@ use zellij_utils::{
pane_size::Size,
};
-const RETRY_INTERVAL_MS: u64 = 100;
-
pub struct WasmBridge {
connected_clients: Arc<Mutex<Vec<ClientId>>>,
plugins: PluginsConfig,
@@ -49,6 +47,9 @@ pub struct WasmBridge {
// payload>
loading_plugins: HashMap<(PluginId, RunPlugin), JoinHandle<()>>, // plugin_id to join-handle
pending_plugin_reloads: HashSet<RunPlugin>,
+ path_to_default_shell: PathBuf,
+ watcher: Option<RecommendedWatcher>,
+ zellij_cwd: PathBuf,
}
impl WasmBridge {
@@ -57,11 +58,20 @@ impl WasmBridge {
senders: ThreadSenders,
store: Store,
plugin_dir: PathBuf,
+ path_to_default_shell: PathBuf,
+ zellij_cwd: PathBuf,
) -> Self {
let plugin_map = Arc::new(Mutex::new(PluginMap::default()));
let connected_clients: Arc<Mutex<Vec<ClientId>>> = Arc::new(Mutex::new(vec![]));
let plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>> =
Arc::new(Mutex::new(HashMap::new()));
+ let watcher = match watch_filesystem(senders.clone(), &zellij_cwd) {
+ Ok(watcher) => Some(watcher),
+ Err(e) => {
+ log::error!("Failed to watch filesystem: {:?}", e);
+ None
+ },
+ };
WasmBridge {
connected_clients,
plugins,
@@ -70,12 +80,15 @@ impl WasmBridge {
plugin_dir,
plugin_cache,
plugin_map,
+ path_to_default_shell,
+ watcher,
next_plugin_id: 0,
cached_events_for_pending_plugins: HashMap::new(),
cached_resizes_for_pending_plugins: HashMap::new(),
cached_worker_messages: HashMap::new(),
loading_plugins: HashMap::new(),
pending_plugin_reloads: HashSet::new(),
+ zellij_cwd,
}
}
pub fn load_plugin(
@@ -122,6 +135,8 @@ impl WasmBridge {
let store = self.store.clone();
let plugin_map = self.plugin_map.clone();
let connected_clients = self.connected_clients.clone();
+ let path_to_default_shell = self.path_to_default_shell.clone();
+ let zellij_cwd = self.zellij_cwd.clone();
async move {
let _ =
senders.send_to_background_jobs(BackgroundJob::AnimatePluginLoading(plugin_id));
@@ -139,6 +154,8 @@ impl WasmBridge {
size,
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell,
+ zellij_cwd.clone(),
) {
Ok(_) => handle_plugin_successful_loading(&senders, plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -160,7 +177,10 @@ impl WasmBridge {
pub fn unload_plugin(&mut self, pid: PluginId) -> Result<()> {
info!("Bye from plugin {}", &pid);
let mut plugin_map = self.plugin_map.lock().unwrap();
- for (running_plugin, _, _) in plugin_map.remove_plugins(pid) {
+ for (running_plugin, _, workers) in plugin_map.remove_plugins(pid) {
+ for (_worker_name, worker_sender) in workers {
+ drop(worker_sender.send(MessageToWorker::Exit));
+ }
let running_plugin = running_plugin.lock().unwrap();
let cache_dir = running_plugin.plugin_env.plugin_own_data_dir.clone();
if let Err(e) = std::fs::remove_dir_all(cache_dir) {
@@ -195,6 +215,8 @@ impl WasmBridge {
let store = self.store.clone();
let plugin_map = self.plugin_map.clone();
let connected_clients = self.connected_clients.clone();
+ let path_to_default_shell = self.path_to_default_shell.clone();
+ let zellij_cwd = self.zellij_cwd.clone();
async move {
match PluginLoader::reload_plugin(
first_plugin_id,
@@ -205,6 +227,8 @@ impl WasmBridge {
plugin_map.clone(),
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
) {
Ok(_) => {
handle_plugin_successful_loading(&senders, first_plugin_id);
@@ -223,6 +247,8 @@ impl WasmBridge {
plugin_map.clone(),
connected_clients.clone(),
&mut loading_indication,
+ path_to_default_shell.clone(),
+ zellij_cwd.clone(),
) {
Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -263,6 +289,8 @@ impl WasmBridge {
self.plugin_map.clone(),
self.connected_clients.clone(),
&mut loading_indication,
+ self.path_to_default_shell.clone(),
+ self.zellij_cwd.clone(),
) {
Ok(_) => {
let _ = self
@@ -414,7 +442,17 @@ impl WasmBridge {
));
},
Err(e) => {
- log::error!("{}", e);
+ log::error!("{:?}", e);
+
+ // https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
+ let stringified_error =
+ format!("{:?}", e).replace("\n", "\n\r");
+
+ handle_plugin_crash(
+ plugin_id,
+ stringified_error,
+ senders.clone(),
+ );
},
}
}
@@ -460,6 +498,7 @@ impl WasmBridge {
for plugin_id in &plugin_ids {
drop(self.unload_plugin(*plugin_id));
}
+ drop(self.watcher.as_mut().map(|w| w.unwatch(&self.zellij_cwd)));
}
fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<&RunPlugin> {
self.loading_plugins
@@ -599,36 +638,23 @@ impl WasmBridge {
self.plugin_map
.lock()
.unwrap()
- .clone_worker(plugin_id, client_id, &worker_name);
- let mut cache_messages = || {
- for (message, payload) in messages.drain(..) {
- self.cached_worker_messages
- .entry(plugin_id)
- .or_default()
- .push((client_id, worker_name.clone(), message, payload));
- }
- };
+ .worker_sender(plugin_id, client_id, &worker_name);
match worker {
Some(worker) => {
- let worker_is_busy = { worker.try_lock().is_err() };
- if worker_is_busy {
- // most messages will be caught here, we do this once before the async task to
- // bulk most messages together and prevent them from cascading
- cache_messages();
- } else {
- async_send_messages_to_worker(
- self.senders.clone(),
- messages,
- worker,
- plugin_id,
- client_id,
- worker_name,
- );
+ for (message, payload) in messages.drain(..) {
+ if let Err(e) = worker.try_send(MessageToWorker::Message(message, payload)) {
+ log::error!("Failed to send message to worker: {:?}", e);
+ }
}
},
None => {
- log::warn!("Worker {worker_name} not found, placing message in cache");
- cache_messages();
+ log::warn!("Worker {worker_name} not found, caching messages");
+ for (message, payload) in messages.drain(..) {
+ self.cached_worker_messages
+ .entry(plugin_id)
+ .or_default()
+ .push((client_id, worker_name.clone(), message, payload));
+ }
},
}
Ok(())
@@ -708,52 +734,12 @@ pub fn apply_event_to_plugin(
Ok(())
}
-fn async_send_messages_to_worker(
- senders: ThreadSenders,
- mut messages: Vec<(String, String)>,
- worker: Arc<Mutex<RunningWorker>>,
- plugin_id: PluginId,
- client_id: ClientId,
- worker_name: String,
-) {
- task::spawn({
- async move {
- match worker.try_lock() {
- Ok(worker) => {
- for (message, payload) in messages.drain(..) {
- worker.send_message(message, payload).ok();
- }
- let _ = senders
- .send_to_plugin(PluginInstruction::ApplyCachedWorkerMessages(plugin_id));
- },
- Err(TryLockError::WouldBlock) => {
- task::spawn({
- async move {
- log::warn!(
- "Worker {} busy, retrying sending message after: {}ms",
- worker_name,
- RETRY_INTERVAL_MS
- );
- task::sleep(std::time::Duration::from_millis(RETRY_INTERVAL_MS)).await;
- let _ = senders.send_to_plugin(
- PluginInstruction::PostMessagesToPluginWorker(
- plugin_id,
- client_id,
- worker_name,
- messages,
- ),
- );
- }
- });
- },
- Err(e) => {
- log::error!(
- "Failed to send message to worker \"{}\": {:?}",
- worker_name,
- e
- );
- },
- }
- }
- });
+pub fn handle_plugin_crash(plugin_id: PluginId, message: String, senders: ThreadSenders) {
+ let mut loading_indication = LoadingIndication::new("Panic!".to_owned());
+ loading_indication.indicate_loading_error(message);
+ let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
+ plugin_id,
+ loading_indication,
+ ));
+ let _ = senders.send_to_plugin(PluginInstruction::Unload(plugin_id));
}
diff --git a/zellij-server/src/plugins/watch_filesystem.rs b/zellij-server/src/plugins/watch_filesystem.rs
new file mode 100644
index 00000000..864bd45f
--- /dev/null
+++ b/zellij-server/src/plugins/watch_filesystem.rs
@@ -0,0 +1,63 @@
+use super::PluginInstruction;
+use std::path::PathBuf;
+
+use crate::thread_bus::ThreadSenders;
+use std::path::Path;
+
+use zellij_utils::{data::Event, errors::prelude::*};
+
+use zellij_utils::notify::{self, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
+pub fn watch_filesystem(senders: ThreadSenders, zellij_cwd: &Path) -> Result<RecommendedWatcher> {
+ let path_prefix_in_plugins = PathBuf::from("/host");
+ let current_dir = PathBuf::from(zellij_cwd);
+ let mut watcher = notify::recommended_watcher({
+ move |res: notify::Result<notify::Event>| match res {
+ Ok(event) => {
+ let paths: Vec<PathBuf> = event
+ .paths
+ .iter()
+ .map(|p| {
+ let stripped_prefix_path =
+ p.strip_prefix(&current_dir).unwrap_or_else(|_| p);
+ path_prefix_in_plugins.join(stripped_prefix_path)
+ })
+ .collect();
+ match event.kind {
+ EventKind::Access(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemRead(paths),
+ )]));
+ },
+ EventKind::Create(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemCreate(paths),
+ )]));
+ },
+ EventKind::Modify(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemUpdate(paths),
+ )]));
+ },
+ EventKind::Remove(_) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::FileSystemDelete(paths),
+ )]));
+ },
+ _ => {},
+ }
+ },
+ Err(e) => log::error!("watch error: {:?}", e),
+ }
+ })?;
+
+ watcher.watch(zellij_cwd, RecursiveMode::Recursive)?;
+ Ok(watcher)
+}
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 3515cb34..d64d867b 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -1,5 +1,6 @@
use super::PluginInstruction;
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
+use crate::plugins::wasm_bridge::handle_plugin_crash;
use log::{debug, warn};
use serde::{de::DeserializeOwned, Serialize};
use std::{
@@ -23,7 +24,10 @@ use zellij_utils::{
consts::VERSION,
data::{Event, EventType, PluginIds},
errors::prelude::*,
- input::{command::TerminalAction, plugins::PluginType},
+ input::{
+ command::{RunCommand, TerminalAction},
+ plugins::PluginType,
+ },
serde,
};
@@ -50,13 +54,18 @@ pub fn zellij_exports(
host_get_plugin_ids,
host_get_zellij_version,
host_open_file,
+ host_open_file_floating,
host_open_file_with_line,
+ host_open_file_with_line_floating,
+ host_open_terminal,
+ host_open_terminal_floating,
host_switch_tab_to,
host_set_timeout,
host_exec_cmd,
host_report_panic,
host_post_message_to,
host_post_message_to_plugin,
+ host_hide_self,
}
}
@@ -160,9 +169,30 @@ fn host_open_file(env: &ForeignFunctionEnv) {
.senders
.send_to_pty(PtyInstruction::SpawnTerminal(
Some(TerminalAction::OpenFile(path, None, None)),
+ Some(false),
None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open file on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_file_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::OpenFile(path, None, None)),
+ Some(true),
None,
- ClientOrTabIndex::TabIndex(env.plugin_env.tab_index),
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
))
})
.with_context(|| {
@@ -181,9 +211,30 @@ fn host_open_file_with_line(env: &ForeignFunctionEnv) {
.senders
.send_to_pty(PtyInstruction::SpawnTerminal(
Some(TerminalAction::OpenFile(path, Some(line), None)), // TODO: add cwd
+ Some(false),
None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open file on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_file_with_line_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<(PathBuf, usize)>(&env.plugin_env.wasi_env)
+ .and_then(|(path, line)| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::OpenFile(path, Some(line), None)), // TODO: add cwd
+ Some(true),
None,
- ClientOrTabIndex::TabIndex(env.plugin_env.tab_index),
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
))
})
.with_context(|| {
@@ -195,6 +246,54 @@ fn host_open_file_with_line(env: &ForeignFunctionEnv) {
.non_fatal();
}
+fn host_open_terminal(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::RunCommand(
+ RunCommand::new(env.plugin_env.path_to_default_shell.clone())
+ .with_cwd(path),
+ )),
+ Some(false),
+ None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open terminal on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
+fn host_open_terminal_floating(env: &ForeignFunctionEnv) {
+ wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
+ .and_then(|path| {
+ env.plugin_env
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(
+ Some(TerminalAction::RunCommand(
+ RunCommand::new(env.plugin_env.path_to_default_shell.clone())
+ .with_cwd(path),
+ )),
+ Some(true),
+ None,
+ ClientOrTabIndex::ClientId(env.plugin_env.client_id),
+ ))
+ })
+ .with_context(|| {
+ format!(
+ "failed to open terminal on host from plugin {}",
+ env.plugin_env.name()
+ )
+ })
+ .non_fatal();
+}
+
fn host_switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) {
env.plugin_env
.senders
@@ -314,6 +413,17 @@ fn host_post_message_to_plugin(env: &ForeignFunctionEnv) {
.fatal();
}
+fn host_hide_self(env: &ForeignFunctionEnv) {
+ env.plugin_env
+ .senders
+ .send_to_screen(ScreenInstruction::SuppressPane(
+ PaneId::Plugin(env.plugin_env.plugin_id),
+ env.plugin_env.client_id,
+ ))
+ .with_context(|| format!("failed to hide self"))
+ .fatal();
+}
+
// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
@@ -328,7 +438,12 @@ fn host_report_panic(env: &ForeignFunctionEnv) {
)
})
.fatal();
- panic!("{}", msg);
+ log::error!("PANIC IN PLUGIN! {}", msg);
+ handle_plugin_crash(
+ env.plugin_env.plugin_id,
+ msg,
+ env.plugin_env.senders.clone(),
+ );
}
// Helper Functions ---------------------------------------------------------------------------------------------------
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index fb58aef8..84f282f8 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -7,7 +7,7 @@ use crate::{
ClientId, ServerInstruction,
};
use async_std::task::{self, JoinHandle};
-use std::{collections::HashMap, env, os::unix::io::RawFd, path::PathBuf};
+use std::{collections::HashMap, os::unix::io::RawFd, path::PathBuf};
use zellij_utils::nix::unistd::Pid;
use zellij_utils::{
async_std,
@@ -468,10 +468,7 @@ impl Pty {
default_shell
},
None => {
- let shell = PathBuf::from(env::var("SHELL").unwrap_or_else(|_| {
- log::warn!("Cannot read SHELL env, falling back to use /bin/sh");
- "/bin/sh".to_string()
- }));
+ let shell = get_default_shell();
TerminalAction::RunCommand(RunCommand {
args: vec![],
command: shell,
@@ -1048,3 +1045,10 @@ fn send_command_not_found_to_screen(
.with_context(err_context)?;
Ok(())
}
+
+pub fn get_default_shell() -> PathBuf {
+ PathBuf::from(std::env::var("SHELL").unwrap_or_else(|_| {
+ log::warn!("Cannot read SHELL env, falling back to use /bin/sh");
+ "/bin/sh".to_string()
+ }))
+}
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index f3455379..c43e99f1 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -702,6 +702,16 @@ pub(crate) fn route_action(
))
.with_context(err_context)?;
},
+ Action::LaunchOrFocusPlugin(run_plugin, should_float) => {
+ session
+ .senders
+ .send_to_screen(ScreenInstruction::LaunchOrFocusPlugin(
+ run_plugin,
+ should_float,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
}
Ok(should_break)
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 030a63a5..e88baf29 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -273,6 +273,8 @@ pub enum ScreenInstruction {
StartPluginLoadingIndication(u32, LoadingIndication), // u32 - plugin_id
ProgressPluginLoadingOffset(u32), // u32 - plugin id
RequestStateUpdateForPlugins,
+ LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float
+ SuppressPane(PaneId, ClientId),
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -435,6 +437,8 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::RequestStateUpdateForPlugins => {
ScreenContext::RequestStateUpdateForPlugins
},
+ ScreenInstruction::LaunchOrFocusPlugin(..) => ScreenContext::LaunchOrFocusPlugin,
+ ScreenInstruction::SuppressPane(..) => ScreenContext::SuppressPane,
}
}
}
@@ -1462,6 +1466,24 @@ impl Screen {
self.render()
}
+ pub fn focus_plugin_pane(
+ &mut self,
+ run_plugin: &RunPlugin,
+ should_float: bool,
+ client_id: ClientId,
+ ) -> Result<bool> {
+ // true => found and focused, false => not
+ let all_tabs = self.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if let Some(plugin_pane_id) = tab.find_plugin(&run_plugin) {
+ tab.focus_pane_with_id(plugin_pane_id, should_float, client_id)
+ .context("failed to focus plugin pane")?;
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ }
+
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@@ -1557,6 +1579,7 @@ pub(crate) fn screen_thread_main(
client_id: ClientId| tab .new_pane(pid,
initial_pane_title,
should_float,
+ None,
Some(client_id)),
?);
if let Some(hold_for_command) = hold_for_command {
@@ -1575,7 +1598,13 @@ pub(crate) fn screen_thread_main(
},
ClientOrTabIndex::TabIndex(tab_index) => {
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
- active_tab.new_pane(pid, initial_pane_title, should_float, None)?;
+ active_tab.new_pane(
+ pid,
+ initial_pane_title,
+ should_float,
+ None,
+ None,
+ )?;
if let Some(hold_for_command) = hold_for_command {
let is_first_run = true;
active_tab.hold_pane(pid, None, is_first_run, hold_for_command);
@@ -2558,11 +2587,11 @@ pub(crate) fn screen_thread_main(
pane_title.unwrap_or_else(|| run_plugin_location.location.to_string());
let run_plugin = Run::Plugin(run_plugin_location);
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
- active_tab.new_plugin_pane(
+ active_tab.new_pane(
PaneId::Plugin(plugin_id),
- pane_title,
+ Some(pane_title),
should_float,
- run_plugin,
+ Some(run_plugin),
None,
)?;
} else {
@@ -2608,6 +2637,46 @@ pub(crate) fn screen_thread_main(
screen.update_tabs()?;
screen.render()?;
},
+ ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
+ let client_id = if screen.active_tab_indices.contains_key(&client_id) {
+ Some(client_id)
+ } else {
+ screen.get_first_client_id()
+ };
+ let client_id_and_focused_tab = client_id.and_then(|client_id| {
+ screen
+ .active_tab_indices
+ .get(&client_id)
+ .map(|tab_index| (*tab_index, client_id))
+ });
+ match client_id_and_focused_tab {
+ Some((tab_index, client_id)) => {
+ if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
+ screen.render()?;
+ } else {
+ screen.bus.senders.send_to_plugin(PluginInstruction::Load(
+ Some(should_float),
+ None,
+ run_plugin,
+ tab_index,
+ client_id,
+ Size::default(),
+ ))?;
+ }
+ },
+ None => log::error!("No connected clients found - cannot load or focus plugin"),
+ }
+ },
+ ScreenInstruction::SuppressPane(pane_id, client_id) => {
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_pane_with_pid(&pane_id) {
+ tab.suppress_pane(pane_id, client_id);
+ drop(screen.render());
+ break;
+ }
+ }
+ },
}
}
Ok(())
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 4a37a090..e695117e 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -47,8 +47,8 @@ use zellij_utils::{
input::{
command::TerminalAction,
layout::{
- FloatingPaneLayout, Run, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
- TiledPaneLayout,
+ FloatingPaneLayout, Run, RunPlugin, RunPluginLocation, SwapFloatingLayout,
+ SwapTiledLayout, TiledPaneLayout,
},
parse_keys,
},
@@ -901,7 +901,6 @@ impl Tab {
pub fn toggle_pane_embed_or_floating(&mut self, client_id: ClientId) -> Result<()> {
let err_context =
|| format!("failed to toggle embedded/floating pane for client {client_id}");
-
if self.tiled_panes.fullscreen_is_active() {
self.tiled_panes.unset_fullscreen();
}
@@ -914,55 +913,24 @@ impl Tab {
"failed to find floating pane (ID: {focused_floating_pane_id:?}) to embed for client {client_id}",
))
.with_context(err_context)?;
- self.tiled_panes
- .insert_pane(focused_floating_pane_id, floating_pane_to_embed);
- self.should_clear_display_before_rendering = true;
- self.tiled_panes
- .focus_pane(focused_floating_pane_id, client_id);
self.hide_floating_panes();
- if self.auto_layout && !self.swap_layouts.is_tiled_damaged() {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(Some(client_id), true)?;
- }
+ self.add_tiled_pane(
+ floating_pane_to_embed,
+ focused_floating_pane_id,
+ Some(client_id),
+ )?;
}
}
} else if let Some(focused_pane_id) = self.tiled_panes.focused_pane_id(client_id) {
- if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
- if self.get_selectable_tiled_panes().count() <= 1 {
- // don't close the only pane on screen...
- return Ok(());
- }
- if let Some(mut embedded_pane_to_float) =
- self.close_pane(focused_pane_id, true, Some(client_id))
- {
- if !embedded_pane_to_float.borderless() {
- // floating panes always have a frame unless they're explicitly borderless
- embedded_pane_to_float.set_content_offset(Offset::frame(1));
- }
- embedded_pane_to_float.set_geom(new_pane_geom);
- resize_pty!(
- embedded_pane_to_float,
- self.os_api,
- self.senders,
- self.character_cell_size
- )
- .with_context(err_context)?;
- embedded_pane_to_float.set_active_at(Instant::now());
- self.floating_panes
- .add_pane(focused_pane_id, embedded_pane_to_float);
- self.floating_panes.focus_pane(focused_pane_id, client_id);
- self.show_floating_panes();
- if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(Some(client_id), true)?;
- }
- }
+ if self.get_selectable_tiled_panes().count() <= 1 {
+ // don't close the only pane on screen...
+ return Ok(());
+ }
+ if let Some(embedded_pane_to_float) =
+ self.close_pane(focused_pane_id, true, Some(client_id))
+ {
+ self.show_floating_panes();
+ self.add_floating_pane(embedded_pane_to_float, focused_pane_id, Some(client_id))?;
}
}
Ok(())
@@ -1018,10 +986,10 @@ impl Tab {
pid: PaneId,
initial_pane_title: Option<String>,
should_float: Option<bool>,
+ run_plugin: Option<Run>, // only relevant if this is a plugin pane
client_id: Option<ClientId>,
) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}");
-
match should_float {
Some(true) => self.show_floating_panes(),
Some(false) => self.hide_floating_panes(),
@@ -1029,198 +997,51 @@ impl Tab {
};
self.close_down_to_max_terminals()
.with_context(err_context)?;
- if self.floating_panes.panes_are_visible() {
- if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
+ let new_pane = match pid {
+ PaneId::Terminal(term_pid) => {
let next_terminal_position = self.get_next_terminal_position();
- if let PaneId::Terminal(term_pid) = pid {
- let mut new_pane = TerminalPane::new(
- term_pid,
- new_pane_geom,
- self.style,
- next_terminal_position,
- String::new(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- initial_pane_title,
- None,
- );
- new_pane.set_active_at(Instant::now());
- new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
- resize_pty!(
- new_pane,
- self.os_api,
- self.senders,
- self.character_cell_size
- )
- .with_context(err_context)?;
- self.floating_panes.add_pane(pid, Box::new(new_pane));
- self.floating_panes.focus_pane_for_all_clients(pid);
- }
- if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(client_id, true)?;
- }
- }
- } else {
- if self.tiled_panes.fullscreen_is_active() {
- self.tiled_panes.unset_fullscreen();
- }
- let should_auto_layout = self.auto_layout && !self.swap_layouts.is_tiled_damaged();
- if self.tiled_panes.has_room_for_new_pane() {
- if let PaneId::Terminal(term_pid) = pid {
- let next_terminal_position = self.get_next_terminal_position();
- let mut new_terminal = TerminalPane::new(
- term_pid,
- PaneGeom::default(), // the initial size will be set later
- self.style,
- next_terminal_position,
- String::new(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- initial_pane_title,
- None,
- );
- new_terminal.set_active_at(Instant::now());
- if should_auto_layout {
- // no need to relayout here, we'll do it when reapplying the swap layout
- // below
- self.tiled_panes
- .insert_pane_without_relayout(pid, Box::new(new_terminal));
- } else {
- self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
- }
- self.should_clear_display_before_rendering = true;
- if let Some(client_id) = client_id {
- self.tiled_panes.focus_pane(pid, client_id);
- }
- }
- }
- if should_auto_layout {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(client_id, true)?;
- }
- }
- Ok(())
- }
- pub fn new_plugin_pane(
- &mut self,
- pid: PaneId,
- initial_pane_title: String,
- should_float: Option<bool>,
- run_plugin: Run,
- client_id: Option<ClientId>,
- ) -> Result<()> {
- let err_context = || format!("failed to create new pane with id {pid:?}");
-
- match should_float {
- Some(true) => self.show_floating_panes(),
- Some(false) => self.hide_floating_panes(),
- None => {},
+ Box::new(TerminalPane::new(
+ term_pid,
+ PaneGeom::default(), // this will be filled out later
+ self.style,
+ next_terminal_position,
+ String::new(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ initial_pane_title,
+ None,
+ )) as Box<dyn Pane>
+ },
+ PaneId::Plugin(plugin_pid) => {
+ Box::new(PluginPane::new(
+ plugin_pid,
+ PaneGeom::default(), // this will be filled out later
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ initial_pane_title.unwrap_or("".to_owned()),
+ String::new(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.connected_clients.borrow().iter().copied().collect(),
+ self.style,
+ run_plugin,
+ )) as Box<dyn Pane>
+ },
};
if self.floating_panes.panes_are_visible() {
- if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
- if let PaneId::Plugin(plugin_pid) = pid {
- let mut new_pane = PluginPane::new(
- plugin_pid,
- new_pane_geom,
- self.senders
- .to_plugin
- .as_ref()
- .with_context(err_context)?
- .clone(),
- initial_pane_title,
- String::new(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.connected_clients.borrow().iter().copied().collect(),
- self.style,
- Some(run_plugin),
- );
- new_pane.set_active_at(Instant::now());
- new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
- resize_pty!(
- new_pane,
- self.os_api,
- self.senders,
- self.character_cell_size
- )
- .with_context(err_context)?;
- self.floating_panes.add_pane(pid, Box::new(new_pane));
- self.floating_panes.focus_pane_for_all_clients(pid);
- }
- if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(client_id, true)?;
- }
- }
+ self.add_floating_pane(new_pane, pid, client_id)
} else {
- if self.tiled_panes.fullscreen_is_active() {
- self.tiled_panes.unset_fullscreen();
- }
- let should_auto_layout = self.auto_layout && !self.swap_layouts.is_tiled_damaged();
- if self.tiled_panes.has_room_for_new_pane() {
- if let PaneId::Plugin(plugin_pid) = pid {
- let mut new_pane = PluginPane::new(
- plugin_pid,
- PaneGeom::default(), // the initial size will be set later
- self.senders
- .to_plugin
- .as_ref()
- .with_context(err_context)?
- .clone(),
- initial_pane_title,
- String::new(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.connected_clients.borrow().iter().copied().collect(),
- self.style,
- Some(run_plugin),
- );
- new_pane.set_active_at(Instant::now());
- if should_auto_layout {
- // no need to relayout here, we'll do it when reapplying the swap layout
- // below
- self.tiled_panes
- .insert_pane_without_relayout(pid, Box::new(new_pane));
- } else {
- self.tiled_panes.insert_pane(pid, Box::new(new_pane));
- }
- self.should_clear_display_before_rendering = true;
- if let Some(client_id) = client_id {
- self.tiled_panes.focus_pane(pid, client_id);
- }
- }
- }
- if should_auto_layout {
- // only do this if we're already in this layout, otherwise it might be
- // confusing and not what the user intends
- self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
- // next layout
- self.next_swap_layout(client_id, true)?;
- }
+ self.add_tiled_pane(new_pane, pid, client_id)
}
- Ok(())
}
pub fn suppress_active_pane(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> {
// this method creates a new pane from pid and replaces it with the active pane
@@ -3426,6 +3247,112 @@ impl Tab {
self.floating_panes.toggle_show_panes(false);
self.tiled_panes.focus_all_panes();
}
+
+ pub fn find_plugin(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
+ self.tiled_panes
+ .get_plugin_pane_id(run_plugin)
+ .or_else(|| self.floating_panes.get_plugin_pane_id(run_plugin))
+ .or_else(|| {
+ let run = Some(Run::Plugin(run_plugin.clone()));
+ self.suppressed_panes
+ .iter()
+ .find(|(_id, s_p)| s_p.invoked_with() == &run)
+ .map(|(id, _)| *id)
+ })
+ }
+
+ pub fn focus_pane_with_id(
+ &mut self,
+ pane_id: PaneId,
+ should_float: bool,
+ client_id: ClientId,
+ ) -> Result<()> {
+ self.tiled_panes
+ .focus_pane_if_exists(pane_id, client_id)
+ .or_else(|_| {
+ let focused_floating_pane =
+ self.floating_panes.focus_pane_if_exists(pane_id, client_id);
+ if focused_floating_pane.is_ok() {
+ self.show_floating_panes()
+ };
+ focused_floating_pane
+ })
+ .or_else(|_| match self.suppressed_panes.remove(&pane_id) {
+ Some(pane) => {
+ if should_float {
+ self.show_floating_panes();
+ self.add_floating_pane(pane, pane_id, Some(client_id))
+ } else {
+ self.hide_floating_panes();
+ self.add_tiled_pane(pane, pane_id, Some(client_id))
+ }
+ },
+ None => Ok(()),
+ })
+ }
+ pub fn suppress_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
+ if let Some(pane) = self.close_pane(pane_id, true, Some(client_id)) {
+ self.suppressed_panes.insert(pane_id, pane);
+ }
+ }
+ fn add_floating_pane(
+ &mut self,
+ mut pane: Box<dyn Pane>,
+ pane_id: PaneId,
+ client_id: Option<ClientId>,
+ ) -> Result<()> {
+ let err_context = || format!("failed to add floating pane");
+ if let Some(new_pane_geom) = self.floating_panes.find_room_for_new_pane() {
+ pane.set_active_at(Instant::now());
+ pane.set_geom(new_pane_geom);
+ pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
+ resize_pty!(pane, self.os_api, self.senders, self.character_cell_size)
+ .with_context(err_context)?;
+ self.floating_panes.add_pane(pane_id, pane);
+ self.floating_panes.focus_pane_for_all_clients(pane_id);
+ }
+ if self.auto_layout && !self.swap_layouts.is_floating_damaged() {
+ // only do this if we're already in this layout, otherwise it might be
+ // confusing and not what the user intends
+ self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
+ // next layout
+ self.next_swap_layout(client_id, true)?;
+ }
+ Ok(())
+ }
+ fn add_tiled_pane(
+ &mut self,
+ mut pane: Box<dyn Pane>,
+ pane_id: PaneId,
+ client_id: Option<ClientId>,
+ ) -> Result<()> {
+ if self.tiled_panes.fullscreen_is_active() {
+ self.tiled_panes.unset_fullscreen();
+ }
+ let should_auto_layout = self.auto_layout && !self.swap_layouts.is_tiled_damaged();
+ if self.tiled_panes.has_room_for_new_pane() {
+ pane.set_active_at(Instant::now());
+ if should_auto_layout {
+ // no need to relayout here, we'll do it when reapplying the swap layout
+ // below
+ self.tiled_panes.insert_pane_without_relayout(pane_id, pane);
+ } else {
+ self.tiled_panes.insert_pane(pane_id, pane);
+ }
+ self.should_clear_display_before_rendering = true;
+ if let Some(client_id) = client_id {
+ self.tiled_panes.focus_pane(pane_id, client_id);
+ }
+ }
+ if should_auto_layout {
+ // only do this if we're already in this layout, otherwise it might be
+ // confusing and not what the user intends
+ self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
+ // next layout
+ self.next_swap_layout(client_id, true)?;
+ }
+ Ok(())
+ }
}
#[cfg(test)]
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index 48d841a5..7cd32c0f 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -696,7 +696,7 @@ fn dump_screen() {
..Default::default()
});
let new_pane_id = PaneId::Terminal(2);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
.unwrap();
@@ -724,7 +724,7 @@ fn clear_screen() {
..Default::default()
});
let new_pane_id = PaneId::Terminal(2);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
.unwrap();
@@ -750,7 +750,7 @@ fn new_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -778,7 +778,7 @@ fn floating_panes_persist_across_toggles() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
// here we send bytes to the pane when it's not visible to make sure they're still handled and
@@ -810,7 +810,7 @@ fn toggle_floating_panes_off() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -839,7 +839,7 @@ fn toggle_floating_panes_on() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -873,15 +873,15 @@ fn five_new_floating_panes() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -917,7 +917,7 @@ fn increase_floating_pane_size() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -947,7 +947,7 @@ fn decrease_floating_pane_size() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -977,7 +977,7 @@ fn resize_floating_pane_left() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1010,7 +1010,7 @@ fn resize_floating_pane_right() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1043,7 +1043,7 @@ fn resize_floating_pane_up() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1076,7 +1076,7 @@ fn resize_floating_pane_down() {
let new_pane_id_1 = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1113,15 +1113,15 @@ fn move_floating_pane_focus_left() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1168,15 +1168,15 @@ fn move_floating_pane_focus_right() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1224,15 +1224,15 @@ fn move_floating_pane_focus_up() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1279,15 +1279,15 @@ fn move_floating_pane_focus_down() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1335,15 +1335,15 @@ fn move_floating_pane_focus_with_mouse() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1393,15 +1393,15 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1451,15 +1451,15 @@ fn drag_pane_with_mouse() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1509,15 +1509,15 @@ fn mark_text_inside_floating_pane() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1575,15 +1575,15 @@ fn resize_tab_with_floating_panes() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1629,15 +1629,15 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() {
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1679,15 +1679,15 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_b
let new_pane_id_5 = PaneId::Terminal(6);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1730,7 +1730,7 @@ fn embed_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1758,7 +1758,7 @@ fn float_embedded_pane() {
let mut tab = create_new_tab(size, ModeInfo::default());
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1788,7 +1788,7 @@ fn embed_floating_pane_without_pane_frames() {
let mut output = Output::default();
tab.set_pane_frames(false);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1817,7 +1817,7 @@ fn float_embedded_pane_without_pane_frames() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.set_pane_frames(false);
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -1920,7 +1920,7 @@ fn rename_floating_pane() {
let mut tab = create_new_tab(size, ModeInfo::default());
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
2,
@@ -2006,7 +2006,7 @@ fn move_floating_pane_with_sixel_image() {
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let fixture = read_fixture("sixel-image-500px.six");
tab.handle_pty_bytes(2, fixture).unwrap();
@@ -2044,7 +2044,7 @@ fn floating_pane_above_sixel_image() {
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let fixture = read_fixture("sixel-image-500px.six");
tab.handle_pty_bytes(1, fixture).unwrap();
@@ -2101,7 +2101,7 @@ fn suppress_floating_pane() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2155,7 +2155,7 @@ fn close_suppressing_floating_pane() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2213,7 +2213,7 @@ fn suppress_floating_pane_embed_it_and_close_it() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2273,7 +2273,7 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() {
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
@@ -2374,7 +2374,7 @@ fn enter_search_floating_pane() {
let new_pane_id = PaneId::Terminal(2);
let mut output = Output::default();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id, None, None, Some(client_id))
+ tab.new_pane(new_pane_id, None, None, None, Some(client_id))
.unwrap();
let pane_content = read_fixture("grid_copy");
@@ -2879,7 +2879,7 @@ fn move_pane_focus_sends_tty_csi_event() {
});
let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -2914,9 +2914,9 @@ fn move_floating_pane_focus_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -2957,9 +2957,9 @@ fn toggle_floating_panes_on_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.toggle_floating_panes(Some(client_id), None).unwrap();
tab.handle_pty_bytes(
@@ -3001,9 +3001,9 @@ fn toggle_floating_panes_off_sends_tty_csi_event() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.handle_pty_bytes(
1,
@@ -3063,7 +3063,7 @@ fn can_swap_tiled_layout_at_runtime() {
);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.render(&mut output, None).unwrap();
@@ -3118,9 +3118,9 @@ fn can_swap_floating_layout_at_runtime() {
let new_pane_id_2 = PaneId::Terminal(3);
tab.toggle_floating_panes(Some(client_id), None).unwrap();
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.render(&mut output, None).unwrap();
@@ -3171,7 +3171,7 @@ fn swapping_layouts_after_resize_snaps_to_current_layout() {
);
let new_pane_id_1 = PaneId::Terminal(2);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
tab.next_swap_layout(Some(client_id), false).unwrap();
tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
@@ -3221,11 +3221,11 @@ fn swap_tiled_layout_with_stacked_children() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3268,11 +3268,11 @@ fn swap_tiled_layout_with_only_stacked_children() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3318,11 +3318,11 @@ fn swap_tiled_layout_with_stacked_children_and_no_pane_frames() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.render(&mut output, None).unwrap();
let snapshot = take_snapshot(
@@ -3368,11 +3368,11 @@ fn move_focus_up_with_stacked_panes() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3420,11 +3420,11 @@ fn move_focus_down_with_stacked_panes() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3474,8 +3474,14 @@ fn move_focus_right_into_stacked_panes() {
);
for i in 0..12 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_left(client_id);
tab.horizontal_split(PaneId::Terminal(16), None, client_id)
@@ -3535,8 +3541,14 @@ fn move_focus_left_into_stacked_panes() {
);
for i in 0..13 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_right(client_id);
tab.horizontal_split(PaneId::Terminal(1), None, client_id)
@@ -3598,8 +3610,14 @@ fn move_focus_up_into_stacked_panes() {
);
for i in 0..4 {
let new_pane_id = i + 3;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3662,8 +3680,14 @@ fn move_focus_down_into_stacked_panes() {
);
for i in 0..4 {
let new_pane_id = i + 3;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
}
tab.move_focus_left(client_id);
tab.move_focus_up(client_id);
@@ -3722,11 +3746,11 @@ fn close_main_stacked_pane() {
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
tab.close_pane(new_pane_id_2, false, None);
tab.render(&mut output, None).unwrap();
@@ -3775,15 +3799,15 @@ fn close_main_stacked_pane_in_mid_stack() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3835,15 +3859,15 @@ fn close_one_liner_stacked_pane_below_main_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_left(client_id);
tab.move_focus_right(client_id);
@@ -3896,15 +3920,15 @@ fn close_one_liner_stacked_pane_above_main_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_up(client_id);
@@ -3956,15 +3980,15 @@ fn can_increase_size_of_main_pane_in_stack_horizontally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.resize(
@@ -4020,15 +4044,15 @@ fn can_increase_size_of_main_pane_in_stack_vertically() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.resize(
@@ -4084,15 +4108,15 @@ fn can_increase_size_of_main_pane_in_stack_non_directionally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
let _ = tab.move_focus_up(client_id);
let _ = tab.move_focus_right(client_id);
@@ -4144,15 +4168,15 @@ fn can_increase_size_into_pane_stack_horizontally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize(
client_id,
@@ -4207,15 +4231,15 @@ fn can_increase_size_into_pane_stack_vertically() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_right(client_id);
tab.move_focus_down(client_id);
@@ -4272,15 +4296,15 @@ fn can_increase_size_into_pane_stack_non_directionally() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
let _ = tab.move_focus_up(client_id);
tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
@@ -4331,15 +4355,15 @@ fn decreasing_size_of_whole_tab_treats_stacked_panes_properly() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize_whole_tab(Size {
cols: 100,
@@ -4391,15 +4415,15 @@ fn increasing_size_of_whole_tab_treats_stacked_panes_properly() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.resize_whole_tab(Size {
cols: 100,
@@ -4456,15 +4480,15 @@ fn cannot_decrease_stack_size_beyond_minimum_height() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_down(client_id);
for _ in 0..6 {
@@ -4521,15 +4545,15 @@ fn focus_stacked_pane_over_flexible_pane_with_the_mouse() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(1, 71), client_id)
.unwrap();
@@ -4580,15 +4604,15 @@ fn focus_stacked_pane_under_flexible_pane_with_the_mouse() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(1, 71), client_id)
.unwrap();
@@ -4641,15 +4665,15 @@ fn close_stacked_pane_with_previously_focused_other_pane() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.handle_left_click(&Position::new(2, 71), client_id)
.unwrap();
@@ -4708,15 +4732,15 @@ fn close_pane_near_stacked_panes() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.close_pane(PaneId::Terminal(6), false, None);
tab.render(&mut output, None).unwrap();
@@ -4772,15 +4796,15 @@ fn focus_next_pane_expands_stacked_panes() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_left(client_id);
tab.focus_next_pane(client_id);
@@ -4832,15 +4856,15 @@ fn stacked_panes_can_become_fullscreen() {
let new_pane_id_4 = PaneId::Terminal(5);
let new_pane_id_5 = PaneId::Terminal(6);
- tab.new_pane(new_pane_id_1, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_1, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_2, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_2, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_3, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_3, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_4, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_4, None, None, None, Some(client_id))
.unwrap();
- tab.new_pane(new_pane_id_5, None, None, Some(client_id))
+ tab.new_pane(new_pane_id_5, None, None, None, Some(client_id))
.unwrap();
tab.move_focus_up(client_id);
tab.toggle_active_pane_fullscreen(client_id);
@@ -5467,8 +5491,14 @@ fn new_pane_in_auto_layout() {
];
for i in 0..7 {
let new_pane_id = i + 2;
- tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id))
- .unwrap();
+ tab.new_pane(
+ PaneId::Terminal(new_pane_id),
+ None,
+ None,
+ None,
+ Some(client_id),
+ )
+ .unwrap();
tab.render(&mut output, None).unwrap();
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
@@ -6379,6 +6409,7 @@ fn new_floating_pane_in_auto_layout() {
PaneId::Terminal(new_pane_id),
None,
Some(should_float),
+ None,
Some(client_id),
)
.unwrap();
diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs
index bcf02202..5374ed70 100644
--- a/zellij-server/src/tab/unit/tab_tests.rs
+++ b/zellij-server/src/tab/unit/tab_tests.rs
@@ -537,7 +537,8 @@ fn split_largest_pane() {
let mut tab = create_new_tab(size);
for i in 2..5 {
let new_pane_id = PaneId::Terminal(i);
- tab.new_pane(new_pane_id, None, None, Some(1)).unwrap();
+ tab.new_pane(new_pane_id, None, None, None, Some(1))
+ .unwrap();
}
assert_eq!(tab.tiled_panes.panes.len(), 4, "The tab has four panes");
@@ -742,7 +743,7 @@ pub fn cannot_split_panes_horizontally_when_active_pane_is_too_small() {
pub fn cannot_split_largest_pane_when_there_is_no_room() {
let size = Size { cols: 8, rows: 4 };
let mut tab = create_new_tab(size);
- tab.new_pane(PaneId::Terminal(2), None, None, Some(1))
+ tab.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
assert_eq!(
tab.tiled_panes.panes.len(),
@@ -786,7 +787,8 @@ pub fn toggle_focused_pane_fullscreen() {
let mut tab = create_new_tab(size);
for i in 2..5 {
let new_pane_id = PaneId::Terminal(i);
- tab.new_pane(new_pane_id, None, None, Some(1)).unwrap();
+ tab.new_pane(new_pane_id, None, None, None, Some(1))
+ .unwrap();
}
tab.toggle_active_pane_fullscreen(1);
assert_eq!(
@@ -860,16 +862,16 @@ fn switch_to_next_pane_fullscreen() {
let mut active_tab = create_new_tab(size);
active_tab
- .new_pane(PaneId::Terminal(1), None, None, Some(1))
+ .new_pane(PaneId::Terminal(1), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(3), None, None, Some(1))
+ .new_pane(PaneId::Terminal(3), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(4), None, None, Some(1))
+ .new_pane(PaneId::Terminal(4), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
@@ -900,16 +902,16 @@ fn switch_to_prev_pane_fullscreen() {
//testing four consecutive switches in fullscreen mode
active_tab
- .new_pane(PaneId::Terminal(1), None, None, Some(1))
+ .new_pane(PaneId::Terminal(1), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(3), None, None, Some(1))
+ .new_pane(PaneId::Terminal(3), None, None, None, Some(1))
.unwrap();
active_tab
- .new_pane(PaneId::Terminal(4), None, None, Some(1))
+ .new_pane(PaneId::Terminal(4), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
// order is now 1 2 3 4
@@ -14391,7 +14393,7 @@ fn correctly_resize_frameless_panes_on_pane_close() {
let content_size = (pane.get_content_columns(), pane.get_content_rows());
assert_eq!(content_size, (cols, rows));
- tab.new_pane(PaneId::Terminal(2), None, None, Some(1))
+ tab.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
tab.close_pane(PaneId::Terminal(2), true, None);
diff --git a/zellij-server/src/ui/loading_indication.rs b/zellij-server/src/ui/loading_indication.rs
index 78af2c10..1aa514ec 100644
--- a/zellij-server/src/ui/loading_indication.rs
+++ b/zellij-server/src/ui/loading_indication.rs
@@ -45,9 +45,15 @@ impl LoadingIndication {
pub fn merge(&mut self, other: LoadingIndication) {
let current_animation_offset = self.animation_offset;
let current_terminal_emulator_colors = self.terminal_emulator_colors.take();
+ let mut current_error = self.error.take();
drop(std::mem::replace(self, other));
self.animation_offset = current_animation_offset;
self.terminal_emulator_colors = current_terminal_emulator_colors;
+ if let Some(current_error) = current_error.take() {
+ // we do this so that only the first error (usually the root cause) will be shown
+ // when plugins support scrolling, we might want to do an append here
+ self.error = Some(current_error);
+ }
}
pub fn indicate_loading_plugin_from_memory(&mut self) {
self.loading_from_memory = Some(LoadingStatus::InProgress);
@@ -104,6 +110,9 @@ impl LoadingIndication {
pub fn indicate_loading_error(&mut self, error_text: String) {
self.error = Some(error_text);
}
+ pub fn is_error(&self) -> bool {
+ self.error.is_some()
+ }
fn started_loading(&self) -> bool {
self.loading_from_memory.is_some()
|| self.loading_from_hd_cache.is_some()
@@ -257,6 +266,13 @@ impl Display for LoadingIndication {
}
if let Some(error_text) = &self.error {
stringified.push_str(&format!("\n\r{} {error_text}", red.bold().paint("ERROR:")));
+ // we add this additional line explicitly to make it easier to realize when something
+ // is wrong in very small plugins (eg. the tab-bar and status-bar)
+ stringified.push_str(&format!(
+ "\n\r{}",
+ red.bold()
+ .paint("ERROR IN PLUGIN - check logs for more info")
+ ));
}
write!(f, "{}", stringified)
}
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index d4cd8c1e..8639970b 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -14,7 +14,9 @@ use zellij_utils::data::Resize;
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction};
-use zellij_utils::input::layout::{Layout, SplitDirection, TiledPaneLayout};
+use zellij_utils::input::layout::{
+ Layout, Run, RunPlugin, RunPluginLocation, SplitDirection, TiledPaneLayout,
+};
use zellij_utils::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
@@ -291,7 +293,11 @@ impl MockScreen {
let pane_layout = initial_layout.unwrap_or_default();
let pane_count = pane_layout.extract_run_instructions().len();
let mut pane_ids = vec![];
- let plugin_ids = HashMap::new();
+ let mut plugin_ids = HashMap::new();
+ plugin_ids.insert(
+ RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
+ vec![1],
+ );
for i in 0..pane_count {
pane_ids.push((i as u32, None));
}
@@ -864,7 +870,7 @@ fn switch_to_tab_with_fullscreen() {
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
@@ -979,7 +985,7 @@ fn attach_after_first_tab_closed() {
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
- .new_pane(PaneId::Terminal(2), None, None, Some(1))
+ .new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
@@ -2715,3 +2721,145 @@ pub fn send_cli_query_tab_names_action() {
.cloned();
assert_snapshot!(format!("{:#?}", log_tab_names_instruction));
}
+
+#[test]
+pub fn send_cli_launch_or_focus_plugin_action() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10; // fake client id should not appear in the screen's state
+ let mut mock_screen = MockScreen::new(size);
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let screen_thread = mock_screen.run(None);
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ let cli_action = CliAction::LaunchOrFocusPlugin {
+ floating: true,
+ url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
+ };
+ send_cli_action_to_server(&session_metadata, cli_action, &mut mock_screen, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
+ mock_screen.teardown(vec![plugin_thread, screen_thread]);
+
+ let plugin_load_instruction = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find(|instruction| match instruction {
+ PluginInstruction::Load(..) => true,
+ _ => false,
+ })
+ .cloned();
+
+ assert_snapshot!(format!("{:#?}", plugin_load_instruction));
+}
+
+#[test]
+pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 10; // fake client id should not appear in the screen's state
+ let mut mock_screen = MockScreen::new(size);
+ let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
+ let session_metadata = mock_screen.clone_session_metadata();
+ let mut initial_layout = TiledPaneLayout::default();
+ let existing_plugin_pane = TiledPaneLayout {
+ run: Some(Run::Plugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
+ })),
+ ..Default::default()
+ };
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), existing_plugin_pane];
+ let screen_thread = mock_screen.run(Some(initial_layout));
+ let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
+ let plugin_thread = log_actions_in_thread!(
+ received_plugin_instructions,
+ PluginInstruction::Exit,
+ plugin_receiver
+ );
+ let received_server_instructions = Arc::new(Mutex::new(vec![]));
+ let server_receiver = mock_screen.server_receiver.take().unwrap();
+ let server_thread = log_actions_in_thread!(
+ received_server_instructions,
+ ServerInstruction::KillSession,
+ server_receiver
+ );
+ let cli_action = CliAction::LaunchOrFocusPlugin {
+ floating: true,
+ url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
+ };
+ send_cli_action_to_server(&session_metadata, cli_action, &mut mock_screen, client_id);
+ std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
+ mock_screen.teardown(vec![plugin_thread, server_thread, screen_thread]);
+
+ let plugin_load_instruction_sent = received_plugin_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find(|instruction| match instruction {
+ PluginInstruction::Load(..) => true,
+ _ => false,
+ })
+ .is_some();
+ assert!(
+ !plugin_load_instruction_sent,
+ "Plugin Load instruction should not be sent for an already loaded plugin"
+ );
+ let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
+ received_server_instructions.lock().unwrap().iter(),
+ size,
+ );
+ let snapshot_count = snapshots.len();
+ assert_eq!(
+ snapshot_count, 2,
+ "Another render was sent for focusing the already loaded plugin"
+ );
+ for (cursor_coordinates, _snapshot) in snapshots.iter().skip(1) {
+ assert!(
+ cursor_coordinates.is_none(),
+ "Cursor moved to existing plugin in final snapshot indicating focus changed"
+ );
+ }
+}
+
+#[test]
+pub fn screen_can_suppress_pane() {
+ let size = Size { cols: 80, rows: 20 };
+ let mut initial_layout = TiledPaneLayout::default();
+ initial_layout.children_split_direction = SplitDirection::Vertical;
+ initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
+ let mut mock_screen = MockScreen::new(size);
+ let screen_thread = mock_screen.run(Some(initial_layout));
+ let received_server_instructions = Arc::new(Mutex::new(vec![]));
+ let server_receiver = mock_screen.server_receiver.take().unwrap();
+ let server_thread = log_actions_in_thread!(
+ received_server_instructions,
+ ServerInstruction::KillSession,
+ server_receiver
+ );
+ let _ = mock_screen
+ .to_screen
+ .send(ScreenInstruction::SuppressPane(PaneId::Terminal(1), 1));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ mock_screen.teardown(vec![server_thread, screen_thread]);
+
+ let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
+ received_server_instructions.lock().unwrap().iter(),
+ size,
+ );
+ let snapshot_count = snapshots.len();
+ for (_cursor_coordinates, snapshot) in snapshots {
+ assert_snapshot!(format!("{}", snapshot));
+ }
+ assert_snapshot!(format!("{}", snapshot_count));
+}
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap
new file mode 100644
index 00000000..e4fbe942
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-2.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2852
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
+01 (C): │ │
+02 (C): │ │
+03 (C): │ │
+04 (C): │ │
+05 (C): │ │
+06 (C): │ │
+07 (C): │ │
+08 (C): │ │
+09 (C): │ │
+10 (C): │ │
+11 (C): │ │
+12 (C): │ │
+13 (C): │ │
+14 (C): │ │
+15 (C): │ │
+16 (C): │ │
+17 (C): │ │
+18 (C): │ │
+19 (C): └──────────────────────────────────────────────────────────────────────────────┘
+
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap
new file mode 100644
index 00000000..ea3ddf34
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane-3.snap
@@ -0,0 +1,6 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2854
+expression: "format!(\"{}\", snapshot_count)"
+---
+2
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap
new file mode 100644
index 00000000..3a14dc55
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_suppress_pane.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2852
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────┐
+01 (C): │ ││ │
+02 (C): │ ││ │
+03 (C): │ ││ │
+04 (C): │ ││ │
+05 (C): │ ││ │
+06 (C): │ ││ │
+07 (C): │ ││ │
+08 (C): │ ││ │
+09 (C): │ ││ │
+10 (C): │ ││ │
+11 (C): │ ││ │
+12 (C): │ ││ │
+13 (C): │ ││ │
+14 (C): │ ││ │
+15 (C): │ ││ │
+16 (C): │ ││ │
+17 (C): │ ││ │
+18 (C): │ ││ │
+19 (C): └──────────────────────────────────────┘└──────────────────────────────────────┘
+
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap
new file mode 100644
index 00000000..0751fa5f
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_launch_or_focus_plugin_action.snap
@@ -0,0 +1,25 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 2758
+expression: "format!(\"{:#?}\", plugin_load_instruction)"
+---
+Some(
+ Load(
+ Some(
+ true,
+ ),
+ None,
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: File(
+ "/path/to/fake/plugin",
+ ),
+ },
+ 0,
+ 1,
+ Size {
+ rows: 0,
+ cols: 0,
+ },
+ ),
+)
diff --git a/zellij-tile/src/lib.rs b/zellij-tile/src/lib.rs
index 685de4af..7968ced3 100644
--- a/zellij-tile/src/lib.rs
+++ b/zellij-tile/src/lib.rs
@@ -14,7 +14,6 @@ pub trait ZellijPlugin {
}
#[allow(unused_variables)]
-// TODO: can we get rid of the lifetime? maybe with generics?
pub trait ZellijWorker<'de>: Default + Serialize + Deserialize<'de> {
fn on_message(&mut self, message: String, payload: String) {}
}
@@ -54,12 +53,13 @@ macro_rules! register_plugin {
#[no_mangle]
pub fn update() -> bool {
- let object = $crate::shim::object_from_stdin()
- .context($crate::PLUGIN_MISMATCH)
- .to_stdout()
- .unwrap();
-
- STATE.with(|state| state.borrow_mut().update(object))
+ STATE.with(|state| {
+ let object = $crate::shim::object_from_stdin()
+ .context($crate::PLUGIN_MISMATCH)
+ .to_stdout()
+ .unwrap();
+ state.borrow_mut().update(object)
+ })
}
#[no_mangle]
@@ -78,10 +78,14 @@ macro_rules! register_plugin {
#[macro_export]
macro_rules! register_worker {
- ($worker:ty, $worker_name:ident) => {
+ ($worker:ty, $worker_name:ident, $worker_static_name:ident) => {
+ // persist worker state in memory in a static variable
+ thread_local! {
+ static $worker_static_name: std::cell::RefCell<$worker> = std::cell::RefCell::new(Default::default());
+ }
#[no_mangle]
pub fn $worker_name() {
- use serde_json::*;
+
let worker_display_name = std::stringify!($worker_name);
// read message from STDIN
@@ -93,43 +97,10 @@ macro_rules! register_worker {
);
Default::default()
});
-
- // read previous worker state from HD if it exists
- let mut worker_instance = match std::fs::read(&format!("/data/{}", worker_display_name))
- .map_err(|e| format!("Failed to read file: {:?}", e))
- .and_then(|s| {
- serde_json::from_str::<$worker>(&String::from_utf8_lossy(&s))
- .map_err(|e| format!("Failed to deserialize: {:?}", e))
- }) {
- Ok(s) => s,
- Err(e) => {
- eprintln!(
- "Failed to read existing state ({:?}), creating new state for worker",
- e
- );
- <$worker>::default()
- },
- };
-
- // invoke worker
- worker_instance.on_message(message, payload);
-
- // persist worker state to HD for next run
- match serde_json::to_string(&worker_instance)
- .map_err(|e| format!("Failed to serialize worker state"))
- .and_then(|serialized_state| {
- std::fs::write(
- &format!("/data/{}", worker_display_name),
- serialized_state.as_bytes(),
- )
- .map_err(|e| format!("Failed to persist state to HD: {:?}", e))
- }) {
- Ok(()) => {},
- Err(e) => eprintln!(
- "Failed to serialize and persist worker state to hd: {:?}",
- e
- ),
- }
- }
+ $worker_static_name.with(|worker_instance| {
+ let mut worker_instance = worker_instance.borrow_mut();
+ worker_instance.on_message(message, payload);
+ });
+ }
};
}
diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs
index f1c1fb54..c43fc8db 100644
--- a/zellij-tile/src/shim.rs
+++ b/zellij-tile/src/shim.rs
@@ -39,11 +39,31 @@ pub fn open_file(path: &Path) {
unsafe { host_open_file() };
}
+pub fn open_file_floating(path: &Path) {
+ object_to_stdout(&path);
+ unsafe { host_open_file_floating() };
+}
+
pub fn open_file_with_line(path: &Path, line: usize) {
object_to_stdout(&(path, line));
unsafe { host_open_file_with_line() };
}
+pub fn open_file_with_line_floating(path: &Path, line: usize) {
+ object_to_stdout(&(path, line));
+ unsafe { host_open_file_with_line_floating() };
+}
+
+pub fn open_terminal(path: &Path) {
+ object_to_stdout(&path);
+ unsafe { host_open_terminal() };
+}
+
+pub fn open_terminal_floating(path: &Path) {
+ object_to_stdout(&path);
+ unsafe { host_open_terminal_floating() };
+}
+
pub fn switch_tab_to(tab_idx: u32) {
unsafe { host_switch_tab_to(tab_idx) };
}
@@ -56,6 +76,10 @@ pub fn exec_cmd(cmd: &[&str]) {
unsafe { host_exec_cmd() };
}
+pub fn hide_self() {
+ unsafe { host_hide_self() };
+}
+
pub fn report_panic(info: &std::panic::PanicInfo) {
println!("");
println!("A panic occured in a plugin");
@@ -105,11 +129,16 @@ extern "C" {
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_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();
}
diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml
index f12d8c78..2469715f 100644
--- a/zellij-utils/Cargo.toml
+++ b/zellij-utils/Cargo.toml
@@ -40,6 +40,8 @@ tempfile = "3.2.0"
kdl = { version = "4.5.0", features = ["span"] }
shellexpand = "3.0.0"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
+notify = "6.0.0"
+async-channel = "1.8.0"
#[cfg(not(target_family = "wasm"))]
[target.'cfg(not(target_family = "wasm"))'.dependencies]
diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm
index 3145fe1d..e033de69 100755
--- a/zellij-utils/assets/plugins/compact-bar.wasm
+++ b/zellij-utils/assets/plugins/compact-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
index a5421d72..cf68f60a 100755
--- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
+++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm
index 8f7ed782..317c839b 100755
--- a/zellij-utils/assets/plugins/status-bar.wasm
+++ b/zellij-utils/assets/plugins/status-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm
index 929f5e8c..51fcf72d 100755
--- a/zellij-utils/assets/plugins/strider.wasm
+++ b/zellij-utils/assets/plugins/strider.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm
index a8742f67..f58671b9 100755
--- a/zellij-utils/assets/plugins/tab-bar.wasm
+++ b/zellij-utils/assets/plugins/tab-bar.wasm
Binary files differ
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 14bcfae7..b222af00 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -377,4 +377,9 @@ pub enum CliAction {
StartOrReloadPlugin {
url: Url,
},
+ LaunchOrFocusPlugin {
+ #[clap(short, long, value_parser)]
+ floating: bool,
+ url: Url,
+ },
}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 285532cc..d470c248 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -3,6 +3,7 @@ use crate::input::config::ConversionError;
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
use std::fmt;
+use std::path::PathBuf;
use std::str::FromStr;
use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString};
@@ -473,6 +474,10 @@ pub enum Event {
String, // message
String, // payload
),
+ FileSystemCreate(Vec<PathBuf>),
+ FileSystemRead(Vec<PathBuf>),
+ FileSystemUpdate(Vec<PathBuf>),
+ FileSystemDelete(Vec<PathBuf>),
}
/// Describes the different input modes, which change the way that keystrokes will be interpreted.
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 74395e57..239cf5ab 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -333,6 +333,8 @@ pub enum ScreenContext {
ProgressPluginLoadingOffset,
StartPluginLoadingIndication,
RequestStateUpdateForPlugins,
+ LaunchOrFocusPlugin,
+ SuppressPane,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index c288b8d2..36d59dde 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -2,7 +2,7 @@
use super::command::RunCommandAction;
use super::layout::{
- FloatingPaneLayout, Layout, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
+ FloatingPaneLayout, Layout, RunPlugin, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
TiledPaneLayout,
};
use crate::cli::CliAction;
@@ -205,6 +205,7 @@ pub enum Action {
LeftClick(Position),
RightClick(Position),
MiddleClick(Position),
+ LaunchOrFocusPlugin(RunPlugin, bool), // bool => should float
LeftMouseRelease(Position),
RightMouseRelease(Position),
MiddleMouseRelease(Position),
@@ -474,6 +475,15 @@ impl Action {
CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
CliAction::StartOrReloadPlugin { url } => Ok(vec![Action::StartOrReloadPlugin(url)]),
+ CliAction::LaunchOrFocusPlugin { url, floating } => {
+ let run_plugin_location = RunPluginLocation::parse(url.as_str())
+ .map_err(|e| format!("Failed to parse plugin location: {}", e))?;
+ let run_plugin = RunPlugin {
+ location: run_plugin_location,
+ _allow_exec_host_cmd: false,
+ };
+ Ok(vec![Action::LaunchOrFocusPlugin(run_plugin, floating)])
+ },
}
}
}
diff --git a/zellij-utils/src/input/command.rs b/zellij-utils/src/input/command.rs
index 3d2a87ab..3212d26e 100644
--- a/zellij-utils/src/input/command.rs
+++ b/zellij-utils/src/input/command.rs
@@ -68,3 +68,16 @@ impl From<RunCommandAction> for RunCommand {
}
}
}
+
+impl RunCommand {
+ pub fn new(command: PathBuf) -> Self {
+ RunCommand {
+ command,
+ ..Default::default()
+ }
+ }
+ pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
+ self.cwd = Some(cwd);
+ self
+ }
+}
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index dceaaf36..58dda9c8 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -876,6 +876,29 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
};
Ok(Action::Run(run_command_action))
},
+ "LaunchOrFocusPlugin" => {
+ let arguments = action_arguments.iter().copied();
+ let mut args = kdl_arguments_that_are_strings(arguments)?;
+ if args.is_empty() {
+ return Err(ConfigError::new_kdl_error(
+ "No plugin found to launch in LaunchOrFocusPlugin".into(),
+ kdl_action.span().offset(),
+ kdl_action.span().len(),
+ ));
+ }
+ let plugin_path = args.remove(0);
+
+ let command_metadata = action_children.iter().next();
+ let should_float = command_metadata
+ .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
+ .unwrap_or(false);
+ let location = RunPluginLocation::parse(&plugin_path)?;
+ let run_plugin = RunPlugin {
+ location,
+ _allow_exec_host_cmd: false,
+ };
+ Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float))
+ },
"PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
"NextSwapLayout" => Ok(Action::NextSwapLayout),
_ => Err(ConfigError::new_kdl_error(
diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs
index 2ee24806..87637de5 100644
--- a/zellij-utils/src/lib.rs
+++ b/zellij-utils/src/lib.rs
@@ -20,6 +20,6 @@ pub mod logging; // Requires log4rs
#[cfg(not(target_family = "wasm"))]
pub use ::{
- anyhow, async_std, clap, interprocess, lazy_static, libc, miette, nix, regex, serde,
- signal_hook, tempfile, termwiz, vte,
+ anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix, notify,
+ regex, serde, signal_hook, tempfile, termwiz, vte,
};