diff options
Diffstat (limited to 'default-plugins/strider/src/search.rs')
-rw-r--r-- | default-plugins/strider/src/search.rs | 415 |
1 files changed, 0 insertions, 415 deletions
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(current_search_term) = std::fs::read(CURRENT_SEARCH_TERM) { - let current_search_term = String::from_utf8_lossy(¤t_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 - } -} |