mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 01:26:04 -08:00
Added options to explorer, in order to define sorting modes and other options. Added bitflags to dependencies; Moved Explorer to Fs module
This commit is contained in:
@@ -41,6 +41,7 @@ FIXME: Released on
|
|||||||
- `A`: Toggle hidden files
|
- `A`: Toggle hidden files
|
||||||
- `N`: New file
|
- `N`: New file
|
||||||
- Dependencies:
|
- Dependencies:
|
||||||
|
- added `bitflags 1.2.1`
|
||||||
- removed `data-encoding`
|
- removed `data-encoding`
|
||||||
- updated `rand` to `0.8.0`
|
- updated `rand` to `0.8.0`
|
||||||
- removed `ring`
|
- removed `ring`
|
||||||
|
|||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1049,6 +1049,7 @@ dependencies = [
|
|||||||
name = "termscp"
|
name = "termscp"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
"chrono",
|
"chrono",
|
||||||
"content_inspector",
|
"content_inspector",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ readme = "README.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags = "1.2.1"
|
||||||
bytesize = "1.0.1"
|
bytesize = "1.0.1"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
content_inspector = "0.2.4"
|
content_inspector = "0.2.4"
|
||||||
|
|||||||
139
src/fs/explorer/builder.rs
Normal file
139
src/fs/explorer/builder.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
//! ## Builder
|
||||||
|
//!
|
||||||
|
//! `builder` is the module which provides a builder for FileExplorer
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||||
|
*
|
||||||
|
* This file is part of "TermSCP"
|
||||||
|
*
|
||||||
|
* TermSCP is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* TermSCP is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Locals
|
||||||
|
use super::{ExplorerOpts, FileExplorer};
|
||||||
|
// Ext
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
/// ## FileExplorerBuilder
|
||||||
|
///
|
||||||
|
/// Struct used to create a `FileExplorer`
|
||||||
|
pub struct FileExplorerBuilder {
|
||||||
|
explorer: Option<FileExplorer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileExplorerBuilder {
|
||||||
|
/// ### new
|
||||||
|
///
|
||||||
|
/// Build a new `FileExplorerBuilder`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FileExplorerBuilder {
|
||||||
|
explorer: Some(FileExplorer::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### build
|
||||||
|
///
|
||||||
|
/// Take FileExplorer out of builder
|
||||||
|
pub fn build(&mut self) -> FileExplorer {
|
||||||
|
self.explorer.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### with_hidden_files
|
||||||
|
///
|
||||||
|
/// Enable HIDDEN_FILES option
|
||||||
|
pub fn with_hidden_files(&mut self) -> &mut FileExplorerBuilder {
|
||||||
|
if let Some(e) = self.explorer.as_mut() {
|
||||||
|
e.opts.insert(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### sort_by_name
|
||||||
|
///
|
||||||
|
/// Enable SORT_BY_NAME option
|
||||||
|
pub fn sort_by_name(&mut self) -> &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);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### with_dirs_first
|
||||||
|
///
|
||||||
|
/// Enable DIRS_FIRST option
|
||||||
|
pub fn with_dirs_first(&mut self) -> &mut FileExplorerBuilder {
|
||||||
|
if let Some(e) = self.explorer.as_mut() {
|
||||||
|
e.opts.insert(ExplorerOpts::DIRS_FIRST);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### with_stack_size
|
||||||
|
///
|
||||||
|
/// Set stack size for FileExplorer
|
||||||
|
pub fn with_stack_size(&mut self, sz: usize) -> &mut FileExplorerBuilder {
|
||||||
|
if let Some(e) = self.explorer.as_mut() {
|
||||||
|
e.stack_size = sz;
|
||||||
|
e.dirstack = VecDeque::with_capacity(sz);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fs_explorer_builder_new_default() {
|
||||||
|
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.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_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.stack_size, 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//! ## FileTransferActivity
|
//! ## Explorer
|
||||||
//!
|
//!
|
||||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
//! `explorer` is the module which provides an Helper in handling Directory status through
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
@@ -23,39 +23,51 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Mods
|
||||||
|
pub(crate) mod builder;
|
||||||
|
// Deps
|
||||||
|
extern crate bitflags;
|
||||||
// Locals
|
// Locals
|
||||||
use super::FsEntry;
|
use super::FsEntry;
|
||||||
// Ext
|
// Ext
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub(crate) struct ExplorerOpts: u32 {
|
||||||
|
const SHOW_HIDDEN_FILES = 0b00000001;
|
||||||
|
const SORT_BY_NAME = 0b00000010;
|
||||||
|
const SORT_BY_MTIME = 0b00000100;
|
||||||
|
const DIRS_FIRST = 0b00001000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## FileExplorer
|
/// ## FileExplorer
|
||||||
///
|
///
|
||||||
/// File explorer states
|
/// File explorer states
|
||||||
pub struct FileExplorer {
|
pub struct FileExplorer {
|
||||||
pub wrkdir: PathBuf, // Current directory
|
pub wrkdir: PathBuf, // Current directory
|
||||||
index: usize, // Selected file
|
index: usize, // Selected file
|
||||||
files: Vec<FsEntry>, // Files in directory
|
files: Vec<FsEntry>, // Files in directory
|
||||||
dirstack: VecDeque<PathBuf>, // Stack of visited directory (max 16)
|
pub(crate) dirstack: VecDeque<PathBuf>, // Stack of visited directory (max 16)
|
||||||
stack_size: usize, // Directory stack size
|
pub(crate) stack_size: usize, // Directory stack size
|
||||||
hidden_files: bool, // Should hidden files be shown or not; hidden if false
|
pub(crate) opts: ExplorerOpts, // Explorer options
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileExplorer {
|
impl Default for FileExplorer {
|
||||||
/// ### new
|
fn default() -> Self {
|
||||||
///
|
|
||||||
/// Instantiates a new FileExplorer
|
|
||||||
pub fn new(stack_size: usize) -> FileExplorer {
|
|
||||||
FileExplorer {
|
FileExplorer {
|
||||||
wrkdir: PathBuf::from("/"),
|
wrkdir: PathBuf::from("/"),
|
||||||
index: 0,
|
index: 0,
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
dirstack: VecDeque::with_capacity(stack_size),
|
dirstack: VecDeque::with_capacity(16),
|
||||||
stack_size,
|
stack_size: 16,
|
||||||
hidden_files: false, // Default: don't show hidden files
|
opts: ExplorerOpts::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileExplorer {
|
||||||
/// ### pushd
|
/// ### pushd
|
||||||
///
|
///
|
||||||
/// push directory to stack
|
/// push directory to stack
|
||||||
@@ -78,10 +90,13 @@ impl FileExplorer {
|
|||||||
/// ### set_files
|
/// ### set_files
|
||||||
///
|
///
|
||||||
/// Set Explorer files
|
/// Set Explorer files
|
||||||
/// Index is then moved to first valid `FsEntry` for current setup
|
/// This method will also sort entries based on current options
|
||||||
|
/// Once all sorting have been performed, index is moved to first valid entry.
|
||||||
pub fn set_files(&mut self, files: Vec<FsEntry>) {
|
pub fn set_files(&mut self, files: Vec<FsEntry>) {
|
||||||
self.files = files;
|
self.files = files;
|
||||||
// Set index to first valid entry
|
// Sort
|
||||||
|
self.sort();
|
||||||
|
// Reset index
|
||||||
self.index_at_first();
|
self.index_at_first();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +112,17 @@ impl FileExplorer {
|
|||||||
/// Iterate over files
|
/// Iterate over files
|
||||||
/// Filters are applied based on current options (e.g. hidden files not returned)
|
/// Filters are applied based on current options (e.g. hidden files not returned)
|
||||||
pub fn iter_files(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
|
pub fn iter_files(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
|
||||||
// Match options
|
// Filter
|
||||||
match self.hidden_files {
|
let opts: ExplorerOpts = self.opts;
|
||||||
false => Box::new(self.files.iter().filter(|x| !x.is_hidden())), // Show only visible files
|
Box::new(self.files.iter().filter(move |x| {
|
||||||
true => self.iter_files_all(), // Show all
|
// If true, element IS NOT filtered
|
||||||
}
|
let mut pass: bool = true;
|
||||||
|
// If hidden files SHOULDN'T be shown, AND pass with not hidden
|
||||||
|
if !opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES) {
|
||||||
|
pass &= !x.is_hidden();
|
||||||
|
}
|
||||||
|
pass
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### iter_files_all
|
/// ### iter_files_all
|
||||||
@@ -118,16 +139,46 @@ impl FileExplorer {
|
|||||||
self.files.get(self.index)
|
self.files.get(self.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
|
||||||
|
/// ### 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();
|
||||||
|
}
|
||||||
|
// Directories first (MUST COME AFTER NAME)
|
||||||
|
if self.opts.intersects(ExplorerOpts::DIRS_FIRST) {
|
||||||
|
self.sort_files_directories_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ### sort_files_by_name
|
/// ### sort_files_by_name
|
||||||
///
|
///
|
||||||
/// Sort explorer files by their name. All names are converted to lowercase
|
/// Sort explorer files by their name. All names are converted to lowercase
|
||||||
pub fn sort_files_by_name(&mut self) {
|
fn sort_files_by_name(&mut self) {
|
||||||
self.files.sort_by_key(|x: &FsEntry| match x {
|
self.files
|
||||||
FsEntry::Directory(dir) => dir.name.as_str().to_lowercase(),
|
.sort_by_key(|x: &FsEntry| x.get_name().to_lowercase());
|
||||||
FsEntry::File(file) => file.name.as_str().to_lowercase(),
|
}
|
||||||
});
|
|
||||||
// Reset index
|
/// ### sort_files_by_mtime
|
||||||
self.index_at_first();
|
///
|
||||||
|
/// Sort files by mtime; the newest comes first
|
||||||
|
fn sort_files_by_mtime(&mut self) {
|
||||||
|
self.files
|
||||||
|
.sort_by_key(|x: &FsEntry| x.get_last_change_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### sort_files_directories_first
|
||||||
|
///
|
||||||
|
/// Sort files; directories come first
|
||||||
|
fn sort_files_directories_first(&mut self) {
|
||||||
|
self.files.sort_by_key(|x: &FsEntry| x.is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### incr_index
|
/// ### incr_index
|
||||||
@@ -145,7 +196,7 @@ impl FileExplorer {
|
|||||||
// Validate
|
// Validate
|
||||||
match self.files.get(self.index) {
|
match self.files.get(self.index) {
|
||||||
Some(assoc_entry) => {
|
Some(assoc_entry) => {
|
||||||
if !self.hidden_files {
|
if !self.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES) {
|
||||||
// Check if file is hidden, otherwise increment
|
// Check if file is hidden, otherwise increment
|
||||||
if assoc_entry.is_hidden() {
|
if assoc_entry.is_hidden() {
|
||||||
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
|
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
|
||||||
@@ -194,7 +245,7 @@ impl FileExplorer {
|
|||||||
// Validate index
|
// Validate index
|
||||||
match self.files.get(self.index) {
|
match self.files.get(self.index) {
|
||||||
Some(assoc_entry) => {
|
Some(assoc_entry) => {
|
||||||
if !self.hidden_files {
|
if !self.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES) {
|
||||||
// Check if file is hidden, otherwise increment
|
// Check if file is hidden, otherwise increment
|
||||||
if assoc_entry.is_hidden() {
|
if assoc_entry.is_hidden() {
|
||||||
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
|
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
|
||||||
@@ -238,7 +289,7 @@ impl FileExplorer {
|
|||||||
///
|
///
|
||||||
/// Return first valid index
|
/// Return first valid index
|
||||||
fn get_first_valid_index(&self) -> usize {
|
fn get_first_valid_index(&self) -> usize {
|
||||||
match self.hidden_files {
|
match self.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES) {
|
||||||
true => 0,
|
true => 0,
|
||||||
false => {
|
false => {
|
||||||
// Look for first "non-hidden" entry
|
// Look for first "non-hidden" entry
|
||||||
@@ -302,7 +353,7 @@ impl FileExplorer {
|
|||||||
///
|
///
|
||||||
/// Enable/disable hidden files
|
/// Enable/disable hidden files
|
||||||
pub fn toggle_hidden_files(&mut self) {
|
pub fn toggle_hidden_files(&mut self) {
|
||||||
self.hidden_files = !self.hidden_files;
|
self.opts.toggle(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||||
// Adjust index
|
// Adjust index
|
||||||
if self.index < self.get_first_valid_index() {
|
if self.index < self.get_first_valid_index() {
|
||||||
self.index_at_first();
|
self.index_at_first();
|
||||||
@@ -316,23 +367,26 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::fs::{FsDirectory, FsFile};
|
use crate::fs::{FsDirectory, FsFile};
|
||||||
|
|
||||||
use std::time::SystemTime;
|
use std::thread::sleep;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ui_filetransfer_activity_explorer_new() {
|
fn test_fs_explorer_new() {
|
||||||
let explorer: FileExplorer = FileExplorer::new(16);
|
let explorer: FileExplorer = FileExplorer::default();
|
||||||
// Verify
|
// Verify
|
||||||
assert_eq!(explorer.dirstack.len(), 0);
|
assert_eq!(explorer.dirstack.len(), 0);
|
||||||
assert_eq!(explorer.files.len(), 0);
|
assert_eq!(explorer.files.len(), 0);
|
||||||
assert_eq!(explorer.hidden_files, false);
|
assert_eq!(explorer.opts, ExplorerOpts::empty());
|
||||||
assert_eq!(explorer.wrkdir, PathBuf::from("/"));
|
assert_eq!(explorer.wrkdir, PathBuf::from("/"));
|
||||||
assert_eq!(explorer.stack_size, 16);
|
assert_eq!(explorer.stack_size, 16);
|
||||||
assert_eq!(explorer.index, 0);
|
assert_eq!(explorer.index, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ui_filetransfer_activity_explorer_stack() {
|
fn test_fs_explorer_stack() {
|
||||||
let mut explorer: FileExplorer = FileExplorer::new(2);
|
let mut explorer: FileExplorer = FileExplorer::default();
|
||||||
|
explorer.stack_size = 2;
|
||||||
|
explorer.dirstack = VecDeque::with_capacity(2);
|
||||||
// Push dir
|
// Push dir
|
||||||
explorer.pushd(&Path::new("/tmp"));
|
explorer.pushd(&Path::new("/tmp"));
|
||||||
explorer.pushd(&Path::new("/home/omar"));
|
explorer.pushd(&Path::new("/home/omar"));
|
||||||
@@ -356,10 +410,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ui_filetransfer_activity_explorer_files() {
|
fn test_fs_explorer_files() {
|
||||||
let mut explorer: FileExplorer = FileExplorer::new(16);
|
let mut explorer: FileExplorer = FileExplorer::default();
|
||||||
explorer.hidden_files = false;
|
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES); // Don't show hidden files
|
||||||
// Create files
|
// Create files
|
||||||
explorer.set_files(vec![
|
explorer.set_files(vec![
|
||||||
make_fs_entry("README.md", false),
|
make_fs_entry("README.md", false),
|
||||||
make_fs_entry("src/", true),
|
make_fs_entry("src/", true),
|
||||||
@@ -391,10 +445,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ui_filetransfer_activity_explorer_index() {
|
fn test_fs_explorer_index() {
|
||||||
let mut explorer: FileExplorer = FileExplorer::new(16);
|
let mut explorer: FileExplorer = FileExplorer::default();
|
||||||
explorer.hidden_files = false;
|
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||||
// Create files
|
explorer.opts.insert(ExplorerOpts::SORT_BY_NAME);
|
||||||
|
// Create files (files are then sorted by name)
|
||||||
explorer.set_files(vec![
|
explorer.set_files(vec![
|
||||||
make_fs_entry("README.md", false),
|
make_fs_entry("README.md", false),
|
||||||
make_fs_entry("src/", true),
|
make_fs_entry("src/", true),
|
||||||
@@ -409,14 +464,15 @@ mod tests {
|
|||||||
make_fs_entry(".gitignore", false),
|
make_fs_entry(".gitignore", false),
|
||||||
]);
|
]);
|
||||||
let sz: usize = explorer.count();
|
let sz: usize = explorer.count();
|
||||||
// Sort by name
|
|
||||||
explorer.sort_files_by_name();
|
|
||||||
// Get first index
|
// Get first index
|
||||||
assert_eq!(explorer.get_first_valid_index(), 2);
|
assert_eq!(explorer.get_first_valid_index(), 2);
|
||||||
// Index should be 2 now; files hidden; this happens because `index_at_first` is called after loading files
|
// Index should be 2 now; files hidden; this happens because `index_at_first` is called after loading files
|
||||||
assert_eq!(explorer.get_index(), 2);
|
assert_eq!(explorer.get_index(), 2);
|
||||||
assert_eq!(explorer.get_relative_index(), 0); // Relative index should be 0
|
assert_eq!(explorer.get_relative_index(), 0); // Relative index should be 0
|
||||||
assert_eq!(explorer.hidden_files, false);
|
assert_eq!(
|
||||||
|
explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES),
|
||||||
|
false
|
||||||
|
);
|
||||||
// Increment index
|
// Increment index
|
||||||
explorer.incr_index();
|
explorer.incr_index();
|
||||||
// Index should now be 3 (was 0, + 2 + 1); first 2 files are hidden (.git, .gitignore)
|
// Index should now be 3 (was 0, + 2 + 1); first 2 files are hidden (.git, .gitignore)
|
||||||
@@ -462,7 +518,10 @@ mod tests {
|
|||||||
assert_eq!(explorer.get_relative_index(), 0);
|
assert_eq!(explorer.get_relative_index(), 0);
|
||||||
// Toggle hidden files
|
// Toggle hidden files
|
||||||
explorer.toggle_hidden_files();
|
explorer.toggle_hidden_files();
|
||||||
assert_eq!(explorer.hidden_files, true);
|
assert_eq!(
|
||||||
|
explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES),
|
||||||
|
true
|
||||||
|
);
|
||||||
// Move index to 0
|
// Move index to 0
|
||||||
explorer.set_index(0);
|
explorer.set_index(0);
|
||||||
assert_eq!(explorer.get_index(), 0);
|
assert_eq!(explorer.get_index(), 0);
|
||||||
@@ -483,7 +542,10 @@ mod tests {
|
|||||||
assert_eq!(explorer.get_relative_index(), 0); // Now relative matches
|
assert_eq!(explorer.get_relative_index(), 0); // Now relative matches
|
||||||
// Toggle; move at first
|
// Toggle; move at first
|
||||||
explorer.toggle_hidden_files();
|
explorer.toggle_hidden_files();
|
||||||
assert_eq!(explorer.hidden_files, false);
|
assert_eq!(
|
||||||
|
explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES),
|
||||||
|
false
|
||||||
|
);
|
||||||
explorer.index_at_first();
|
explorer.index_at_first();
|
||||||
assert_eq!(explorer.get_index(), 2);
|
assert_eq!(explorer.get_index(), 2);
|
||||||
assert_eq!(explorer.get_relative_index(), 0);
|
assert_eq!(explorer.get_relative_index(), 0);
|
||||||
@@ -498,6 +560,74 @@ mod tests {
|
|||||||
assert_eq!(explorer.get_relative_index(), 0);
|
assert_eq!(explorer.get_relative_index(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
make_fs_entry("src/", 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),
|
||||||
|
]);
|
||||||
|
// First entry should be "Cargo.lock"
|
||||||
|
assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock");
|
||||||
|
// Last should be "src/"
|
||||||
|
assert_eq!(explorer.files.get(8).unwrap().get_name(), "src/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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]);
|
||||||
|
// 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_dir() {
|
||||||
|
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),
|
||||||
|
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),
|
||||||
|
]);
|
||||||
|
// 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/");
|
||||||
|
// 3rd is file first for alphabetical order
|
||||||
|
assert_eq!(explorer.files.get(2).unwrap().get_name(), "Cargo.lock");
|
||||||
|
// Last should be "README.md" (last file for alphabetical ordening)
|
||||||
|
assert_eq!(explorer.files.get(9).unwrap().get_name(), "README.md");
|
||||||
|
}
|
||||||
|
|
||||||
fn make_fs_entry(name: &str, is_dir: bool) -> FsEntry {
|
fn make_fs_entry(name: &str, is_dir: bool) -> FsEntry {
|
||||||
let t_now: SystemTime = SystemTime::now();
|
let t_now: SystemTime = SystemTime::now();
|
||||||
match is_dir {
|
match is_dir {
|
||||||
@@ -23,12 +23,16 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Mod
|
||||||
|
pub mod explorer;
|
||||||
|
|
||||||
|
// Deps
|
||||||
extern crate bytesize;
|
extern crate bytesize;
|
||||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||||
extern crate users;
|
extern crate users;
|
||||||
|
// Locals
|
||||||
use crate::utils::fmt::{fmt_pex, fmt_time};
|
use crate::utils::fmt::{fmt_pex, fmt_time};
|
||||||
|
// Ext
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
|||||||
// Crates
|
// Crates
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate magic_crypt;
|
extern crate magic_crypt;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use super::{
|
|||||||
Color, ConfigClient, FileTransferActivity, InputField, InputMode, LogLevel, LogRecord,
|
Color, ConfigClient, FileTransferActivity, InputField, InputMode, LogLevel, LogRecord,
|
||||||
PopupType,
|
PopupType,
|
||||||
};
|
};
|
||||||
|
use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer};
|
||||||
use crate::system::environment;
|
use crate::system::environment;
|
||||||
use crate::system::sshkey_storage::SshKeyStorage;
|
use crate::system::sshkey_storage::SshKeyStorage;
|
||||||
// Ext
|
// Ext
|
||||||
@@ -125,6 +126,17 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### build_explorer
|
||||||
|
///
|
||||||
|
/// Build explorer reading configuration from `ConfigClient`
|
||||||
|
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
|
||||||
|
FileExplorerBuilder::new()
|
||||||
|
.sort_by_name()
|
||||||
|
.with_dirs_first()
|
||||||
|
.with_stack_size(16)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
/// ### setup_text_editor
|
/// ### setup_text_editor
|
||||||
///
|
///
|
||||||
/// Set text editor to use
|
/// Set text editor to use
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
// This module is split into files, cause it's just too big
|
// This module is split into files, cause it's just too big
|
||||||
mod callbacks;
|
mod callbacks;
|
||||||
mod explorer;
|
|
||||||
mod input;
|
mod input;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod misc;
|
mod misc;
|
||||||
@@ -44,9 +43,9 @@ use crate::filetransfer::ftp_transfer::FtpFileTransfer;
|
|||||||
use crate::filetransfer::scp_transfer::ScpFileTransfer;
|
use crate::filetransfer::scp_transfer::ScpFileTransfer;
|
||||||
use crate::filetransfer::sftp_transfer::SftpFileTransfer;
|
use crate::filetransfer::sftp_transfer::SftpFileTransfer;
|
||||||
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
|
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
|
||||||
|
use crate::fs::explorer::FileExplorer;
|
||||||
use crate::fs::FsEntry;
|
use crate::fs::FsEntry;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use explorer::FileExplorer;
|
|
||||||
|
|
||||||
// Includes
|
// Includes
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
@@ -269,10 +268,10 @@ impl FileTransferActivity {
|
|||||||
Self::make_ssh_storage(config_client.as_ref()),
|
Self::make_ssh_storage(config_client.as_ref()),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
config_cli: config_client,
|
|
||||||
params,
|
params,
|
||||||
local: FileExplorer::new(16),
|
local: Self::build_explorer(config_client.as_ref()),
|
||||||
remote: FileExplorer::new(16),
|
remote: Self::build_explorer(config_client.as_ref()),
|
||||||
|
config_cli: config_client,
|
||||||
tab: FileExplorerTab::Local,
|
tab: FileExplorerTab::Local,
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||||
|
|||||||
@@ -603,9 +603,8 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn local_scan(&mut self, path: &Path) {
|
pub(super) fn local_scan(&mut self, path: &Path) {
|
||||||
match self.context.as_ref().unwrap().local.scan_dir(path) {
|
match self.context.as_ref().unwrap().local.scan_dir(path) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
|
// Set files and sort (sorting is implicit)
|
||||||
self.local.set_files(files);
|
self.local.set_files(files);
|
||||||
// Sort files
|
|
||||||
self.local.sort_files_by_name();
|
|
||||||
// Set index; keep if possible, otherwise set to last item
|
// Set index; keep if possible, otherwise set to last item
|
||||||
self.local.set_index(match self.local.get_current_file() {
|
self.local.set_index(match self.local.get_current_file() {
|
||||||
Some(_) => self.local.get_index(),
|
Some(_) => self.local.get_index(),
|
||||||
@@ -630,9 +629,8 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn remote_scan(&mut self, path: &Path) {
|
pub(super) fn remote_scan(&mut self, path: &Path) {
|
||||||
match self.client.list_dir(path) {
|
match self.client.list_dir(path) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
|
// Set files and sort (sorting is implicit)
|
||||||
self.remote.set_files(files);
|
self.remote.set_files(files);
|
||||||
// Sort files
|
|
||||||
self.remote.sort_files_by_name();
|
|
||||||
// Set index; keep if possible, otherwise set to last item
|
// Set index; keep if possible, otherwise set to last item
|
||||||
self.remote.set_index(match self.remote.get_current_file() {
|
self.remote.set_index(match self.remote.get_current_file() {
|
||||||
Some(_) => self.remote.get_index(),
|
Some(_) => self.remote.get_index(),
|
||||||
|
|||||||
Reference in New Issue
Block a user