From c4907c8ce5fc478e5a54820270be6227f5caaef0 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 7 Jun 2021 22:28:52 +0200 Subject: [PATCH 01/12] Added open-rs to deps --- CHANGELOG.md | 1 + Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d28ab0..57b10e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Released on FIXME: ?? - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Dependencies: + - Added `open 1.7.0` - Updated `textwrap` to `0.14.0` - Updated `tui-realm` to `0.4.0` diff --git a/Cargo.lock b/Cargo.lock index 8b112b0..f4f3ea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,6 +757,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1711eb4b31ce4ad35b0f316d8dfba4fe5c7ad601c448446d84aae7a896627b20" +dependencies = [ + "which", + "winapi", +] + [[package]] name = "openssl" version = "0.10.34" @@ -1329,6 +1339,7 @@ dependencies = [ "lazy_static", "log", "magic-crypt", + "open", "path-slash", "pretty_assertions", "rand 0.8.3", diff --git a/Cargo.toml b/Cargo.toml index 275b633..dc46e83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ hostname = "0.3.1" lazy_static = "1.4.0" log = "0.4.14" magic-crypt = "3.1.7" +open = "1.7.0" rand = "0.8.3" regex = "1.5.4" rpassword = "5.0.1" From a8354ee38fa50302faf5941114213133365b0ba5 Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 10 Jun 2021 11:08:17 +0200 Subject: [PATCH 02/12] Open file on --- src/ui/activities/filetransfer/actions/mod.rs | 2 + .../activities/filetransfer/actions/open.rs | 102 ++++++++++++++++++ .../activities/filetransfer/actions/submit.rs | 94 ++++++++++++++++ src/ui/activities/filetransfer/mod.rs | 12 +++ src/ui/activities/filetransfer/session.rs | 53 ++++++--- src/ui/activities/filetransfer/update.rs | 4 +- src/ui/activities/filetransfer/view.rs | 6 +- 7 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 src/ui/activities/filetransfer/actions/open.rs create mode 100644 src/ui/activities/filetransfer/actions/submit.rs diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index 2e13003..e8091a4 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -37,8 +37,10 @@ pub(crate) mod exec; pub(crate) mod find; pub(crate) mod mkdir; pub(crate) mod newfile; +pub(crate) mod open; pub(crate) mod rename; pub(crate) mod save; +pub(crate) mod submit; pub(crate) enum SelectedEntry { One(FsEntry), diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs new file mode 100644 index 0000000..b697ef0 --- /dev/null +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -0,0 +1,102 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * 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. + */ +// deps +extern crate open; +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; + +impl FileTransferActivity { + /// ### action_open_local + /// + /// Open local file + pub(crate) fn action_open_local(&mut self, entry: FsEntry, open_with: Option) { + let real_entry: FsEntry = entry.get_realfile(); + if let FsEntry::File(file) = real_entry { + // Open file + let result = match open_with { + None => open::that(file.abs_path.as_path()), + Some(with) => open::with(file.abs_path.as_path(), with), + }; + // Log result + match result { + Ok(_) => self.log( + LogLevel::Info, + format!("Opened file `{}`", entry.get_abs_path().display(),), + ), + Err(err) => self.log( + LogLevel::Error, + format!( + "Failed to open filoe `{}`: {}", + entry.get_abs_path().display(), + err + ), + ), + } + } + } + + /// ### action_open_local + /// + /// Open remote file. The file is first downloaded to a temporary directory on localhost + pub(crate) fn action_open_remote(&mut self, entry: FsEntry, open_with: Option) { + let real_entry: FsEntry = entry.get_realfile(); + if let FsEntry::File(file) = real_entry { + // Download file + let tmp = match self.download_file_as_temp(&file) { + Ok(f) => f, + Err(err) => { + self.log( + LogLevel::Error, + format!("Could not open `{}`: {}", file.abs_path.display(), err), + ); + return; + } + }; + // Open file + let result = match open_with { + None => open::that(tmp.as_path()), + Some(with) => open::with(tmp.as_path(), with), + }; + // Log result + match result { + Ok(_) => self.log( + LogLevel::Info, + format!("Opened file `{}`", entry.get_abs_path().display()), + ), + Err(err) => self.log( + LogLevel::Error, + format!( + "Failed to open filoe `{}`: {}", + entry.get_abs_path().display(), + err + ), + ), + } + } + } +} diff --git a/src/ui/activities/filetransfer/actions/submit.rs b/src/ui/activities/filetransfer/actions/submit.rs new file mode 100644 index 0000000..a814942 --- /dev/null +++ b/src/ui/activities/filetransfer/actions/submit.rs @@ -0,0 +1,94 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * 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. + */ +// locals +use super::{FileTransferActivity, FsEntry}; + +enum SubmitAction { + ChangeDir, + OpenFile, +} + +impl FileTransferActivity { + /// ### action_submit_local + /// + /// Decides which action to perform on submit for local explorer + /// Return true whether the directory changed + pub(crate) fn action_submit_local(&mut self, entry: FsEntry) -> bool { + let action: SubmitAction = match &entry { + FsEntry::Directory(_) => SubmitAction::ChangeDir, + FsEntry::File(file) => { + match &file.symlink { + Some(symlink_entry) => { + // If symlink and is directory, point to symlink + match &**symlink_entry { + FsEntry::Directory(_) => SubmitAction::ChangeDir, + _ => SubmitAction::OpenFile, + } + } + None => SubmitAction::OpenFile, + } + } + }; + match action { + SubmitAction::ChangeDir => self.action_enter_local_dir(entry, false), + SubmitAction::OpenFile => { + self.action_open_local(entry, None); + false + } + } + } + + /// ### action_submit_remote + /// + /// Decides which action to perform on submit for remote explorer + /// Return true whether the directory changed + pub(crate) fn action_submit_remote(&mut self, entry: FsEntry) -> bool { + let action: SubmitAction = match &entry { + FsEntry::Directory(_) => SubmitAction::ChangeDir, + FsEntry::File(file) => { + match &file.symlink { + Some(symlink_entry) => { + // If symlink and is directory, point to symlink + match &**symlink_entry { + FsEntry::Directory(_) => SubmitAction::ChangeDir, + _ => SubmitAction::OpenFile, + } + } + None => SubmitAction::OpenFile, + } + } + }; + match action { + SubmitAction::ChangeDir => self.action_enter_remote_dir(entry, false), + SubmitAction::OpenFile => { + self.action_open_remote(entry, None); + false + } + } + } +} diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 5cedb60..6a73d07 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -58,6 +58,7 @@ use chrono::{DateTime, Local}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use std::collections::VecDeque; use std::path::PathBuf; +use tempfile::TempDir; use tuirealm::View; // -- Storage keys @@ -134,6 +135,7 @@ pub struct FileTransferActivity { browser: Browser, // Browser log_records: VecDeque, // Log records transfer: TransferStates, // Transfer states + cache: Option, // Temporary directory where to store stuff } impl FileTransferActivity { @@ -160,6 +162,10 @@ impl FileTransferActivity { browser: Browser::new(config_client.as_ref()), log_records: VecDeque::with_capacity(256), // 256 events is enough I guess transfer: TransferStates::default(), + cache: match TempDir::new() { + Ok(d) => Some(d), + Err(_) => None, + }, } } @@ -279,6 +285,12 @@ impl Activity for FileTransferActivity { /// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity. /// This function must be called once before terminating the activity. fn on_destroy(&mut self) -> Option { + // Destroy cache + if let Some(cache) = self.cache.take() { + if let Err(err) = cache.close() { + error!("Failed to delete cache: {}", err); + } + } // Disable raw mode if let Err(err) = disable_raw_mode() { error!("Failed to disable raw mode: {}", err); diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index d9aa06d..74b01e7 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -844,38 +844,32 @@ impl FileTransferActivity { /// Edit file on remote host pub(super) fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> { // Create temp file - let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { - Ok(f) => f, - Err(err) => { - return Err(format!("Could not create temporary file: {}", err)); - } + let tmpfile: PathBuf = match self.download_file_as_temp(file) { + Ok(p) => p, + Err(err) => return Err(err), }; - // Download file - if let Err(err) = self.filetransfer_recv_file(tmpfile.path(), file, file.name.clone()) { - return Err(format!("Could not open file {}: {}", file.name, err)); - } // Get current file modification time - let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) { + let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) { Ok(e) => e.get_last_change_time(), Err(err) => { return Err(format!( "Could not stat \"{}\": {}", - tmpfile.path().display(), + tmpfile.as_path().display(), err )) } }; // Edit file - if let Err(err) = self.edit_local_file(tmpfile.path()) { + if let Err(err) = self.edit_local_file(tmpfile.as_path()) { return Err(err); } // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) { Ok(e) => e, Err(err) => { return Err(format!( "Could not stat \"{}\": {}", - tmpfile.path().display(), + tmpfile.as_path().display(), err )) } @@ -891,12 +885,12 @@ impl FileTransferActivity { ), ); // Get local fs entry - let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) { Ok(e) => e, Err(err) => { return Err(format!( "Could not stat \"{}\": {}", - tmpfile.path().display(), + tmpfile.as_path().display(), err )) } @@ -1035,6 +1029,33 @@ impl FileTransferActivity { } } + /// ### download_file_as_temp + /// + /// Download provided file as a temporary file + pub(super) fn download_file_as_temp(&mut self, file: &FsFile) -> Result { + let tmpfile: PathBuf = match self.cache.as_ref() { + Some(cache) => { + let mut p: PathBuf = cache.path().to_path_buf(); + p.push(file.name.as_str()); + p + } + None => { + return Err(String::from( + "Could not create tempfile: cache not available", + )) + } + }; + // Download file + match self.filetransfer_recv_file(tmpfile.as_path(), file, file.name.clone()) { + Err(err) => Err(format!( + "Could not download {} to temporary file: {}", + file.abs_path.display(), + err + )), + Ok(()) => Ok(tmpfile), + } + } + // -- transfer sizes /// ### get_total_transfer_size_local diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 7f63f2d..4d9cf02 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -87,7 +87,7 @@ impl Update for FileTransferActivity { entry = Some(e.clone()); } if let Some(entry) = entry { - if self.action_enter_local_dir(entry, false) { + if self.action_submit_local(entry) { // Update file list if sync if self.browser.sync_browsing { let _ = self.update_remote_filelist(); @@ -150,7 +150,7 @@ impl Update for FileTransferActivity { entry = Some(e.clone()); } if let Some(entry) = entry { - if self.action_enter_remote_dir(entry, false) { + if self.action_submit_remote(entry) { // Update file list if sync if self.browser.sync_browsing { let _ = self.update_local_filelist(); diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index bab6290..9d654e9 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -934,7 +934,7 @@ impl FileTransferActivity { .with_foreground(Color::Cyan) .build(), ) - .add_col(TextSpan::from(" Enter directory")) + .add_col(TextSpan::from(" Enter directory / Open file")) .add_row() .add_col( TextSpanBuilder::new("") @@ -1030,7 +1030,9 @@ impl FileTransferActivity { .with_foreground(Color::Cyan) .build(), ) - .add_col(TextSpan::from(" Open text file")) + .add_col(TextSpan::from( + " Open text file with preferred editor", + )) .add_row() .add_col( TextSpanBuilder::new("") From 4e50038b4167af03b8fd2978274c7fe0d1cc473f Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 10 Jun 2021 12:14:09 +0200 Subject: [PATCH 03/12] Open file with --- .../activities/filetransfer/actions/find.rs | 45 +++++++++++++++++++ .../activities/filetransfer/actions/open.rs | 36 +++++++++++++-- .../activities/filetransfer/actions/submit.rs | 4 +- src/ui/activities/filetransfer/mod.rs | 1 + src/ui/activities/filetransfer/update.rs | 32 ++++++++++--- src/ui/activities/filetransfer/view.rs | 25 +++++++++++ src/ui/keymap.rs | 2 +- 7 files changed, 134 insertions(+), 11 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 7e0a97d..6e8ead8 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -140,4 +140,49 @@ impl FileTransferActivity { } } } + + pub(crate) fn action_find_open(&mut self) { + match self.get_found_selected_entries() { + SelectedEntry::One(entry) => { + // Open file + self.open_found_file(&entry, None); + } + SelectedEntry::Many(entries) => { + // Iter files + for entry in entries.iter() { + // Open file + self.open_found_file(entry, None); + } + } + SelectedEntry::None => {} + } + } + + pub(crate) fn action_find_open_with(&mut self, with: &str) { + match self.get_found_selected_entries() { + SelectedEntry::One(entry) => { + // Open file + self.open_found_file(&entry, Some(with)); + } + SelectedEntry::Many(entries) => { + // Iter files + for entry in entries.iter() { + // Open file + self.open_found_file(entry, Some(with)); + } + } + SelectedEntry::None => {} + } + } + + fn open_found_file(&mut self, entry: &FsEntry, with: Option<&str>) { + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + self.action_open_local(entry, with); + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + self.action_open_remote(entry, with); + } + } + } } diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index b697ef0..00b692a 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -28,13 +28,13 @@ // deps extern crate open; // locals -use super::{FileTransferActivity, FsEntry, LogLevel}; +use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; impl FileTransferActivity { /// ### action_open_local /// /// Open local file - pub(crate) fn action_open_local(&mut self, entry: FsEntry, open_with: Option) { + pub(crate) fn action_open_local(&mut self, entry: &FsEntry, open_with: Option<&str>) { let real_entry: FsEntry = entry.get_realfile(); if let FsEntry::File(file) = real_entry { // Open file @@ -63,7 +63,7 @@ impl FileTransferActivity { /// ### action_open_local /// /// Open remote file. The file is first downloaded to a temporary directory on localhost - pub(crate) fn action_open_remote(&mut self, entry: FsEntry, open_with: Option) { + pub(crate) fn action_open_remote(&mut self, entry: &FsEntry, open_with: Option<&str>) { let real_entry: FsEntry = entry.get_realfile(); if let FsEntry::File(file) = real_entry { // Download file @@ -99,4 +99,34 @@ impl FileTransferActivity { } } } + + /// ### action_local_open_with + /// + /// Open selected file with provided application + pub(crate) fn action_local_open_with(&mut self, with: &str) { + let entries: Vec = match self.get_local_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Many(entries) => entries, + SelectedEntry::None => vec![], + }; + // Open all entries + for entry in entries.iter() { + self.action_open_local(entry, Some(with)); + } + } + + /// ### action_remote_open_with + /// + /// Open selected file with provided application + pub(crate) fn action_remote_open_with(&mut self, with: &str) { + let entries: Vec = match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Many(entries) => entries, + SelectedEntry::None => vec![], + }; + // Open all entries + for entry in entries.iter() { + self.action_open_remote(entry, Some(with)); + } + } } diff --git a/src/ui/activities/filetransfer/actions/submit.rs b/src/ui/activities/filetransfer/actions/submit.rs index a814942..712eb16 100644 --- a/src/ui/activities/filetransfer/actions/submit.rs +++ b/src/ui/activities/filetransfer/actions/submit.rs @@ -57,7 +57,7 @@ impl FileTransferActivity { match action { SubmitAction::ChangeDir => self.action_enter_local_dir(entry, false), SubmitAction::OpenFile => { - self.action_open_local(entry, None); + self.action_open_local(&entry, None); false } } @@ -86,7 +86,7 @@ impl FileTransferActivity { match action { SubmitAction::ChangeDir => self.action_enter_remote_dir(entry, false), SubmitAction::OpenFile => { - self.action_open_remote(entry, None); + self.action_open_remote(&entry, None); false } } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 6a73d07..61d4bb2 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -83,6 +83,7 @@ const COMPONENT_INPUT_FIND: &str = "INPUT_FIND"; const COMPONENT_INPUT_GOTO: &str = "INPUT_GOTO"; const COMPONENT_INPUT_MKDIR: &str = "INPUT_MKDIR"; const COMPONENT_INPUT_NEWFILE: &str = "INPUT_NEWFILE"; +const COMPONENT_INPUT_OPEN_WITH: &str = "INPUT_OPEN_WITH"; const COMPONENT_INPUT_RENAME: &str = "INPUT_RENAME"; const COMPONENT_INPUT_SAVEAS: &str = "INPUT_SAVEAS"; const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE"; diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 4d9cf02..decc4fc 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -32,11 +32,11 @@ use super::{ actions::SelectedEntry, browser::FileExplorerTab, FileTransferActivity, LogLevel, 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_RENAME, COMPONENT_INPUT_SAVEAS, - COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR_FULL, - COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT, - COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, - COMPONENT_TEXT_HELP, + COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_OPEN_WITH, + COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, + COMPONENT_PROGRESS_BAR_FULL, COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, + COMPONENT_RADIO_DISCONNECT, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SORTING, + COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP, }; use crate::fs::explorer::FileSorting; use crate::fs::FsEntry; @@ -266,6 +266,12 @@ impl Update for FileTransferActivity { self.mount_saveas(); None } + (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_W) + | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_W) + | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_W) => { + self.mount_openwith(); + None + } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_X) | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_X) => { // Mount exec @@ -484,6 +490,22 @@ impl Update for FileTransferActivity { _ => None, } } + // -- open with + (COMPONENT_INPUT_OPEN_WITH, &MSG_KEY_ESC) => { + self.umount_openwith(); + None + } + (COMPONENT_INPUT_OPEN_WITH, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { + match self.browser.tab() { + FileExplorerTab::Local => self.action_local_open_with(input), + FileExplorerTab::Remote => self.action_remote_open_with(input), + FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { + self.action_find_open_with(input) + } + } + self.umount_openwith(); + None + } // -- rename (COMPONENT_INPUT_RENAME, &MSG_KEY_ESC) => { self.umount_rename(); diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index 9d654e9..e08d179 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -215,6 +215,14 @@ impl FileTransferActivity { self.view.render(super::COMPONENT_INPUT_NEWFILE, f, popup); } } + if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_OPEN_WITH) { + if props.visible { + let popup = draw_area_in(f.size(), 40, 10); + f.render_widget(Clear, popup); + // make popup + self.view.render(super::COMPONENT_INPUT_OPEN_WITH, f, popup); + } + } if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_RENAME) { if props.visible { let popup = draw_area_in(f.size(), 40, 10); @@ -593,6 +601,23 @@ impl FileTransferActivity { self.view.umount(super::COMPONENT_INPUT_NEWFILE); } + pub(super) fn mount_openwith(&mut self) { + self.view.mount( + super::COMPONENT_INPUT_OPEN_WITH, + Box::new(Input::new( + InputPropsBuilder::default() + .with_borders(Borders::ALL, BorderType::Rounded, Color::White) + .with_label(String::from("Open file with...")) + .build(), + )), + ); + self.view.active(super::COMPONENT_INPUT_OPEN_WITH); + } + + pub(super) fn umount_openwith(&mut self) { + self.view.umount(super::COMPONENT_INPUT_OPEN_WITH); + } + pub(super) fn mount_rename(&mut self) { self.view.mount( super::COMPONENT_INPUT_RENAME, diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 0540013..8a6fee4 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -170,11 +170,11 @@ pub const MSG_KEY_CHAR_V: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('v'), modifiers: KeyModifiers::NONE, }); +*/ pub const MSG_KEY_CHAR_W: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('w'), modifiers: KeyModifiers::NONE, }); -*/ pub const MSG_KEY_CHAR_X: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('x'), modifiers: KeyModifiers::NONE, From cd3fc15bcbb5c6c5368261ba96c1b3a870c667ea Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 10 Jun 2021 12:16:48 +0200 Subject: [PATCH 04/12] Fmt symlink with len 48 in found dialog --- src/ui/activities/filetransfer/lib/browser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/activities/filetransfer/lib/browser.rs b/src/ui/activities/filetransfer/lib/browser.rs index 5fe8147..2726082 100644 --- a/src/ui/activities/filetransfer/lib/browser.rs +++ b/src/ui/activities/filetransfer/lib/browser.rs @@ -171,7 +171,7 @@ impl Browser { .with_group_dirs(Some(GroupDirs::First)) .with_hidden_files(true) .with_stack_size(0) - .with_formatter(Some("{NAME} {SYMLINK}")) + .with_formatter(Some("{NAME:32} {SYMLINK}")) .build() } } From 541a9a55b5c6984b191eea29853ec30725c7defd Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 10 Jun 2021 20:55:12 +0200 Subject: [PATCH 05/12] Handle shift enter (file list) --- src/ui/components/file_list.rs | 27 ++++++++++++++++++++++----- src/ui/keymap.rs | 4 ++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index ec21799..c0ca43a 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -393,10 +393,10 @@ impl Component for FileList { self.states.toggle_file(self.states.list_index()); Msg::None } - KeyCode::Enter => { - // Report event - Msg::OnSubmit(self.get_state()) - } + KeyCode::Enter => match key.modifiers.intersects(KeyModifiers::SHIFT) { + false => Msg::OnSubmit(self.get_state()), + true => Msg::OnKey(key), + }, _ => { // Return key event to activity Msg::OnKey(key) @@ -449,7 +449,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - use tuirealm::event::KeyEvent; + use tuirealm::event::{KeyEvent, KeyModifiers}; #[test] fn test_ui_components_file_list_states() { @@ -616,6 +616,14 @@ mod tests { component.on(Event::Key(KeyEvent::from(KeyCode::Enter))), Msg::OnSubmit(Payload::One(Value::Usize(0))) ); + // Enter shift + assert_eq!( + component.on(Event::Key(KeyEvent::new( + KeyCode::Enter, + KeyModifiers::SHIFT + ))), + Msg::OnKey(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)) + ); // On key assert_eq!( component.on(Event::Key(KeyEvent::from(KeyCode::Backspace))), @@ -626,6 +634,15 @@ mod tests { component.on(Event::Key(KeyEvent::from(KeyCode::Char('a')))), Msg::OnKey(KeyEvent::from(KeyCode::Char('a'))) ); + // Ctrl + a + assert_eq!( + component.on(Event::Key(KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::CONTROL + ))), + Msg::None + ); + assert_eq!(component.states.selected.len(), component.states.list_len()); } #[test] diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 8a6fee4..8ea32e7 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -34,6 +34,10 @@ pub const MSG_KEY_ENTER: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, }); +pub const MSG_KEY_SHIFT_ENTER: Msg = Msg::OnKey(KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::SHIFT, +}); pub const MSG_KEY_ESC: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::NONE, From d981b77ed759608b7d2ba8df58aac946b0c04aa9 Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 10 Jun 2021 22:36:29 +0200 Subject: [PATCH 06/12] Working on open --- .../activities/filetransfer/actions/find.rs | 4 +- .../activities/filetransfer/actions/open.rs | 120 ++++++++++++------ .../activities/filetransfer/actions/submit.rs | 20 +-- src/ui/activities/filetransfer/mod.rs | 16 +++ src/ui/activities/filetransfer/update.rs | 12 ++ 5 files changed, 115 insertions(+), 57 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index 6e8ead8..d229d8e 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -178,10 +178,10 @@ impl FileTransferActivity { fn open_found_file(&mut self, entry: &FsEntry, with: Option<&str>) { match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { - self.action_open_local(entry, with); + self.action_open_local_file(entry, with); } FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - self.action_open_remote(entry, with); + self.action_open_remote_file(entry, with); } } } diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index 00b692a..c98796a 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -29,54 +29,90 @@ extern crate open; // locals use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; +// ext +use std::path::PathBuf; impl FileTransferActivity { /// ### action_open_local /// /// Open local file - pub(crate) fn action_open_local(&mut self, entry: &FsEntry, open_with: Option<&str>) { - let real_entry: FsEntry = entry.get_realfile(); - if let FsEntry::File(file) = real_entry { - // Open file - let result = match open_with { - None => open::that(file.abs_path.as_path()), - Some(with) => open::with(file.abs_path.as_path(), with), - }; - // Log result - match result { - Ok(_) => self.log( - LogLevel::Info, - format!("Opened file `{}`", entry.get_abs_path().display(),), + pub(crate) fn action_open_local(&mut self) { + let entries: Vec = match self.get_local_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Many(entries) => entries, + SelectedEntry::None => vec![], + }; + entries + .iter() + .for_each(|x| self.action_open_local_file(x, None)); + } + + /// ### action_open_remote + /// + /// Open local file + pub(crate) fn action_open_remote(&mut self) { + let entries: Vec = match self.get_remote_selected_entries() { + SelectedEntry::One(entry) => vec![entry], + SelectedEntry::Many(entries) => entries, + SelectedEntry::None => vec![], + }; + entries + .iter() + .for_each(|x| self.action_open_remote_file(x, None)); + } + + /// ### action_open_local_file + /// + /// Perform open lopcal file + pub(crate) fn action_open_local_file(&mut self, entry: &FsEntry, open_with: Option<&str>) { + let entry: FsEntry = entry.get_realfile(); + // Open file + let result = match open_with { + None => open::that(entry.get_abs_path().as_path()), + Some(with) => open::with(entry.get_abs_path().as_path(), with), + }; + // Log result + match result { + Ok(_) => self.log( + LogLevel::Info, + format!("Opened file `{}`", entry.get_abs_path().display(),), + ), + Err(err) => self.log( + LogLevel::Error, + format!( + "Failed to open filoe `{}`: {}", + entry.get_abs_path().display(), + err ), - Err(err) => self.log( - LogLevel::Error, - format!( - "Failed to open filoe `{}`: {}", - entry.get_abs_path().display(), - err - ), - ), - } + ), } } /// ### action_open_local /// /// Open remote file. The file is first downloaded to a temporary directory on localhost - pub(crate) fn action_open_remote(&mut self, entry: &FsEntry, open_with: Option<&str>) { - let real_entry: FsEntry = entry.get_realfile(); - if let FsEntry::File(file) = real_entry { - // Download file - let tmp = match self.download_file_as_temp(&file) { - Ok(f) => f, - Err(err) => { - self.log( - LogLevel::Error, - format!("Could not open `{}`: {}", file.abs_path.display(), err), - ); - return; - } - }; + pub(crate) fn action_open_remote_file(&mut self, entry: &FsEntry, open_with: Option<&str>) { + let entry: FsEntry = entry.get_realfile(); + // Download file + let tmpfile: String = match self.get_cache_tmp_name(entry.get_name()) { + None => { + self.log(LogLevel::Error, String::from("Could not create tempdir")); + return; + } + Some(p) => p, + }; + let cache: PathBuf = match self.cache.as_ref() { + None => { + self.log(LogLevel::Error, String::from("Could not create tempdir")); + return; + } + Some(p) => p.path().to_path_buf(), + }; + self.filetransfer_recv(entry, cache.as_path(), Some(tmpfile.clone())); + // Make file and open if file exists + let mut tmp: PathBuf = cache; + tmp.push(tmpfile.as_str()); + if tmp.exists() { // Open file let result = match open_with { None => open::that(tmp.as_path()), @@ -110,9 +146,9 @@ impl FileTransferActivity { SelectedEntry::None => vec![], }; // Open all entries - for entry in entries.iter() { - self.action_open_local(entry, Some(with)); - } + entries + .iter() + .for_each(|x| self.action_open_local_file(x, Some(with))); } /// ### action_remote_open_with @@ -125,8 +161,8 @@ impl FileTransferActivity { SelectedEntry::None => vec![], }; // Open all entries - for entry in entries.iter() { - self.action_open_remote(entry, Some(with)); - } + entries + .iter() + .for_each(|x| self.action_open_remote_file(x, Some(with))); } } diff --git a/src/ui/activities/filetransfer/actions/submit.rs b/src/ui/activities/filetransfer/actions/submit.rs index 712eb16..ea034a7 100644 --- a/src/ui/activities/filetransfer/actions/submit.rs +++ b/src/ui/activities/filetransfer/actions/submit.rs @@ -30,7 +30,7 @@ use super::{FileTransferActivity, FsEntry}; enum SubmitAction { ChangeDir, - OpenFile, + None, } impl FileTransferActivity { @@ -47,19 +47,16 @@ impl FileTransferActivity { // If symlink and is directory, point to symlink match &**symlink_entry { FsEntry::Directory(_) => SubmitAction::ChangeDir, - _ => SubmitAction::OpenFile, + _ => SubmitAction::None, } } - None => SubmitAction::OpenFile, + None => SubmitAction::None, } } }; match action { SubmitAction::ChangeDir => self.action_enter_local_dir(entry, false), - SubmitAction::OpenFile => { - self.action_open_local(&entry, None); - false - } + SubmitAction::None => false, } } @@ -76,19 +73,16 @@ impl FileTransferActivity { // If symlink and is directory, point to symlink match &**symlink_entry { FsEntry::Directory(_) => SubmitAction::ChangeDir, - _ => SubmitAction::OpenFile, + _ => SubmitAction::None, } } - None => SubmitAction::OpenFile, + None => SubmitAction::None, } } }; match action { SubmitAction::ChangeDir => self.action_enter_remote_dir(entry, false), - SubmitAction::OpenFile => { - self.action_open_remote(&entry, None); - false - } + SubmitAction::None => false, } } } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 61d4bb2..00437eb 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -193,6 +193,22 @@ impl FileTransferActivity { pub(crate) fn found_mut(&mut self) -> Option<&mut FileExplorer> { self.browser.found_mut() } + + /// ### get_cache_tmp_name + /// + /// Get file name for a file in cache + pub(crate) fn get_cache_tmp_name(&self, name: &str) -> Option { + self.cache.as_ref().map(|_| { + format!( + "{}-{}", + name, + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + ) + }) + } } /** diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index decc4fc..4ab9a4d 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -211,6 +211,18 @@ impl Update for FileTransferActivity { self.update_remote_filelist() } // -- common explorer keys + (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SHIFT_ENTER) + | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SHIFT_ENTER) + | (COMPONENT_EXPLORER_FIND, &MSG_KEY_SHIFT_ENTER) => { + match self.browser.tab() { + FileExplorerTab::Local => self.action_open_local(), + FileExplorerTab::Remote => self.action_open_remote(), + FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { + self.action_find_open() + } + } + None + } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_B) | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_B) => { // Show sorting file From 90afe204b1c1a324f607673c49326ba82bb37fb1 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 11 Jun 2021 09:52:50 +0200 Subject: [PATCH 07/12] Open files with ; fixed cache file names --- .../activities/filetransfer/actions/open.rs | 28 ++++++++----------- src/ui/activities/filetransfer/mod.rs | 10 +++++-- src/ui/activities/filetransfer/update.rs | 26 +++++++++-------- src/ui/activities/filetransfer/view.rs | 22 ++++++++++++++- src/ui/components/file_list.rs | 13 +-------- src/ui/keymap.rs | 8 +----- 6 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index c98796a..9a72f24 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -94,13 +94,14 @@ impl FileTransferActivity { pub(crate) fn action_open_remote_file(&mut self, entry: &FsEntry, open_with: Option<&str>) { let entry: FsEntry = entry.get_realfile(); // Download file - let tmpfile: String = match self.get_cache_tmp_name(entry.get_name()) { - None => { - self.log(LogLevel::Error, String::from("Could not create tempdir")); - return; - } - Some(p) => p, - }; + let tmpfile: String = + match self.get_cache_tmp_name(entry.get_name(), entry.get_ftype().as_deref()) { + None => { + self.log(LogLevel::Error, String::from("Could not create tempdir")); + return; + } + Some(p) => p, + }; let cache: PathBuf = match self.cache.as_ref() { None => { self.log(LogLevel::Error, String::from("Could not create tempdir")); @@ -108,7 +109,7 @@ impl FileTransferActivity { } Some(p) => p.path().to_path_buf(), }; - self.filetransfer_recv(entry, cache.as_path(), Some(tmpfile.clone())); + self.filetransfer_recv(&entry, cache.as_path(), Some(tmpfile.clone())); // Make file and open if file exists let mut tmp: PathBuf = cache; tmp.push(tmpfile.as_str()); @@ -120,17 +121,10 @@ impl FileTransferActivity { }; // Log result match result { - Ok(_) => self.log( - LogLevel::Info, - format!("Opened file `{}`", entry.get_abs_path().display()), - ), + Ok(_) => self.log(LogLevel::Info, format!("Opened file `{}`", tmp.display())), Err(err) => self.log( LogLevel::Error, - format!( - "Failed to open filoe `{}`: {}", - entry.get_abs_path().display(), - err - ), + format!("Failed to open filoe `{}`: {}", tmp.display(), err), ), } } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 00437eb..5bfa149 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -197,16 +197,20 @@ impl FileTransferActivity { /// ### get_cache_tmp_name /// /// Get file name for a file in cache - pub(crate) fn get_cache_tmp_name(&self, name: &str) -> Option { + pub(crate) fn get_cache_tmp_name(&self, name: &str, file_type: Option<&str>) -> Option { self.cache.as_ref().map(|_| { - format!( + let base: String = format!( "{}-{}", name, std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() - ) + ); + match file_type { + None => base, + Some(file_type) => format!("{}.{}", base, file_type), + } }) } } diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 4ab9a4d..0463805 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -211,18 +211,6 @@ impl Update for FileTransferActivity { self.update_remote_filelist() } // -- common explorer keys - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SHIFT_ENTER) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SHIFT_ENTER) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_SHIFT_ENTER) => { - match self.browser.tab() { - FileExplorerTab::Local => self.action_open_local(), - FileExplorerTab::Remote => self.action_open_remote(), - FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { - self.action_find_open() - } - } - None - } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_B) | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_B) => { // Show sorting file @@ -278,9 +266,23 @@ impl Update for FileTransferActivity { self.mount_saveas(); None } + (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_V) + | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_V) + | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_V) => { + // View + match self.browser.tab() { + FileExplorerTab::Local => self.action_open_local(), + FileExplorerTab::Remote => self.action_open_remote(), + FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { + self.action_find_open() + } + } + None + } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_W) | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_W) | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_W) => { + // Open with self.mount_openwith(); None } diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index e08d179..11b5c83 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -959,7 +959,7 @@ impl FileTransferActivity { .with_foreground(Color::Cyan) .build(), ) - .add_col(TextSpan::from(" Enter directory / Open file")) + .add_col(TextSpan::from(" Enter directory")) .add_row() .add_col( TextSpanBuilder::new("") @@ -1091,6 +1091,26 @@ impl FileTransferActivity { ) .add_col(TextSpan::from(" Go to parent directory")) .add_row() + .add_col( + TextSpanBuilder::new("") + .bold() + .with_foreground(Color::Cyan) + .build(), + ) + .add_col(TextSpan::from( + " Open file with default application for file type", + )) + .add_row() + .add_col( + TextSpanBuilder::new("") + .bold() + .with_foreground(Color::Cyan) + .build(), + ) + .add_col(TextSpan::from( + " Open file with specified application", + )) + .add_row() .add_col( TextSpanBuilder::new("") .bold() diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index c0ca43a..1c512a9 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -393,10 +393,7 @@ impl Component for FileList { self.states.toggle_file(self.states.list_index()); Msg::None } - KeyCode::Enter => match key.modifiers.intersects(KeyModifiers::SHIFT) { - false => Msg::OnSubmit(self.get_state()), - true => Msg::OnKey(key), - }, + KeyCode::Enter => Msg::OnSubmit(self.get_state()), _ => { // Return key event to activity Msg::OnKey(key) @@ -616,14 +613,6 @@ mod tests { component.on(Event::Key(KeyEvent::from(KeyCode::Enter))), Msg::OnSubmit(Payload::One(Value::Usize(0))) ); - // Enter shift - assert_eq!( - component.on(Event::Key(KeyEvent::new( - KeyCode::Enter, - KeyModifiers::SHIFT - ))), - Msg::OnKey(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)) - ); // On key assert_eq!( component.on(Event::Key(KeyEvent::from(KeyCode::Backspace))), diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 8ea32e7..99bc124 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -34,10 +34,6 @@ pub const MSG_KEY_ENTER: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, }); -pub const MSG_KEY_SHIFT_ENTER: Msg = Msg::OnKey(KeyEvent { - code: KeyCode::Enter, - modifiers: KeyModifiers::SHIFT, -}); pub const MSG_KEY_ESC: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::NONE, @@ -128,7 +124,7 @@ pub const MSG_KEY_CHAR_L: Msg = Msg::OnKey(KeyEvent { modifiers: KeyModifiers::NONE, }); /* -pub const MSG_KEY_CHAR_M: Msg = Msg::OnKey(KeyEvent { +pub const MSG_KEY_CHAR_M: Msg = Msg::OnKey(KeyEvent { NOTE: used for mark code: KeyCode::Char('m'), modifiers: KeyModifiers::NONE, }); @@ -169,12 +165,10 @@ pub const MSG_KEY_CHAR_U: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('u'), modifiers: KeyModifiers::NONE, }); -/* pub const MSG_KEY_CHAR_V: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('v'), modifiers: KeyModifiers::NONE, }); -*/ pub const MSG_KEY_CHAR_W: Msg = Msg::OnKey(KeyEvent { code: KeyCode::Char('w'), modifiers: KeyModifiers::NONE, From bc50328006196496fd3ebfbbc0eeb522fa119727 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 11 Jun 2021 13:01:28 +0200 Subject: [PATCH 08/12] open-rs docs --- README.md | 15 +++++++++++++++ docs/man.md | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/README.md b/README.md index d51afeb..a0dda99 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,20 @@ while if you're a Windows user, you can install termscp with [Chocolatey](https: For more information or other platforms, please visit [veeso.github.io](https://veeso.github.io/termscp/#get-started) to view all installation methods. +### Soft Requirements ✔️ + +These requirements are not forcely required to run termscp, but to enjoy all of its features + +- **Linux** users + - To **open** files via `V` (at least one of these) + - *xdg-open* + - *gio* + - *gnome-open* + - *kde-open* +- **WSL** users + - To **open** files via `V` (at least one of these) + - [wslu](https://github.com/wslutilities/wslu) + --- ## Buy me a coffee ☕ @@ -134,6 +148,7 @@ termscp is powered by these aweseome projects: - [crossterm](https://github.com/crossterm-rs/crossterm) - [edit](https://github.com/milkey-mouse/edit) - [keyring-rs](https://github.com/hwchen/keyring-rs) +- [open-rs](https://github.com/Byron/open-rs) - [rpassword](https://github.com/conradkleinespel/rpassword) - [rust-ftp](https://github.com/mattnenterprise/rust-ftp) - [ssh2-rs](https://github.com/alexcrichton/ssh2-rs) diff --git a/docs/man.md b/docs/man.md index aa49c00..b1a9bf1 100644 --- a/docs/man.md +++ b/docs/man.md @@ -105,6 +105,8 @@ In order to change panel you need to type `` to move the remote explorer p | `` | Rename file | Rename | | `` | Save file as... | Save | | `` | Go to parent directory | Upper | +| `` | Open file with default program for filetype | View | +| `` | Open file with provided program | With | | `` | Execute a command | eXecute | | `` | Toggle synchronized browsing | sYnc | | `` | Delete file | | @@ -130,6 +132,20 @@ This means that whenever you'll change the working directory on one panel, the s *Warning*: at the moment, whenever you try to access an unexisting directory, you won't be prompted to create it. This might change in a future update. +### Open and Open With 🚪 + +Open and open with commands are powered by [open-rs](https://docs.rs/crate/open/1.7.0). +When opening files with View command (``), the system default application for the file type will be used. To do so, the default operting system service will be used, so be sure to have at least one of these installed on your system: + +- **Windows** users: you don't have to worry about it, since the crate will use the `start` command. +- **MacOS** users: you don't have to worry either, since the crate will use `open`, which is already installed on your system. +- **Linux** users: one of these should be installed + - *xdg-open* + - *gio* + - *gnome-open* + - *kde-open* +- **WSL** users: *wslview* is required, you must install [wslu](https://github.com/wslutilities/wslu). + --- ## Bookmarks ⭐ From fe494c52b18cfb9e360f3de2e90ff3aaea6906e1 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 11 Jun 2021 14:46:20 +0200 Subject: [PATCH 09/12] Clear screen once opened file, to prevent crap on stderr --- .../activities/filetransfer/actions/open.rs | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index 9a72f24..ecd8735 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -30,7 +30,7 @@ extern crate open; // locals use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry}; // ext -use std::path::PathBuf; +use std::path::{Path, PathBuf}; impl FileTransferActivity { /// ### action_open_local @@ -66,26 +66,7 @@ impl FileTransferActivity { /// Perform open lopcal file pub(crate) fn action_open_local_file(&mut self, entry: &FsEntry, open_with: Option<&str>) { let entry: FsEntry = entry.get_realfile(); - // Open file - let result = match open_with { - None => open::that(entry.get_abs_path().as_path()), - Some(with) => open::with(entry.get_abs_path().as_path(), with), - }; - // Log result - match result { - Ok(_) => self.log( - LogLevel::Info, - format!("Opened file `{}`", entry.get_abs_path().display(),), - ), - Err(err) => self.log( - LogLevel::Error, - format!( - "Failed to open filoe `{}`: {}", - entry.get_abs_path().display(), - err - ), - ), - } + self.open_path_with(entry.get_abs_path().as_path(), open_with); } /// ### action_open_local @@ -114,19 +95,7 @@ impl FileTransferActivity { let mut tmp: PathBuf = cache; tmp.push(tmpfile.as_str()); if tmp.exists() { - // Open file - let result = match open_with { - None => open::that(tmp.as_path()), - Some(with) => open::with(tmp.as_path(), with), - }; - // Log result - match result { - Ok(_) => self.log(LogLevel::Info, format!("Opened file `{}`", tmp.display())), - Err(err) => self.log( - LogLevel::Error, - format!("Failed to open filoe `{}`: {}", tmp.display(), err), - ), - } + self.open_path_with(tmp.as_path(), open_with); } } @@ -159,4 +128,28 @@ impl FileTransferActivity { .iter() .for_each(|x| self.action_open_remote_file(x, Some(with))); } + + /// ### open_path_with + /// + /// Common function which opens a path with default or specified program. + fn open_path_with(&mut self, p: &Path, with: Option<&str>) { + // Open file + let result = match with { + None => open::that(p), + Some(with) => open::with(p, with), + }; + // Log result + match result { + Ok(_) => self.log(LogLevel::Info, format!("Opened file `{}`", p.display())), + Err(err) => self.log( + LogLevel::Error, + format!("Failed to open filoe `{}`: {}", p.display(), err), + ), + } + // NOTE: clear screen in order to prevent crap on stderr + if let Some(ctx) = self.context.as_mut() { + // Clear screen + ctx.clear_screen(); + } + } } From 99d4177e89cb1da71c76ea8035881031de20b233 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 11 Jun 2021 15:20:49 +0200 Subject: [PATCH 10/12] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57b10e5..e70594e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ Released on FIXME: ?? > 🏄 Summer update 2021🌴 +- **Open any file** in explorer: + - Open file with default program for file type with `` + - Open file with a specific program with `` - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Dependencies: From b73c3228e4693a92078be508fbc5b814f105e664 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 12 Jun 2021 09:26:33 +0200 Subject: [PATCH 11/12] manual --- docs/man.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/man.md b/docs/man.md index b1a9bf1..4e2a7a9 100644 --- a/docs/man.md +++ b/docs/man.md @@ -146,6 +146,9 @@ When opening files with View command (``), the system default application for - *kde-open* - **WSL** users: *wslview* is required, you must install [wslu](https://github.com/wslutilities/wslu). +> Q: Can I edit remote files using the view command? +> A: No, at least not directly from the "remote panel". You have to download it to a local directory first, that's due to the fact that when you open a remote file, the file is downloaded into a temporary directory, but there's no way to create a watcher for the file to check when the program you used to open it was closed, so termscp is not able to know when you're done editing the file. + --- ## Bookmarks ⭐ From 00bee04c2cf32c814c603533c3a9e4e7f3b027b7 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 19 Jun 2021 15:03:28 +0200 Subject: [PATCH 12/12] open-rs fixes --- src/ui/activities/filetransfer/actions/mod.rs | 2 + src/ui/activities/filetransfer/mod.rs | 8 +- src/ui/activities/filetransfer/session.rs | 97 ++++++++++--------- src/ui/activities/filetransfer/update.rs | 6 +- 4 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index e8091a4..6df793b 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -42,12 +42,14 @@ pub(crate) mod rename; pub(crate) mod save; pub(crate) mod submit; +#[derive(Debug)] pub(crate) enum SelectedEntry { One(FsEntry), Many(Vec), None, } +#[derive(Debug)] enum SelectedEntryIndex { One(usize), Many(Vec), diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 5bfa149..9b21a84 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -57,7 +57,6 @@ use lib::transfer::TransferStates; use chrono::{DateTime, Local}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use std::collections::VecDeque; -use std::path::PathBuf; use tempfile::TempDir; use tuirealm::View; @@ -236,11 +235,8 @@ impl Activity for FileTransferActivity { if let Err(err) = enable_raw_mode() { error!("Failed to enter raw mode: {}", err); } - // Set working directory - let pwd: PathBuf = self.host.pwd(); - // Get files at current wd - self.local_scan(pwd.as_path()); - self.local_mut().wrkdir = pwd; + // Get files at current pwd + self.reload_local_dir(); debug!("Read working directory"); // Configure text editor self.setup_text_editor(); diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 74b01e7..f5fb653 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -135,19 +135,59 @@ impl FileTransferActivity { /// ### reload_remote_dir /// - /// Reload remote directory entries + /// Reload remote directory entries and update browser pub(super) fn reload_remote_dir(&mut self) { // Get current entries - if let Ok(pwd) = self.client.pwd() { - self.remote_scan(pwd.as_path()); + if let Ok(wrkdir) = self.client.pwd() { + self.remote_scan(wrkdir.as_path()); // Set wrkdir - self.remote_mut().wrkdir = pwd; + self.remote_mut().wrkdir = wrkdir; } } + /// ### reload_local_dir + /// + /// Reload local directory entries and update browser pub(super) fn reload_local_dir(&mut self) { - let wrkdir: PathBuf = self.local().wrkdir.clone(); + let wrkdir: PathBuf = self.host.pwd(); self.local_scan(wrkdir.as_path()); + self.local_mut().wrkdir = wrkdir; + } + + /// ### local_scan + /// + /// Scan current local directory + fn local_scan(&mut self, path: &Path) { + match self.host.scan_dir(path) { + Ok(files) => { + // Set files and sort (sorting is implicit) + self.local_mut().set_files(files); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not scan current directory: {}", err), + ); + } + } + } + + /// ### remote_scan + /// + /// Scan current remote directory + fn remote_scan(&mut self, path: &Path) { + match self.client.list_dir(path) { + Ok(files) => { + // Set files and sort (sorting is implicit) + self.remote_mut().set_files(files); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not scan current directory: {}", err), + ); + } + } } /// ### filetransfer_send @@ -559,7 +599,7 @@ impl FileTransferActivity { } } // Reload directory on local - self.local_scan(local_path); + self.reload_local_dir(); // if aborted; show alert if self.transfer.aborted() { // Log abort @@ -688,42 +728,6 @@ impl FileTransferActivity { Ok(()) } - /// ### local_scan - /// - /// Scan current local directory - pub(super) fn local_scan(&mut self, path: &Path) { - match self.host.scan_dir(path) { - Ok(files) => { - // Set files and sort (sorting is implicit) - self.local_mut().set_files(files); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not scan current directory: {}", err), - ); - } - } - } - - /// ### remote_scan - /// - /// Scan current remote directory - pub(super) fn remote_scan(&mut self, path: &Path) { - match self.client.list_dir(path) { - Ok(files) => { - // Set files and sort (sorting is implicit) - self.remote_mut().set_files(files); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not scan current directory: {}", err), - ); - } - } - } - /// ### local_changedir /// /// Change directory for local @@ -738,9 +742,7 @@ impl FileTransferActivity { format!("Changed directory on local: {}", path.display()), ); // Reload files - self.local_scan(path); - // Set wrkdir - self.local_mut().wrkdir = PathBuf::from(path); + self.reload_local_dir(); // Push prev_dir to stack if push { self.local_mut().pushd(prev_dir.as_path()) @@ -767,9 +769,7 @@ impl FileTransferActivity { format!("Changed directory on remote: {}", path.display()), ); // Update files - self.remote_scan(path); - // Set wrkdir - self.remote_mut().wrkdir = PathBuf::from(path); + self.reload_remote_dir(); // Push prev_dir to stack if push { self.remote_mut().pushd(prev_dir.as_path()) @@ -809,6 +809,7 @@ impl FileTransferActivity { return Err(format!("Could not read file: {}", err)); } } + debug!("Ok, file {} is textual; opening file...", path.display()); // Put input mode back to normal if let Err(err) = disable_raw_mode() { error!("Failed to disable raw mode: {}", err); diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 0463805..6d9f42d 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -118,8 +118,7 @@ impl Update for FileTransferActivity { } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_L) => { // Reload directory - let pwd: PathBuf = self.local().wrkdir.clone(); - self.local_scan(pwd.as_path()); + self.reload_local_dir(); // Reload file list component self.update_local_filelist() } @@ -191,8 +190,7 @@ impl Update for FileTransferActivity { } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_L) => { // Reload directory - let pwd: PathBuf = self.remote().wrkdir.clone(); - self.remote_scan(pwd.as_path()); + self.reload_remote_dir(); // Reload file list component self.update_remote_filelist() }