mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
FileSorting and GroupDirs as enums
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
|
||||
// Locals
|
||||
use super::{ExplorerOpts, FileExplorer};
|
||||
use super::{ExplorerOpts, FileExplorer, FileSorting, GroupDirs};
|
||||
// Ext
|
||||
use std::collections::VecDeque;
|
||||
|
||||
@@ -62,22 +62,12 @@ impl FileExplorerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// ### sort_by_name
|
||||
/// ### with_file_sorting
|
||||
///
|
||||
/// Enable SORT_BY_NAME option
|
||||
pub fn sort_by_name(&mut self) -> &mut FileExplorerBuilder {
|
||||
/// Set sorting method
|
||||
pub fn with_file_sorting(&mut self, sorting: FileSorting) -> &mut FileExplorerBuilder {
|
||||
if let Some(e) = self.explorer.as_mut() {
|
||||
e.opts.insert(ExplorerOpts::SORT_BY_NAME);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// ### sort_by_mtime
|
||||
///
|
||||
/// Enable SORT_BY_MTIME option
|
||||
pub fn sort_by_mtime(&mut self) -> &mut FileExplorerBuilder {
|
||||
if let Some(e) = self.explorer.as_mut() {
|
||||
e.opts.insert(ExplorerOpts::SORT_BY_MTIME);
|
||||
e.sort_by(sorting);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -85,9 +75,9 @@ impl FileExplorerBuilder {
|
||||
/// ### with_dirs_first
|
||||
///
|
||||
/// Enable DIRS_FIRST option
|
||||
pub fn with_dirs_first(&mut self) -> &mut FileExplorerBuilder {
|
||||
pub fn with_group_dirs(&mut self, group_dirs: Option<GroupDirs>) -> &mut FileExplorerBuilder {
|
||||
if let Some(e) = self.explorer.as_mut() {
|
||||
e.opts.insert(ExplorerOpts::DIRS_FIRST);
|
||||
e.group_dirs_by(group_dirs);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -114,26 +104,23 @@ mod tests {
|
||||
let explorer: FileExplorer = FileExplorerBuilder::new().build();
|
||||
// Verify
|
||||
assert!(!explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES));
|
||||
assert!(!explorer.opts.intersects(ExplorerOpts::SORT_BY_MTIME));
|
||||
assert!(!explorer.opts.intersects(ExplorerOpts::SORT_BY_NAME));
|
||||
assert!(!explorer.opts.intersects(ExplorerOpts::DIRS_FIRST));
|
||||
assert_eq!(explorer.file_sorting, FileSorting::ByName); // Default
|
||||
assert_eq!(explorer.group_dirs, None);
|
||||
assert_eq!(explorer.stack_size, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_builder_new_all() {
|
||||
let explorer: FileExplorer = FileExplorerBuilder::new()
|
||||
.sort_by_mtime()
|
||||
.sort_by_name()
|
||||
.with_dirs_first()
|
||||
.with_file_sorting(FileSorting::ByModifyTime)
|
||||
.with_group_dirs(Some(GroupDirs::First))
|
||||
.with_hidden_files()
|
||||
.with_stack_size(24)
|
||||
.build();
|
||||
// Verify
|
||||
assert!(explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES));
|
||||
assert!(explorer.opts.intersects(ExplorerOpts::SORT_BY_MTIME));
|
||||
assert!(explorer.opts.intersects(ExplorerOpts::SORT_BY_NAME));
|
||||
assert!(explorer.opts.intersects(ExplorerOpts::DIRS_FIRST));
|
||||
assert_eq!(explorer.file_sorting, FileSorting::ByModifyTime); // Default
|
||||
assert_eq!(explorer.group_dirs, Some(GroupDirs::First));
|
||||
assert_eq!(explorer.stack_size, 24);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,35 +34,58 @@ use std::collections::VecDeque;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
bitflags! {
|
||||
/// ## ExplorerOpts
|
||||
///
|
||||
/// ExplorerOpts are bit options which provides different behaviours to `FileExplorer`
|
||||
pub(crate) struct ExplorerOpts: u32 {
|
||||
const SHOW_HIDDEN_FILES = 0b00000001;
|
||||
const SORT_BY_NAME = 0b00000010;
|
||||
const SORT_BY_MTIME = 0b00000100;
|
||||
const DIRS_FIRST = 0b00001000;
|
||||
}
|
||||
}
|
||||
|
||||
/// ## FileSorting
|
||||
///
|
||||
/// FileSorting defines the criteria for sorting files
|
||||
#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
|
||||
pub enum FileSorting {
|
||||
ByName,
|
||||
ByModifyTime,
|
||||
ByCreationTime,
|
||||
}
|
||||
|
||||
/// ## GroupDirs
|
||||
///
|
||||
/// GroupDirs defines how directories should be grouped in sorting files
|
||||
#[derive(PartialEq, std::fmt::Debug)]
|
||||
pub enum GroupDirs {
|
||||
First,
|
||||
Last,
|
||||
}
|
||||
|
||||
/// ## FileExplorer
|
||||
///
|
||||
/// File explorer states
|
||||
pub struct FileExplorer {
|
||||
pub wrkdir: PathBuf, // Current directory
|
||||
index: usize, // Selected file
|
||||
files: Vec<FsEntry>, // Files in directory
|
||||
pub(crate) dirstack: VecDeque<PathBuf>, // Stack of visited directory (max 16)
|
||||
pub(crate) stack_size: usize, // Directory stack size
|
||||
pub(crate) file_sorting: FileSorting, // File sorting criteria
|
||||
pub(crate) group_dirs: Option<GroupDirs>, // If Some, defines how to group directories
|
||||
pub(crate) opts: ExplorerOpts, // Explorer options
|
||||
index: usize, // Selected file
|
||||
files: Vec<FsEntry>, // Files in directory
|
||||
}
|
||||
|
||||
impl Default for FileExplorer {
|
||||
fn default() -> Self {
|
||||
FileExplorer {
|
||||
wrkdir: PathBuf::from("/"),
|
||||
index: 0,
|
||||
files: Vec::new(),
|
||||
dirstack: VecDeque::with_capacity(16),
|
||||
stack_size: 16,
|
||||
file_sorting: FileSorting::ByName,
|
||||
group_dirs: None,
|
||||
opts: ExplorerOpts::empty(),
|
||||
index: 0,
|
||||
files: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,20 +164,52 @@ impl FileExplorer {
|
||||
|
||||
// Sorting
|
||||
|
||||
/// ### sort_by
|
||||
///
|
||||
/// Choose sorting method; then sort files
|
||||
pub fn sort_by(&mut self, sorting: FileSorting) {
|
||||
// If method HAS ACTUALLY CHANGED, sort (performance!)
|
||||
if self.file_sorting != sorting {
|
||||
self.file_sorting = sorting;
|
||||
self.sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// ### get_file_sorting
|
||||
///
|
||||
/// Get current file sorting method
|
||||
pub fn get_file_sorting(&self) -> FileSorting {
|
||||
self.file_sorting
|
||||
}
|
||||
|
||||
/// ### group_dirs_by
|
||||
///
|
||||
/// Choose group dirs method; then sort files
|
||||
pub fn group_dirs_by(&mut self, group_dirs: Option<GroupDirs>) {
|
||||
// If method HAS ACTUALLY CHANGED, sort (performance!)
|
||||
if self.group_dirs != group_dirs {
|
||||
self.group_dirs = group_dirs;
|
||||
self.sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// ### sort
|
||||
///
|
||||
/// Sort files based on Explorer options.
|
||||
fn sort(&mut self) {
|
||||
// Sort by name
|
||||
if self.opts.intersects(ExplorerOpts::SORT_BY_NAME) {
|
||||
self.sort_files_by_name();
|
||||
} else if self.opts.intersects(ExplorerOpts::SORT_BY_MTIME) {
|
||||
// Sort by mtime NOTE: else if cause exclusive
|
||||
self.sort_files_by_name();
|
||||
// Choose sorting method
|
||||
match &self.file_sorting {
|
||||
FileSorting::ByName => self.sort_files_by_name(),
|
||||
FileSorting::ByCreationTime => self.sort_files_by_creation_time(),
|
||||
FileSorting::ByModifyTime => self.sort_files_by_mtime(),
|
||||
}
|
||||
// Directories first (NOTE: MUST COME AFTER OTHER SORTING)
|
||||
// Group directories if necessary
|
||||
if let Some(group_dirs) = &self.group_dirs {
|
||||
match group_dirs {
|
||||
GroupDirs::First => self.sort_files_directories_first(),
|
||||
GroupDirs::Last => self.sort_files_directories_last(),
|
||||
}
|
||||
// Directories first (MUST COME AFTER NAME)
|
||||
if self.opts.intersects(ExplorerOpts::DIRS_FIRST) {
|
||||
self.sort_files_directories_first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +225,17 @@ impl FileExplorer {
|
||||
///
|
||||
/// Sort files by mtime; the newest comes first
|
||||
fn sort_files_by_mtime(&mut self) {
|
||||
self.files.sort_by(|a: &FsEntry, b: &FsEntry| {
|
||||
b.get_last_change_time().cmp(&a.get_last_change_time())
|
||||
});
|
||||
}
|
||||
|
||||
/// ### sort_files_by_creation_time
|
||||
///
|
||||
/// Sort files by creation time; the newest comes first
|
||||
fn sort_files_by_creation_time(&mut self) {
|
||||
self.files
|
||||
.sort_by_key(|x: &FsEntry| x.get_last_change_time());
|
||||
.sort_by(|a: &FsEntry, b: &FsEntry| b.get_creation_time().cmp(&a.get_creation_time()));
|
||||
}
|
||||
|
||||
/// ### sort_files_directories_first
|
||||
@@ -181,6 +245,13 @@ impl FileExplorer {
|
||||
self.files.sort_by_key(|x: &FsEntry| x.is_file());
|
||||
}
|
||||
|
||||
/// ### sort_files_directories_last
|
||||
///
|
||||
/// Sort files; directories come last
|
||||
fn sort_files_directories_last(&mut self) {
|
||||
self.files.sort_by_key(|x: &FsEntry| x.is_dir());
|
||||
}
|
||||
|
||||
/// ### incr_index
|
||||
///
|
||||
/// Increment index to the first visible FsEntry.
|
||||
@@ -380,6 +451,9 @@ mod tests {
|
||||
assert_eq!(explorer.wrkdir, PathBuf::from("/"));
|
||||
assert_eq!(explorer.stack_size, 16);
|
||||
assert_eq!(explorer.index, 0);
|
||||
assert_eq!(explorer.group_dirs, None);
|
||||
assert_eq!(explorer.file_sorting, FileSorting::ByName);
|
||||
assert_eq!(explorer.get_file_sorting(), FileSorting::ByName);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -412,7 +486,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fs_explorer_files() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES); // Don't show hidden files
|
||||
// Don't show hidden files
|
||||
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||
// Create files
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
@@ -423,14 +498,7 @@ mod tests {
|
||||
make_fs_entry(".gitignore", false),
|
||||
]);
|
||||
assert_eq!(explorer.count(), 6);
|
||||
// Verify
|
||||
assert_eq!(
|
||||
explorer.files.get(0).unwrap().get_name(),
|
||||
String::from("README.md")
|
||||
);
|
||||
// Sort files
|
||||
explorer.sort_files_by_name();
|
||||
// Verify
|
||||
// Verify (files are sorted by name)
|
||||
assert_eq!(
|
||||
explorer.files.get(0).unwrap().get_name(),
|
||||
String::from(".git/")
|
||||
@@ -448,8 +516,7 @@ mod tests {
|
||||
fn test_fs_explorer_index() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||
explorer.opts.insert(ExplorerOpts::SORT_BY_NAME);
|
||||
// Create files (files are then sorted by name)
|
||||
// Create files (files are then sorted by name DEFAULT)
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
make_fs_entry("src/", true),
|
||||
@@ -563,7 +630,6 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_name() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.insert(ExplorerOpts::SORT_BY_NAME);
|
||||
// Create files (files are then sorted by name)
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
@@ -576,6 +642,7 @@ mod tests {
|
||||
make_fs_entry("Cargo.lock", false),
|
||||
make_fs_entry("codecov.yml", false),
|
||||
]);
|
||||
explorer.sort_by(FileSorting::ByName);
|
||||
// First entry should be "Cargo.lock"
|
||||
assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock");
|
||||
// Last should be "src/"
|
||||
@@ -585,13 +652,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_mtime() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.insert(ExplorerOpts::SORT_BY_MTIME);
|
||||
let entry1: FsEntry = make_fs_entry("README.md", false);
|
||||
// Wait 1 sec
|
||||
sleep(Duration::from_secs(1));
|
||||
let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false);
|
||||
// Create files (files are then sorted by name)
|
||||
explorer.set_files(vec![entry1, entry2]);
|
||||
explorer.sort_by(FileSorting::ByModifyTime);
|
||||
// First entry should be "CODE_OF_CONDUCT.md"
|
||||
assert_eq!(
|
||||
explorer.files.get(0).unwrap().get_name(),
|
||||
@@ -602,10 +669,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_name_and_dir() {
|
||||
fn test_fs_explorer_sort_by_creation_time() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
let entry1: FsEntry = make_fs_entry("README.md", false);
|
||||
// Wait 1 sec
|
||||
sleep(Duration::from_secs(1));
|
||||
let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false);
|
||||
// Create files (files are then sorted by name)
|
||||
explorer.set_files(vec![entry1, entry2]);
|
||||
explorer.sort_by(FileSorting::ByCreationTime);
|
||||
// First entry should be "CODE_OF_CONDUCT.md"
|
||||
assert_eq!(
|
||||
explorer.files.get(0).unwrap().get_name(),
|
||||
"CODE_OF_CONDUCT.md"
|
||||
);
|
||||
// Last should be "src/"
|
||||
assert_eq!(explorer.files.get(1).unwrap().get_name(), "README.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_name_and_dirs_first() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.insert(ExplorerOpts::SORT_BY_NAME);
|
||||
explorer.opts.insert(ExplorerOpts::DIRS_FIRST);
|
||||
// Create files (files are then sorted by name)
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
@@ -619,6 +703,8 @@ mod tests {
|
||||
make_fs_entry("Cargo.lock", false),
|
||||
make_fs_entry("codecov.yml", false),
|
||||
]);
|
||||
explorer.sort_by(FileSorting::ByName);
|
||||
explorer.group_dirs_by(Some(GroupDirs::First));
|
||||
// First entry should be "docs"
|
||||
assert_eq!(explorer.files.get(0).unwrap().get_name(), "docs/");
|
||||
assert_eq!(explorer.files.get(1).unwrap().get_name(), "src/");
|
||||
@@ -628,6 +714,33 @@ mod tests {
|
||||
assert_eq!(explorer.files.get(9).unwrap().get_name(), "README.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_name_and_dirs_last() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
// Create files (files are then sorted by name)
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
make_fs_entry("src/", true),
|
||||
make_fs_entry("docs/", true),
|
||||
make_fs_entry("CONTRIBUTING.md", false),
|
||||
make_fs_entry("CODE_OF_CONDUCT.md", false),
|
||||
make_fs_entry("CHANGELOG.md", false),
|
||||
make_fs_entry("LICENSE", false),
|
||||
make_fs_entry("Cargo.toml", false),
|
||||
make_fs_entry("Cargo.lock", false),
|
||||
make_fs_entry("codecov.yml", false),
|
||||
]);
|
||||
explorer.sort_by(FileSorting::ByName);
|
||||
explorer.group_dirs_by(Some(GroupDirs::Last));
|
||||
// Last entry should be "src"
|
||||
assert_eq!(explorer.files.get(8).unwrap().get_name(), "docs/");
|
||||
assert_eq!(explorer.files.get(9).unwrap().get_name(), "src/");
|
||||
// first is file for alphabetical order
|
||||
assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock");
|
||||
// Last in files should be "README.md" (last file for alphabetical ordening)
|
||||
assert_eq!(explorer.files.get(7).unwrap().get_name(), "README.md");
|
||||
}
|
||||
|
||||
fn make_fs_entry(name: &str, is_dir: bool) -> FsEntry {
|
||||
let t_now: SystemTime = SystemTime::now();
|
||||
match is_dir {
|
||||
|
||||
@@ -24,7 +24,7 @@ use super::{
|
||||
Color, ConfigClient, FileTransferActivity, InputField, InputMode, LogLevel, LogRecord,
|
||||
PopupType,
|
||||
};
|
||||
use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer};
|
||||
use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs};
|
||||
use crate::system::environment;
|
||||
use crate::system::sshkey_storage::SshKeyStorage;
|
||||
// Ext
|
||||
@@ -131,8 +131,8 @@ impl FileTransferActivity {
|
||||
/// Build explorer reading configuration from `ConfigClient`
|
||||
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
|
||||
FileExplorerBuilder::new()
|
||||
.sort_by_name()
|
||||
.with_dirs_first()
|
||||
.with_file_sorting(FileSorting::ByName)
|
||||
.with_group_dirs(Some(GroupDirs::First))
|
||||
.with_stack_size(16)
|
||||
.build()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user