From a0b357cf8c2ccd9c938d952225caecee1f2c982f Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Sun, 9 Nov 2025 21:14:42 +0100 Subject: [PATCH] feat: Added `` keybinding to get the total size of selected paths. (#367) * feat: Added `` keybinding to get the total size of selected paths. closes #297 --- CHANGELOG.md | 1 + docs/de/man.md | 1 + docs/es/man.md | 21 +++-- docs/fr/man.md | 21 +++-- docs/it/man.md | 19 ++-- docs/man.md | 1 + docs/pt-BR/man.md | 1 + docs/zh-CN/man.md | 19 ++-- .../filetransfer/actions/file_size.rs | 94 +++++++++++++++++++ src/ui/activities/filetransfer/actions/mod.rs | 1 + .../filetransfer/components/popups.rs | 5 + .../filetransfer/components/transfer/mod.rs | 16 ++++ src/ui/activities/filetransfer/mod.rs | 1 + src/ui/activities/filetransfer/update.rs | 3 + 14 files changed, 166 insertions(+), 38 deletions(-) create mode 100644 src/ui/activities/filetransfer/actions/file_size.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 17af7c0..c7d8209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Released on 20/09/2025 +- [Issue 297](https://github.com/veeso/termscp/issues/297): Added `` 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. diff --git a/docs/de/man.md b/docs/de/man.md index be25040..9a38028 100644 --- a/docs/de/man.md +++ b/docs/de/man.md @@ -316,6 +316,7 @@ Diese Panels sind im Wesentlichen 3 (ja, tatsächlich drei): | | Alle Dateien auswählen | | | | Alle Dateien abwählen | | | | Dateiübertragungsvorgang abbrechen | | +| `` | Gesamte Größe des ausgewählten Pfads abrufen | Size | | | Alle synchronisierten Pfade anzeigen | Track | ### Mit mehreren Dateien arbeiten 🥷 diff --git a/docs/es/man.md b/docs/es/man.md index 8ce718e..43904cf 100644 --- a/docs/es/man.md +++ b/docs/es/man.md @@ -251,25 +251,25 @@ Para cambiar de panel, debe escribir `` para mover el panel del explorador | `` | Cambiar entre la pestaña de registro y el explorador | | | `` | Alternar archivos ocultos | All | | `` | Ordenar archivos por | Bubblesort? | -| `` | Copiar archivo / directorio | Copy | -| `` | Hacer directorio | Directory | -| `` | Eliminar archivo | Erase | +| `` | Copiar archivo / directorio | Copy | +| `` | Hacer directorio | Directory | +| `` | Eliminar archivo | Erase | | `` | Búsqueda de archivos | Find | | `` | Ir a la ruta proporcionada | Go to | -| `` | Mostrar ayuda | Help | +| `` | Mostrar ayuda | Help | | `` | Mostrar información sobre el archivo | Info | | `` | Crear un enlace simbólico que apunte a la entrada seleccionada actualmente | symlinK | | `` | Recargar contenido del directorio / Borrar selección | List | | `` | Seleccione un archivo | Mark | | `` | Crear un nuevo archivo con el nombre proporcionado | New | -| `` | Editar archivo | Open | +| `` | Editar archivo | Open | | `

` | Ouvre le panel de journals | Panel | -| `` | Quitter termscp | Quit | -| `` | Renommer le fichier | Rename | -| `` | Enregistrer le fichier sous... | Save | +| `` | Quitter termscp | Quit | +| `` | Renommer le fichier | Rename | +| `` | Enregistrer le fichier sous... | Save | | `` | Synchroniser les modifications apportées au chemin sélectionné | Track | | `` | Aller dans le répertoire parent | Upper | -| `` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View | +| `` | Ouvrir le fichier avec le programme défaut pour le type de fichier | View | | `` | Ouvrir le fichier avec le programme spécifié | With | | `` | Exécuter une commande | eXecute | | `` | Basculer la navigation synchronisée | sYnc | @@ -276,9 +276,10 @@ Pour changer de panneau, vous devez taper `` pour déplacer le panneau de | `` | Sélectionner tous les fichiers | | | `` | Desélectionner tous les fichiers | | | `` | Abandonner le processus de transfert de fichiers | | +| `` | Obtenir la taille totale du chemin sélectionné | Size | | `` | 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 : diff --git a/docs/it/man.md b/docs/it/man.md index d583026..770974a 100644 --- a/docs/it/man.md +++ b/docs/it/man.md @@ -245,25 +245,25 @@ Per cambiare pannello ti puoi muovere con le frecce, `` per andare sul pan | `` | Cambia tra explorer e pannello di log | | | `` | Mostra/nascondi file nascosti | All | | `` | Ordina file per | Bubblesort? | -| `` | Copia file/directory | Copy | -| `` | Crea directory | Directory | -| `` | Elimina file | Erase | +| `` | Copia file/directory | Copy | +| `` | Crea directory | Directory | +| `` | Elimina file | Erase | | `` | Cerca file (wild match supportato) | Find | | `` | Vai al percorso indicato | Go to | -| `` | Mostra help | Help | +| `` | Mostra help | Help | | `` | Mostra informazioni per il file selezionato | Info | | `` | Crea un link simbolico che punta al file selezionato | symlinK | | `` | Ricarica posizione corrente / pulisci selezione file | List | | `` | Seleziona file | Mark | | `` | Crea nuovo file con il nome fornito | New | -| `` | Modifica file; Vedi text editor | Open | +| `` | Modifica file; Vedi text editor | Open | | `

` | Apri pannello log | Panel | -| `` | Termina termscp | Quit | -| `` | Rinomina file | Rename | -| `` | Salva file con nome | Save | +| `` | Termina termscp | Quit | +| `` | Rinomina file | Rename | +| `` | Salva file con nome | Save | | `` | Sincronizza il percorso locale con l'host remoto | Track | | `` | Vai alla directory padre | Upper | -| `` | Apri il file con il programma definito dal sistema | View | +| `` | Apri il file con il programma definito dal sistema | View | | `` | Apri il file con il programma specificato | With | | `` | Esegui comando shell | eXecute | | `` | Abilita/disabilita Sync-Browsing | sYnc | @@ -272,6 +272,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `` per andare sul pan | `` | Seleziona tutti i file | | | `` | Deseleziona tutti i file | | | `` | Annulla trasferimento file | | +| `` | Ottieni la dimensione totale del percorso selezionato | Size | | `` | Visualizza tutti i percorsi sincronizzati | Track | ### Lavora con più file 🥷 diff --git a/docs/man.md b/docs/man.md index 178ddae..9a29fc8 100644 --- a/docs/man.md +++ b/docs/man.md @@ -278,6 +278,7 @@ In order to change panel you need to type `` to move the remote explorer p | `` | Select all files | | | `` | Deselect all files | | | `` | Abort file transfer process | | +| `` | Get total size of the selected path | Size | | `` | Show all synchronized paths | Track | ### Work on multiple files 🥷 diff --git a/docs/pt-BR/man.md b/docs/pt-BR/man.md index 2fa2d34..c688c3e 100644 --- a/docs/pt-BR/man.md +++ b/docs/pt-BR/man.md @@ -278,6 +278,7 @@ Para trocar de painel, você precisa pressionar `` para mover para o paine | `` | Selecionar todos os arquivos | | | `` | Deselecionar todos os arquivos | | | `` | Abortir processo de transferência de arquivo | | +| `` | Obter o tamanho total do caminho selecionado | | Size | | `` | Mostrar todos os caminhos sincronizados | Track | ### Trabalhar com múltiplos arquivos 🥷 diff --git a/docs/zh-CN/man.md b/docs/zh-CN/man.md index a04fc62..e2b21ba 100644 --- a/docs/zh-CN/man.md +++ b/docs/zh-CN/man.md @@ -245,25 +245,25 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到 | `` | 在日志面板和管理器面板之间切换 | | | `` | 是否显示隐藏文件 | All | | `` | 按..排序 | Bubblesort? | -| `` | 复制文件(夹) | Copy | -| `` | 创建文件夹 | Directory | -| `` | 删除文件 | Erase | +| `` | 复制文件(夹) | Copy | +| `` | 创建文件夹 | Directory | +| `` | 删除文件 | Erase | | `` | 文件搜索 (支持通配符) | Find | | `` | 跳转到指定路径 | Go to | -| `` | 显示帮助 | Help | +| `` | 显示帮助 | Help | | `` | 显示选中文件(夹)信息 | Info | | `` | 创建指向当前选定条目的符号链接 | symlinK | | `` | 刷新当前目录列表 / 清除选中状态 | List | | `` | 选中文件 | Mark | | `` | 使用键入的名称新建文件 | New | -| `` | 编辑文件;参考文本编辑器文档 | Open | +| `` | 编辑文件;参考文本编辑器文档 | Open | | `

` | 打开日志面板 | Panel | -| `` | 退出termscp | Quit | -| `` | 重命名文件 | Rename | -| `` | 另存为... | Save | +| `` | 退出termscp | Quit | +| `` | 重命名文件 | Rename | +| `` | 另存为... | Save | | `` | 显示所有同步路径 | Track | | `` | 进入上层目录 | Upper | -| `` | 使用默认方式打开文件 | View | +| `` | 使用默认方式打开文件 | View | | `` | 使用指定程序打开文件 | With | | `` | 运行命令 | eXecute | | `` | 是否开启同步浏览 | sYnc | @@ -272,6 +272,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到 | `` | 选中所有文件 | | | `` | 取消选择所有文件 | | | `` | 终止文件传输 | | +| `` | 获取所选路径的总大小 | Size | | `` | 显示所有同步路径 | Track | ### 操作多个文件 🥷 diff --git a/src/ui/activities/filetransfer/actions/file_size.rs b/src/ui/activities/filetransfer/actions/file_size.rs new file mode 100644 index 0000000..fb0251e --- /dev/null +++ b/src/ui/activities/filetransfer/actions/file_size.rs @@ -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, 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 + } + } +} diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index ee89bc6..600a0b0 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -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; diff --git a/src/ui/activities/filetransfer/components/popups.rs b/src/ui/activities/filetransfer/components/popups.rs index efd6a61..2c5469a 100644 --- a/src/ui/activities/filetransfer/components/popups.rs +++ b/src/ui/activities/filetransfer/components/popups.rs @@ -644,6 +644,11 @@ impl KeybindingsPopup { .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Interrupt file transfer")) .add_row() + .add_col(TextSpan::new("").bold().fg(key_color)) + .add_col(TextSpan::from( + " Get total path size of selected files", + )) + .add_row() .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show watched paths")) .build(), diff --git a/src/ui/activities/filetransfer/components/transfer/mod.rs b/src/ui/activities/filetransfer/components/transfer/mod.rs index 9ef347a..29eb61a 100644 --- a/src/ui/activities/filetransfer/components/transfer/mod.rs +++ b/src/ui/activities/filetransfer/components/transfer/mod.rs @@ -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 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 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 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, diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index a3ba76d..d0b43e7 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -117,6 +117,7 @@ enum TransferMsg { DeleteFile, EnterDirectory, ExecuteCmd(String), + GetFileSize, GoTo(String), GoToParentDirectory, GoToPreviousDirectory, diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 99f897f..bce542c 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -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),

` | Open log panel | Panel | -| `` | Salir de termscp | Quit | -| `` | Renombrar archivo | Rename | -| `` | Guardar archivo como... | Save | +| `` | Salir de termscp | Quit | +| `` | Renombrar archivo | Rename | +| `` | Guardar archivo como... | Save | | `` | Sincronizar los cambios en la ruta seleccionada con el control remoto | Track | | `` | Ir al directorio principal | Upper | -| `` | Abrir archivo con el programa predeterminado | View | +| `` | Abrir archivo con el programa predeterminado | View | | `` | Abrir archivo con el programa proporcionado | With | | `` | Ejecutar un comando | eXecute | | `` | Alternar navegación sincronizada | sYnc | @@ -278,9 +278,10 @@ Para cambiar de panel, debe escribir `` para mover el panel del explorador | `` | Seleccionar todos los archivos | | | `` | Deseleccionar todos los archivos | | | `` | Abortar el proceso de transferencia de archivos | | +| `` | Obtener el tamaño total de la ruta seleccionada | Size | | `` | 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: diff --git a/docs/fr/man.md b/docs/fr/man.md index d3c177a..37fc5e0 100644 --- a/docs/fr/man.md +++ b/docs/fr/man.md @@ -249,25 +249,25 @@ Pour changer de panneau, vous devez taper `` pour déplacer le panneau de | `` | Basculer entre l'onglet journal et l'explorateur | | | `` | Basculer les fichiers cachés | All | | `` | Trier les fichiers par | Bubblesort? | -| `` | Copier le fichier/répertoire | Copy | -| `` | Créer un dossier | Directory | -| `` | Supprimer le fichier (Identique à `DEL`) | Erase | +| `` | Copier le fichier/répertoire | Copy | +| `` | Créer un dossier | Directory | +| `` | Supprimer le fichier (Identique à `DEL`) | Erase | | `` | Rechercher des fichiers | Find | | `` | Aller au chemin fourni | Go to | -| `` | Afficher l'aide | Help | +| `` | Afficher l'aide | Help | | `` | Afficher les informations sur le fichier ou le dossier sélectionné | Info | | `` | Créer un lien symbolique pointant vers l'entrée actuellement sélectionnée | symlinK | | `` | Recharger le contenu du répertoire actuel / Effacer la sélection | List | | `` | Sélectionner un fichier | Mark | | `` | Créer un nouveau fichier avec le nom fourni | New | -| `` | Modifier le fichier | Open | +| `` | Modifier le fichier | Open | | `