From 28bc4b482359ae9af37dbc8afcff8c7b1227b0d9 Mon Sep 17 00:00:00 2001 From: Penwing Date: Wed, 31 Jan 2024 11:04:36 +0100 Subject: [PATCH] file tree lazy update --- src/core/app.rs | 13 +++-- src/core/ui.rs | 26 ++++++--- src/panels/file_tree.rs | 121 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 17 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index 4f706a0..d4d7267 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -190,30 +190,35 @@ impl Calcifer { &mut self, ui: &mut egui::Ui, file: &panels::FileEntry, - depth: isize, n_files: &mut usize, - ) { + ) -> bool { *n_files += 1; if let Some(folder_content) = &file.folder_content { + let mut check_for_update: bool = false; let collapsing_response = egui::CollapsingHeader::new(file.name.clone()) - .default_open(depth > 0) + .default_open(self.tree_dir_opened.contains(&file.name)) .show(ui, |ui| { if !self.tree_dir_opened.contains(&file.name) { return; } for deeper_file in folder_content { - self.list_files(ui, deeper_file, depth - 1, n_files); + if self.list_files(ui, deeper_file, n_files) { + check_for_update = true; + } } }); if collapsing_response.fully_closed() { self.tree_dir_opened.retain(|s| s != &file.name); } else if !self.tree_dir_opened.contains(&file.name) { self.tree_dir_opened.push(file.name.clone()); + return !file.content_checked; } + return check_for_update; } else if ui.button(&file.name).clicked() { self.open_file(Some(&file.path)); } + return false; } } diff --git a/src/core/ui.rs b/src/core/ui.rs index 1a09bee..131e290 100644 --- a/src/core/ui.rs +++ b/src/core/ui.rs @@ -54,20 +54,28 @@ impl Calcifer { if !self.tree_visible { return; } + if self.file_tree.is_none() { + self.file_tree = Some(panels::generate_folder_entry(self.home.as_path())); + } + let mut n_files: usize = 0; egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { ui.horizontal(|ui| { ui.label("Bookshelf "); - if ui.add(egui::Button::new("📖")).clicked() { - self.file_tree = panels::generate_file_tree(self.home.as_path(), 7); - } }); ui.separator(); - let mut n_files: usize = 0; - if let Some(file_tree) = self.file_tree.clone() { - self.list_files(ui, &file_tree, 1, &mut n_files); - } else { - ui.label("No book on the Bookshelf"); - } + egui::ScrollArea::vertical().show(ui, |ui| { + if let Some(file_tree) = self.file_tree.clone() { + let update_requested = self.list_files(ui, &file_tree, &mut n_files); + if update_requested { + self.file_tree = Some(panels::update_file_tree( + file_tree, + self.tree_dir_opened.clone(), + )); + } + } else { + ui.label("No book on the Bookshelf"); + } + }); ui.separator(); ui.label(format!("{} files displayed", n_files)); }); diff --git a/src/panels/file_tree.rs b/src/panels/file_tree.rs index ed549de..a75eb2b 100644 --- a/src/panels/file_tree.rs +++ b/src/panels/file_tree.rs @@ -12,7 +12,7 @@ pub struct FileEntry { pub name: String, pub path: PathBuf, pub folder_content: Option>, - pub folder_open: bool, + pub content_checked: bool, } impl FileEntry { @@ -21,7 +21,15 @@ impl FileEntry { name, path, folder_content: None, - folder_open: false, + content_checked: true, + } + } + pub fn end_of_branch(name: String, path: PathBuf) -> Self { + Self { + name, + path, + folder_content: Some(vec![]), + content_checked: false, } } } @@ -45,10 +53,14 @@ pub fn generate_file_tree(path: &Path, depth: isize) -> Option { .to_string_lossy() .into_owned(); - if !path.is_dir() || depth < 0 { + if !path.is_dir() { return Some(FileEntry::new_entry(name, path.to_path_buf())); } + if depth < 0 { + return Some(FileEntry::end_of_branch(name, path.to_path_buf())); + } + match fs::read_dir(path) { Err(err) => Some(FileEntry::new_entry( format!("Error reading directory: {}", err), @@ -92,12 +104,113 @@ pub fn generate_file_tree(path: &Path, depth: isize) -> Option { name, path: path.to_path_buf(), folder_content: Some(folder_content), - folder_open: false, + content_checked: true, }) } } } +pub fn update_file_tree(file: FileEntry, opened_dirs: Vec) -> FileEntry { + if opened_dirs.contains(&file.name) { + if let Some(folder_content) = &file.folder_content { + if !file.content_checked { + return generate_folder_entry(&file.path); + } + let updated_content: Vec = folder_content + .iter() + .map(|entry| update_file_tree(entry.clone(), opened_dirs.clone())) + .collect(); + return FileEntry { + name: file.name, + path: file.path, + folder_content: Some(updated_content), + content_checked: true, + }; + } else { + return file; + } + } else { + return file; + } +} + +pub fn generate_folder_entry(path: &Path) -> FileEntry { + if let Some(file_name) = path.file_name() { + let name = file_name.to_string_lossy().into_owned(); + + match fs::read_dir(path) { + Err(err) => FileEntry::new_entry( + format!("Error reading directory: {}", err), + path.to_path_buf(), + ), + Ok(entries) => { + let mut paths: Vec> = entries + .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) + .collect(); + + paths.sort_by(|a, b| match (a, b) { + (Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b), + (Err(_), Ok(_)) => std::cmp::Ordering::Greater, + (Ok(_), Err(_)) => std::cmp::Ordering::Less, + (Err(_), Err(_)) => std::cmp::Ordering::Equal, + }); + + let mut folder_content = Vec::new(); + + for result in paths { + match result { + Ok(entry) => { + if let Some(file) = generate_entry(&entry.path()) { + folder_content.push(file); + } + } + Err(err) => { + folder_content.push(FileEntry::new_entry( + format!("Error reading entry: {}", err), + path.to_path_buf(), + )); + } + } + } + + FileEntry { + name, + path: path.to_path_buf(), + folder_content: Some(folder_content), + content_checked: true, + } + } + } + } else { + FileEntry::new_entry(format!("Error reading directory name"), path.to_path_buf()) + } +} + +fn generate_entry(path: &Path) -> Option { + if let Some(file_name) = path.file_name() { + if file_name.to_string_lossy().starts_with('.') { + return None; + } + let extension = path.extension().and_then(|ext| ext.to_str()); + if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) { + return None; + } + } else { + return None; + } + + let name = path + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_string_lossy() + .into_owned(); + + if !path.is_dir() { + return Some(FileEntry::new_entry(name, path.to_path_buf())); + } + return Some(FileEntry::end_of_branch(name, path.to_path_buf())); +} + fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering { let a_is_dir = a.path().is_dir(); let b_is_dir = b.path().is_dir();