diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b79ee91..42ff050 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,7 +16,7 @@ jobs: - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: - args: "--ignore-tests -- --test-threads 1" + args: "--ignore-tests --features githubActions -- --test-threads 1" - name: Upload to codecov.io uses: codecov/codecov-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index b1580e5..e4e7fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ Released on FIXME: ?? +- **Remote and Local hosts file formatter**: + - Added the possibility to set different formatters for local and remote hosts - Bugfix: - Fixed wrong text wrap in log box - Dependencies: diff --git a/docs/man.md b/docs/man.md index 96d758c..b926857 100644 --- a/docs/man.md +++ b/docs/man.md @@ -196,7 +196,7 @@ You can access the SSH key storage, from configuration moving to the `SSH Keys` ### File Explorer Format -It is possible through configuration to define a custom format for the file explorer. This field, with name `File formatter syntax` will define how the file entries will be displayed in the file explorer. +It is possible through configuration to define a custom format for the file explorer. This is possible both for local and remote host, so you can have two different syntax in use. These fields, with name `File formatter syntax (local)` and `File formatter syntax (remote)` will define how the file entries will be displayed in the file explorer. The syntax for the formatter is the following `{KEY1}... {KEY2:LENGTH}... {KEY3:LENGTH:EXTRA} {KEYn}...`. Each key in bracket will be replaced with the related attribute, while everything outside brackets will be left unchanged. diff --git a/src/config/mod.rs b/src/config/mod.rs index f3c0a57..a611af6 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -60,7 +60,8 @@ pub struct UserInterfaceConfig { pub show_hidden_files: bool, pub check_for_updates: Option, // @! Since 0.3.3 pub group_dirs: Option, - pub file_fmt: Option, + pub file_fmt: Option, // Refers to local host (for backward compatibility) + pub remote_file_fmt: Option, // @! Since 0.5.0 } #[derive(Deserialize, Serialize, std::fmt::Debug)] @@ -92,6 +93,7 @@ impl Default for UserInterfaceConfig { check_for_updates: Some(true), group_dirs: None, file_fmt: None, + remote_file_fmt: None, } } } @@ -178,6 +180,7 @@ mod tests { check_for_updates: Some(true), group_dirs: Some(String::from("first")), file_fmt: Some(String::from("{NAME}")), + remote_file_fmt: Some(String::from("{USER}")), }; let cfg: UserConfig = UserConfig { user_interface: ui, @@ -196,6 +199,10 @@ mod tests { assert_eq!(cfg.user_interface.check_for_updates, Some(true)); assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first"))); assert_eq!(cfg.user_interface.file_fmt, Some(String::from("{NAME}"))); + assert_eq!( + cfg.user_interface.remote_file_fmt, + Some(String::from("{USER}")) + ); } #[test] @@ -218,6 +225,8 @@ mod tests { ); assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true); assert_eq!(cfg.remote.ssh_keys.len(), 0); + assert!(cfg.user_interface.file_fmt.is_none()); + assert!(cfg.user_interface.remote_file_fmt.is_none()); } #[test] diff --git a/src/config/serializer.rs b/src/config/serializer.rs index a4ba940..a95d0cf 100644 --- a/src/config/serializer.rs +++ b/src/config/serializer.rs @@ -114,6 +114,10 @@ mod tests { cfg.user_interface.file_fmt, Some(String::from("{NAME} {PEX}")) ); + assert_eq!( + cfg.user_interface.remote_file_fmt, + Some(String::from("{NAME} {USER}")), + ); // Verify keys assert_eq!( *cfg.remote @@ -149,7 +153,8 @@ mod tests { assert_eq!(cfg.user_interface.show_hidden_files, true); assert_eq!(cfg.user_interface.group_dirs, None); assert!(cfg.user_interface.check_for_updates.is_none()); - assert_eq!(cfg.user_interface.file_fmt, None); + assert!(cfg.user_interface.file_fmt.is_none()); + assert!(cfg.user_interface.remote_file_fmt.is_none()); // Verify keys assert_eq!( *cfg.remote @@ -208,6 +213,7 @@ mod tests { check_for_updates = true group_dirs = "last" file_fmt = "{NAME} {PEX}" + remote_file_fmt = "{NAME} {USER}" [remote.ssh_keys] "192.168.1.31" = "/home/omar/.ssh/raspberry.key" diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 57f47fa..532f1a9 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -176,23 +176,40 @@ impl ConfigClient { self.config.user_interface.group_dirs = val.map(|val| val.to_string()); } - /// ### get_file_fmt + /// ### get_local_file_fmt /// - /// Get current file fmt - pub fn get_file_fmt(&self) -> Option { + /// Get current file fmt for local host + pub fn get_local_file_fmt(&self) -> Option { self.config.user_interface.file_fmt.clone() } - /// ### set_file_fmt + /// ### set_local_file_fmt /// - /// Set file fmt parameter - pub fn set_file_fmt(&mut self, s: String) { + /// Set file fmt parameter for local host + pub fn set_local_file_fmt(&mut self, s: String) { self.config.user_interface.file_fmt = match s.is_empty() { true => None, false => Some(s), }; } + /// ### get_remote_file_fmt + /// + /// Get current file fmt for remote host + pub fn get_remote_file_fmt(&self) -> Option { + self.config.user_interface.remote_file_fmt.clone() + } + + /// ### set_remote_file_fmt + /// + /// Set file fmt parameter for remote host + pub fn set_remote_file_fmt(&mut self, s: String) { + self.config.user_interface.remote_file_fmt = match s.is_empty() { + true => None, + false => Some(s), + }; + } + // SSH Keys /// ### save_ssh_key @@ -496,18 +513,36 @@ mod tests { } #[test] - fn test_system_config_file_fmt() { + fn test_system_config_local_file_fmt() { 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(); - assert_eq!(client.get_file_fmt(), None); - client.set_file_fmt(String::from("{NAME}")); - assert_eq!(client.get_file_fmt().unwrap(), String::from("{NAME}")); + assert_eq!(client.get_local_file_fmt(), None); + client.set_local_file_fmt(String::from("{NAME}")); + assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}")); // Delete - client.set_file_fmt(String::from("")); - assert_eq!(client.get_file_fmt(), None); + client.set_local_file_fmt(String::from("")); + assert_eq!(client.get_local_file_fmt(), None); + } + + #[test] + fn test_system_config_remote_file_fmt() { + 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(); + assert_eq!(client.get_remote_file_fmt(), None); + client.set_remote_file_fmt(String::from("{NAME}")); + assert_eq!( + client.get_remote_file_fmt().unwrap(), + String::from("{NAME}") + ); + // Delete + client.set_remote_file_fmt(String::from("")); + assert_eq!(client.get_remote_file_fmt(), None); } #[test] diff --git a/src/ui/activities/filetransfer_activity/misc.rs b/src/ui/activities/filetransfer_activity/misc.rs index cc37e65..cb56463 100644 --- a/src/ui/activities/filetransfer_activity/misc.rs +++ b/src/ui/activities/filetransfer_activity/misc.rs @@ -94,21 +94,46 @@ impl FileTransferActivity { /// ### build_explorer /// /// Build explorer reading configuration from `ConfigClient` - pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer { + fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorerBuilder { + let mut builder: FileExplorerBuilder = FileExplorerBuilder::new(); + // Set common keys + builder + .with_file_sorting(FileSorting::ByName) + .with_stack_size(16); match &cli { - Some(cli) => FileExplorerBuilder::new() // Build according to current configuration - .with_file_sorting(FileSorting::ByName) - .with_group_dirs(cli.get_group_dirs()) - .with_hidden_files(cli.get_show_hidden_files()) - .with_stack_size(16) - .with_formatter(cli.get_file_fmt().as_deref()) - .build(), - None => FileExplorerBuilder::new() // Build default - .with_file_sorting(FileSorting::ByName) - .with_group_dirs(Some(GroupDirs::First)) - .with_stack_size(16) - .build(), + Some(cli) => { + builder // Build according to current configuration + .with_group_dirs(cli.get_group_dirs()) + .with_hidden_files(cli.get_show_hidden_files()); + } + None => { + builder // Build default + .with_group_dirs(Some(GroupDirs::First)); + } + }; + builder + } + + /// ### build_local_explorer + /// + /// Build a file explorer with local host setup + pub(super) fn build_local_explorer(cli: Option<&ConfigClient>) -> FileExplorer { + let mut builder = Self::build_explorer(cli); + if let Some(cli) = cli { + builder.with_formatter(cli.get_local_file_fmt().as_deref()); } + builder.build() + } + + /// ### build_remote_explorer + /// + /// Build a file explorer with remote host setup + pub(super) fn build_remote_explorer(cli: Option<&ConfigClient>) -> FileExplorer { + let mut builder = Self::build_explorer(cli); + if let Some(cli) = cli { + builder.with_formatter(cli.get_remote_file_fmt().as_deref()); + } + builder.build() } /// ### build_found_explorer diff --git a/src/ui/activities/filetransfer_activity/mod.rs b/src/ui/activities/filetransfer_activity/mod.rs index 26f7545..dca8952 100644 --- a/src/ui/activities/filetransfer_activity/mod.rs +++ b/src/ui/activities/filetransfer_activity/mod.rs @@ -239,8 +239,8 @@ impl FileTransferActivity { Self::make_ssh_storage(config_client.as_ref()), )), }, - local: Self::build_explorer(config_client.as_ref()), - remote: Self::build_explorer(config_client.as_ref()), + local: Self::build_local_explorer(config_client.as_ref()), + remote: Self::build_remote_explorer(config_client.as_ref()), found: None, tab: FileExplorerTab::Local, log_records: VecDeque::with_capacity(256), // 256 events is enough I guess diff --git a/src/ui/activities/setup_activity/mod.rs b/src/ui/activities/setup_activity/mod.rs index 0ec7d00..5eed9f8 100644 --- a/src/ui/activities/setup_activity/mod.rs +++ b/src/ui/activities/setup_activity/mod.rs @@ -53,7 +53,8 @@ const COMPONENT_RADIO_DEFAULT_PROTOCOL: &str = "RADIO_DEFAULT_PROTOCOL"; const COMPONENT_RADIO_HIDDEN_FILES: &str = "RADIO_HIDDEN_FILES"; const COMPONENT_RADIO_UPDATES: &str = "RADIO_CHECK_UPDATES"; const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS"; -const COMPONENT_INPUT_FILE_FMT: &str = "INPUT_FILE_FMT"; +const COMPONENT_INPUT_LOCAL_FILE_FMT: &str = "INPUT_LOCAL_FILE_FMT"; +const COMPONENT_INPUT_REMOTE_FILE_FMT: &str = "INPUT_REMOTE_FILE_FMT"; const COMPONENT_RADIO_TAB: &str = "RADIO_TAB"; const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS"; const COMPONENT_INPUT_SSH_HOST: &str = "INPUT_SSH_HOST"; diff --git a/src/ui/activities/setup_activity/update.rs b/src/ui/activities/setup_activity/update.rs index 036907b..631cedf 100644 --- a/src/ui/activities/setup_activity/update.rs +++ b/src/ui/activities/setup_activity/update.rs @@ -28,11 +28,11 @@ */ // locals use super::{ - SetupActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_SSH_HOST, - COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS, - COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY, COMPONENT_RADIO_GROUP_DIRS, - COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SAVE, - COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, + SetupActivity, COMPONENT_INPUT_LOCAL_FILE_FMT, COMPONENT_INPUT_REMOTE_FILE_FMT, + COMPONENT_INPUT_SSH_HOST, COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR, + COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY, + COMPONENT_RADIO_GROUP_DIRS, COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT, + COMPONENT_RADIO_SAVE, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, }; use crate::ui::activities::keymap::*; @@ -68,15 +68,23 @@ impl SetupActivity { None } (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => { - self.view.active(COMPONENT_INPUT_FILE_FMT); + self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); None } - (COMPONENT_INPUT_FILE_FMT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => { + self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); + None + } + (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => { self.view.active(COMPONENT_INPUT_TEXT_EDITOR); None } // Input field - (COMPONENT_INPUT_FILE_FMT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => { + self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); + None + } + (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_UP) => { self.view.active(COMPONENT_RADIO_GROUP_DIRS); None } @@ -97,7 +105,7 @@ impl SetupActivity { None } (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => { - self.view.active(COMPONENT_INPUT_FILE_FMT); + self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); None } // Error or diff --git a/src/ui/activities/setup_activity/view.rs b/src/ui/activities/setup_activity/view.rs index d5d06d8..91f2ba8 100644 --- a/src/ui/activities/setup_activity/view.rs +++ b/src/ui/activities/setup_activity/view.rs @@ -173,12 +173,22 @@ impl SetupActivity { )), ); self.view.mount( - super::COMPONENT_INPUT_FILE_FMT, + super::COMPONENT_INPUT_LOCAL_FILE_FMT, Box::new(Input::new( InputPropsBuilder::default() .with_foreground(Color::LightBlue) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue) - .with_label(String::from("File formatter syntax")) + .with_label(String::from("File formatter syntax (local)")) + .build(), + )), + ); + self.view.mount( + super::COMPONENT_INPUT_REMOTE_FILE_FMT, + Box::new(Input::new( + InputPropsBuilder::default() + .with_foreground(Color::LightGreen) + .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) + .with_label(String::from("File formatter syntax (remote)")) .build(), )), ); @@ -280,7 +290,8 @@ impl SetupActivity { Constraint::Length(3), // Hidden files Constraint::Length(3), // Updates tab Constraint::Length(3), // Group dirs - Constraint::Length(3), // Format input + Constraint::Length(3), // Local Format input + Constraint::Length(3), // Remote Format input Constraint::Length(1), // Empty ? ] .as_ref(), @@ -297,7 +308,9 @@ impl SetupActivity { self.view .render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[4]); self.view - .render(super::COMPONENT_INPUT_FILE_FMT, f, ui_cfg_chunks[5]); + .render(super::COMPONENT_INPUT_LOCAL_FILE_FMT, f, ui_cfg_chunks[5]); + self.view + .render(super::COMPONENT_INPUT_REMOTE_FILE_FMT, f, ui_cfg_chunks[6]); } ViewLayout::SshKeys => { let sshcfg_chunks = Layout::default() @@ -692,11 +705,21 @@ impl SetupActivity { let props = RadioPropsBuilder::from(props).with_value(dirs).build(); let _ = self.view.update(super::COMPONENT_RADIO_GROUP_DIRS, props); } - // File Fmt - if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_FILE_FMT) { - let file_fmt: String = cli.get_file_fmt().unwrap_or_default(); + // Local File Fmt + if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_LOCAL_FILE_FMT) { + let file_fmt: String = cli.get_local_file_fmt().unwrap_or_default(); let props = InputPropsBuilder::from(props).with_value(file_fmt).build(); - let _ = self.view.update(super::COMPONENT_INPUT_FILE_FMT, props); + let _ = self + .view + .update(super::COMPONENT_INPUT_LOCAL_FILE_FMT, props); + } + // Remote File Fmt + if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_REMOTE_FILE_FMT) { + let file_fmt: String = cli.get_remote_file_fmt().unwrap_or_default(); + let props = InputPropsBuilder::from(props).with_value(file_fmt).build(); + let _ = self + .view + .update(super::COMPONENT_INPUT_REMOTE_FILE_FMT, props); } } } @@ -734,8 +757,15 @@ impl SetupActivity { let check: bool = matches!(opt, 0); cli.set_check_for_updates(check); } - if let Some(Payload::Text(fmt)) = self.view.get_state(super::COMPONENT_INPUT_FILE_FMT) { - cli.set_file_fmt(fmt); + if let Some(Payload::Text(fmt)) = + self.view.get_state(super::COMPONENT_INPUT_LOCAL_FILE_FMT) + { + cli.set_local_file_fmt(fmt); + } + if let Some(Payload::Text(fmt)) = + self.view.get_state(super::COMPONENT_INPUT_REMOTE_FILE_FMT) + { + cli.set_remote_file_fmt(fmt); } if let Some(Payload::Unsigned(opt)) = self.view.get_state(super::COMPONENT_RADIO_GROUP_DIRS)