summaryrefslogtreecommitdiffstats
path: root/default-plugins
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 /default-plugins
parentb8f095330a57c905f23563ca7c2bfae3171abf57 (diff)
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
Diffstat (limited to 'default-plugins')
-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
11 files changed, 1548 insertions, 557 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index 124b2dc75..7e4139f55 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 d45a8ff21..65b5d23e9 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 4f299c508..966b53a4f 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 299882eac..000000000
--- 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(cur