From ca13ebb193ddf9d80b48a4f8e9d08a1887c4d42d Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Mon, 19 Apr 2021 08:56:23 +0530 Subject: [PATCH] Added inode size Also supports sorting by inode size. Closes: https://github.com/sayanarijit/xplr/issues/84 --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/app.rs | 150 +++++++++++++++++++++++++++++-------------------- src/config.yml | 76 +++++++++---------------- src/runner.rs | 6 +- src/ui.rs | 14 +++-- 6 files changed, 136 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93eddc3..8f575f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -552,6 +552,12 @@ dependencies = [ "libc", ] +[[package]] +name = "humansize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" + [[package]] name = "ident_case" version = "1.0.1" @@ -1632,6 +1638,7 @@ dependencies = [ "crossterm", "dirs", "handlebars", + "humansize", "indexmap", "lazy_static", "mime_guess", diff --git a/Cargo.toml b/Cargo.toml index 4b29e1c..c9795f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ notify = "4.0.12" lazy_static = "1.4.0" indexmap = { version = "1.6.2", features = ["serde"] } natord = "1.0.9" +humansize = "1.1.0" [dev-dependencies] criterion = "0.3" diff --git a/src/app.rs b/src/app.rs index 261be42..9979722 100644 --- a/src/app.rs +++ b/src/app.rs @@ -91,6 +91,7 @@ pub struct ResolvedNode { pub is_file: bool, pub is_readonly: bool, pub mime_essence: String, + pub size: u64, } impl ResolvedNode { @@ -100,15 +101,10 @@ impl ResolvedNode { .map(|e| e.to_string_lossy().to_string()) .unwrap_or_default(); - let maybe_metadata = path.metadata().ok(); - - let is_dir = maybe_metadata.clone().map(|m| m.is_dir()).unwrap_or(false); - - let is_file = maybe_metadata.clone().map(|m| m.is_file()).unwrap_or(false); - - let is_readonly = maybe_metadata - .map(|m| m.permissions().readonly()) - .unwrap_or(false); + let (is_dir, is_file, is_readonly, size) = path + .metadata() + .map(|m| (m.is_dir(), m.is_file(), m.permissions().readonly(), m.len())) + .unwrap_or((false, false, false, 0)); let mime_essence = mime_guess::from_path(&path) .first() @@ -122,6 +118,7 @@ impl ResolvedNode { is_file, is_readonly, mime_essence, + size, } } } @@ -138,6 +135,7 @@ pub struct Node { pub is_broken: bool, pub is_readonly: bool, pub mime_essence: String, + pub size: u64, pub canonical: Option, pub symlink: Option, } @@ -156,25 +154,23 @@ impl Node { .map(|e| e.to_string_lossy().to_string()) .unwrap_or_default(); - let maybe_metadata = path.symlink_metadata().ok(); - - let is_symlink = maybe_metadata - .clone() - .map(|m| m.file_type().is_symlink()) - .unwrap_or(false); - let (is_broken, maybe_canonical_meta) = path .canonicalize() .map(|p| (false, Some(ResolvedNode::from(p)))) .unwrap_or_else(|_| (true, None)); - let is_dir = maybe_metadata.clone().map(|m| m.is_dir()).unwrap_or(false); - - let is_file = maybe_metadata.clone().map(|m| m.is_file()).unwrap_or(false); - - let is_readonly = maybe_metadata - .map(|m| m.permissions().readonly()) - .unwrap_or(false); + let (is_symlink, is_dir, is_file, is_readonly, size) = path + .symlink_metadata() + .map(|m| { + ( + m.file_type().is_symlink(), + m.is_dir(), + m.is_file(), + m.permissions().readonly(), + m.len(), + ) + }) + .unwrap_or((false, false, false, false, 0)); let mime_essence = mime_guess::from_path(&path) .first() @@ -192,6 +188,7 @@ impl Node { is_broken, is_readonly, mime_essence, + size, canonical: maybe_canonical_meta.clone(), symlink: if is_symlink { maybe_canonical_meta @@ -258,6 +255,7 @@ pub enum NodeSorter { ByIsBroken, ByIsReadonly, ByMimeEssence, + BySize, ByCanonicalAbsolutePath, ByICanonicalAbsolutePath, @@ -266,6 +264,7 @@ pub enum NodeSorter { ByCanonicalIsFile, ByCanonicalIsReadonly, ByCanonicalMimeEssence, + ByCanonicalSize, BySymlinkAbsolutePath, ByISymlinkAbsolutePath, @@ -274,6 +273,7 @@ pub enum NodeSorter { BySymlinkIsFile, BySymlinkIsReadonly, BySymlinkMimeEssence, + BySymlinkSize, } #[derive(Debug, Clone, Eq, Serialize, Deserialize)] @@ -330,6 +330,8 @@ impl NodeSorterApplicable { (NodeSorter::ByIsReadonly, true) => b.is_readonly.cmp(&a.is_readonly), (NodeSorter::ByMimeEssence, false) => a.mime_essence.cmp(&b.mime_essence), (NodeSorter::ByMimeEssence, true) => b.mime_essence.cmp(&a.mime_essence), + (NodeSorter::BySize, false) => a.size.cmp(&b.size), + (NodeSorter::BySize, true) => b.size.cmp(&a.size), (NodeSorter::ByCanonicalAbsolutePath, false) => natord::compare( &a.canonical @@ -342,23 +344,23 @@ impl NodeSorterApplicable { .unwrap_or_default(), ), - (NodeSorter::ByICanonicalAbsolutePath, false) => natord::compare_ignore_case( - &a.canonical + (NodeSorter::ByCanonicalAbsolutePath, true) => natord::compare( + &b.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), - &b.canonical + &a.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), - (NodeSorter::ByCanonicalAbsolutePath, true) => natord::compare( - &b.canonical + (NodeSorter::ByICanonicalAbsolutePath, false) => natord::compare_ignore_case( + &a.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), - &a.canonical + &b.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), @@ -393,17 +395,29 @@ impl NodeSorterApplicable { .map(|s| &s.is_dir) .cmp(&b.canonical.as_ref().map(|s| &s.is_dir)), + (NodeSorter::ByCanonicalIsDir, true) => b + .canonical + .as_ref() + .map(|s| &s.is_dir) + .cmp(&a.canonical.as_ref().map(|s| &s.is_dir)), + + (NodeSorter::ByCanonicalIsFile, false) => a + .canonical + .as_ref() + .map(|s| &s.is_file) + .cmp(&b.canonical.as_ref().map(|s| &s.is_file)), + (NodeSorter::ByCanonicalIsFile, true) => b .canonical .as_ref() .map(|s| &s.is_file) .cmp(&a.canonical.as_ref().map(|s| &s.is_file)), - (NodeSorter::ByCanonicalIsDir, true) => b + (NodeSorter::ByCanonicalIsReadonly, false) => a .canonical .as_ref() - .map(|s| &s.is_dir) - .cmp(&a.canonical.as_ref().map(|s| &s.is_dir)), + .map(|s| &s.is_readonly) + .cmp(&b.canonical.as_ref().map(|s| &s.is_readonly)), (NodeSorter::ByCanonicalIsReadonly, true) => b .canonical @@ -411,11 +425,11 @@ impl NodeSorterApplicable { .map(|s| &s.is_readonly) .cmp(&a.canonical.as_ref().map(|s| &s.is_readonly)), - (NodeSorter::ByCanonicalIsFile, false) => a + (NodeSorter::ByCanonicalMimeEssence, false) => a .canonical .as_ref() - .map(|s| &s.is_file) - .cmp(&b.canonical.as_ref().map(|s| &s.is_file)), + .map(|s| &s.mime_essence) + .cmp(&b.canonical.as_ref().map(|s| &s.mime_essence)), (NodeSorter::ByCanonicalMimeEssence, true) => b .canonical @@ -423,17 +437,17 @@ impl NodeSorterApplicable { .map(|s| &s.mime_essence) .cmp(&a.canonical.as_ref().map(|s| &s.mime_essence)), - (NodeSorter::ByCanonicalIsReadonly, false) => a + (NodeSorter::ByCanonicalSize, false) => a .canonical .as_ref() - .map(|s| &s.is_readonly) - .cmp(&b.canonical.as_ref().map(|s| &s.is_readonly)), + .map(|s| &s.size) + .cmp(&b.canonical.as_ref().map(|s| &s.size)), - (NodeSorter::ByCanonicalMimeEssence, false) => a + (NodeSorter::ByCanonicalSize, true) => b .canonical .as_ref() - .map(|s| &s.mime_essence) - .cmp(&b.canonical.as_ref().map(|s| &s.mime_essence)), + .map(|s| &s.size) + .cmp(&a.canonical.as_ref().map(|s| &s.size)), (NodeSorter::BySymlinkAbsolutePath, false) => natord::compare( &a.symlink @@ -446,23 +460,23 @@ impl NodeSorterApplicable { .unwrap_or_default(), ), - (NodeSorter::ByISymlinkAbsolutePath, false) => natord::compare_ignore_case( - &a.symlink + (NodeSorter::BySymlinkAbsolutePath, true) => natord::compare( + &b.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), - &b.symlink + &a.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), - (NodeSorter::BySymlinkAbsolutePath, true) => natord::compare( - &b.symlink + (NodeSorter::ByISymlinkAbsolutePath, false) => natord::compare_ignore_case( + &a.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), - &a.symlink + &b.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), @@ -479,17 +493,23 @@ impl NodeSorterApplicable { .unwrap_or_default(), ), + (NodeSorter::BySymlinkExtension, false) => a + .symlink + .as_ref() + .map(|s| &s.extension) + .cmp(&b.symlink.as_ref().map(|s| &s.extension)), + (NodeSorter::BySymlinkExtension, true) => b .symlink .as_ref() .map(|s| &s.extension) .cmp(&a.symlink.as_ref().map(|s| &s.extension)), - (NodeSorter::BySymlinkExtension, false) => a + (NodeSorter::BySymlinkIsDir, false) => a .symlink .as_ref() - .map(|s| &s.extension) - .cmp(&b.symlink.as_ref().map(|s| &s.extension)), + .map(|s| &s.is_dir) + .cmp(&b.symlink.as_ref().map(|s| &s.is_dir)), (NodeSorter::BySymlinkIsDir, true) => b .symlink @@ -497,11 +517,11 @@ impl NodeSorterApplicable { .map(|s| &s.is_dir) .cmp(&a.symlink.as_ref().map(|s| &s.is_dir)), - (NodeSorter::BySymlinkIsDir, false) => a + (NodeSorter::BySymlinkIsFile, false) => a .symlink .as_ref() - .map(|s| &s.is_dir) - .cmp(&b.symlink.as_ref().map(|s| &s.is_dir)), + .map(|s| &s.is_file) + .cmp(&b.symlink.as_ref().map(|s| &s.is_file)), (NodeSorter::BySymlinkIsFile, true) => b .symlink @@ -509,11 +529,11 @@ impl NodeSorterApplicable { .map(|s| &s.is_file) .cmp(&a.symlink.as_ref().map(|s| &s.is_file)), - (NodeSorter::BySymlinkIsFile, false) => a + (NodeSorter::BySymlinkIsReadonly, false) => a .symlink .as_ref() - .map(|s| &s.is_file) - .cmp(&b.symlink.as_ref().map(|s| &s.is_file)), + .map(|s| &s.is_readonly) + .cmp(&b.symlink.as_ref().map(|s| &s.is_readonly)), (NodeSorter::BySymlinkIsReadonly, true) => b .symlink @@ -521,11 +541,11 @@ impl NodeSorterApplicable { .map(|s| &s.is_readonly) .cmp(&a.symlink.as_ref().map(|s| &s.is_readonly)), - (NodeSorter::BySymlinkIsReadonly, false) => a + (NodeSorter::BySymlinkMimeEssence, false) => a .symlink .as_ref() - .map(|s| &s.is_readonly) - .cmp(&b.symlink.as_ref().map(|s| &s.is_readonly)), + .map(|s| &s.mime_essence) + .cmp(&b.symlink.as_ref().map(|s| &s.mime_essence)), (NodeSorter::BySymlinkMimeEssence, true) => b .symlink @@ -533,11 +553,17 @@ impl NodeSorterApplicable { .map(|s| &s.mime_essence) .cmp(&a.symlink.as_ref().map(|s| &s.mime_essence)), - (NodeSorter::BySymlinkMimeEssence, false) => a + (NodeSorter::BySymlinkSize, false) => a .symlink .as_ref() - .map(|s| &s.mime_essence) - .cmp(&b.symlink.as_ref().map(|s| &s.mime_essence)), + .map(|s| &s.size) + .cmp(&b.symlink.as_ref().map(|s| &s.size)), + + (NodeSorter::BySymlinkSize, true) => b + .symlink + .as_ref() + .map(|s| &s.size) + .cmp(&a.symlink.as_ref().map(|s| &s.size)), } } } diff --git a/src/config.yml b/src/config.yml index 9640f81..a7e546b 100644 --- a/src/config.yml +++ b/src/config.yml @@ -27,22 +27,6 @@ general: table: header: cols: - - format: │ path - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 - - format: type - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 - format: ' index' style: fg: null @@ -51,6 +35,9 @@ general: bits: 0 sub_modifier: bits: 0 + - format: '╭─ path' + - format: size + - format: type style: fg: null bg: null @@ -61,10 +48,10 @@ general: height: 1 row: cols: + - format: '{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}│{{{index}}}' - format: > {{{tree}}}{{{prefix}}}{{{meta.icon}}}{{#if (ne meta.icon "")}} {{/if}}{{{relativePath}}}{{#if isDir}}/{{/if}}{{{suffix}}} {{#if isSymlink}}-> {{#if isBroken}}×{{else}}{{{symlink.absolutePath}}}{{/if}}{{#if symlink.isDir}}/{{/if}}{{/if}} - style: fg: null bg: null @@ -72,22 +59,8 @@ general: bits: 0 sub_modifier: bits: 0 + - format: '{{humansize size}}' - format: '{{#if isSymlink}}{{{symlink.mimeEssence}}}{{else}}{{{mimeEssence}}}{{/if}}' - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 - - format: '{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}/{{{index}}}/{{{total}}}' - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 style: fg: null bg: null @@ -113,24 +86,11 @@ general: sub_modifier: bits: 0 - format: ├─ - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 - format: ╰─ - style: - fg: null - bg: null - add_modifier: - bits: 0 - sub_modifier: - bits: 0 col_spacing: 3 col_widths: - - percentage: 60 + - percentage: 10 + - percentage: 50 - percentage: 20 - percentage: 20 default_ui: @@ -192,6 +152,8 @@ general: format: "ro" ByMimeEssence: format: "mime" + BySize: + format: "size" ByCanonicalAbsolutePath: format: "[c]abs" ByICanonicalAbsolutePath: @@ -206,6 +168,8 @@ general: format: "[c]ro" ByCanonicalMimeEssence: format: "[c]mime" + ByCanonicalSize: + format: "[c]size" BySymlinkAbsolutePath: format: "[s]abs" ByISymlinkAbsolutePath: @@ -220,6 +184,8 @@ general: format: "[s]ro" BySymlinkMimeEssence: format: "[s]mime" + BySymlinkSize: + format: "[s]size" filter_identifiers: RelativePathIs: @@ -692,14 +658,12 @@ modes: sorter: ByIsSymlink reverse: true - Explore - m: help: by canonical mime essence messages: - AddNodeSorter: sorter: ByCanonicalMimeEssence - Explore - M: help: by canonical mime essence reverse messages: @@ -707,11 +671,23 @@ modes: sorter: ByCanonicalMimeEssence reverse: true - Explore + s: + help: by size + messages: + - AddNodeSorter: + sorter: BySize + - Explore + S: + help: by size reverse + messages: + - AddNodeSorter: + sorter: BySize + reverse: true + - Explore ctrl-c: help: terminate messages: - Terminate - default: messages: - SwitchMode: default diff --git a/src/runner.rs b/src/runner.rs index 2d1a6a0..294e7cb 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -10,7 +10,8 @@ use crate::ui; use anyhow::Result; use crossterm::execute; use crossterm::terminal as term; -use handlebars::Handlebars; +use handlebars::{handlebars_helper, Handlebars}; +use humansize::{file_size_opts as options, FileSize}; use std::fs; use std::io; use std::io::prelude::*; @@ -20,6 +21,8 @@ use termion::get_tty; use tui::backend::CrosstermBackend; use tui::Terminal; +handlebars_helper!(to_humansize: |size: i64| size.file_size(options::CONVENTIONAL).unwrap_or_default()); + fn call(app: &app::App, cmd: app::Command, silent: bool) -> io::Result { let input_buffer = app.input_buffer().unwrap_or_default(); @@ -86,6 +89,7 @@ pub fn run(mut app: app::App, focused_path: Option) -> Result for ResolvedNodeUiMetadata { @@ -85,6 +86,7 @@ impl From for ResolvedNodeUiMetadata { is_file: node.is_file, is_readonly: node.is_readonly, mime_essence: node.mime_essence, + size: node.size, } } } @@ -103,6 +105,7 @@ struct NodeUiMetadata { pub is_file: bool, pub is_readonly: bool, pub mime_essence: String, + pub size: u64, pub canonical: Option, pub symlink: Option, @@ -146,6 +149,7 @@ impl NodeUiMetadata { is_file: node.is_file, is_readonly: node.is_readonly, mime_essence: node.mime_essence.clone(), + size: node.size, canonical: node.canonical.to_owned().map(|s| s.into()), symlink: node.symlink.to_owned().map(|s| s.into()), index, @@ -287,11 +291,11 @@ fn draw_table(f: &mut Frame, rect: Rect, app: &app::App, hb: &Han .style(config.general.table.style.into()) .highlight_style(config.general.focus_ui.style.into()) .column_spacing(config.general.table.col_spacing.unwrap_or_default()) - .block( - Block::default() - .borders(Borders::ALL) - .title(format!(" {} ", app.pwd())), - ); + .block(Block::default().borders(Borders::ALL).title(format!( + " {} ({}) ", + app.pwd(), + app.directory_buffer().map(|d| d.total).unwrap_or_default() + ))); let table = table.clone().header( Row::new(