mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
Remote file syntax for formatter
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ pub struct UserInterfaceConfig {
|
||||
pub show_hidden_files: bool,
|
||||
pub check_for_updates: Option<bool>, // @! Since 0.3.3
|
||||
pub group_dirs: Option<String>,
|
||||
pub file_fmt: Option<String>,
|
||||
pub file_fmt: Option<String>, // Refers to local host (for backward compatibility)
|
||||
pub remote_file_fmt: Option<String>, // @! 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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<String> {
|
||||
/// Get current file fmt for local host
|
||||
pub fn get_local_file_fmt(&self) -> Option<String> {
|
||||
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<String> {
|
||||
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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 <UP>
|
||||
(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 <ENTER> or <ESC>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user