summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2024-07-04 17:57:35 +0200
committerCanop <cano.petrole@gmail.com>2024-07-04 17:57:35 +0200
commitfc05ea77cfeb31f862bd1aecd28ec1ebb322a9a8 (patch)
treeba8ae28b213fb78e331f6a8f87ba20e61df9f88e
parent7dd815abd35c368011948ac7b3a1db704988ff4d (diff)
remove name and subpath from bline
-rw-r--r--src/errors.rs1
-rw-r--r--src/tree_build/bline.rs63
-rw-r--r--src/tree_build/builder.rs115
3 files changed, 107 insertions, 72 deletions
diff --git a/src/errors.rs b/src/errors.rs
index bfdc8e5..789b091 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -70,6 +70,7 @@ custom_error! {pub TreeBuildError
NotADirectory { path: String } = "Not a directory: {path}",
FileNotFound { path: String } = "File not found: {path}",
Interrupted = "Task Interrupted",
+ InvalidUtf8 { path: String } = "Invalid UTF-8 in {path}",
TooManyMatches { max: usize } = "Too many matches (max allowed: {max})",
}
diff --git a/src/tree_build/bline.rs b/src/tree_build/bline.rs
index d53a4b6..f7a1c1b 100644
--- a/src/tree_build/bline.rs
+++ b/src/tree_build/bline.rs
@@ -1,7 +1,6 @@
use {
super::bid::BId,
crate::{
- app::AppContext,
errors::TreeBuildError,
git::GitIgnoreChain,
path::{normalize_path, Directive, SpecialHandling},
@@ -22,8 +21,6 @@ pub struct BLine {
pub parent_id: Option<BId>,
pub path: PathBuf,
pub depth: u16,
- pub subpath: String,
- pub name: String,
pub file_type: fs::FileType,
pub children: Option<Vec<BId>>, // sorted and filtered
pub next_child_idx: usize, // index for iteration, among the children
@@ -37,6 +34,15 @@ pub struct BLine {
}
impl BLine {
+ pub fn name(
+ &self
+ ) -> &str {
+ self
+ .path
+ .file_name()
+ .and_then(|os_str| os_str.to_str())
+ .unwrap_or("")
+ }
/// a special constructor, checking nothing
pub fn from_root(
blines: &mut Arena<BLine>,
@@ -44,18 +50,12 @@ impl BLine {
git_ignore_chain: GitIgnoreChain,
_options: &TreeOptions,
) -> Result<BId, TreeBuildError> {
- let name = match path.file_name() {
- Some(name) => name.to_string_lossy().to_string(),
- None => String::from("???"), // should not happen
- };
if let Ok(md) = fs::metadata(&path) {
let file_type = md.file_type();
Ok(blines.alloc(BLine {
parent_id: None,
path,
depth: 0,
- name,
- subpath: String::new(),
children: None,
next_child_idx: 0,
file_type,
@@ -118,49 +118,4 @@ impl BLine {
}
false
}
- pub fn to_tree_line(&self, bid: BId, con: &AppContext) -> std::io::Result<TreeLine> {
- let has_error = self.has_error;
- let line_type = TreeLineType::new(&self.path, &self.file_type);
- let unlisted = if let Some(children) = &self.children {
- // number of not listed children
- children.len() - self.next_child_idx
- } else {
- 0
- };
- let metadata = fs::symlink_metadata(&self.path)?;
- let subpath = self.subpath.replace('\n', "");
- let name = self.name.replace('\n', "");
- let icon = 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,
- parent_bid: self.parent_id,
- left_branches: vec![false; self.depth as usize].into_boxed_slice(),
- depth: self.depth,
- icon,
- name,
- subpath,
- path: self.path.clone(),
- line_type,
- has_error,
- nb_kept_children: self.nb_kept_children as usize,
- unlisted,
- score: self.score,
- direct_match: self.direct_match,
- sum: None,
- metadata,
- git_status: None,
- })
- }
}
diff --git a/src/tree_build/builder.rs b/src/tree_build/builder.rs
index d1c85b2..9d8328b 100644
--- a/src/tree_build/builder.rs
+++ b/src/tree_build/builder.rs
@@ -19,7 +19,9 @@ use {
std::{
collections::{BinaryHeap, VecDeque},
fs,
- path::PathBuf,
+ path::{
+ PathBuf,
+ },
result::Result,
time::{Duration, Instant},
},
@@ -58,6 +60,7 @@ pub struct TreeBuilder<'c> {
targeted_size: usize, // the number of lines we should fill (height of the screen)
blines: Arena<BLine>,
root_id: BId,
+ subpath_offset: usize,
total_search: bool,
git_ignorer: GitIgnorer,
line_status_computer: Option<LineStatusComputer>,
@@ -76,6 +79,11 @@ impl<'c> TreeBuilder<'c> {
con: &'c AppContext,
) -> Result<TreeBuilder<'c>, TreeBuildError> {
let mut blines = Arena::new();
+ let subpath_offset = path
+ .as_os_str()
+ .as_bytes()
+ .len()
+ + 1; // +1 for the separator
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 {
@@ -104,6 +112,7 @@ impl<'c> TreeBuilder<'c> {
targeted_size,
blines,
root_id,
+ subpath_offset,
total_search: true, // we'll set it to false if we don't look at all children
git_ignorer,
line_status_computer,
@@ -116,7 +125,7 @@ impl<'c> TreeBuilder<'c> {
}
/// Return a bline if the dir_entry directly matches the options and there's no error
- fn make_line(
+ fn make_bline(
&mut self,
parent_id: BId,
e: &fs::DirEntry,
@@ -124,6 +133,8 @@ impl<'c> TreeBuilder<'c> {
) -> Option<BLine> {
let name = e.file_name();
if name.is_empty() {
+ // this should not really happen as the only path with an empty name is the root
+ // and we don't call this function for the tree root
self.report.error_count += 1;
return None;
}
@@ -139,7 +150,11 @@ impl<'c> TreeBuilder<'c> {
self.report.hidden_count += 1;
return None;
}
- let name = name.to_string_lossy();
+ let Some(name) = name.to_str() else {
+ warn!("invalid utf8 file name: {:?}", name);
+ self.report.error_count += 1;
+ return None;
+ };
let mut has_match = true;
let mut score = 10000 - i32::from(depth); // we dope less deep entries
let file_type = match e.file_type() {
@@ -149,15 +164,16 @@ impl<'c> TreeBuilder<'c> {
return None;
}
};
- let parent_subpath = &self.blines[parent_id].subpath;
- let subpath = if !parent_subpath.is_empty() {
- format!("{}/{}", parent_subpath, &name)
- } else {
- name.to_string()
+
+ let subpath_bytes = &path
+ .as_os_str()
+ .as_bytes()[self.subpath_offset..];
+ let subpath = unsafe {
+ std::str::from_utf8_unchecked(subpath_bytes)
};
let candidate = Candidate {
- name: &name,
- subpath: &subpath,
+ name,
+ subpath,
path: &path,
regular_file: file_type.is_file(),
};
@@ -169,7 +185,6 @@ impl<'c> TreeBuilder<'c> {
has_match = false;
false
};
- let name = name.to_string();
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) {
@@ -197,7 +212,7 @@ impl<'c> TreeBuilder<'c> {
let parent_chain = &self.blines[parent_id].git_ignore_chain;
if !self
.git_ignorer
- .accepts(parent_chain, &path, &name, file_type.is_dir())
+ .accepts(parent_chain, &path, name, file_type.is_dir())
{
if special_handling.show != Directive::Always {
return None;
@@ -208,8 +223,6 @@ impl<'c> TreeBuilder<'c> {
parent_id: Some(parent_id),
path,
depth,
- subpath,
- name,
file_type,
children: None,
next_child_idx: 0,
@@ -223,6 +236,8 @@ 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 {
let mut has_child_match = false;
@@ -232,7 +247,7 @@ impl<'c> TreeBuilder<'c> {
let child_depth = self.blines[bid].depth + 1;
let mut lines = Vec::new();
for e in entries.flatten() {
- if let Some(line) = self.make_line(bid, &e, child_depth) {
+ if let Some(line) = self.make_bline(bid, &e, child_depth) {
lines.push(line);
}
}
@@ -254,9 +269,9 @@ impl<'c> TreeBuilder<'c> {
}
children.sort_by(|&a, &b| {
self.blines[a]
- .name
+ .name()
.to_lowercase()
- .cmp(&self.blines[b].name.to_lowercase())
+ .cmp(&self.blines[b].name().to_lowercase())
});
self.blines[bid].children = Some(children);
}
@@ -428,6 +443,70 @@ impl<'c> TreeBuilder<'c> {
}
}
+ fn make_tree_line(
+ &self,
+ bid: BId,
+ ) -> Result<TreeLine, TreeBuildError> {
+ let bline = &self.blines[bid];
+ let line_type = TreeLineType::new(&bline.path, &bline.file_type);
+ 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 {
+ 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
+ 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(),
+ })?
+ };
+ let name = bline
+ .path
+ .file_name()
+ .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,
+ )
+ });
+
+ Ok(TreeLine {
+ bid,
+ parent_bid: bline.parent_id,
+ left_branches: vec![false; bline.depth as usize].into_boxed_slice(),
+ depth: bline.depth,
+ icon,
+ name,
+ subpath,
+ path: bline.path.clone(),
+ line_type,
+ has_error: bline.has_error,
+ nb_kept_children: bline.nb_kept_children as usize,
+ unlisted,
+ score: bline.score,
+ direct_match: bline.direct_match,
+ sum: None,
+ metadata,
+ git_status: None,
+ })
+
+
+ }
+
/// make a tree from the builder's specific structure
fn take_as_tree(mut self, out_blines: &[BId]) -> Tree {
let mut lines: Vec<TreeLine> = Vec::new();
@@ -437,7 +516,7 @@ impl<'c> TreeBuilder<'c> {
if self.blines[*id].can_enter() && self.blines[*id].children.is_none() {
self.load_children(*id);
}
- if let Ok(tree_line) = self.blines[*id].to_tree_line(*id, self.con) {
+ if let Ok(tree_line) = self.make_tree_line(*id) {
lines.push(tree_line);
} else {
// I guess the file went missing during tree computation