From 557439738966e55b278c3b6a6ae7e309aced45b0 Mon Sep 17 00:00:00 2001 From: Canop Date: Tue, 16 Jul 2024 08:11:58 +0200 Subject: [PATCH] Fix search broken on root Fix #904 --- src/tree_build/builder.rs | 208 +++++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 92 deletions(-) diff --git a/src/tree_build/builder.rs b/src/tree_build/builder.rs index 9d8328be..65fe9a46 100644 --- a/src/tree_build/builder.rs +++ b/src/tree_build/builder.rs @@ -1,29 +1,42 @@ use { super::{ - bid::{BId, SortableBId}, - BuildReport, + bid::{ + BId, + SortableBId, + }, bline::BLine, + BuildReport, }, crate::{ app::AppContext, errors::TreeBuildError, - git::{GitIgnoreChain, GitIgnorer, LineStatusComputer}, - pattern::Candidate, + git::{ + GitIgnoreChain, + GitIgnorer, + LineStatusComputer, + }, path::Directive, - task_sync::ComputationResult, - task_sync::Dam, + pattern::Candidate, + task_sync::{ + ComputationResult, + Dam, + }, tree::*, }, git2::Repository, id_arena::Arena, std::{ - collections::{BinaryHeap, VecDeque}, - fs, - path::{ - PathBuf, + collections::{ + BinaryHeap, + VecDeque, }, + fs, + path::PathBuf, result::Result, - time::{Duration, Instant}, + time::{ + Duration, + Instant, + }, }, }; @@ -71,7 +84,6 @@ pub struct TreeBuilder<'c> { report: BuildReport, } impl<'c> TreeBuilder<'c> { - pub fn from( path: PathBuf, options: TreeOptions, @@ -79,11 +91,7 @@ impl<'c> TreeBuilder<'c> { con: &'c AppContext, ) -> Result, TreeBuildError> { let mut blines = Arena::new(); - let subpath_offset = path - .as_os_str() - .as_bytes() - .len() - + 1; // +1 for the separator + let subpath_offset = path.components().count(); let mut git_ignorer = time!(GitIgnorer::default()); let root_ignore_chain = git_ignorer.root_chain(&path); let line_status_computer = if options.filter_by_git_status || options.show_git_file_info { @@ -97,7 +105,11 @@ impl<'c> TreeBuilder<'c> { None }; let root_id = BLine::from_root(&mut blines, path, root_ignore_chain, &options)?; - let trim_root = match (options.trim_root, options.pattern.is_some(), options.sort.prevent_deep_display()) { + let trim_root = match ( + options.trim_root, + options.pattern.is_some(), + options.sort.prevent_deep_display(), + ) { // we never want to trim the root if there's a sort (_, _, true) => false, // if the user don't want root trimming, we don't trim @@ -164,27 +176,25 @@ impl<'c> TreeBuilder<'c> { return None; } }; - - let subpath_bytes = &path - .as_os_str() - .as_bytes()[self.subpath_offset..]; - let subpath = unsafe { - std::str::from_utf8_unchecked(subpath_bytes) - }; + let subpath = path + .components() + .skip(self.subpath_offset) + .collect::(); let candidate = Candidate { name, - subpath, + subpath: &subpath.to_string_lossy(), path: &path, regular_file: file_type.is_file(), }; - let direct_match = if let Some(pattern_score) = self.options.pattern.pattern.score_of(candidate) { - // we dope direct matches to compensate for depth doping of parent folders - score += pattern_score + 10; - true - } else { - has_match = false; - false - }; + let direct_match = + if let Some(pattern_score) = self.options.pattern.pattern.score_of(candidate) { + // we dope direct matches to compensate for depth doping of parent folders + score += pattern_score + 10; + true + } else { + has_match = false; + false + }; if has_match && self.options.filter_by_git_status { if let Some(line_status_computer) = &self.line_status_computer { if !line_status_computer.is_interesting(&path) { @@ -239,7 +249,10 @@ impl<'c> TreeBuilder<'c> { /// Fill the bline's children vec of blines. /// /// Return true when there are direct matches among children - fn load_children(&mut self, bid: BId) -> bool { + fn load_children( + &mut self, + bid: BId, + ) -> bool { let mut has_child_match = false; match self.blines[bid].read_dir() { Ok(entries) => { @@ -285,7 +298,10 @@ impl<'c> TreeBuilder<'c> { /// return the next child. /// load_children must have been called before on parent_id - fn next_child(&mut self, parent_id: BId) -> Option { + fn next_child( + &mut self, + parent_id: BId, + ) -> Option { let bline = &mut self.blines[parent_id]; if let Some(children) = &bline.children { if bline.next_child_idx < children.len() { @@ -303,7 +319,11 @@ impl<'c> TreeBuilder<'c> { /// first step of the build: we explore the directories and gather lines. /// If there's no search pattern we stop when we have enough lines to fill the screen. /// If there's a pattern, we try to gather more lines that will be sorted afterwards. - fn gather_lines(&mut self, total_search: bool, dam: &Dam) -> Result, TreeBuildError> { + fn gather_lines( + &mut self, + total_search: bool, + dam: &Dam, + ) -> Result, TreeBuildError> { let start = Instant::now(); let mut out_blines: Vec = Vec::new(); // the blines we want to display let optimal_size = if self.options.pattern.pattern.has_real_scores() { @@ -319,16 +339,16 @@ impl<'c> TreeBuilder<'c> { open_dirs.push_back(self.root_id); let deep = self.deep && self.options.show_tree && !self.options.sort.prevent_deep_display(); loop { - if !total_search && ( - (nb_lines_ok > optimal_size) - || (nb_lines_ok >= self.targeted_size && start.elapsed() > NOT_LONG) - ) { + if !total_search + && ((nb_lines_ok > optimal_size) + || (nb_lines_ok >= self.targeted_size && start.elapsed() > NOT_LONG)) + { self.total_search = false; break; } if let Some(max) = self.matches_max { if nb_lines_ok > max { - return Err(TreeBuildError::TooManyMatches{max}); + return Err(TreeBuildError::TooManyMatches { max }); } } if let Some(open_dir_id) = open_dirs.pop_front() { @@ -381,7 +401,7 @@ impl<'c> TreeBuilder<'c> { } if let Some(max) = self.matches_max { if nb_lines_ok > max { - return Err(TreeBuildError::TooManyMatches{max}); + return Err(TreeBuildError::TooManyMatches { max }); } } if !self.trim_root { @@ -399,7 +419,10 @@ impl<'c> TreeBuilder<'c> { /// strictly necessary to fill the screen. /// This function keeps only the best ones while taking care of not /// removing a parent before its children. - fn trim_excess(&mut self, out_blines: &[BId]) { + fn trim_excess( + &mut self, + out_blines: &[BId], + ) { let mut count = 1; for id in out_blines[1..].iter() { if self.blines[*id].has_match { @@ -413,9 +436,10 @@ impl<'c> TreeBuilder<'c> { let mut remove_queue: BinaryHeap = BinaryHeap::new(); for id in out_blines[1..].iter() { let bline = &self.blines[*id]; - if bline.has_match && bline.nb_kept_children == 0 && (bline.depth > 1 || self.trim_root) + if bline.has_match + && bline.nb_kept_children == 0 + && (bline.depth > 1 || self.trim_root) { - //debug!("in list: {:?} score: {}", &bline.path, bline.score); remove_queue.push(SortableBId { id: *id, score: bline.score, @@ -449,21 +473,25 @@ impl<'c> TreeBuilder<'c> { ) -> Result { let bline = &self.blines[bid]; let line_type = TreeLineType::new(&bline.path, &bline.file_type); - let unlisted = bline.children.as_ref() + let unlisted = bline + .children + .as_ref() .map_or(0, |children| children.len() - bline.next_child_idx); - let metadata = fs::symlink_metadata(&bline.path) - .map_err(|_| TreeBuildError::FileNotFound { + let metadata = + fs::symlink_metadata(&bline.path).map_err(|_| TreeBuildError::FileNotFound { path: bline.path.to_string_lossy().to_string(), })?; - let subpath_bytes = bline.path.as_os_str().as_bytes(); - let subpath = if self.subpath_offset > subpath_bytes.len() { - // This should be the root line + let subpath = if bline.depth == 0 { bline.path.to_string_lossy().to_string() } else { - String::from_utf8(subpath_bytes[self.subpath_offset..].to_vec()) - .map_err(|_| TreeBuildError::InvalidUtf8 { - path: bline.path.to_string_lossy().to_string(), - })? + bline + .path + .components() + .skip(self.subpath_offset) + .map(|c| c.as_os_str()) + .collect::() + .to_string_lossy() + .to_string() }; let name = bline .path @@ -471,18 +499,12 @@ impl<'c> TreeBuilder<'c> { .and_then(|os_str| os_str.to_str()) .unwrap_or("") .replace('\n', ""); - let icon = self.con.icons.as_ref() - .map(|icon_plugin| { - let extension = TreeLine::extension_from_name(&name); - let double_extension = extension - .and_then(|_| TreeLine::double_extension_from_name(&name)); - icon_plugin.get_icon( - &line_type, - &name, - double_extension, - extension, - ) - }); + let icon = self.con.icons.as_ref().map(|icon_plugin| { + let extension = TreeLine::extension_from_name(&name); + let double_extension = + extension.and_then(|_| TreeLine::double_extension_from_name(&name)); + icon_plugin.get_icon(&line_type, &name, double_extension, extension) + }); Ok(TreeLine { bid, @@ -503,12 +525,13 @@ impl<'c> TreeBuilder<'c> { metadata, git_status: None, }) - - } /// make a tree from the builder's specific structure - fn take_as_tree(mut self, out_blines: &[BId]) -> Tree { + fn take_as_tree( + mut self, + out_blines: &[BId], + ) -> Tree { let mut lines: Vec = Vec::new(); for id in out_blines.iter() { if self.blines[*id].has_match { @@ -554,34 +577,35 @@ impl<'c> TreeBuilder<'c> { /// /// Return None if the lifetime expires before end of computation /// (usually because the user hit a key) - pub fn build_tree(mut self, total_search: bool, dam: &Dam) -> Result { - self.gather_lines(total_search, dam) - .map(|blines_ids| { - debug!("blines before trimming: {}", blines_ids.len()); - if !self.total_search { - self.trim_excess(&blines_ids); - } - self.take_as_tree(&blines_ids) - }) + pub fn build_tree( + mut self, + total_search: bool, + dam: &Dam, + ) -> Result { + self.gather_lines(total_search, dam).map(|blines_ids| { + debug!("blines before trimming: {}", blines_ids.len()); + if !self.total_search { + self.trim_excess(&blines_ids); + } + self.take_as_tree(&blines_ids) + }) } pub fn build_paths( mut self, total_search: bool, dam: &Dam, - filter: F + filter: F, ) -> Result, TreeBuildError> - where F: Fn(&BLine) -> bool + where + F: Fn(&BLine) -> bool, { - self.gather_lines(total_search, dam) - .map(|mut blines_ids| { - blines_ids - .drain(..) - .filter(|&bid| { - self.blines[bid].direct_match && filter(&self.blines[bid]) - }) - .map(|id| self.blines[id].path.clone()) - .collect() - }) + self.gather_lines(total_search, dam).map(|mut blines_ids| { + blines_ids + .drain(..) + .filter(|&bid| self.blines[bid].direct_match && filter(&self.blines[bid])) + .map(|id| self.blines[id].path.clone()) + .collect() + }) } }