mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
show_hidden_files and group_dirs in termscp configuration; instantiate FileExplorer based on current configuration in FileTransferActivity
This commit is contained in:
@@ -28,6 +28,8 @@ FIXME: Released on
|
|||||||
- Windows: `C:\Users\Alice\AppData\Roaming\termscp\config.toml`
|
- Windows: `C:\Users\Alice\AppData\Roaming\termscp\config.toml`
|
||||||
- Added Text editor to configuration
|
- Added Text editor to configuration
|
||||||
- Added Default File transfer protocol to configuration
|
- Added Default File transfer protocol to configuration
|
||||||
|
- Added "Show hidden files` to configuration
|
||||||
|
- Added "Group directories" to configuration
|
||||||
- Added SSH keys to configuration; SSH keys will be stored at
|
- Added SSH keys to configuration; SSH keys will be stored at
|
||||||
- Linux: `/home/alice/.config/termscp/.ssh/`
|
- Linux: `/home/alice/.config/termscp/.ssh/`
|
||||||
- MacOS: `/Users/Alice/Library/Application Support/termscp/.ssh/`
|
- MacOS: `/Users/Alice/Library/Application Support/termscp/.ssh/`
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ These parameters can be changed:
|
|||||||
|
|
||||||
- **Default Protocol**: the default protocol is the default value for the file transfer protocol to be used in termscp. This applies for the login page and for the address CLI argument.
|
- **Default Protocol**: the default protocol is the default value for the file transfer protocol to be used in termscp. This applies for the login page and for the address CLI argument.
|
||||||
- **Text Editor**: the text editor to use. By default termscp will find the default editor for you; with this option you can force an editor to be used (e.g. `vim`). **Also GUI editors are supported**, unless they `nohup` from the parent process so if you ask: yes, you can use `notepad.exe`, and no: **Visual Studio Code doesn't work**.
|
- **Text Editor**: the text editor to use. By default termscp will find the default editor for you; with this option you can force an editor to be used (e.g. `vim`). **Also GUI editors are supported**, unless they `nohup` from the parent process so if you ask: yes, you can use `notepad.exe`, and no: **Visual Studio Code doesn't work**.
|
||||||
|
- **Show Hidden Files**: select whether hidden files shall be displayed by default. You will be able to decide whether to show or not hidden files at runtime pressing `A` anyway.
|
||||||
|
- **Group Dirs**: select whether directories should be groupped or not in file explorers. If `Display first` is selected, directories will be sorted using the configured method but displayed before files, viceversa if `Display last` is selected.
|
||||||
|
|
||||||
### SSH Key Storage 🔐
|
### SSH Key Storage 🔐
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ pub struct UserConfig {
|
|||||||
pub struct UserInterfaceConfig {
|
pub struct UserInterfaceConfig {
|
||||||
pub text_editor: PathBuf,
|
pub text_editor: PathBuf,
|
||||||
pub default_protocol: String,
|
pub default_protocol: String,
|
||||||
|
pub show_hidden_files: bool,
|
||||||
|
pub group_dirs: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||||
@@ -81,6 +83,8 @@ impl Default for UserInterfaceConfig {
|
|||||||
Err(_) => PathBuf::from("nano"), // Default to nano
|
Err(_) => PathBuf::from("nano"), // Default to nano
|
||||||
},
|
},
|
||||||
default_protocol: FileTransferProtocol::Sftp.to_string(),
|
default_protocol: FileTransferProtocol::Sftp.to_string(),
|
||||||
|
show_hidden_files: false,
|
||||||
|
group_dirs: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,14 +169,15 @@ mod tests {
|
|||||||
let ui: UserInterfaceConfig = UserInterfaceConfig {
|
let ui: UserInterfaceConfig = UserInterfaceConfig {
|
||||||
default_protocol: String::from("SFTP"),
|
default_protocol: String::from("SFTP"),
|
||||||
text_editor: PathBuf::from("nano"),
|
text_editor: PathBuf::from("nano"),
|
||||||
|
show_hidden_files: true,
|
||||||
|
group_dirs: Some(String::from("first")),
|
||||||
};
|
};
|
||||||
let cfg: UserConfig = UserConfig {
|
let cfg: UserConfig = UserConfig {
|
||||||
user_interface: ui,
|
user_interface: ui,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*cfg
|
*cfg.remote
|
||||||
.remote
|
|
||||||
.ssh_keys
|
.ssh_keys
|
||||||
.get(&String::from("192.168.1.31"))
|
.get(&String::from("192.168.1.31"))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -180,6 +185,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
||||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
|
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
|
||||||
|
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||||
|
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -106,6 +106,43 @@ mod tests {
|
|||||||
// Verify ui
|
// Verify ui
|
||||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||||
|
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||||
|
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
|
||||||
|
// Verify keys
|
||||||
|
assert_eq!(
|
||||||
|
*cfg.remote
|
||||||
|
.ssh_keys
|
||||||
|
.get(&String::from("192.168.1.31"))
|
||||||
|
.unwrap(),
|
||||||
|
PathBuf::from("/home/omar/.ssh/raspberry.key")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*cfg.remote
|
||||||
|
.ssh_keys
|
||||||
|
.get(&String::from("192.168.1.32"))
|
||||||
|
.unwrap(),
|
||||||
|
PathBuf::from("/home/omar/.ssh/beaglebone.key")
|
||||||
|
);
|
||||||
|
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_serializer_deserialize_ok_no_opts() {
|
||||||
|
let toml_file: tempfile::NamedTempFile = create_good_toml_no_opts();
|
||||||
|
toml_file.as_file().sync_all().unwrap();
|
||||||
|
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
// Parse
|
||||||
|
let deserializer: ConfigSerializer = ConfigSerializer {};
|
||||||
|
let cfg = deserializer.deserialize(Box::new(toml_file));
|
||||||
|
println!("{:?}", cfg);
|
||||||
|
assert!(cfg.is_ok());
|
||||||
|
let cfg: UserConfig = cfg.ok().unwrap();
|
||||||
|
// Verify configuration
|
||||||
|
// Verify ui
|
||||||
|
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||||
|
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||||
|
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||||
|
assert_eq!(cfg.user_interface.group_dirs, None);
|
||||||
// Verify keys
|
// Verify keys
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*cfg.remote
|
*cfg.remote
|
||||||
@@ -160,6 +197,25 @@ mod tests {
|
|||||||
[user_interface]
|
[user_interface]
|
||||||
default_protocol = "SCP"
|
default_protocol = "SCP"
|
||||||
text_editor = "vim"
|
text_editor = "vim"
|
||||||
|
show_hidden_files = true
|
||||||
|
group_dirs = "last"
|
||||||
|
|
||||||
|
[remote.ssh_keys]
|
||||||
|
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||||
|
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||||
|
"#;
|
||||||
|
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||||
|
tmpfile
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_good_toml_no_opts() -> tempfile::NamedTempFile {
|
||||||
|
// Write
|
||||||
|
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
let file_content: &str = r#"
|
||||||
|
[user_interface]
|
||||||
|
default_protocol = "SCP"
|
||||||
|
text_editor = "vim"
|
||||||
|
show_hidden_files = true
|
||||||
|
|
||||||
[remote.ssh_keys]
|
[remote.ssh_keys]
|
||||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||||
|
|||||||
@@ -55,9 +55,12 @@ impl FileExplorerBuilder {
|
|||||||
/// ### with_hidden_files
|
/// ### with_hidden_files
|
||||||
///
|
///
|
||||||
/// Enable HIDDEN_FILES option
|
/// Enable HIDDEN_FILES option
|
||||||
pub fn with_hidden_files(&mut self) -> &mut FileExplorerBuilder {
|
pub fn with_hidden_files(&mut self, val: bool) -> &mut FileExplorerBuilder {
|
||||||
if let Some(e) = self.explorer.as_mut() {
|
if let Some(e) = self.explorer.as_mut() {
|
||||||
e.opts.insert(ExplorerOpts::SHOW_HIDDEN_FILES);
|
match val {
|
||||||
|
true => e.opts.insert(ExplorerOpts::SHOW_HIDDEN_FILES),
|
||||||
|
false => e.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -114,7 +117,7 @@ mod tests {
|
|||||||
let explorer: FileExplorer = FileExplorerBuilder::new()
|
let explorer: FileExplorer = FileExplorerBuilder::new()
|
||||||
.with_file_sorting(FileSorting::ByModifyTime)
|
.with_file_sorting(FileSorting::ByModifyTime)
|
||||||
.with_group_dirs(Some(GroupDirs::First))
|
.with_group_dirs(Some(GroupDirs::First))
|
||||||
.with_hidden_files()
|
.with_hidden_files(true)
|
||||||
.with_stack_size(24)
|
.with_stack_size(24)
|
||||||
.build();
|
.build();
|
||||||
// Verify
|
// Verify
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ extern crate rand;
|
|||||||
use crate::config::serializer::ConfigSerializer;
|
use crate::config::serializer::ConfigSerializer;
|
||||||
use crate::config::{SerializerError, SerializerErrorKind, UserConfig};
|
use crate::config::{SerializerError, SerializerErrorKind, UserConfig};
|
||||||
use crate::filetransfer::FileTransferProtocol;
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
use crate::fs::explorer::GroupDirs;
|
||||||
// Ext
|
// Ext
|
||||||
use std::fs::{create_dir, remove_file, File, OpenOptions};
|
use std::fs::{create_dir, remove_file, File, OpenOptions};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -123,6 +124,45 @@ impl ConfigClient {
|
|||||||
self.config.user_interface.default_protocol = proto.to_string();
|
self.config.user_interface.default_protocol = proto.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### get_show_hidden_files
|
||||||
|
///
|
||||||
|
/// Get value of `show_hidden_files`
|
||||||
|
pub fn get_show_hidden_files(&self) -> bool {
|
||||||
|
self.config.user_interface.show_hidden_files
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### set_show_hidden_files
|
||||||
|
///
|
||||||
|
/// Set new value for `show_hidden_files`
|
||||||
|
pub fn set_show_hidden_files(&mut self, value: bool) {
|
||||||
|
self.config.user_interface.show_hidden_files = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### get_group_dirs
|
||||||
|
///
|
||||||
|
/// Get GroupDirs value from configuration (will be converted from string)
|
||||||
|
pub fn get_group_dirs(&self) -> Option<GroupDirs> {
|
||||||
|
// Convert string to `GroupDirs`
|
||||||
|
match &self.config.user_interface.group_dirs {
|
||||||
|
None => None,
|
||||||
|
Some(val) => match GroupDirs::from_str(val.as_str()) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### set_group_dirs
|
||||||
|
///
|
||||||
|
/// Set value for group_dir in configuration.
|
||||||
|
/// Provided value, if `Some` will be converted to `GroupDirs`
|
||||||
|
pub fn set_group_dirs(&mut self, val: Option<GroupDirs>) {
|
||||||
|
self.config.user_interface.group_dirs = match val {
|
||||||
|
None => None,
|
||||||
|
Some(val) => Some(val.to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// SSH Keys
|
// SSH Keys
|
||||||
|
|
||||||
/// ### save_ssh_key
|
/// ### save_ssh_key
|
||||||
@@ -387,6 +427,30 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_config_show_hidden_files() {
|
||||||
|
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||||
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||||
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
client.set_show_hidden_files(true);
|
||||||
|
assert_eq!(client.get_show_hidden_files(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_config_group_dirs() {
|
||||||
|
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||||
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||||
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
client.set_group_dirs(Some(GroupDirs::First));
|
||||||
|
assert_eq!(client.get_group_dirs(), Some(GroupDirs::First),);
|
||||||
|
client.set_group_dirs(None);
|
||||||
|
assert_eq!(client.get_group_dirs(), None,);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_config_ssh_keys() {
|
fn test_system_config_ssh_keys() {
|
||||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_form
|
/// ### handle_input_event_mode_form
|
||||||
///
|
///
|
||||||
/// Handler for input event when in form mode
|
/// Handler for input event when in form mode
|
||||||
pub(super) fn handle_input_event_mode_form(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_form(&mut self, ev: &InputEvent) {
|
||||||
match self.input_form {
|
match self.input_form {
|
||||||
InputForm::AuthCredentials => self.handle_input_event_mode_form_auth(ev),
|
InputForm::AuthCredentials => self.handle_input_event_mode_form_auth(ev),
|
||||||
InputForm::Bookmarks => self.handle_input_event_mode_form_bookmarks(ev),
|
InputForm::Bookmarks => self.handle_input_event_mode_form_bookmarks(ev),
|
||||||
@@ -64,7 +64,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_form_auth
|
/// ### handle_input_event_mode_form_auth
|
||||||
///
|
///
|
||||||
/// Handle input event when input mode is Form and Tab is Auth
|
/// Handle input event when input mode is Form and Tab is Auth
|
||||||
pub(super) fn handle_input_event_mode_form_auth(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_form_auth(&mut self, ev: &InputEvent) {
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
@@ -220,7 +220,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_form_bookmarks
|
/// ### handle_input_event_mode_form_bookmarks
|
||||||
///
|
///
|
||||||
/// Handle input event when input mode is Form and Tab is Bookmarks
|
/// Handle input event when input mode is Form and Tab is Bookmarks
|
||||||
pub(super) fn handle_input_event_mode_form_bookmarks(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_form_bookmarks(&mut self, ev: &InputEvent) {
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
@@ -304,7 +304,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_form_recents
|
/// ### handle_input_event_mode_form_recents
|
||||||
///
|
///
|
||||||
/// Handle input event when input mode is Form and Tab is Recents
|
/// Handle input event when input mode is Form and Tab is Recents
|
||||||
pub(super) fn handle_input_event_mode_form_recents(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_form_recents(&mut self, ev: &InputEvent) {
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
@@ -388,7 +388,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_text
|
/// ### handle_input_event_mode_text
|
||||||
///
|
///
|
||||||
/// Handler for input event when in popup mode
|
/// Handler for input event when in popup mode
|
||||||
pub(super) fn handle_input_event_mode_popup(&mut self, ev: &InputEvent, ptype: PopupType) {
|
fn handle_input_event_mode_popup(&mut self, ev: &InputEvent, ptype: PopupType) {
|
||||||
match ptype {
|
match ptype {
|
||||||
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
|
||||||
PopupType::Help => self.handle_input_event_mode_popup_help(ev),
|
PopupType::Help => self.handle_input_event_mode_popup_help(ev),
|
||||||
@@ -402,7 +402,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_popup_alert
|
/// ### handle_input_event_mode_popup_alert
|
||||||
///
|
///
|
||||||
/// Handle input event when the input mode is popup, and popup type is alert
|
/// Handle input event when the input mode is popup, and popup type is alert
|
||||||
pub(super) fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) {
|
||||||
// Only enter should be allowed here
|
// Only enter should be allowed here
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
||||||
@@ -414,7 +414,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_popup_help
|
/// ### handle_input_event_mode_popup_help
|
||||||
///
|
///
|
||||||
/// Input event handler for popup help
|
/// Input event handler for popup help
|
||||||
pub(super) fn handle_input_event_mode_popup_help(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_popup_help(&mut self, ev: &InputEvent) {
|
||||||
// If enter, close popup
|
// If enter, close popup
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
|
||||||
@@ -427,7 +427,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_popup_save_bookmark
|
/// ### handle_input_event_mode_popup_save_bookmark
|
||||||
///
|
///
|
||||||
/// Input event handler for SaveBookmark popup
|
/// Input event handler for SaveBookmark popup
|
||||||
pub(super) fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) {
|
fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) {
|
||||||
// If enter, close popup, otherwise push chars to input
|
// If enter, close popup, otherwise push chars to input
|
||||||
if let InputEvent::Key(key) = ev {
|
if let InputEvent::Key(key) = ev {
|
||||||
match key.code {
|
match key.code {
|
||||||
@@ -466,7 +466,7 @@ impl AuthActivity {
|
|||||||
/// ### handle_input_event_mode_popup_yesno
|
/// ### handle_input_event_mode_popup_yesno
|
||||||
///
|
///
|
||||||
/// Input event handler for popup alert
|
/// Input event handler for popup alert
|
||||||
pub(super) fn handle_input_event_mode_popup_yesno(
|
fn handle_input_event_mode_popup_yesno(
|
||||||
&mut self,
|
&mut self,
|
||||||
ev: &InputEvent,
|
ev: &InputEvent,
|
||||||
yes_cb: DialogCallback,
|
yes_cb: DialogCallback,
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ impl AuthActivity {
|
|||||||
/// ### draw_local_explorer
|
/// ### draw_local_explorer
|
||||||
///
|
///
|
||||||
/// Draw local explorer list
|
/// Draw local explorer list
|
||||||
pub(super) fn draw_bookmarks_tab(&self) -> Option<List> {
|
fn draw_bookmarks_tab(&self) -> Option<List> {
|
||||||
self.bookmarks_client.as_ref()?;
|
self.bookmarks_client.as_ref()?;
|
||||||
let hosts: Vec<ListItem> = self
|
let hosts: Vec<ListItem> = self
|
||||||
.bookmarks_client
|
.bookmarks_client
|
||||||
@@ -366,7 +366,7 @@ impl AuthActivity {
|
|||||||
/// ### draw_local_explorer
|
/// ### draw_local_explorer
|
||||||
///
|
///
|
||||||
/// Draw local explorer list
|
/// Draw local explorer list
|
||||||
pub(super) fn draw_recents_tab(&self) -> Option<List> {
|
fn draw_recents_tab(&self) -> Option<List> {
|
||||||
self.bookmarks_client.as_ref()?;
|
self.bookmarks_client.as_ref()?;
|
||||||
let hosts: Vec<ListItem> = self
|
let hosts: Vec<ListItem> = self
|
||||||
.bookmarks_client
|
.bookmarks_client
|
||||||
@@ -463,7 +463,7 @@ impl AuthActivity {
|
|||||||
/// ### draw_popup_input
|
/// ### draw_popup_input
|
||||||
///
|
///
|
||||||
/// Draw input popup
|
/// Draw input popup
|
||||||
pub(super) fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) {
|
fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) {
|
||||||
let input: Paragraph = Paragraph::new(self.input_txt.as_ref())
|
let input: Paragraph = Paragraph::new(self.input_txt.as_ref())
|
||||||
.style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
.block(
|
.block(
|
||||||
@@ -497,7 +497,7 @@ impl AuthActivity {
|
|||||||
/// ### draw_popup_yesno
|
/// ### draw_popup_yesno
|
||||||
///
|
///
|
||||||
/// Draw yes/no select popup
|
/// Draw yes/no select popup
|
||||||
pub(super) fn draw_popup_yesno(&self, text: String) -> Tabs {
|
fn draw_popup_yesno(&self, text: String) -> Tabs {
|
||||||
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||||
let index: usize = match self.choice_opt {
|
let index: usize = match self.choice_opt {
|
||||||
DialogYesNoOption::Yes => 0,
|
DialogYesNoOption::Yes => 0,
|
||||||
@@ -522,7 +522,7 @@ impl AuthActivity {
|
|||||||
/// ### draw_popup_help
|
/// ### draw_popup_help
|
||||||
///
|
///
|
||||||
/// Draw authentication page help popup
|
/// Draw authentication page help popup
|
||||||
pub(super) fn draw_popup_help(&self) -> List {
|
fn draw_popup_help(&self) -> List {
|
||||||
// Write header
|
// Write header
|
||||||
let cmds: Vec<ListItem> = vec![
|
let cmds: Vec<ListItem> = vec![
|
||||||
ListItem::new(Spans::from(vec![
|
ListItem::new(Spans::from(vec![
|
||||||
|
|||||||
@@ -130,11 +130,19 @@ impl FileTransferActivity {
|
|||||||
///
|
///
|
||||||
/// Build explorer reading configuration from `ConfigClient`
|
/// Build explorer reading configuration from `ConfigClient`
|
||||||
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
|
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
|
||||||
FileExplorerBuilder::new()
|
match &cli {
|
||||||
.with_file_sorting(FileSorting::ByName)
|
Some(cli) => FileExplorerBuilder::new() // Build according to current configuration
|
||||||
.with_group_dirs(Some(GroupDirs::First))
|
.with_file_sorting(FileSorting::ByName)
|
||||||
.with_stack_size(16)
|
.with_group_dirs(cli.get_group_dirs())
|
||||||
.build()
|
.with_hidden_files(cli.get_show_hidden_files())
|
||||||
|
.with_stack_size(16)
|
||||||
|
.build(),
|
||||||
|
None => FileExplorerBuilder::new() // Build default
|
||||||
|
.with_file_sorting(FileSorting::ByName)
|
||||||
|
.with_group_dirs(Some(GroupDirs::First))
|
||||||
|
.with_stack_size(16)
|
||||||
|
.build(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### setup_text_editor
|
/// ### setup_text_editor
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use super::{
|
|||||||
UserInterfaceInputField, YesNoDialogOption,
|
UserInterfaceInputField, YesNoDialogOption,
|
||||||
};
|
};
|
||||||
use crate::filetransfer::FileTransferProtocol;
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
use crate::fs::explorer::GroupDirs;
|
||||||
// Ext
|
// Ext
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -195,59 +196,105 @@ impl SetupActivity {
|
|||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
// Move left on fields which are tabs
|
// Move left on fields which are tabs
|
||||||
if let Some(config_cli) = self.config_cli.as_mut() {
|
if let Some(config_cli) = self.config_cli.as_mut() {
|
||||||
if matches!(field, UserInterfaceInputField::DefaultProtocol) {
|
match field {
|
||||||
// Move left
|
UserInterfaceInputField::DefaultProtocol => {
|
||||||
config_cli.set_default_protocol(
|
// Move left
|
||||||
match config_cli.get_default_protocol() {
|
config_cli.set_default_protocol(
|
||||||
FileTransferProtocol::Ftp(secure) => match secure {
|
match config_cli.get_default_protocol() {
|
||||||
true => FileTransferProtocol::Ftp(false),
|
FileTransferProtocol::Ftp(secure) => match secure {
|
||||||
false => FileTransferProtocol::Scp,
|
true => FileTransferProtocol::Ftp(false),
|
||||||
|
false => FileTransferProtocol::Scp,
|
||||||
|
},
|
||||||
|
FileTransferProtocol::Scp => FileTransferProtocol::Sftp,
|
||||||
|
FileTransferProtocol::Sftp => {
|
||||||
|
FileTransferProtocol::Ftp(true)
|
||||||
|
} // Wrap
|
||||||
},
|
},
|
||||||
FileTransferProtocol::Scp => FileTransferProtocol::Sftp,
|
);
|
||||||
FileTransferProtocol::Sftp => FileTransferProtocol::Ftp(true), // Wrap
|
}
|
||||||
},
|
UserInterfaceInputField::GroupDirs => {
|
||||||
);
|
// Move left
|
||||||
|
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
|
||||||
|
None => Some(GroupDirs::Last),
|
||||||
|
Some(val) => match val {
|
||||||
|
GroupDirs::Last => Some(GroupDirs::First),
|
||||||
|
GroupDirs::First => None,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles => {
|
||||||
|
// Move left
|
||||||
|
config_cli.set_show_hidden_files(true);
|
||||||
|
}
|
||||||
|
_ => { /* Not a tab field */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
// Move right on fields which are tabs
|
// Move right on fields which are tabs
|
||||||
if let Some(config_cli) = self.config_cli.as_mut() {
|
if let Some(config_cli) = self.config_cli.as_mut() {
|
||||||
if matches!(field, UserInterfaceInputField::DefaultProtocol) {
|
match field {
|
||||||
// Move left
|
UserInterfaceInputField::DefaultProtocol => {
|
||||||
config_cli.set_default_protocol(
|
// Move left
|
||||||
match config_cli.get_default_protocol() {
|
config_cli.set_default_protocol(
|
||||||
FileTransferProtocol::Sftp => FileTransferProtocol::Scp,
|
match config_cli.get_default_protocol() {
|
||||||
FileTransferProtocol::Scp => FileTransferProtocol::Ftp(false),
|
FileTransferProtocol::Sftp => FileTransferProtocol::Scp,
|
||||||
FileTransferProtocol::Ftp(secure) => match secure {
|
FileTransferProtocol::Scp => {
|
||||||
false => FileTransferProtocol::Ftp(true),
|
FileTransferProtocol::Ftp(false)
|
||||||
true => FileTransferProtocol::Sftp, // Wrap
|
}
|
||||||
|
FileTransferProtocol::Ftp(secure) => match secure {
|
||||||
|
false => FileTransferProtocol::Ftp(true),
|
||||||
|
true => FileTransferProtocol::Sftp, // Wrap
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
);
|
}
|
||||||
|
UserInterfaceInputField::GroupDirs => {
|
||||||
|
// Move right
|
||||||
|
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
|
||||||
|
Some(val) => match val {
|
||||||
|
GroupDirs::First => Some(GroupDirs::Last),
|
||||||
|
GroupDirs::Last => None,
|
||||||
|
},
|
||||||
|
None => Some(GroupDirs::First),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles => {
|
||||||
|
// Move right
|
||||||
|
config_cli.set_show_hidden_files(false);
|
||||||
|
}
|
||||||
|
_ => { /* Not a tab field */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
// Change selected field
|
// Change selected field
|
||||||
self.tab = SetupTab::UserInterface(match field {
|
self.tab = SetupTab::UserInterface(match field {
|
||||||
UserInterfaceInputField::TextEditor => {
|
UserInterfaceInputField::GroupDirs => {
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles
|
||||||
|
}
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles => {
|
||||||
UserInterfaceInputField::DefaultProtocol
|
UserInterfaceInputField::DefaultProtocol
|
||||||
}
|
}
|
||||||
UserInterfaceInputField::DefaultProtocol => {
|
UserInterfaceInputField::DefaultProtocol => {
|
||||||
UserInterfaceInputField::TextEditor
|
UserInterfaceInputField::TextEditor
|
||||||
} // Wrap
|
}
|
||||||
|
UserInterfaceInputField::TextEditor => UserInterfaceInputField::GroupDirs, // Wrap
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
// Change selected field
|
// Change selected field
|
||||||
self.tab = SetupTab::UserInterface(match field {
|
self.tab = SetupTab::UserInterface(match field {
|
||||||
UserInterfaceInputField::DefaultProtocol => {
|
|
||||||
UserInterfaceInputField::TextEditor
|
|
||||||
}
|
|
||||||
UserInterfaceInputField::TextEditor => {
|
UserInterfaceInputField::TextEditor => {
|
||||||
UserInterfaceInputField::DefaultProtocol
|
UserInterfaceInputField::DefaultProtocol
|
||||||
} // Wrap
|
}
|
||||||
|
UserInterfaceInputField::DefaultProtocol => {
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles
|
||||||
|
}
|
||||||
|
UserInterfaceInputField::ShowHiddenFiles => {
|
||||||
|
UserInterfaceInputField::GroupDirs
|
||||||
|
}
|
||||||
|
UserInterfaceInputField::GroupDirs => UserInterfaceInputField::TextEditor, // Wrap
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) => {
|
KeyCode::Char(ch) => {
|
||||||
@@ -450,7 +497,7 @@ impl SetupActivity {
|
|||||||
/// ### handle_input_event_mode_popup_yesno
|
/// ### handle_input_event_mode_popup_yesno
|
||||||
///
|
///
|
||||||
/// Input event handler for popup alert
|
/// Input event handler for popup alert
|
||||||
pub(super) fn handle_input_event_mode_popup_yesno(
|
fn handle_input_event_mode_popup_yesno(
|
||||||
&mut self,
|
&mut self,
|
||||||
ev: &InputEvent,
|
ev: &InputEvent,
|
||||||
yes_cb: OnChoiceCallback,
|
yes_cb: OnChoiceCallback,
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ use super::{
|
|||||||
YesNoDialogOption,
|
YesNoDialogOption,
|
||||||
};
|
};
|
||||||
use crate::filetransfer::FileTransferProtocol;
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
use crate::fs::explorer::GroupDirs;
|
||||||
use crate::utils::fmt::align_text_center;
|
use crate::utils::fmt::align_text_center;
|
||||||
|
// Ext
|
||||||
use tui::{
|
use tui::{
|
||||||
layout::{Constraint, Corner, Direction, Layout, Rect},
|
layout::{Constraint, Corner, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
@@ -84,6 +85,8 @@ impl SetupActivity {
|
|||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(
|
.constraints(
|
||||||
[
|
[
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
@@ -98,6 +101,12 @@ impl SetupActivity {
|
|||||||
if let Some(tab) = self.draw_default_protocol_tab() {
|
if let Some(tab) = self.draw_default_protocol_tab() {
|
||||||
f.render_widget(tab, ui_cfg_chunks[1]);
|
f.render_widget(tab, ui_cfg_chunks[1]);
|
||||||
}
|
}
|
||||||
|
if let Some(tab) = self.draw_hidden_files_tab() {
|
||||||
|
f.render_widget(tab, ui_cfg_chunks[2]);
|
||||||
|
}
|
||||||
|
if let Some(tab) = self.draw_default_group_dirs_tab() {
|
||||||
|
f.render_widget(tab, ui_cfg_chunks[3]);
|
||||||
|
}
|
||||||
// Set cursor
|
// Set cursor
|
||||||
if let Some(cli) = &self.config_cli {
|
if let Some(cli) = &self.config_cli {
|
||||||
if matches!(form_field, UserInterfaceInputField::TextEditor) {
|
if matches!(form_field, UserInterfaceInputField::TextEditor) {
|
||||||
@@ -217,6 +226,33 @@ impl SetupActivity {
|
|||||||
Paragraph::new(footer_text)
|
Paragraph::new(footer_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### draw_text_editor_input
|
||||||
|
///
|
||||||
|
/// Draw input text field for text editor parameter
|
||||||
|
fn draw_text_editor_input(&self) -> Option<Paragraph> {
|
||||||
|
match &self.config_cli {
|
||||||
|
Some(cli) => Some(
|
||||||
|
Paragraph::new(String::from(
|
||||||
|
cli.get_text_editor().as_path().to_string_lossy(),
|
||||||
|
))
|
||||||
|
.style(Style::default().fg(match &self.tab {
|
||||||
|
SetupTab::SshConfig => Color::White,
|
||||||
|
SetupTab::UserInterface(field) => match field {
|
||||||
|
UserInterfaceInputField::TextEditor => Color::LightGreen,
|
||||||
|
_ => Color::White,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.title("Text Editor"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ### draw_default_protocol_tab
|
/// ### draw_default_protocol_tab
|
||||||
///
|
///
|
||||||
/// Draw default protocol input tab
|
/// Draw default protocol input tab
|
||||||
@@ -267,29 +303,91 @@ impl SetupActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### draw_text_editor_input
|
/// ### draw_default_protocol_tab
|
||||||
///
|
///
|
||||||
/// Draw input text field for text editor parameter
|
/// Draw default protocol input tab
|
||||||
fn draw_text_editor_input(&self) -> Option<Paragraph> {
|
fn draw_hidden_files_tab(&self) -> Option<Tabs> {
|
||||||
|
// Check if config client is some
|
||||||
match &self.config_cli {
|
match &self.config_cli {
|
||||||
Some(cli) => Some(
|
Some(cli) => {
|
||||||
Paragraph::new(String::from(
|
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
|
||||||
cli.get_text_editor().as_path().to_string_lossy(),
|
let index: usize = match cli.get_show_hidden_files() {
|
||||||
))
|
true => 0,
|
||||||
.style(Style::default().fg(match &self.tab {
|
false => 1,
|
||||||
SetupTab::SshConfig => Color::White,
|
};
|
||||||
|
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||||
SetupTab::UserInterface(field) => match field {
|
SetupTab::UserInterface(field) => match field {
|
||||||
UserInterfaceInputField::TextEditor => Color::LightGreen,
|
UserInterfaceInputField::ShowHiddenFiles => {
|
||||||
_ => Color::White,
|
(Color::LightRed, Color::Black, Color::LightRed)
|
||||||
|
}
|
||||||
|
_ => (Color::Reset, Color::LightRed, Color::Reset),
|
||||||
},
|
},
|
||||||
}))
|
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||||
.block(
|
};
|
||||||
Block::default()
|
Some(
|
||||||
.borders(Borders::ALL)
|
Tabs::new(choices)
|
||||||
.border_type(BorderType::Rounded)
|
.block(
|
||||||
.title("Text Editor"),
|
Block::default()
|
||||||
),
|
.borders(Borders::ALL)
|
||||||
),
|
.border_type(BorderType::Rounded)
|
||||||
|
.style(Style::default().fg(block_fg))
|
||||||
|
.title("Show hidden files (by default)"),
|
||||||
|
)
|
||||||
|
.select(index)
|
||||||
|
.style(Style::default())
|
||||||
|
.highlight_style(
|
||||||
|
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### draw_default_group_dirs_tab
|
||||||
|
///
|
||||||
|
/// Draw group dirs input tab
|
||||||
|
fn draw_default_group_dirs_tab(&self) -> Option<Tabs> {
|
||||||
|
// Check if config client is some
|
||||||
|
match &self.config_cli {
|
||||||
|
Some(cli) => {
|
||||||
|
let choices: Vec<Spans> = vec![
|
||||||
|
Spans::from("Display First"),
|
||||||
|
Spans::from("Display Last"),
|
||||||
|
Spans::from("No"),
|
||||||
|
];
|
||||||
|
let index: usize = match cli.get_group_dirs() {
|
||||||
|
None => 2,
|
||||||
|
Some(val) => match val {
|
||||||
|
GroupDirs::First => 0,
|
||||||
|
GroupDirs::Last => 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
|
||||||
|
SetupTab::UserInterface(field) => match field {
|
||||||
|
UserInterfaceInputField::GroupDirs => {
|
||||||
|
(Color::LightMagenta, Color::Black, Color::LightMagenta)
|
||||||
|
}
|
||||||
|
_ => (Color::Reset, Color::LightMagenta, Color::Reset),
|
||||||
|
},
|
||||||
|
_ => (Color::Reset, Color::Reset, Color::Reset),
|
||||||
|
};
|
||||||
|
Some(
|
||||||
|
Tabs::new(choices)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.style(Style::default().fg(block_fg))
|
||||||
|
.title("Group directories"),
|
||||||
|
)
|
||||||
|
.select(index)
|
||||||
|
.style(Style::default())
|
||||||
|
.highlight_style(
|
||||||
|
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,7 +579,7 @@ impl SetupActivity {
|
|||||||
/// ### draw_popup_help
|
/// ### draw_popup_help
|
||||||
///
|
///
|
||||||
/// Draw authentication page help popup
|
/// Draw authentication page help popup
|
||||||
pub(super) fn draw_popup_help(&self) -> List {
|
fn draw_popup_help(&self) -> List {
|
||||||
// Write header
|
// Write header
|
||||||
let cmds: Vec<ListItem> = vec![
|
let cmds: Vec<ListItem> = vec![
|
||||||
ListItem::new(Spans::from(vec![
|
ListItem::new(Spans::from(vec![
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ type OnChoiceCallback = fn(&mut SetupActivity);
|
|||||||
enum UserInterfaceInputField {
|
enum UserInterfaceInputField {
|
||||||
DefaultProtocol,
|
DefaultProtocol,
|
||||||
TextEditor,
|
TextEditor,
|
||||||
|
ShowHiddenFiles,
|
||||||
|
GroupDirs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### SetupTab
|
/// ### SetupTab
|
||||||
|
|||||||
Reference in New Issue
Block a user