feat: Added <CTRL+S> keybinding to get the total size of selected paths. (#367)
Some checks failed
Linux / build (push) Has been cancelled
MacOS / build (push) Has been cancelled
Windows / build (push) Has been cancelled

* feat: Added `<CTRL+S>` keybinding to get the total size of selected paths.

closes #297
This commit is contained in:
Christian Visintin
2025-11-09 21:14:42 +01:00
committed by GitHub
parent 75943f2b93
commit a0b357cf8c
14 changed files with 166 additions and 38 deletions

View File

@@ -46,6 +46,7 @@
Released on 20/09/2025
- [Issue 297](https://github.com/veeso/termscp/issues/297): Added `<CTRL+S>` keybinding to get the total size of selected paths.
- [Issue 331](https://github.com/veeso/termscp/issues/331): Added new `import-ssh-hosts` CLI subcommand to import all the hosts from the ssh config as bookmarks.
- [Issue 335](https://github.com/veeso/termscp/issues/335): Changed file overwrite behaviour
- Now the user can choose for each file whether to overwrite, skip or overwrite all/skip all.

View File

@@ -316,6 +316,7 @@ Diese Panels sind im Wesentlichen 3 (ja, tatsächlich drei):
| <CTRL+A> | Alle Dateien auswählen | |
| <ALT+A> | Alle Dateien abwählen | |
| <CTRL+C> | Dateiübertragungsvorgang abbrechen | |
| `<CTRL+S>` | Gesamte Größe des ausgewählten Pfads abrufen | Size |
| <CTRL+T> | Alle synchronisierten Pfade anzeigen | Track |
### Mit mehreren Dateien arbeiten 🥷

View File

@@ -251,25 +251,25 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
| `<BACKTAB>` | Cambiar entre la pestaña de registro y el explorador | |
| `<A>` | Alternar archivos ocultos | All |
| `<B>` | Ordenar archivos por | Bubblesort? |
| `<C|F5>` | Copiar archivo / directorio | Copy |
| `<D|F7>` | Hacer directorio | Directory |
| `<E|F8|DEL>` | Eliminar archivo | Erase |
| `<C\|F5>` | Copiar archivo / directorio | Copy |
| `<D\|F7>` | Hacer directorio | Directory |
| `<E\|F8\|DEL>` | Eliminar archivo | Erase |
| `<F>` | Búsqueda de archivos | Find |
| `<G>` | Ir a la ruta proporcionada | Go to |
| `<H|F1>` | Mostrar ayuda | Help |
| `<H\|F1>` | Mostrar ayuda | Help |
| `<I>` | Mostrar información sobre el archivo | Info |
| `<K>` | Crear un enlace simbólico que apunte a la entrada seleccionada actualmente | symlinK |
| `<L>` | Recargar contenido del directorio / Borrar selección | List |
| `<M>` | Seleccione un archivo | Mark |
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
| `<O|F4>` | Editar archivo | Open |
| `<O\|F4>` | Editar archivo | Open |
| `<P>` | Open log panel | Panel |
| `<Q|F10>` | Salir de termscp | Quit |
| `<R|F6>` | Renombrar archivo | Rename |
| `<S|F2>` | Guardar archivo como... | Save |
| `<Q\|F10>` | Salir de termscp | Quit |
| `<R\|F6>` | Renombrar archivo | Rename |
| `<S\|F2>` | Guardar archivo como... | Save |
| `<T>` | Sincronizar los cambios en la ruta seleccionada con el control remoto | Track |
| `<U>` | Ir al directorio principal | Upper |
| `<V|F3>` | Abrir archivo con el programa predeterminado | View |
| `<V\|F3>` | Abrir archivo con el programa predeterminado | View |
| `<W>` | Abrir archivo con el programa proporcionado | With |
| `<X>` | Ejecutar un comando | eXecute |
| `<Y>` | Alternar navegación sincronizada | sYnc |
@@ -278,9 +278,10 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
| `<CTRL+A>` | Seleccionar todos los archivos | |
| `<ALT+A>` | Deseleccionar todos los archivos | |
| `<CTRL+C>` | Abortar el proceso de transferencia de archivos | |
| `<CTRL+S>` | Obtener el tamaño total de la ruta seleccionada | Size |
| `<CTRL+T>` | Mostrar todas las rutas sincronizadas | Track |
### Trabajar con múltiples archivos 🥷
### Trabajar con múltiples archivos 🥷
Puedes optar por trabajar con varios archivos, usando estos controles:

View File

@@ -249,25 +249,25 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
| `<BACKTAB>` | Basculer entre l'onglet journal et l'explorateur | |
| `<A>` | Basculer les fichiers cachés | All |
| `<B>` | Trier les fichiers par | Bubblesort? |
| `<C|F5>` | Copier le fichier/répertoire | Copy |
| `<D|F7>` | Créer un dossier | Directory |
| `<E|F8|DEL>` | Supprimer le fichier (Identique à `DEL`) | Erase |
| `<C\|F5>` | Copier le fichier/répertoire | Copy |
| `<D\|F7>` | Créer un dossier | Directory |
| `<E\|F8\|DEL>` | Supprimer le fichier (Identique à `DEL`) | Erase |
| `<F>` | Rechercher des fichiers | Find |
| `<G>` | Aller au chemin fourni | Go to |
| `<H|F1>` | Afficher l'aide | Help |
| `<H\|F1>` | Afficher l'aide | Help |
| `<I>` | Afficher les informations sur le fichier ou le dossier sélectionné | Info |
| `<K>` | Créer un lien symbolique pointant vers l'entrée actuellement sélectionnée | symlinK |
| `<L>` | Recharger le contenu du répertoire actuel / Effacer la sélection | List |
| `<M>` | Sélectionner un fichier | Mark |
| `<N>` | Créer un nouveau fichier avec le nom fourni | New |
| `<O|F4>` | Modifier le fichier | Open |
| `<O\|F4>` | Modifier le fichier | Open |
| `<P>` | Ouvre le panel de journals | Panel |
| `<Q|F10>` | Quitter termscp | Quit |
| `<R|F6>` | Renommer le fichier | Rename |
| `<S|F2>` | Enregistrer le fichier sous... | Save |
| `<Q\|F10>` | Quitter termscp | Quit |
| `<R\|F6>` | Renommer le fichier | Rename |
| `<S\|F2>` | Enregistrer le fichier sous... | Save |
| `<T>` | Synchroniser les modifications apportées au chemin sélectionné | Track |
| `<U>` | Aller dans le répertoire parent | Upper |
| `<V|F3>` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View |
| `<V\|F3>` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View |
| `<W>` | Ouvrir le fichier avec le programme spécifié | With |
| `<X>` | Exécuter une commande | eXecute |
| `<Y>` | Basculer la navigation synchronisée | sYnc |
@@ -276,9 +276,10 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
| `<CTRL+A>` | Sélectionner tous les fichiers | |
| `<ALT+A>` | Desélectionner tous les fichiers | |
| `<CTRL+C>` | Abandonner le processus de transfert de fichiers | |
| `<CTRL+S>` | Obtenir la taille totale du chemin sélectionné | Size |
| `<CTRL+T>` | Afficher tous les chemins synchronisés | Track |
### Travailler sur plusieurs fichiers 🥷
### Travailler sur plusieurs fichiers 🥷
Vous pouvez choisir de travailler sur plusieurs fichiers avec ces simples commandes :

View File

@@ -245,25 +245,25 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
| `<BACKTAB>` | Cambia tra explorer e pannello di log | |
| `<A>` | Mostra/nascondi file nascosti | All |
| `<B>` | Ordina file per | Bubblesort? |
| `<C|F5>` | Copia file/directory | Copy |
| `<D|F7>` | Crea directory | Directory |
| `<E|F8|DEL>` | Elimina file | Erase |
| `<C\|F5>` | Copia file/directory | Copy |
| `<D\|F7>` | Crea directory | Directory |
| `<E\|F8\|DEL>` | Elimina file | Erase |
| `<F>` | Cerca file (wild match supportato) | Find |
| `<G>` | Vai al percorso indicato | Go to |
| `<H|F1>` | Mostra help | Help |
| `<H\|F1>` | Mostra help | Help |
| `<I>` | Mostra informazioni per il file selezionato | Info |
| `<K>` | Crea un link simbolico che punta al file selezionato | symlinK |
| `<L>` | Ricarica posizione corrente / pulisci selezione file | List |
| `<M>` | Seleziona file | Mark |
| `<N>` | Crea nuovo file con il nome fornito | New |
| `<O|F4>` | Modifica file; Vedi text editor | Open |
| `<O\|F4>` | Modifica file; Vedi text editor | Open |
| `<P>` | Apri pannello log | Panel |
| `<Q|F10>` | Termina termscp | Quit |
| `<R|F6>` | Rinomina file | Rename |
| `<S|F2>` | Salva file con nome | Save |
| `<Q\|F10>` | Termina termscp | Quit |
| `<R\|F6>` | Rinomina file | Rename |
| `<S\|F2>` | Salva file con nome | Save |
| `<T>` | Sincronizza il percorso locale con l'host remoto | Track |
| `<U>` | Vai alla directory padre | Upper |
| `<V|F3>` | Apri il file con il programma definito dal sistema | View |
| `<V\|F3>` | Apri il file con il programma definito dal sistema | View |
| `<W>` | Apri il file con il programma specificato | With |
| `<X>` | Esegui comando shell | eXecute |
| `<Y>` | Abilita/disabilita Sync-Browsing | sYnc |
@@ -272,6 +272,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
| `<CTRL+A>` | Seleziona tutti i file | |
| `<ALT+A>` | Deseleziona tutti i file | |
| `<CTRL+C>` | Annulla trasferimento file | |
| `<CTRL+S>` | Ottieni la dimensione totale del percorso selezionato | Size |
| `<CTRL+T>` | Visualizza tutti i percorsi sincronizzati | Track |
### Lavora con più file 🥷

View File

@@ -278,6 +278,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
| `<CTRL+A>` | Select all files | |
| `<ALT+A>` | Deselect all files | |
| `<CTRL+C>` | Abort file transfer process | |
| `<CTRL+S>` | Get total size of the selected path | Size |
| `<CTRL+T>` | Show all synchronized paths | Track |
### Work on multiple files 🥷

View File

@@ -278,6 +278,7 @@ Para trocar de painel, você precisa pressionar `<LEFT>` para mover para o paine
| `<CTRL+A>` | Selecionar todos os arquivos | |
| `<ALT+A>` | Deselecionar todos os arquivos | |
| `<CTRL+C>` | Abortir processo de transferência de arquivo | |
| `<CTRL+S>` | Obter o tamanho total do caminho selecionado | | Size |
| `<CTRL+T>` | Mostrar todos os caminhos sincronizados | Track |
### Trabalhar com múltiplos arquivos 🥷

View File

@@ -245,25 +245,25 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
| `<BACKTAB>` | 在日志面板和管理器面板之间切换 | |
| `<A>` | 是否显示隐藏文件 | All |
| `<B>` | 按..排序 | Bubblesort? |
| `<C|F5>` | 复制文件(夹) | Copy |
| `<D|F7>` | 创建文件夹 | Directory |
| `<E|F8|DEL>` | 删除文件 | Erase |
| `<C\|F5>` | 复制文件(夹) | Copy |
| `<D\|F7>` | 创建文件夹 | Directory |
| `<E\|F8\|DEL>` | 删除文件 | Erase |
| `<F>` | 文件搜索 (支持通配符) | Find |
| `<G>` | 跳转到指定路径 | Go to |
| `<H|F1>` | 显示帮助 | Help |
| `<H\|F1>` | 显示帮助 | Help |
| `<I>` | 显示选中文件(夹)信息 | Info |
| `<K>` | 创建指向当前选定条目的符号链接 | symlinK |
| `<L>` | 刷新当前目录列表 / 清除选中状态 | List |
| `<M>` | 选中文件 | Mark |
| `<N>` | 使用键入的名称新建文件 | New |
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
| `<O\|F4>` | 编辑文件;参考文本编辑器文档 | Open |
| `<P>` | 打开日志面板 | Panel |
| `<Q|F10>` | 退出termscp | Quit |
| `<R|F7>` | 重命名文件 | Rename |
| `<S|F2>` | 另存为... | Save |
| `<Q\|F10>` | 退出termscp | Quit |
| `<R\|F7>` | 重命名文件 | Rename |
| `<S\|F2>` | 另存为... | Save |
| `<T>` | 显示所有同步路径 | Track |
| `<U>` | 进入上层目录 | Upper |
| `<V|F3>` | 使用默认方式打开文件 | View |
| `<V\|F3>` | 使用默认方式打开文件 | View |
| `<W>` | 使用指定程序打开文件 | With |
| `<X>` | 运行命令 | eXecute |
| `<Y>` | 是否开启同步浏览 | sYnc |
@@ -272,6 +272,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
| `<CTRL+A>` | 选中所有文件 | |
| `<ALT+A>` | 取消选择所有文件 | |
| `<CTRL+C>` | 终止文件传输 | |
| `<CTRL+S>` | 获取所选路径的总大小 | Size |
| `<CTRL+T>` | 显示所有同步路径 | Track |
### 操作多个文件 🥷

View File

@@ -0,0 +1,94 @@
use remotefs::File;
use super::{FileTransferActivity, LogLevel};
use crate::ui::activities::filetransfer::lib::browser::FileExplorerTab;
#[derive(Debug, Copy, Clone)]
enum Host {
HostBridge,
Remote,
}
impl FileTransferActivity {
pub(crate) fn action_get_file_size(&mut self) {
// Get selected file
self.mount_blocking_wait("Getting total path size...");
let total_size = match self.browser.tab() {
FileExplorerTab::HostBridge => {
let files = self.get_local_selected_entries().get_files();
self.get_files_size(files, Host::HostBridge)
}
FileExplorerTab::Remote => {
let files = self.get_remote_selected_entries().get_files();
self.get_files_size(files, Host::Remote)
}
FileExplorerTab::FindHostBridge => {
let files = self.get_found_selected_entries().get_files();
self.get_files_size(files, Host::HostBridge)
}
FileExplorerTab::FindRemote => {
let files = self.get_found_selected_entries().get_files();
self.get_files_size(files, Host::Remote)
}
};
self.umount_wait();
self.mount_info(format!(
"Total file size: {size}",
size = bytesize::ByteSize::b(total_size)
));
}
fn get_files_size(&mut self, files: Vec<File>, host: Host) -> u64 {
files.into_iter().map(|f| self.get_file_size(f, host)).sum()
}
fn get_file_size(&mut self, file: File, host: Host) -> u64 {
if let Some(symlink) = &file.metadata().symlink {
// stat
let stat_res = match host {
Host::HostBridge => self.host_bridge.stat(symlink).map_err(|e| e.to_string()),
Host::Remote => self.client.stat(symlink).map_err(|e| e.to_string()),
};
match stat_res {
Ok(stat) => stat.metadata().size,
Err(err_msg) => {
self.log(
LogLevel::Error,
format!(
"Failed to stat symlink target {path}: {err_msg}",
path = symlink.display(),
),
);
0
}
}
} else if file.is_dir() {
// list and sum
let list_res = match host {
Host::HostBridge => self
.host_bridge
.list_dir(&file.path)
.map_err(|e| e.to_string()),
Host::Remote => self.client.list_dir(&file.path).map_err(|e| e.to_string()),
};
match list_res {
Ok(list) => list.into_iter().map(|f| self.get_file_size(f, host)).sum(),
Err(err_msg) => {
self.log(
LogLevel::Error,
format!(
"Failed to list directory {path}: {err_msg}",
path = file.path.display(),
),
);
0
}
}
} else {
file.metadata().size
}
}
}

View File

@@ -23,6 +23,7 @@ pub(crate) mod copy;
pub(crate) mod delete;
pub(crate) mod edit;
pub(crate) mod exec;
pub(crate) mod file_size;
pub(crate) mod filter;
pub(crate) mod find;
pub(crate) mod mark;

View File

@@ -644,6 +644,11 @@ impl KeybindingsPopup {
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
.add_col(TextSpan::from(" Interrupt file transfer"))
.add_row()
.add_col(TextSpan::new("<CTRL+S>").bold().fg(key_color))
.add_col(TextSpan::from(
" Get total path size of selected files",
))
.add_row()
.add_col(TextSpan::new("<CTRL+T>").bold().fg(key_color))
.add_col(TextSpan::from(" Show watched paths"))
.build(),

View File

@@ -192,6 +192,10 @@ impl ExplorerFuzzy {
code: Key::Char('i'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowFileInfoPopup)),
Event::Keyboard(KeyEvent {
code: Key::Char('s'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Msg::Transfer(TransferMsg::GetFileSize)),
Event::Keyboard(KeyEvent {
code: Key::Char('s') | Key::Function(2),
modifiers: KeyModifiers::NONE,
@@ -338,6 +342,10 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
code: Key::Char('i'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowFileInfoPopup)),
Event::Keyboard(KeyEvent {
code: Key::Char('s'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Msg::Transfer(TransferMsg::GetFileSize)),
Event::Keyboard(KeyEvent {
code: Key::Char('s') | Key::Function(2),
modifiers: KeyModifiers::NONE,
@@ -528,6 +536,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowRenamePopup)),
Event::Keyboard(KeyEvent {
code: Key::Char('s'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Msg::Transfer(TransferMsg::GetFileSize)),
Event::Keyboard(KeyEvent {
code: Key::Char('s') | Key::Function(2),
modifiers: KeyModifiers::NONE,
@@ -742,6 +754,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
code: Key::Char('r') | Key::Function(6),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::Ui(UiMsg::ShowRenamePopup)),
Event::Keyboard(KeyEvent {
code: Key::Char('s'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Msg::Transfer(TransferMsg::GetFileSize)),
Event::Keyboard(KeyEvent {
code: Key::Char('s') | Key::Function(2),
modifiers: KeyModifiers::NONE,

View File

@@ -117,6 +117,7 @@ enum TransferMsg {
DeleteFile,
EnterDirectory,
ExecuteCmd(String),
GetFileSize,
GoTo(String),
GoToParentDirectory,
GoToPreviousDirectory,

View File

@@ -152,6 +152,9 @@ impl FileTransferActivity {
_ => panic!("Found tab doesn't support EXEC"),
};
}
TransferMsg::GetFileSize => {
self.action_get_file_size();
}
TransferMsg::GoTo(dir) => {
match self.browser.tab() {
FileExplorerTab::HostBridge => self.action_change_local_dir(dir),