From 21273ac95a2fed6b07d8550fe2d4f65993be7037 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Mon, 26 Feb 2024 15:30:15 +0100 Subject: feat(plugins): introduce plugin aliases (#3157) * working prototype with passing tests * new tests and passing plugin tests as well * style(code): cleanups * cleanup strider from unused search feature * prototype of removing old plugin block from the config * aliases working from config file and all tests passing * fixups and cleanups * use aliases in layouts * update test snapshot * style(fmt): rustfmt --- default-plugins/strider/src/main.rs | 138 ------- .../strider/src/search/controls_line.rs | 353 ------------------ default-plugins/strider/src/search/mod.rs | 317 ---------------- .../strider/src/search/search_results.rs | 308 --------------- default-plugins/strider/src/search/search_state.rs | 264 ------------- .../strider/src/search/selection_controls_area.rs | 61 --- default-plugins/strider/src/search/ui.rs | 120 ------ default-plugins/strider/src/state.rs | 22 -- zellij-client/src/lib.rs | 2 +- zellij-server/src/lib.rs | 13 +- zellij-server/src/panes/floating_panes/mod.rs | 7 +- zellij-server/src/panes/tiled_panes/mod.rs | 7 +- zellij-server/src/plugins/mod.rs | 214 ++++++----- zellij-server/src/plugins/unit/plugin_tests.rs | 415 +++++++++++++-------- ...n_tests__load_new_plugin_with_plugin_alias.snap | 12 + ...gins__plugin_tests__start_or_reload_plugin.snap | 22 +- zellij-server/src/plugins/wasm_bridge.rs | 283 +++++++------- zellij-server/src/plugins/zellij_exports.rs | 14 +- zellij-server/src/pty.rs | 14 +- zellij-server/src/route.rs | 4 +- zellij-server/src/screen.rs | 92 +++-- zellij-server/src/session_layout_metadata.rs | 5 +- zellij-server/src/tab/layout_applier.rs | 18 +- zellij-server/src/tab/mod.rs | 17 +- ...loating_layout_is_included_in_swap_layouts.snap | 4 +- ..._with_plugins_and_commands_swaped_properly.snap | 4 +- ..._command_panes_absent_from_existing_layout.snap | 4 +- ...g_command_panes_present_in_existing_layout.snap | 6 +- ...ng_plugin_panes_present_in_existing_layout.snap | 6 +- ...ng_plugin_panes_present_in_existing_layout.snap | 6 +- .../src/tab/unit/tab_integration_tests.rs | 106 ++---- zellij-server/src/unit/screen_tests.rs | 194 +++++++++- ..._break_floating_plugin_pane_to_a_new_tab-2.snap | 6 +- ..._break_floating_plugin_pane_to_a_new_tab-3.snap | 6 +- ..._break_floating_plugin_pane_to_a_new_tab-4.snap | 6 +- ..._break_floating_plugin_pane_to_a_new_tab-6.snap | 6 +- ..._break_floating_plugin_pane_to_a_new_tab-7.snap | 6 +- ...an_break_floating_plugin_pane_to_a_new_tab.snap | 6 +- ...creen_can_break_plugin_pane_to_a_new_tab-3.snap | 4 +- ...creen_can_break_plugin_pane_to_a_new_tab-4.snap | 4 +- ..._screen_can_break_plugin_pane_to_a_new_tab.snap | 4 +- ...ts__send_cli_launch_or_focus_plugin_action.snap | 22 +- zellij-utils/assets/config/default.kdl | 14 +- zellij-utils/assets/layouts/compact.kdl | 2 +- zellij-utils/assets/layouts/compact.swap.kdl | 2 +- zellij-utils/assets/layouts/default.kdl | 4 +- zellij-utils/assets/layouts/default.swap.kdl | 4 +- zellij-utils/assets/layouts/disable-status-bar.kdl | 2 +- zellij-utils/assets/layouts/strider.kdl | 6 +- zellij-utils/assets/layouts/strider.swap.kdl | 6 +- zellij-utils/assets/layouts/welcome.kdl | 4 +- zellij-utils/src/cli.rs | 2 +- zellij-utils/src/input/actions.rs | 82 ++-- zellij-utils/src/input/config.rs | 86 ++--- zellij-utils/src/input/layout.rs | 196 +++++++++- zellij-utils/src/input/plugins.rs | 113 +++--- zellij-utils/src/input/unit/layout_test.rs | 47 ++- ...an_load_swap_layouts_from_a_different_file.snap | 134 ++++--- zellij-utils/src/ipc.rs | 4 +- zellij-utils/src/kdl/kdl_layout_parser.rs | 31 +- zellij-utils/src/kdl/mod.rs | 99 ++--- zellij-utils/src/plugin_api/action.rs | 104 +++--- zellij-utils/src/session_serialization.rs | 31 +- zellij-utils/src/setup.rs | 14 - ...st__default_config_with_no_cli_arguments-2.snap | 322 ++++++++-------- ...test__default_config_with_no_cli_arguments.snap | 190 +++++----- ...__layout_env_vars_override_config_env_vars.snap | 190 +++++----- ...__layout_keybinds_override_config_keybinds.snap | 168 ++++----- ...st__layout_plugins_override_config_plugins.snap | 24 +- ...test__layout_themes_override_config_themes.snap | 190 +++++----- ...ayout_ui_config_overrides_config_ui_config.snap | 190 +++++----- 71 files changed, 2119 insertions(+), 3264 deletions(-) delete mode 100644 default-plugins/strider/src/search/controls_line.rs delete mode 100644 default-plugins/strider/src/search/mod.rs delete mode 100644 default-plugins/strider/src/search/search_results.rs delete mode 100644 default-plugins/strider/src/search/search_state.rs delete mode 100644 default-plugins/strider/src/search/selection_controls_area.rs delete mode 100644 default-plugins/strider/src/search/ui.rs create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__load_new_plugin_with_plugin_alias.snap diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs index 598bf61d4..2e06240af 100644 --- a/default-plugins/strider/src/main.rs +++ b/default-plugins/strider/src/main.rs @@ -1,49 +1,22 @@ -mod search; mod state; use colored::*; -use search::{FileContentsWorker, FileNameWorker, MessageToSearch, ResultsOfSearch}; -use serde::{Deserialize, Serialize}; -use serde_json; use state::{refresh_directory, FsEntry, State}; use std::collections::BTreeMap; use std::{cmp::min, time::Instant}; use zellij_tile::prelude::*; register_plugin!(State); -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, _configuration: BTreeMap) { refresh_directory(self); - self.search_state.loading = true; subscribe(&[ EventType::Key, EventType::Mouse, EventType::CustomMessage, EventType::Timer, - EventType::FileSystemCreate, - EventType::FileSystemUpdate, - EventType::FileSystemDelete, - EventType::PermissionRequestResult, ]); - post_message_to(PluginMessage { - worker_name: Some("file_name_search".into()), - name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), - payload: "".into(), - }); - post_message_to(PluginMessage { - worker_name: Some("file_contents_search".into()), - name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(), - payload: "".into(), - }); - self.search_state.loading = true; - set_timeout(0.5); // for displaying loading animation } fn update(&mut self, event: Event) -> bool { @@ -55,58 +28,8 @@ impl ZellijPlugin for State { }; self.ev_history.push_back((event.clone(), Instant::now())); match event { - Event::PermissionRequestResult(_) => { - should_render = true; - }, - Event::Timer(_elapsed) => { - if self.search_state.loading { - set_timeout(0.5); - self.search_state.progress_animation(); - should_render = true; - } - }, - Event::CustomMessage(message, payload) => match serde_json::from_str(&message) { - Ok(MessageToPlugin::UpdateFileNameSearchResults) => { - if let Ok(results_of_search) = serde_json::from_str::(&payload) - { - 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::(&payload) - { - self.search_state - .update_file_contents_search_results(results_of_search); - should_render = true; - } - }, - 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 { - Key::Esc if self.typing_search_term() => { - if self.search_state.search_term.is_empty() { - self.stop_typing_search_term(); - } else { - self.search_state.handle_key(key); - } - should_render = true; - }, - _ if self.typing_search_term() => { - self.search_state.handle_key(key); - should_render = true; - }, - Key::Char('/') => { - self.start_typing_search_term(); - should_render = true; - }, Key::Esc => { - self.stop_typing_search_term(); hide_self(); should_render = true; }, @@ -190,54 +113,6 @@ impl ZellijPlugin for State { }, _ => {}, }, - Event::FileSystemCreate(paths) => { - let paths: Vec = paths - .iter() - .map(|p| p.to_string_lossy().to_string()) - .collect(); - post_message_to(PluginMessage { - worker_name: Some("file_name_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - post_message_to(PluginMessage { - worker_name: Some("file_contents_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - }, - Event::FileSystemUpdate(paths) => { - let paths: Vec = paths - .iter() - .map(|p| p.to_string_lossy().to_string()) - .collect(); - post_message_to(PluginMessage { - worker_name: Some("file_name_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - post_message_to(PluginMessage { - worker_name: Some("file_contents_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - }, - Event::FileSystemDelete(paths) => { - let paths: Vec = paths - .iter() - .map(|p| p.to_string_lossy().to_string()) - .collect(); - post_message_to(PluginMessage { - worker_name: Some("file_name_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - post_message_to(PluginMessage { - worker_name: Some("file_contents_search".into()), - name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(), - payload: serde_json::to_string(&paths).unwrap(), - }); - }, _ => { dbg!("Unknown event {:?}", event); }, @@ -246,12 +121,6 @@ impl ZellijPlugin for State { } fn render(&mut self, rows: usize, cols: usize) { - if self.typing_search_term() { - self.search_state.change_size(rows, cols); - print!("{}", self.search_state); - return; - } - for i in 0..rows { if self.selected() < self.scroll() { *self.scroll_mut() = self.selected(); @@ -288,10 +157,3 @@ impl ZellijPlugin for State { } } } - -#[derive(Serialize, Deserialize)] -pub enum MessageToPlugin { - UpdateFileNameSearchResults, - UpdateFileContentsSearchResults, - DoneScanningFolder, -} diff --git a/default-plugins/strider/src/search/controls_line.rs b/default-plugins/strider/src/search/controls_line.rs deleted file mode 100644 index a75715ef1..000000000 --- a/default-plugins/strider/src/search/controls_line.rs +++ /dev/null @@ -1,353 +0,0 @@ -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, - scanning_indication: Option>, - animation_offset: u8, -} - -impl ControlsLine { - pub fn new(controls: Vec, scanning_indication: Option>) -> 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::(); - let mid_length = - loading_animation.mid_len() + self.controls.iter().map(|c| c.mid_len()).sum::(); - let short_length = loading_animation.short_len() - + self.controls.iter().map(|c| c.short_len()).sum::(); - 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>, - animation_offset: u8, - background_color: u8, - foreground_color: u8, -} -impl LoadingAnimation { - pub fn new(scanning_indication: &Option>, 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 deleted file mode 100644 index 6ffa60cba..000000000 --- a/default-plugins/strider/src/search/mod.rs +++ /dev/null @@ -1,317 +0,0 @@ -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 ignore::Walk; -use search_results::SearchResult; -use serde::{Deserialize, Serialize}; - -use std::io::{self, BufRead}; - -#[derive(Default, Serialize, Deserialize)] -pub struct Search { - search_type: SearchType, - file_names: BTreeSet, - file_contents: BTreeMap<(String, usize), String>, // file_name, line_number, line - cached_file_name_results: HashMap>, - cached_file_contents_results: HashMap>, -} - -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::(&message) { - Ok(MessageToSearch::ScanFolder) => { - self.scan_hd(); - post_message_to_plugin(PluginMessage { - worker_name: None, - name: serde_json::to_string(&MessageToPlugin::DoneScanningFolder).unwrap(), - payload: "".to_owned(), - }); - }, - Ok(MessageToSearch::Search) => { - if let Some(current_search_term) = self.read_search_term_from_hd_cache() { - 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 result in Walk::new(ROOT) { - if let Ok(entry) = result { - 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(PluginMessage { - name: serde_json::to_string(&MessageToPlugin::UpdateFileNameSearchResults).unwrap(), - payload: serde_json::to_string(&file_names_search_results).unwrap(), - ..Default::default() - }); - } - if let Some(file_contents_search_results) = file_contents_search_results { - post_message_to_plugin(PluginMessage { - name: serde_json::to_string(&MessageToPlugin::UpdateFileContentsSearchResults) - .unwrap(), - payload: serde_json::to_string(&file_contents_search_results).unwrap(), - ..Default::default() - }); - } - } - pub fn rescan_files(&mut self, paths: String) { - match serde_json::from_str::>(&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::>(&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) { - 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( - (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 { - 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 { - let mut matches = vec![]; - for ((file_name, line_number), line_entry) in &self.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, - )); - } - } - 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 { - match std::fs::read(CURRENT_SEARCH_TERM) { - Ok(current_search_term) => { - Some(String::from_utf8_lossy(¤t_search_term).to_string()) - }, - _ => None, - } - } - fn remove_existing_entries(&mut self, paths: &Vec) { - let file_path_stripped_prefixes: Vec = - 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, -} - -impl ResultsOfSearch { - pub fn new(search_term: String, search_results: Vec) -> 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 deleted file mode 100644 index b76200ae8..000000000 --- a/default-plugins/strider/src/search/search_results.rs +++ /dev/null @@ -1,308 +0,0 @@ -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, - }, - LineInFile { - path: String, - line: String, - line_number: usize, - score: i64, - indices: Vec, - }, -} - -impl SearchResult { - pub fn new_file_name(score: i64, indices: Vec, path: String) -> Self { - SearchResult::File { - path, - score, - indices, - } - } - pub fn new_file_line( - score: i64, - indices: Vec, - 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, - 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, - 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, - max_width: usize, - foreground_color: Option, - ) -> 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, - 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 deleted file mode 100644 index 60d03941d..000000000 --- a/default-plugins/strider/src/search/search_state.rs +++ /dev/null @@ -1,264 +0,0 @@ -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_terminal, open_terminal_floating, - post_message_to, FileToOpen, Key, PluginMessage, -}; - -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, - pub file_contents_search_results: Vec, - 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), // 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 => { - if !self.search_term.is_empty() { - self.clear_state(); - } else { - hide_self(); - } - }, - _ => 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( - FileToOpen { - path: PathBuf::from(path), - ..Default::default() - }, - None, - ); - } else { - open_file(FileToOpen { - path: PathBuf::from(path), - ..Default::default() - }); - } - }, - Some(SearchResult::LineInFile { - path, line_number, .. - }) => { - if self.should_open_floating { - open_file_floating( - FileToOpen { - path: PathBuf::from(path), - line_number: Some(line_number), - ..Default::default() - }, - None, - ); - } else { - open_file(FileToOpen { - path: PathBuf::from(path), - line_number: Some(line_number), - ..Default::default() - }); - } - }, - 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, None); - } 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(PluginMessage { - worker_name: Some("file_name_search".into()), - name: serde_json::to_string(&MessageToSearch::Search).unwrap(), - payload: "".into(), - }); - post_message_to(PluginMessage { - worker_name: Some("file_contents_search".into()), - name: serde_json::to_string(&MessageToSearch::Search).unwrap(), - payload: "".into(), - }); - self.file_name_search_results.clear(); - self.file_contents_search_results.clear(); - } - }, - 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(¤tly_selected_search_result)) - }) - .unwrap_or(0); - self.displayed_search_results = (new_index, displayed_search_results); - } - fn selected_search_result_entry(&self) -> Option { - 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 deleted file mode 100644 index 0805e1e41..000000000 --- a/default-plugins/strider/src/search/selection_controls_area.rs +++ /dev/null @@ -1,61 +0,0 @@ -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("")); - let enter_tip = bold(" - open in editor. "); - let tab = styled_text_foreground(ORANGE, &bold("")); - 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("")); - let enter_tip = bold(" - edit. "); - let tab = styled_text_foreground(ORANGE, &bold("")); - 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 deleted file mode 100644 index 5ee5a3e83..000000000 --- a/default-plugins/strider/src/search/ui.rs +++ /dev/null @@ -1,120 +0,0 @@ -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 =