mirror of
https://github.com/veeso/termscp.git
synced 2026-09-27 06:21:22 -07:00
Merge pull request #30 from veeso/issue-8-synchronized-browsing-of-local-and-remote-directories
Synchronized browsing of local and remote directories
This commit is contained in:
+7
-1
@@ -21,14 +21,20 @@
|
|||||||
|
|
||||||
Released on FIXME: ??
|
Released on FIXME: ??
|
||||||
|
|
||||||
|
- **Synchronized browsing**:
|
||||||
|
- Added the possibility to enabled the synchronized brower navigation
|
||||||
|
- when you enter a directory, the same directory will be entered on the other tab
|
||||||
|
- Enable sync browser with `<Y>`
|
||||||
- **Remote and Local hosts file formatter**:
|
- **Remote and Local hosts file formatter**:
|
||||||
- Added the possibility to set different formatters for local and remote hosts
|
- Added the possibility to set different formatters for local and remote hosts
|
||||||
|
- Enhancements
|
||||||
|
- Added a status bar in the file explorer showing whether the sync browser is enabled and which file sorting mode is selected
|
||||||
- Bugfix:
|
- Bugfix:
|
||||||
- Fixed wrong text wrap in log box
|
- Fixed wrong text wrap in log box
|
||||||
- Fixed error message not being shown after an upload failure
|
- Fixed error message not being shown after an upload failure
|
||||||
- [Issue 23](https://github.com/veeso/termscp/issues/23): Remove created file if transfer failed or was abrupted
|
- [Issue 23](https://github.com/veeso/termscp/issues/23): Remove created file if transfer failed or was abrupted
|
||||||
- Dependencies:
|
- Dependencies:
|
||||||
- Added `tui-realm 0.2.1`
|
- Added `tui-realm 0.2.2`
|
||||||
- Removed `tui` (as direct dependency)
|
- Removed `tui` (as direct dependency)
|
||||||
- Updated `regex` to `1.5.3`
|
- Updated `regex` to `1.5.3`
|
||||||
|
|
||||||
|
|||||||
Generated
+8
-33
@@ -219,22 +219,6 @@ dependencies = [
|
|||||||
"debug-helper",
|
"debug-helper",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossterm"
|
|
||||||
version = "0.18.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"crossterm_winapi 0.6.2",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"mio",
|
|
||||||
"parking_lot 0.11.1",
|
|
||||||
"signal-hook",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@@ -242,7 +226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
|
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi 0.7.0",
|
"crossterm_winapi",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
@@ -251,15 +235,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossterm_winapi"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm_winapi"
|
name = "crossterm_winapi"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -1275,7 +1250,7 @@ dependencies = [
|
|||||||
"bytesize",
|
"bytesize",
|
||||||
"chrono",
|
"chrono",
|
||||||
"content_inspector",
|
"content_inspector",
|
||||||
"crossterm 0.19.0",
|
"crossterm",
|
||||||
"dirs",
|
"dirs",
|
||||||
"edit",
|
"edit",
|
||||||
"ftp4",
|
"ftp4",
|
||||||
@@ -1379,24 +1354,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tui"
|
name = "tui"
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
|
checksum = "861d8f3ad314ede6219bcb2ab844054b1de279ee37a9bc38e3d606f9d3fb2a71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"crossterm 0.18.2",
|
"crossterm",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tuirealm"
|
name = "tuirealm"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cade7d98a40066164d9c8e52bf01f86ecaa4fa810e854855a5a4ec3deca83c2"
|
checksum = "963c590368d3ab9be44ee0fccb7ad86f3611dfb4536b02be0e25cbc5685d51bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossterm 0.19.0",
|
"crossterm",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"tui",
|
"tui",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ tempfile = "3.1.0"
|
|||||||
textwrap = "0.13.4"
|
textwrap = "0.13.4"
|
||||||
thiserror = "^1.0.0"
|
thiserror = "^1.0.0"
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
tuirealm = { version = "0.2.1", features = [ "with-components" ] }
|
tuirealm = { version = "0.2.2", features = [ "with-components" ] }
|
||||||
whoami = "1.1.1"
|
whoami = "1.1.1"
|
||||||
wildmatch = "2.0.0"
|
wildmatch = "2.0.0"
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ Password can be basically provided through 3 ways when address argument is provi
|
|||||||
| `<S>` | Save file as... | Save |
|
| `<S>` | Save file as... | Save |
|
||||||
| `<U>` | Go to parent directory | Upper |
|
| `<U>` | Go to parent directory | Upper |
|
||||||
| `<X>` | Execute a command | eXecute |
|
| `<X>` | Execute a command | eXecute |
|
||||||
|
| `<Y>` | Toggle synchronized browsing | sYnc |
|
||||||
| `<DEL>` | Delete file | |
|
| `<DEL>` | Delete file | |
|
||||||
| `<CTRL+C>` | Abort file transfer process | |
|
| `<CTRL+C>` | Abort file transfer process | |
|
||||||
|
|
||||||
|
|||||||
@@ -32,36 +32,156 @@ use tuirealm::{Payload, Value};
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
|
/// ### action_enter_local_dir
|
||||||
|
///
|
||||||
|
/// Enter a directory on local host from entry
|
||||||
|
/// Return true whether the directory changed
|
||||||
|
pub(super) fn action_enter_local_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool {
|
||||||
|
match entry {
|
||||||
|
FsEntry::Directory(dir) => {
|
||||||
|
self.local_changedir(dir.abs_path.as_path(), true);
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_change_remote_dir(dir.name, true);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
FsEntry::File(file) => {
|
||||||
|
match &file.symlink {
|
||||||
|
Some(symlink_entry) => {
|
||||||
|
// If symlink and is directory, point to symlink
|
||||||
|
match &**symlink_entry {
|
||||||
|
FsEntry::Directory(dir) => {
|
||||||
|
self.local_changedir(dir.abs_path.as_path(), true);
|
||||||
|
// Check whether to sync
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_change_remote_dir(dir.name.clone(), true);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### action_enter_remote_dir
|
||||||
|
///
|
||||||
|
/// Enter a directory on local host from entry
|
||||||
|
/// Return true whether the directory changed
|
||||||
|
pub(super) fn action_enter_remote_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool {
|
||||||
|
match entry {
|
||||||
|
FsEntry::Directory(dir) => {
|
||||||
|
self.remote_changedir(dir.abs_path.as_path(), true);
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_change_local_dir(dir.name, true);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
FsEntry::File(file) => {
|
||||||
|
match &file.symlink {
|
||||||
|
Some(symlink_entry) => {
|
||||||
|
// If symlink and is directory, point to symlink
|
||||||
|
match &**symlink_entry {
|
||||||
|
FsEntry::Directory(dir) => {
|
||||||
|
self.remote_changedir(dir.abs_path.as_path(), true);
|
||||||
|
// Check whether to sync
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_change_local_dir(dir.name.clone(), true);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ### action_change_local_dir
|
/// ### action_change_local_dir
|
||||||
///
|
///
|
||||||
/// Change local directory reading value from input
|
/// Change local directory reading value from input
|
||||||
pub(super) fn action_change_local_dir(&mut self, input: String) {
|
pub(super) fn action_change_local_dir(&mut self, input: String, block_sync: bool) {
|
||||||
let dir_path: PathBuf = PathBuf::from(input.as_str());
|
let dir_path: PathBuf = self.local_to_abs_path(PathBuf::from(input.as_str()).as_path());
|
||||||
let abs_dir_path: PathBuf = match dir_path.is_relative() {
|
self.local_changedir(dir_path.as_path(), true);
|
||||||
true => {
|
// Check whether to sync
|
||||||
let mut d: PathBuf = self.local.wrkdir.clone();
|
if self.browser.sync_browsing && !block_sync {
|
||||||
d.push(dir_path);
|
self.action_change_remote_dir(input, true);
|
||||||
d
|
}
|
||||||
}
|
|
||||||
false => dir_path,
|
|
||||||
};
|
|
||||||
self.local_changedir(abs_dir_path.as_path(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### action_change_remote_dir
|
/// ### action_change_remote_dir
|
||||||
///
|
///
|
||||||
/// Change remote directory reading value from input
|
/// Change remote directory reading value from input
|
||||||
pub(super) fn action_change_remote_dir(&mut self, input: String) {
|
pub(super) fn action_change_remote_dir(&mut self, input: String, block_sync: bool) {
|
||||||
let dir_path: PathBuf = PathBuf::from(input.as_str());
|
let dir_path: PathBuf = self.remote_to_abs_path(PathBuf::from(input.as_str()).as_path());
|
||||||
let abs_dir_path: PathBuf = match dir_path.is_relative() {
|
self.remote_changedir(dir_path.as_path(), true);
|
||||||
true => {
|
// Check whether to sync
|
||||||
let mut wrkdir: PathBuf = self.remote.wrkdir.clone();
|
if self.browser.sync_browsing && !block_sync {
|
||||||
wrkdir.push(dir_path);
|
self.action_change_local_dir(input, true);
|
||||||
wrkdir
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### action_go_to_previous_local_dir
|
||||||
|
///
|
||||||
|
/// Go to previous directory from localhost
|
||||||
|
pub(super) fn action_go_to_previous_local_dir(&mut self, block_sync: bool) {
|
||||||
|
if let Some(d) = self.local.popd() {
|
||||||
|
self.local_changedir(d.as_path(), false);
|
||||||
|
// Check whether to sync
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_go_to_previous_remote_dir(true);
|
||||||
}
|
}
|
||||||
false => dir_path,
|
}
|
||||||
};
|
}
|
||||||
self.remote_changedir(abs_dir_path.as_path(), true);
|
|
||||||
|
/// ### action_go_to_previous_remote_dir
|
||||||
|
///
|
||||||
|
/// Go to previous directory from remote host
|
||||||
|
pub(super) fn action_go_to_previous_remote_dir(&mut self, block_sync: bool) {
|
||||||
|
if let Some(d) = self.remote.popd() {
|
||||||
|
self.remote_changedir(d.as_path(), false);
|
||||||
|
// Check whether to sync
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_go_to_previous_local_dir(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### action_go_to_local_upper_dir
|
||||||
|
///
|
||||||
|
/// Go to upper directory on local host
|
||||||
|
pub(super) fn action_go_to_local_upper_dir(&mut self, block_sync: bool) {
|
||||||
|
// Get pwd
|
||||||
|
let path: PathBuf = self.local.wrkdir.clone();
|
||||||
|
// Go to parent directory
|
||||||
|
if let Some(parent) = path.as_path().parent() {
|
||||||
|
self.local_changedir(parent, true);
|
||||||
|
// If sync is enabled update remote too
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_go_to_remote_upper_dir(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #### action_go_to_remote_upper_dir
|
||||||
|
///
|
||||||
|
/// Go to upper directory on remote host
|
||||||
|
pub(super) fn action_go_to_remote_upper_dir(&mut self, block_sync: bool) {
|
||||||
|
// Get pwd
|
||||||
|
let path: PathBuf = self.remote.wrkdir.clone();
|
||||||
|
// Go to parent directory
|
||||||
|
if let Some(parent) = path.as_path().parent() {
|
||||||
|
self.remote_changedir(parent, true);
|
||||||
|
// If sync is enabled update local too
|
||||||
|
if self.browser.sync_browsing && !block_sync {
|
||||||
|
self.action_go_to_local_upper_dir(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### action_local_copy
|
/// ### action_local_copy
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::system::environment;
|
|||||||
use crate::system::sshkey_storage::SshKeyStorage;
|
use crate::system::sshkey_storage::SshKeyStorage;
|
||||||
// Ext
|
// Ext
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
/// ### log
|
/// ### log
|
||||||
@@ -175,4 +175,32 @@ impl FileTransferActivity {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### local_to_abs_path
|
||||||
|
///
|
||||||
|
/// Convert a path to absolute according to local explorer
|
||||||
|
pub(super) fn local_to_abs_path(&self, path: &Path) -> PathBuf {
|
||||||
|
match path.is_relative() {
|
||||||
|
true => {
|
||||||
|
let mut d: PathBuf = self.local.wrkdir.clone();
|
||||||
|
d.push(path);
|
||||||
|
d
|
||||||
|
}
|
||||||
|
false => path.to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### remote_to_abs_path
|
||||||
|
///
|
||||||
|
/// Convert a path to absolute according to remote explorer
|
||||||
|
pub(super) fn remote_to_abs_path(&self, path: &Path) -> PathBuf {
|
||||||
|
match path.is_relative() {
|
||||||
|
true => {
|
||||||
|
let mut wrkdir: PathBuf = self.remote.wrkdir.clone();
|
||||||
|
wrkdir.push(path);
|
||||||
|
wrkdir
|
||||||
|
}
|
||||||
|
false => path.to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE";
|
|||||||
const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT";
|
const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT";
|
||||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||||
const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING";
|
const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING";
|
||||||
|
const COMPONENT_SPAN_STATUS_BAR: &str = "STATUS_BAR";
|
||||||
const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO";
|
const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO";
|
||||||
|
|
||||||
/// ## FileExplorerTab
|
/// ## FileExplorerTab
|
||||||
@@ -202,6 +203,30 @@ impl Default for TransferStates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## Browser
|
||||||
|
///
|
||||||
|
/// Browser contains the browser options
|
||||||
|
struct Browser {
|
||||||
|
pub sync_browsing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Browser {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
sync_browsing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Browser {
|
||||||
|
/// ### toggle_sync_browsing
|
||||||
|
///
|
||||||
|
/// Invert the current state for the sync browsing
|
||||||
|
pub fn toggle_sync_browsing(&mut self) {
|
||||||
|
self.sync_browsing = !self.sync_browsing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## FileTransferActivity
|
/// ## FileTransferActivity
|
||||||
///
|
///
|
||||||
/// FileTransferActivity is the data holder for the file transfer activity
|
/// FileTransferActivity is the data holder for the file transfer activity
|
||||||
@@ -217,6 +242,7 @@ pub struct FileTransferActivity {
|
|||||||
log_records: VecDeque<LogRecord>, // Log records
|
log_records: VecDeque<LogRecord>, // Log records
|
||||||
log_size: usize, // Log records size (max)
|
log_size: usize, // Log records size (max)
|
||||||
transfer: TransferStates, // Transfer states
|
transfer: TransferStates, // Transfer states
|
||||||
|
browser: Browser, // Browser states
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
@@ -246,6 +272,7 @@ impl FileTransferActivity {
|
|||||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||||
log_size: 256, // Must match with capacity
|
log_size: 256, // Must match with capacity
|
||||||
transfer: TransferStates::default(),
|
transfer: TransferStates::default(),
|
||||||
|
browser: Browser::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => {
|
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => {
|
||||||
// Go to previous directory
|
// Go to previous directory
|
||||||
if let Some(d) = self.local.popd() {
|
self.action_go_to_previous_local_dir(false);
|
||||||
self.local_changedir(d.as_path(), false);
|
if self.browser.sync_browsing {
|
||||||
|
let _ = self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_local_filelist()
|
||||||
@@ -86,25 +87,14 @@ impl FileTransferActivity {
|
|||||||
entry = Some(e.clone());
|
entry = Some(e.clone());
|
||||||
}
|
}
|
||||||
if let Some(entry) = entry {
|
if let Some(entry) = entry {
|
||||||
// If directory, enter directory, otherwise check if symlink
|
if self.action_enter_local_dir(entry, false) {
|
||||||
match entry {
|
// Update file list if sync
|
||||||
FsEntry::Directory(dir) => {
|
if self.browser.sync_browsing {
|
||||||
self.local_changedir(dir.abs_path.as_path(), true);
|
let _ = self.update_remote_filelist();
|
||||||
self.update_local_filelist()
|
|
||||||
}
|
|
||||||
FsEntry::File(file) => {
|
|
||||||
// Check if symlink
|
|
||||||
match &file.symlink {
|
|
||||||
Some(pointer) => match &**pointer {
|
|
||||||
FsEntry::Directory(dir) => {
|
|
||||||
self.local_changedir(dir.abs_path.as_path(), true);
|
|
||||||
self.update_local_filelist()
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.update_local_filelist()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -170,13 +160,11 @@ impl FileTransferActivity {
|
|||||||
self.update_local_filelist()
|
self.update_local_filelist()
|
||||||
}
|
}
|
||||||
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_U) => {
|
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_U) => {
|
||||||
// Get pwd
|
self.action_go_to_local_upper_dir(false);
|
||||||
let path: PathBuf = self.local.wrkdir.clone();
|
if self.browser.sync_browsing {
|
||||||
// Go to parent directory
|
let _ = self.update_remote_filelist();
|
||||||
if let Some(parent) = path.as_path().parent() {
|
|
||||||
self.local_changedir(parent, true);
|
|
||||||
// Reload file list component
|
|
||||||
}
|
}
|
||||||
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_local_filelist()
|
||||||
}
|
}
|
||||||
// -- remote tab
|
// -- remote tab
|
||||||
@@ -193,27 +181,14 @@ impl FileTransferActivity {
|
|||||||
entry = Some(e.clone());
|
entry = Some(e.clone());
|
||||||
}
|
}
|
||||||
if let Some(entry) = entry {
|
if let Some(entry) = entry {
|
||||||
// If directory, enter directory; if file, check if is symlink
|
if self.action_enter_remote_dir(entry, false) {
|
||||||
match entry {
|
// Update file list if sync
|
||||||
FsEntry::Directory(dir) => {
|
if self.browser.sync_browsing {
|
||||||
self.remote_changedir(dir.abs_path.as_path(), true);
|
let _ = self.update_local_filelist();
|
||||||
self.update_remote_filelist()
|
|
||||||
}
|
|
||||||
FsEntry::File(file) => {
|
|
||||||
match &file.symlink {
|
|
||||||
Some(symlink_entry) => {
|
|
||||||
// If symlink and is directory, point to symlink
|
|
||||||
match &**symlink_entry {
|
|
||||||
FsEntry::Directory(dir) => {
|
|
||||||
self.remote_changedir(dir.abs_path.as_path(), true);
|
|
||||||
self.update_remote_filelist()
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.update_remote_filelist()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -234,8 +209,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => {
|
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => {
|
||||||
// Go to previous directory
|
// Go to previous directory
|
||||||
if let Some(d) = self.remote.popd() {
|
self.action_go_to_previous_remote_dir(false);
|
||||||
self.remote_changedir(d.as_path(), false);
|
// If sync is enabled update local too
|
||||||
|
if self.browser.sync_browsing {
|
||||||
|
let _ = self.update_local_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -286,11 +263,9 @@ impl FileTransferActivity {
|
|||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
}
|
}
|
||||||
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_U) => {
|
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_U) => {
|
||||||
// Get pwd
|
self.action_go_to_remote_upper_dir(false);
|
||||||
let path: PathBuf = self.remote.wrkdir.clone();
|
if self.browser.sync_browsing {
|
||||||
// Go to parent directory
|
let _ = self.update_local_filelist();
|
||||||
if let Some(parent) = path.as_path().parent() {
|
|
||||||
self.remote_changedir(parent, true);
|
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -357,6 +332,14 @@ impl FileTransferActivity {
|
|||||||
self.mount_exec();
|
self.mount_exec();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Y)
|
||||||
|
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Y) => {
|
||||||
|
// Toggle browser sync
|
||||||
|
self.browser.toggle_sync_browsing();
|
||||||
|
// Update status bar
|
||||||
|
self.refresh_status_bar();
|
||||||
|
None
|
||||||
|
}
|
||||||
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC)
|
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC)
|
||||||
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_ESC)
|
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_ESC)
|
||||||
| (COMPONENT_LOG_BOX, &MSG_KEY_ESC) => {
|
| (COMPONENT_LOG_BOX, &MSG_KEY_ESC) => {
|
||||||
@@ -505,12 +488,24 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
(COMPONENT_INPUT_GOTO, Msg::OnSubmit(Payload::One(Value::Str(input)))) => {
|
(COMPONENT_INPUT_GOTO, Msg::OnSubmit(Payload::One(Value::Str(input)))) => {
|
||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => self.action_change_local_dir(input.to_string()),
|
FileExplorerTab::Local => {
|
||||||
FileExplorerTab::Remote => self.action_change_remote_dir(input.to_string()),
|
self.action_change_local_dir(input.to_string(), false)
|
||||||
|
}
|
||||||
|
FileExplorerTab::Remote => {
|
||||||
|
self.action_change_remote_dir(input.to_string(), false)
|
||||||
|
}
|
||||||
_ => panic!("Found tab doesn't support GOTO"),
|
_ => panic!("Found tab doesn't support GOTO"),
|
||||||
}
|
}
|
||||||
// Umount
|
// Umount
|
||||||
self.umount_goto();
|
self.umount_goto();
|
||||||
|
// Reload files if sync
|
||||||
|
if self.browser.sync_browsing {
|
||||||
|
match self.tab {
|
||||||
|
FileExplorerTab::Remote => self.update_local_filelist(),
|
||||||
|
FileExplorerTab::Local => self.update_remote_filelist(),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
// Reload files
|
// Reload files
|
||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => self.update_local_filelist(),
|
FileExplorerTab::Local => self.update_local_filelist(),
|
||||||
@@ -682,6 +677,8 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::Remote => self.remote.sort_by(sorting),
|
FileExplorerTab::Remote => self.remote.sort_by(sorting),
|
||||||
_ => panic!("Found result doesn't support SORTING"),
|
_ => panic!("Found result doesn't support SORTING"),
|
||||||
}
|
}
|
||||||
|
// Update status bar
|
||||||
|
self.refresh_status_bar();
|
||||||
// Reload files
|
// Reload files
|
||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => self.update_local_filelist(),
|
FileExplorerTab::Local => self.update_local_filelist(),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ use tuirealm::components::{
|
|||||||
input::{Input, InputPropsBuilder},
|
input::{Input, InputPropsBuilder},
|
||||||
progress_bar::{ProgressBar, ProgressBarPropsBuilder},
|
progress_bar::{ProgressBar, ProgressBarPropsBuilder},
|
||||||
radio::{Radio, RadioPropsBuilder},
|
radio::{Radio, RadioPropsBuilder},
|
||||||
|
span::{Span, SpanPropsBuilder},
|
||||||
table::{Table, TablePropsBuilder},
|
table::{Table, TablePropsBuilder},
|
||||||
};
|
};
|
||||||
use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder};
|
use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder};
|
||||||
@@ -98,6 +99,13 @@ impl FileTransferActivity {
|
|||||||
.build(),
|
.build(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
// Mount status bar
|
||||||
|
self.view.mount(
|
||||||
|
super::COMPONENT_SPAN_STATUS_BAR,
|
||||||
|
Box::new(Span::new(SpanPropsBuilder::default().build())),
|
||||||
|
);
|
||||||
|
// Load process bar
|
||||||
|
self.refresh_status_bar();
|
||||||
// Update components
|
// Update components
|
||||||
let _ = self.update_local_filelist();
|
let _ = self.update_local_filelist();
|
||||||
let _ = self.update_remote_filelist();
|
let _ = self.update_remote_filelist();
|
||||||
@@ -131,6 +139,11 @@ impl FileTransferActivity {
|
|||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.split(chunks[0]);
|
.split(chunks[0]);
|
||||||
|
// Create log box chunks
|
||||||
|
let bottom_chunks = Layout::default()
|
||||||
|
.constraints([Constraint::Length(1), Constraint::Length(10)].as_ref())
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.split(chunks[1]);
|
||||||
// If width is unset in the storage, set width
|
// If width is unset in the storage, set width
|
||||||
if !store.isset(super::STORAGE_EXPLORER_WIDTH) {
|
if !store.isset(super::STORAGE_EXPLORER_WIDTH) {
|
||||||
store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize);
|
store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize);
|
||||||
@@ -159,8 +172,11 @@ impl FileTransferActivity {
|
|||||||
.view
|
.view
|
||||||
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]),
|
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]),
|
||||||
}
|
}
|
||||||
// Draw log box
|
// Draw log box and status bar
|
||||||
self.view.render(super::COMPONENT_LOG_BOX, f, chunks[1]);
|
self.view
|
||||||
|
.render(super::COMPONENT_LOG_BOX, f, bottom_chunks[1]);
|
||||||
|
self.view
|
||||||
|
.render(super::COMPONENT_SPAN_STATUS_BAR, f, bottom_chunks[0]);
|
||||||
// @! Draw popups
|
// @! Draw popups
|
||||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_COPY) {
|
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_COPY) {
|
||||||
if props.visible {
|
if props.visible {
|
||||||
@@ -793,6 +809,41 @@ impl FileTransferActivity {
|
|||||||
self.view.umount(super::COMPONENT_LIST_FILEINFO);
|
self.view.umount(super::COMPONENT_LIST_FILEINFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn refresh_status_bar(&mut self) {
|
||||||
|
let bar_spans: Vec<TextSpan> = vec![
|
||||||
|
TextSpanBuilder::new("Synchronized Browsing: ")
|
||||||
|
.with_foreground(Color::LightGreen)
|
||||||
|
.build(),
|
||||||
|
TextSpanBuilder::new(match self.browser.sync_browsing {
|
||||||
|
true => "ON ",
|
||||||
|
false => "OFF",
|
||||||
|
})
|
||||||
|
.with_foreground(Color::LightGreen)
|
||||||
|
.reversed()
|
||||||
|
.build(),
|
||||||
|
TextSpanBuilder::new(" Localhost file sorting: ")
|
||||||
|
.with_foreground(Color::LightYellow)
|
||||||
|
.build(),
|
||||||
|
TextSpanBuilder::new(Self::get_file_sorting_str(self.local.get_file_sorting()))
|
||||||
|
.with_foreground(Color::LightYellow)
|
||||||
|
.reversed()
|
||||||
|
.build(),
|
||||||
|
TextSpanBuilder::new(" Remote host file sorting: ")
|
||||||
|
.with_foreground(Color::LightBlue)
|
||||||
|
.build(),
|
||||||
|
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote.get_file_sorting()))
|
||||||
|
.with_foreground(Color::LightBlue)
|
||||||
|
.reversed()
|
||||||
|
.build(),
|
||||||
|
];
|
||||||
|
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR) {
|
||||||
|
self.view.update(
|
||||||
|
super::COMPONENT_SPAN_STATUS_BAR,
|
||||||
|
SpanPropsBuilder::from(props).with_spans(bar_spans).build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ### mount_help
|
/// ### mount_help
|
||||||
///
|
///
|
||||||
/// Mount help
|
/// Mount help
|
||||||
@@ -975,6 +1026,22 @@ impl FileTransferActivity {
|
|||||||
)
|
)
|
||||||
.add_col(TextSpan::from(" Go to parent directory"))
|
.add_col(TextSpan::from(" Go to parent directory"))
|
||||||
.add_row()
|
.add_row()
|
||||||
|
.add_col(
|
||||||
|
TextSpanBuilder::new("<X>")
|
||||||
|
.bold()
|
||||||
|
.with_foreground(Color::Cyan)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_col(TextSpan::from(" Execute shell command"))
|
||||||
|
.add_row()
|
||||||
|
.add_col(
|
||||||
|
TextSpanBuilder::new("<Y>")
|
||||||
|
.bold()
|
||||||
|
.with_foreground(Color::Cyan)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_col(TextSpan::from(" Toggle synchronized browsing"))
|
||||||
|
.add_row()
|
||||||
.add_col(
|
.add_col(
|
||||||
TextSpanBuilder::new("<DEL|E>")
|
TextSpanBuilder::new("<DEL|E>")
|
||||||
.bold()
|
.bold()
|
||||||
@@ -1002,4 +1069,13 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn umount_help(&mut self) {
|
pub(super) fn umount_help(&mut self) {
|
||||||
self.view.umount(super::COMPONENT_TEXT_HELP);
|
self.view.umount(super::COMPONENT_TEXT_HELP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_file_sorting_str(mode: FileSorting) -> &'static str {
|
||||||
|
match mode {
|
||||||
|
FileSorting::ByName => "By name",
|
||||||
|
FileSorting::ByCreationTime => "By creation time",
|
||||||
|
FileSorting::ByModifyTime => "By modify time",
|
||||||
|
FileSorting::BySize => "By size",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,11 +179,11 @@ pub const MSG_KEY_CHAR_X: Msg = Msg::OnKey(KeyEvent {
|
|||||||
code: KeyCode::Char('x'),
|
code: KeyCode::Char('x'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
pub const MSG_KEY_CHAR_Y: Msg = Msg::OnKey(KeyEvent {
|
pub const MSG_KEY_CHAR_Y: Msg = Msg::OnKey(KeyEvent {
|
||||||
code: KeyCode::Char('y'),
|
code: KeyCode::Char('y'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
pub const MSG_KEY_CHAR_Z: Msg = Msg::OnKey(KeyEvent {
|
pub const MSG_KEY_CHAR_Z: Msg = Msg::OnKey(KeyEvent {
|
||||||
code: KeyCode::Char('z'),
|
code: KeyCode::Char('z'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
|
|||||||
Reference in New Issue
Block a user