Compare commits

...

11 Commits
main ... 0.8.0

32 changed files with 270 additions and 149 deletions

View File

@ -7,7 +7,6 @@ ignore:
- "C:/*"
- "../*"
- src/main.rs
- src/lib.rs
- src/activity_manager.rs
- src/filetransfer/transfer/s3/mod.rs
- src/support.rs

View File

@ -24,7 +24,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --lib --no-default-features --features github-actions --features with-containers --no-fail-fast
args: --no-default-features --features github-actions --features with-containers --no-fail-fast
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests"

View File

@ -19,4 +19,4 @@ jobs:
/tmp/rustup.sh -y
. $HOME/.cargo/env
cargo build --no-default-features
cargo test --no-default-features --verbose --lib --features github-actions -- --test-threads 1
cargo test --no-default-features --verbose --features github-actions

View File

@ -24,7 +24,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --lib --no-default-features --features github-actions --features with-containers --no-fail-fast
args: --no-default-features --features github-actions --features with-containers --no-fail-fast
- name: Format
run: cargo fmt --all -- --check
- name: Clippy

View File

@ -14,6 +14,6 @@ jobs:
- name: Build
run: cargo build
- name: Run tests
run: cargo test --verbose --lib --features github-actions -- --test-threads 1
run: cargo test --verbose --features github-actions
- name: Clippy
run: cargo clippy
run: cargo clippy -- -Dwarnings

View File

@ -14,6 +14,6 @@ jobs:
- name: Build
run: cargo build
- name: Run tests
run: cargo test --verbose --lib --features github-actions -- --test-threads 1
run: cargo test --verbose --features github-actions
- name: Clippy
run: cargo clippy
run: cargo clippy -- -Dwarnings

View File

@ -1,6 +1,7 @@
# Changelog
- [Changelog](#changelog)
- [0.8.0](#080)
- [0.7.0](#070)
- [0.6.1](#061)
- [0.6.0](#060)
@ -21,6 +22,20 @@
---
## 0.8.0
Released on FIXME:
> ❄️ Winter update 2022 ⛄
- **Enhancements**:
- **Find** feature:
- A "wait popup" will now be displayed while searching files
- If find command doesn't return any result show an info dialog and not an empty explorer
- It is now possible to keep navigating on the other explorer while "found tab" is open
- ❗ It is not possible though to have the "found tab" on both explorers (otherwise you wouldn't be able to tell whether you're transferring files)
- Files found from search are now displayed with their relative path from working directory
## 0.7.0
Released on 12/10/2021

25
Cargo.lock generated
View File

@ -1970,6 +1970,28 @@ dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
dependencies = [
"lazy_static",
"parking_lot 0.11.2",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.76",
]
[[package]]
name = "sha2"
version = "0.9.8"
@ -2176,7 +2198,7 @@ dependencies = [
[[package]]
name = "termscp"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"argh",
"bitflags 1.3.2",
@ -2201,6 +2223,7 @@ dependencies = [
"rust-s3",
"self_update",
"serde",
"serial_test",
"simplelog",
"ssh2",
"suppaftp",

View File

@ -3,7 +3,7 @@ authors = ["Christian Visintin"]
categories = ["command-line-utilities"]
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
documentation = "https://docs.rs/termscp"
edition = "2018"
edition = "2021"
homepage = "https://veeso.github.io/termscp/"
include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
keywords = ["scp-client", "sftp-client", "ftp-client", "winscp", "command-line-utility"]
@ -11,7 +11,7 @@ license = "MIT"
name = "termscp"
readme = "README.md"
repository = "https://github.com/veeso/termscp"
version = "0.7.0"
version = "0.8.0"
[package.metadata.rpm]
package = "termscp"
@ -67,6 +67,7 @@ wildmatch = "2.0.0"
[dev-dependencies]
pretty_assertions = "0.7.2"
serial_test = "^0.5.1"
[features]
default = [ "with-keyring" ]

View File

@ -63,7 +63,7 @@
</p>
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Current version: 0.7.0 (12/10/2021)</p>
<p align="center">Current version: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -63,7 +63,7 @@
</p>
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Aktuelle Version: 0.7.0 (12/10/2021)</p>
<p align="center">Aktuelle Version: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -339,6 +339,7 @@ These are the keys supported by the formatter:
- `GROUP`: Owner group
- `MTIME`: Last change time (with syntax `%b %d %Y %H:%M`); Extra might be provided as the time syntax (e.g. `{MTIME:8:%H:%M}`)
- `NAME`: File name (Elided if longer than LENGTH)
- `PATH`: File absolute path (Elided if longer than LENGHT)
- `PEX`: File permissions (UNIX format)
- `SIZE`: File size (omitted for directories)
- `SYMLINK`: Symlink (if any `-> {FILE_PATH}`)

View File

@ -63,7 +63,7 @@
</p>
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Versión actual: 0.7.0 (12/10/2021)</p>
<p align="center">Versión actual: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -337,7 +337,8 @@ Estas son las claves admitidas por el formateador:
- `CTIME`: Hora de creación (con sintaxis`%b %d %Y %H:%M`); Se puede proporcionar un extra como sintaxis de tiempo (p. Ej., `{CTIME:8:%H:%M}`)
- `GROUP`: Grupo propietario
- `MTIME`: Hora del último cambio (con sintaxis`%b %d %Y %H:%M`); Se puede proporcionar extra como sintaxis de tiempo (p. Ej., `{MTIME: 8:% H:% M}`)
- `NAME`: nombre de archivo (se omite si es más largo que LENGTH)
- `NAME`: nombre de archivo (Las carpetas entre la raíz y los primeros antepasados se eliminan si es más largo que LENGTH)
- `PATH`: Percorso completo de archivo (Las carpetas entre la raíz y los primeros antepasados se eliminan si es màs largo que LENGHT)
- `PEX`: permisos de archivo (formato UNIX)
- `SIZE`: Tamaño del archivo (se omite para directorios)
- `SYMLINK`: Symlink (si existe` -> {FILE_PATH} `)

View File

@ -63,7 +63,7 @@
</p>
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Version actuelle: 0.7.0 (12/10/2021)</p>
<p align="center">Version actuelle: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -336,6 +336,7 @@ Voici les clés prises en charge par le formateur :
- `GROUP`: Groupe de propriétaires
- `MTIME`: Heure du dernier changement (avec la syntaxe `%b %d %Y %H:%M`); Un supplément peut être fourni comme syntaxe de l'heure (par exemple, `{MTIME:8:%H:%M}`)
- `NAME`: Nom du fichier (élidé si plus long que LENGTH)
- `PATH`: Chemin absolu du fichier (les dossiers entre la racine et les premiers ancêtres sont éludés s'ils sont plus longs que LENGTH)
- `PEX`: Autorisations de fichiers (format UNIX)
- `SIZE`: Taille du fichier (omis pour les répertoires)
- `SYMLINK`: Lien symbolique (le cas échéant `-> {FILE_PATH}`)

View File

@ -63,7 +63,7 @@
</p>
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
<p align="center">Versione corrente: 0.7.0 (12/10/2021)</p>
<p align="center">Versione corrente: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -333,7 +333,8 @@ These are the keys supported by the formatter:
- `CTIME`: Creation time (con sintassi di default `%b %d %Y %H:%M`); Extra definisce il formato data (e.g. `{CTIME:8:%H:%M}`)
- `GROUP`: Owner group
- `MTIME`: Last change time (con sintassi di default `%b %d %Y %H:%M`); Extra definisce il formato data (e.g. `{MTIME:8:%H:%M}`)
- `NAME`: Nome file (Elided if longer than LENGTH)
- `NAME`: Nome file (Le cartelle comprese tra la root ed il genitore del file sono omessi se la lunghezza è maggiore di LENGTH)
- `PATH`: Percorso assoluto del file (Le cartelle comprese tra la root ed il genitore del file sono omessi se la lunghezza è maggiore di LENGHT)
- `PEX`: Permessi utente (formato UNIX)
- `SIZE`: Dimensione file (omesso per le directory)
- `SYMLINK`: Link simbolico (se presente `-> {FILE_PATH}`)

View File

@ -336,7 +336,8 @@ These are the keys supported by the formatter:
- `CTIME`: Creation time (with syntax `%b %d %Y %H:%M`); Extra might be provided as the time syntax (e.g. `{CTIME:8:%H:%M}`)
- `GROUP`: Owner group
- `MTIME`: Last change time (with syntax `%b %d %Y %H:%M`); Extra might be provided as the time syntax (e.g. `{MTIME:8:%H:%M}`)
- `NAME`: File name (Elided if longer than LENGTH)
- `NAME`: File name (Folders between root and first ancestors are elided if longer than LENGTH)
- `PATH`: File absolute path (Folders between root and first ancestors are elided if longer than LENGHT)
- `PEX`: File permissions (UNIX format)
- `SIZE`: File size (omitted for directories)
- `SYMLINK`: Symlink (if any `-> {FILE_PATH}`)

View File

@ -63,7 +63,7 @@
</p>
<p align="center"><a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</p>
<p align="center">当前版本: 0.7.0 (12/10/2021)</p>
<p align="center">当前版本: 0.8.0 (12/10/2021)</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"

View File

@ -330,6 +330,7 @@ termscp和书签一样只需要保证这些路径是可访问的
- `GROUP`: 所属组
- `MTIME`: 最后修改时间(语法为`%b %d %Y %H:%M`Extra参数可以指定时间显示语法例如`{MTIME:8:%H:%M}`
- `NAME`: 文件名(超过 LENGTH 个字符的部分会被省略)
- `PATH`:文件绝对路径(如果长于 LENGTH则根目录和第一个祖先之间的文件夹将被排除
- `PEX`: 文件权限UNIX格式
- `SIZE`: 文件大小(目录不显示)
- `SYMLINK`: 超链接(如果存在的话`-> {FILE_PATH}`)。

View File

@ -8,7 +8,7 @@
# -f, -y, --force, --yes
# Skip the confirmation prompt during installation
TERMSCP_VERSION="0.7.0"
TERMSCP_VERSION="0.8.0"
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"

View File

@ -28,9 +28,11 @@
// Locals
use super::FsEntry;
use crate::utils::fmt::{fmt_path_elide, fmt_pex, fmt_time};
use crate::utils::path::diff_paths;
// Ext
use bytesize::ByteSize;
use regex::Regex;
use std::path::PathBuf;
#[cfg(target_family = "unix")]
use users::{get_group_by_gid, get_user_by_uid};
// Types
@ -43,6 +45,7 @@ const FMT_KEY_CTIME: &str = "CTIME";
const FMT_KEY_GROUP: &str = "GROUP";
const FMT_KEY_MTIME: &str = "MTIME";
const FMT_KEY_NAME: &str = "NAME";
const FMT_KEY_PATH: &str = "PATH";
const FMT_KEY_PEX: &str = "PEX";
const FMT_KEY_SIZE: &str = "SIZE";
const FMT_KEY_SYMLINK: &str = "SYMLINK";
@ -68,10 +71,15 @@ lazy_static! {
/// a chain of function is made using the Formatters method.
/// This method provides an extremely fast way to format fs entries
struct CallChainBlock {
/// The function to call to format current item
func: FmtCallback,
/// All the content which is between two `{KEY}` items
prefix: String,
/// The fmt len, specied for key as `{KEY:LEN}`
fmt_len: Option<usize>,
/// The extra argument for formatting, specified for key as `{KEY:LEN:EXTRA}`
fmt_extra: Option<String>,
/// The next block to format
next_block: Option<Box<CallChainBlock>>,
}
@ -331,6 +339,36 @@ impl Formatter {
format!("{}{}{:0width$}", cur_str, prefix, name, width = file_len)
}
/// ### fmt_path
///
/// Format path
fn fmt_path(
&self,
fsentry: &FsEntry,
cur_str: &str,
prefix: &str,
fmt_len: Option<&usize>,
fmt_extra: Option<&String>,
) -> String {
let p = match fmt_extra {
None => fsentry.get_abs_path(),
Some(rel) => diff_paths(
fsentry.get_abs_path().as_path(),
PathBuf::from(rel.as_str()).as_path(),
)
.unwrap_or_else(|| fsentry.get_abs_path()),
};
format!(
"{}{}{}",
cur_str,
prefix,
match fmt_len {
None => p.display().to_string(),
Some(len) => fmt_path_elide(p.as_path(), *len),
}
)
}
/// ### fmt_pex
///
/// Format file permissions
@ -490,6 +528,7 @@ impl Formatter {
FMT_KEY_GROUP => Self::fmt_group,
FMT_KEY_MTIME => Self::fmt_mtime,
FMT_KEY_NAME => Self::fmt_name,
FMT_KEY_PATH => Self::fmt_path,
FMT_KEY_PEX => Self::fmt_pex,
FMT_KEY_SIZE => Self::fmt_size,
FMT_KEY_SYMLINK => Self::fmt_symlink,
@ -880,6 +919,36 @@ mod tests {
));
}
#[test]
fn should_fmt_path() {
let t: SystemTime = SystemTime::now();
let entry: FsEntry = FsEntry::File(FsFile {
name: String::from("bar.txt"),
abs_path: PathBuf::from("/tmp/a/b/c/bar.txt"),
last_change_time: t,
last_access_time: t,
creation_time: t,
size: 8192,
ftype: Some(String::from("txt")),
symlink: None, // UNIX only
user: None, // UNIX only
group: None, // UNIX only
unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only
});
let formatter: Formatter = Formatter::new("File path: {PATH}");
assert_eq!(
formatter.fmt(&entry).as_str(),
"File path: /tmp/a/b/c/bar.txt"
);
let formatter: Formatter = Formatter::new("File path: {PATH:8}");
assert_eq!(
formatter.fmt(&entry).as_str(),
"File path: /tmp/…/c/bar.txt"
);
let formatter: Formatter = Formatter::new("File path: {PATH:128:/tmp/a/b}");
assert_eq!(formatter.fmt(&entry).as_str(), "File path: c/bar.txt");
}
/// ### dummy_fmt
///
/// Dummy formatter, just yelds an 'A' at the end of the current string

View File

@ -543,10 +543,9 @@ impl Localhost {
}),
false => {
// Is File
let extension: Option<String> = match path.extension() {
Some(s) => Some(String::from(s.to_str().unwrap_or(""))),
None => None,
};
let extension: Option<String> = path
.extension()
.map(|s| String::from(s.to_str().unwrap_or("")));
FsEntry::File(FsFile {
name: file_name,
abs_path: path.clone(),

View File

@ -1,77 +0,0 @@
#![doc(html_playground_url = "https://play.rust-lang.org")]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/veeso/termscp/main/assets/images/termscp-128.png"
)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/veeso/termscp/main/assets/images/termscp-512.png"
)]
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#[macro_use]
extern crate bitflags;
extern crate bytesize;
extern crate chrono;
extern crate content_inspector;
extern crate crossterm;
extern crate dirs;
extern crate edit;
extern crate hostname;
#[cfg(feature = "with-keyring")]
extern crate keyring;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate magic_crypt;
extern crate notify_rust;
extern crate open;
#[cfg(target_os = "windows")]
extern crate path_slash;
extern crate rand;
extern crate regex;
extern crate s3;
extern crate self_update;
extern crate ssh2;
extern crate suppaftp;
extern crate tempfile;
extern crate textwrap;
extern crate tui_realm_stdlib;
extern crate tuirealm;
#[cfg(target_family = "unix")]
extern crate users;
extern crate whoami;
extern crate wildmatch;
pub mod activity_manager;
pub mod config;
pub mod filetransfer;
pub mod fs;
pub mod host;
pub mod support;
pub mod system;
pub mod ui;
pub mod utils;

View File

@ -35,7 +35,6 @@ extern crate lazy_static;
extern crate log;
#[macro_use]
extern crate magic_crypt;
extern crate rpassword;
// External libs
use argh::FromArgs;

View File

@ -110,10 +110,12 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;
use serial_test::serial;
use std::fs::{File, OpenOptions};
use std::io::Write;
#[test]
#[serial]
fn test_system_environment_get_config_dir() {
// Create and get conf_dir
let conf_dir: PathBuf = init_config_dir().ok().unwrap().unwrap();
@ -122,6 +124,7 @@ mod tests {
}
#[test]
#[serial]
fn test_system_environment_get_config_dir_err() {
let mut conf_dir: PathBuf = std::env::temp_dir();
conf_dir.push("termscp");
@ -143,6 +146,7 @@ mod tests {
}
#[test]
#[serial]
fn test_system_environment_get_bookmarks_paths() {
assert_eq!(
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
@ -151,6 +155,7 @@ mod tests {
}
#[test]
#[serial]
fn test_system_environment_get_config_paths() {
assert_eq!(
get_config_paths(&Path::new("/home/omar/.config/termscp/")),
@ -162,6 +167,7 @@ mod tests {
}
#[test]
#[serial]
fn test_system_environment_get_log_paths() {
assert_eq!(
get_log_paths(&Path::new("/home/omar/.config/termscp/")),
@ -170,6 +176,7 @@ mod tests {
}
#[test]
#[serial]
fn test_system_environment_get_theme_path() {
assert_eq!(
get_theme_path(&Path::new("/home/omar/.config/termscp/")),

View File

@ -97,7 +97,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
@ -119,7 +118,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
},
@ -162,7 +160,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
@ -196,7 +193,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}

View File

@ -113,7 +113,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
@ -154,7 +153,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
@ -185,7 +183,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}
@ -226,7 +223,6 @@ impl FileTransferActivity {
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}

View File

@ -29,6 +29,8 @@ use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSortin
use crate::fs::FsEntry;
use crate::system::config_client::ConfigClient;
use std::path::Path;
/// ## FileExplorerTab
///
/// File explorer tab
@ -40,14 +42,23 @@ pub enum FileExplorerTab {
FindRemote, // Find result tab
}
/// ## FoundExplorerTab
///
/// Describes the explorer tab type
#[derive(Copy, Clone, Debug)]
pub enum FoundExplorerTab {
Local,
Remote,
}
/// ## Browser
///
/// Browser contains the browser options
pub struct Browser {
local: FileExplorer, // Local File explorer state
remote: FileExplorer, // Remote File explorer state
found: Option<FileExplorer>, // File explorer for find result
tab: FileExplorerTab, // Current selected tab
local: FileExplorer, // Local File explorer state
remote: FileExplorer, // Remote File explorer state
found: Option<(FoundExplorerTab, FileExplorer)>, // File explorer for find result
tab: FileExplorerTab, // Current selected tab
pub sync_browsing: bool,
}
@ -82,23 +93,30 @@ impl Browser {
}
pub fn found(&self) -> Option<&FileExplorer> {
self.found.as_ref()
self.found.as_ref().map(|x| &x.1)
}
pub fn found_mut(&mut self) -> Option<&mut FileExplorer> {
self.found.as_mut()
self.found.as_mut().map(|x| &mut x.1)
}
pub fn set_found(&mut self, files: Vec<FsEntry>) {
let mut explorer = Self::build_found_explorer();
pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec<FsEntry>, wrkdir: &Path) {
let mut explorer = Self::build_found_explorer(wrkdir);
explorer.set_files(files);
self.found = Some(explorer);
self.found = Some((tab, explorer));
}
pub fn del_found(&mut self) {
self.found = None;
}
/// ### found_tab
///
/// Returns found tab if any
pub fn found_tab(&self) -> Option<FoundExplorerTab> {
self.found.as_ref().map(|x| x.0)
}
pub fn tab(&self) -> FileExplorerTab {
self.tab
}
@ -152,13 +170,15 @@ impl Browser {
/// ### build_found_explorer
///
/// Build explorer reading from `ConfigClient`, for found result (has some differences)
fn build_found_explorer() -> FileExplorer {
fn build_found_explorer(wrkdir: &Path) -> FileExplorer {
FileExplorerBuilder::new()
.with_file_sorting(FileSorting::Name)
.with_group_dirs(Some(GroupDirs::First))
.with_hidden_files(true)
.with_stack_size(0)
.with_formatter(Some("{NAME:32} {SYMLINK}"))
.with_formatter(Some(
format!("{{PATH:36:{}}} {{SYMLINK}}", wrkdir.display()).as_str(),
))
.build()
}
}

View File

@ -27,15 +27,17 @@
*/
// locals
use super::{
actions::SelectedEntry, browser::FileExplorerTab, FileTransferActivity, LogLevel, TransferOpts,
COMPONENT_EXPLORER_FIND, COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE,
COMPONENT_INPUT_COPY, COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO,
COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_OPEN_WITH,
COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO,
COMPONENT_LIST_REPLACING_FILES, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR_FULL,
COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT,
COMPONENT_RADIO_QUIT, COMPONENT_RADIO_REPLACE, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR,
COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP,
actions::SelectedEntry,
browser::{FileExplorerTab, FoundExplorerTab},
FileTransferActivity, LogLevel, TransferOpts, COMPONENT_EXPLORER_FIND,
COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, COMPONENT_INPUT_COPY,
COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, COMPONENT_INPUT_MKDIR,
COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_OPEN_WITH, COMPONENT_INPUT_RENAME,
COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO, COMPONENT_LIST_REPLACING_FILES,
COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR_FULL, COMPONENT_PROGRESS_BAR_PARTIAL,
COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT, COMPONENT_RADIO_QUIT,
COMPONENT_RADIO_REPLACE, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL,
COMPONENT_TEXT_HELP,
};
use crate::fs::explorer::FileSorting;
use crate::fs::FsEntry;
@ -64,6 +66,15 @@ impl Update for FileTransferActivity {
None => None, // Exit after None
Some(msg) => match msg {
// -- local tab
(COMPONENT_EXPLORER_LOCAL, key)
if key == &MSG_KEY_RIGHT
&& matches!(self.browser.found_tab(), Some(FoundExplorerTab::Remote)) =>
{
// Go to find explorer
self.view.active(COMPONENT_EXPLORER_FIND);
self.browser.change_tab(FileExplorerTab::FindRemote);
None
}
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_RIGHT => {
// Change tab
self.view.active(COMPONENT_EXPLORER_REMOTE);
@ -137,6 +148,15 @@ impl Update for FileTransferActivity {
self.update_local_filelist()
}
// -- remote tab
(COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_LEFT
&& matches!(self.browser.found_tab(), Some(FoundExplorerTab::Local)) =>
{
// Go to find explorer
self.view.active(COMPONENT_EXPLORER_FIND);
self.browser.change_tab(FileExplorerTab::FindLocal);
None
}
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_LEFT => {
// Change tab
self.view.active(COMPONENT_EXPLORER_LOCAL);
@ -336,6 +356,24 @@ impl Update for FileTransferActivity {
None
}
// -- find result explorer
(COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_RIGHT
&& matches!(self.browser.tab(), FileExplorerTab::FindLocal) =>
{
// Active remote explorer
self.view.active(COMPONENT_EXPLORER_REMOTE);
self.browser.change_tab(FileExplorerTab::Remote);
None
}
(COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_LEFT
&& matches!(self.browser.tab(), FileExplorerTab::FindRemote) =>
{
// Active local explorer
self.view.active(COMPONENT_EXPLORER_LOCAL);
self.browser.change_tab(FileExplorerTab::Local);
None
}
(COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_ESC => {
// Umount find
self.umount_find();
@ -433,21 +471,43 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_FIND, Msg::OnSubmit(Payload::One(Value::Str(input)))) => {
self.umount_find_input();
// Mount wait
self.mount_blocking_wait(format!(r#"Searching for "{}"…"#, input).as_str());
// Find
let res: Result<Vec<FsEntry>, String> = match self.browser.tab() {
FileExplorerTab::Local => self.action_local_find(input.to_string()),
FileExplorerTab::Remote => self.action_remote_find(input.to_string()),
_ => panic!("Trying to search for files, while already in a find result"),
};
// Umount wait
self.umount_wait();
// Match result
match res {
Err(err) => {
// Mount error
self.mount_error(err.as_str());
}
Ok(files) if files.is_empty() => {
// If no file has been found notify user
self.mount_info(
format!(r#"Could not find any file matching "{}""#, input).as_str(),
);
}
Ok(files) => {
// Get wrkdir
let wrkdir = match self.browser.tab() {
FileExplorerTab::Local => self.local().wrkdir.clone(),
_ => self.remote().wrkdir.clone(),
};
// Create explorer and load files
self.browser.set_found(files);
self.browser.set_found(
match self.browser.tab() {
FileExplorerTab::Local => FoundExplorerTab::Local,
_ => FoundExplorerTab::Remote,
},
files,
wrkdir.as_path(),
);
// Mount result widget
self.mount_find(input);
self.update_find_list();

View File

@ -26,7 +26,10 @@
* SOFTWARE.
*/
// locals
use super::{browser::FileExplorerTab, Context, FileTransferActivity};
use super::{
browser::{FileExplorerTab, FoundExplorerTab},
Context, FileTransferActivity,
};
use crate::fs::explorer::FileSorting;
use crate::fs::FsEntry;
use crate::ui::components::{
@ -165,24 +168,20 @@ impl FileTransferActivity {
}
// Draw explorers
// @! Local explorer (Find or default)
match self.browser.tab() {
FileExplorerTab::FindLocal => {
self.view
.render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[0])
}
_ => self
.view
.render(super::COMPONENT_EXPLORER_LOCAL, f, tabs_chunks[0]),
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Local)) {
self.view
.render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[0]);
} else {
self.view
.render(super::COMPONENT_EXPLORER_LOCAL, f, tabs_chunks[0]);
}
// @! Remote explorer (Find or default)
match self.browser.tab() {
FileExplorerTab::FindRemote => {
self.view
.render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[1])
}
_ => self
.view
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]),
if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Remote)) {
self.view
.render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[1]);
} else {
self.view
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]);
}
// Draw log box
self.view
@ -400,6 +399,15 @@ impl FileTransferActivity {
// -- partials
/// ### mount_info
///
/// Mount info box
pub(super) fn mount_info(&mut self, text: &str) {
// Mount
let info_color = self.theme().misc_info_dialog;
self.mount_text_dialog(super::COMPONENT_TEXT_ERROR, text, info_color);
}
/// ### mount_error
///
/// Mount error box