diff options
author | Canop <cano.petrole@gmail.com> | 2020-06-28 15:33:43 +0200 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2020-06-28 15:33:43 +0200 |
commit | 3accec2a8ea0827b84b7014744cfe94834cc18d5 (patch) | |
tree | 33455c638391102566c2c3db8d4bed876b7cd2a7 | |
parent | 6ae4cc421ef3db9f6f458a023780af899313f821 (diff) |
new `cols_order` attribute in configurationv0.18.1
Allows setting the order of columns, most notably the position
of the branch (left of the tree, just before the name, or in
between).
Fix #127
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 31 | ||||
-rw-r--r-- | src/app/context.rs | 22 | ||||
-rw-r--r-- | src/browser/browser_state.rs | 3 | ||||
-rw-r--r-- | src/cli.rs | 4 | ||||
-rw-r--r-- | src/conf/conf.rs | 6 | ||||
-rw-r--r-- | src/conf/default_conf.rs | 30 | ||||
-rw-r--r-- | src/display/col.rs | 70 | ||||
-rw-r--r-- | src/display/displayable_tree.rs | 318 | ||||
-rw-r--r-- | src/display/mod.rs | 2 | ||||
-rw-r--r-- | src/errors.rs | 3 | ||||
-rw-r--r-- | src/launchable.rs | 9 | ||||
-rw-r--r-- | src/print.rs | 8 | ||||
-rw-r--r-- | src/skin/app_skin.rs | 7 | ||||
-rw-r--r-- | src/skin/style_map.rs | 2 | ||||
-rw-r--r-- | src/tree/tree_options.rs | 4 | ||||
-rw-r--r-- | website/docs/conf_file.md | 300 | ||||
-rw-r--r-- | website/docs/conf_verbs.md | 297 | ||||
-rw-r--r-- | website/docs/img/20200628-sdp.png | bin | 0 -> 158128 bytes | |||
-rw-r--r-- | website/docs/img/20200628-whale-spotting.png | bin | 0 -> 48102 bytes | |||
-rw-r--r-- | website/docs/index.md | 6 | ||||
-rw-r--r-- | website/mkdocs.yml | 7 |
24 files changed, 657 insertions, 480 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c800fe7..42d3bfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +<a name="v0.18.1"></a> +### v0.18.1 - 2020-06-28 +- column order configurable - Fix #127 + <a name="v0.18.0"></a> ### v0.18.0 - 2020-06-26 #### Major change: Recursive last modified date computation @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "broot" -version = "0.18.0" +version = "0.18.1" dependencies = [ "bet", "chrono", @@ -1,6 +1,6 @@ [package] name = "broot" -version = "0.18.0" +version = "0.18.1" authors = ["dystroy <denys.seguret@gmail.com>"] repository = "https://github.com/Canop/broot" documentation = "https://dystroy.org/broot" @@ -33,14 +33,9 @@ Table of Contents * [Check git statuses](#check-git-statuses) * [Further Reading](#further-reading) -## Installation -Full installation documentation can be found [here](https://dystroy.org/broot/documentation/installation/). - -## Feature Showcase - ### Get an overview of a directory, even a big one -![overview](img/20191112-overview.png) +![overview](website/docs/img/20191112-overview.png) Notice the *unlisted*? @@ -50,7 +45,7 @@ That's what makes it usable where the old `tree` command would produce pages of ### Find a directory then `cd` to it -![cd](img/20191112-cd.png) +![cd](website/docs/img/20191112-cd.png) This way, you can navigate to a directory with the minimum amount of keystrokes, even if you don't exactly remember where it is. @@ -66,7 +61,7 @@ Most useful keys for this: ### Never lose track of file hierarchy while you search -![size](img/20191112-mycnf.png) +![size](website/docs/img/20191112-mycnf.png) broot tries to select the most relevant file. You can still go from one match to another one using <kbd>tab</kbd> or arrow keys. @@ -76,7 +71,7 @@ And you have other types of searches, for example searching on file content (sta ![content search](website/docs/img/20200620-content-memm.png) -You may also apply logical operators or combine patterns, for example searching `test` in all files except txt ones could be `!/txt$/&c/test` and searching `carg` both in file names and file contents would be `carg|c/carg`. +You may also apply logical operators or combine patterns, for example searching `test` in all files except json ones could be `!/json$/&c/test` and searching `carg` both in file names and file contents would be `carg|c/carg`. Once the file you want is selected you can @@ -86,7 +81,7 @@ Once the file you want is selected you can ### Manipulate your files -![mv](img/20191112-mv.png) +![mv](website/docs/img/20191112-mv.png) Most often you move your files in the blind. You do a few `ls` before, then your manipulation, and maybe you check after. @@ -110,31 +105,31 @@ If you like you may do it Norton Commander style by binding `:copy_to_panel` to ### Apply a standard or personal shortcut to a file -![size](img/20191112-edit.png) +![size](website/docs/img/20191112-edit.png) Just find the file you want to edit with a few keystrokes, type `:e`, then <kbd>enter</kbd>. -You can add verbs or configure the existing ones; see [documentation](documentation/usage.md#verbs). +You can add verbs or configure the existing ones; see [documentation](../conf_file/#verbs-shortcuts-and-keys). And you can add shorcuts, for example a <kbd>ctrl</kbd> sequence or a function key ### Replace `ls` (and its clones): -If you want to display *dates* and *permissions*, do `br -dp` which gets you this: +If you want to display *sizes*, *dates* and *permissions*, do `br -sdp` which gets you this: -![replace ls](img/20191214-replace-ls.png) +![replace ls](website/docs/img/20200628-sdp.png) You may also toggle options with a few keystrokes while inside broot. For example hitting a space, a `d` then enter shows you the dates. Or a space, then `h` then enter and you see hidden files. ### Sort, see what takes space: -You can toggle sorts from inside broot, or start broot with `--sort-by-size` or `--sort-by-date`. +You may sort by launching broot with `--sort-by-size` or `--sort-by-date`. Or you may, inside broot, just type a space, then `sd`, and <kbd>enter</kbd> and you toggled the `:sort_by_date` mode. When sorting, the whole content of directories is taken into account. So if you want to find on monday morning the most recently modified files, just launch `br --sort-by-date ~`. If you start broot with the `--whale-spotting` option (or its shorcut `-w`), you get a mode tailored to "whale spotting" navigation, making it easy to determine what files or folders take space. -![size](img/20191112-sizes.png) +![size](website/docs/img/20200628-whale-spotting.png) And you keep all broot tools, like filtering or the ability to delete or open files and directories. @@ -142,11 +137,11 @@ Sizes, dates, files counts, are computed in the background, you don't have to wa ### check git statuses: -![size](img/20200203-git.png) +![size](website/docs/img/20200203-git.png) Use `:gf` to display the statuses of files (what are the new ones, the modified ones, etc.), the current branch name and the change statistics. -And if you want to see *only* the files which would be displayed by the `git status` command, do `:gs`. +And if you want to see *only* the files which would be displayed by the `git status` command, do `:gs`. From there it's easy to edit, or diff, selected files. ## Further Reading See **[Broot's web site](https://dystroy.org/broot)** for instructions regarding installation and usage. diff --git a/src/app/context.rs b/src/app/context.rs index ffd9e14..d5fe915 100644 --- a/src/app/context.rs +++ b/src/app/context.rs @@ -1,6 +1,7 @@ use crate::{ cli::AppLaunchArgs, conf::Conf, + display::{Cols, DEFAULT_COLS}, pattern::SearchModeMap, tree::SpecialPath, verb::VerbStore, @@ -9,27 +10,40 @@ use crate::{ /// The immutable container that can be passed around /// to provide the configuration things pub struct AppContext { + + /// where's the config file we're using pub config_path: String, + + /// all the arguments specified at launch pub launch_args: AppLaunchArgs, + + /// the verbs in use (builtins and configured ones) pub verb_store: VerbStore, + + /// the paths for which there's a special behavior to follow (come from conf) pub special_paths: Vec<SpecialPath>, + + /// the map between search prefixes and the search mode to apply pub search_modes: SearchModeMap, + + /// order of columns in tree display + pub cols: Cols, } impl AppContext { pub fn from( launch_args: AppLaunchArgs, verb_store: VerbStore, - special_paths: Vec<SpecialPath>, - search_modes: SearchModeMap, + config: &Conf, ) -> Self { let config_path = Conf::default_location().to_string_lossy().to_string(); Self { config_path, launch_args, verb_store, - special_paths, - search_modes, + special_paths: config.special_paths.clone(), + search_modes: config.search_modes.clone(), + cols: config.cols_order.unwrap_or(DEFAULT_COLS).clone(), } } } diff --git a/src/browser/browser_state.rs b/src/browser/browser_state.rs index 0da7406..dab6c85 100644 --- a/src/browser/browser_state.rs +++ b/src/browser/browser_state.rs @@ -698,11 +698,12 @@ impl AppState for BrowserState { _screen: &Screen, area: Area, panel_skin: &PanelSkin, - _con: &AppContext, + con: &AppContext, ) -> Result<(), ProgramError> { let dp = DisplayableTree { tree: &self.displayed_tree(), skin: &panel_skin.styles, + cols: &con.cols, area, in_app: true, }; @@ -192,9 +192,7 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> { no_style, }; - let context = AppContext::from( - launch_args, verb_store, config.special_paths.clone(), config.search_modes.clone(), - ); + let context = AppContext::from(launch_args, verb_store, &config); let mut w = display::writer(); let mut screen = Screen::new(&context, &config)?; let app = App::new(&context, &screen)?; diff --git a/src/conf/conf.rs b/src/conf/conf.rs index 573963e..a17ce6a 100644 --- a/src/conf/conf.rs +++ b/src/conf/conf.rs @@ -6,6 +6,7 @@ use { default_conf::DEFAULT_CONF_FILE, }, crate::{ + display::{Col, Cols}, errors::ConfError, keys, pattern::{SearchModeMap, SearchModeMapEntry}, @@ -34,6 +35,7 @@ pub struct Conf { pub special_paths: Vec<SpecialPath>, pub search_modes: SearchModeMap, pub disable_mouse_capture: bool, + pub cols_order: Option<Cols>, } fn string_field(value: &Value, field_name: &str) -> Option<String> { @@ -116,6 +118,10 @@ impl Conf { if let Some(mouse_capture) = bool_field(&root, "capture_mouse") { self.disable_mouse_capture = !mouse_capture; } + // cols order + self.cols_order = string_field(&root, "cols_order") + .map(|s| Col::parse_cols(&s)) + .transpose()?; // reading verbs if let Some(Value::Array(verbs_value)) = &root.get("verbs") { for verb_value in verbs_value.iter() { diff --git a/src/conf/default_conf.rs b/src/conf/default_conf.rs index e24eff5..21d6c83 100644 --- a/src/conf/default_conf.rs +++ b/src/conf/default_conf.rs @@ -28,14 +28,6 @@ pub const DEFAULT_CONF_FILE: &str = r#" default_flags = "" ############################################################### -# Date/Time format -# If you want to change the format for date/time, uncomment the -# following line and change it according to -# https://docs.rs/chrono/0.4.11/chrono/format/strftime/index.html -# -# date_time_format = "%Y/%m/%d %R " - -############################################################### # Special paths # If some paths must be handled specially, uncomment (and change # this section as per the examples @@ -46,6 +38,28 @@ default_flags = "" # "/home/dys/my-link-I-want-to-explore" = "enter" ############################################################### +# Date/Time format +# If you want to change the format for date/time, uncomment the +# following line and change it according to +# https://docs.rs/chrono/0.4.11/chrono/format/strftime/index.html +# +# date_time_format = "%Y/%m/%d %R" + +############################################################### +# Column order +# cols_order, if specified, must be a permutation of "gbpdscn" +# where every char denotes a column: +# g : Git file info +# b : branch (shows the depth and parent in the tree) +# p : permissions (mode, user, group) +# d : last modification date +# s : size (with size bar when sorting) +# c : count, number of files in directories +# n : file name +# +# cols_order = "gbdscn" + +############################################################### # Verbs and shortcuts # You can define your own commands which would be applied to # the selection. diff --git a/src/display/col.rs b/src/display/col.rs new file mode 100644 index 0000000..df4957d --- /dev/null +++ b/src/display/col.rs @@ -0,0 +1,70 @@ +use { + crate::{ + errors::ConfError, + }, +}; + +// number of columns in enum +const COLS_COUNT: usize = 7; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Col { + Git, + Branch, + Permission, + Date, + Size, // includes the size bar in sort mode + Count, + Name, // name or subpath, depending on sort +} +impl Col { + pub fn parse(c: char) -> Result<Self, ConfError> { + Ok(match c { + 'g' => Self::Git, + 'b' => Self::Branch, + 'd' => Self::Date, + 's' => Self::Size, + 'c' => Self::Count, + 'n' => Self::Name, + _ => Err(ConfError::InvalidCols { details: format!("column not recognized : {}", c) })?, + }) + } + pub fn index_in(self, cols: &Cols) -> Option<usize> { + for (idx, col) in cols.iter().enumerate() { + if *col==self { + return Some(idx); + } + } + None + } + /// return a Cols which tries to take the s setting into account + /// but is guaranteed to have every Col exactly once. + pub fn parse_cols(s: &str) -> Result<Cols, ConfError> { + let mut cols = DEFAULT_COLS.clone(); + for (idx, c) in s.chars().enumerate() { + if idx >= COLS_COUNT { + return Err(ConfError::InvalidCols { details: format!("too long: {:?}", s) }); + } + // we swap the cols, to ensure both keeps being present + let col = Col::parse(c)?; + let dest_idx = col.index_in(&cols).unwrap(); // can't be none by construct + cols[dest_idx] = cols[idx]; + cols[idx] = col; + } + Ok(cols) + } +} + +pub type Cols = [Col;COLS_COUNT]; + +/// Default column order +pub static DEFAULT_COLS: Cols = [ + Col::Git, + Col::Size, + Col::Count, + Col::Permission, + Col::Date, + Col::Branch, + Col::Name, +]; + diff --git a/src/display/displayable_tree.rs b/src/display/displayable_tree.rs index 99ae99d..8b71183 100644 --- a/src/display/displayable_tree.rs +++ b/src/display/displayable_tree.rs @@ -1,5 +1,7 @@ use { super::{ + Col, + Cols, CropWriter, GitStatusDisplay, MatchedString, @@ -40,14 +42,21 @@ pub struct DisplayableTree<'s, 't> { pub skin: &'s StyleMap, pub area: termimad::Area, pub in_app: bool, // if true we show the selection and scrollbar + pub cols: &'s Cols, } impl<'s, 't> DisplayableTree<'s, 't> { - pub fn out_of_app(tree: &'t Tree, skin: &'s StyleMap, width: u16) -> DisplayableTree<'s, 't> { + pub fn out_of_app( + tree: &'t Tree, + skin: &'s StyleMap, + cols: &'s Cols, + width: u16, + ) -> DisplayableTree<'s, 't> { DisplayableTree { tree, skin, + cols, area: termimad::Area { left: 0, top: 0, @@ -73,113 +82,102 @@ impl<'s, 't> DisplayableTree<'s, 't> { } } - fn write_line_count<'w, W>( + fn write_line_count<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, line: &TreeLine, selected: bool, - ) -> Result<(), termimad::Error> - where - W: Write, - { - if let Some(s) = line.sum { + ) -> Result<usize, termimad::Error> { + Ok(if let Some(s) = line.sum { cond_bg!(count_style, self, selected, self.skin.count); cw.queue_string(&count_style, format!("{:>8}", s.to_count()))?; - cw.queue_char(&self.skin.default, ' ') + 1 } else { - cw.queue_str(&self.skin.tree, "─────────") - } + 9 + }) } - fn write_line_size<'w, W>( + fn write_line_size<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, line: &TreeLine, selected: bool, - ) -> Result<(), termimad::Error> - where - W: Write, - { - if let Some(s) = line.sum { + ) -> Result<usize, termimad::Error> { + Ok(if let Some(s) = line.sum { cond_bg!(size_style, self, selected, self.name_style(&line)); - cw.queue_string(&size_style, s.to_size_string())?; - cw.queue_char(&self.skin.default, ' ') + cw.queue_string(&size_style, format!("{:>5}", s.to_size_string()))?; + 1 } else { - cw.queue_str(&self.skin.tree, "─────") - } + 6 + }) } /// only makes sense when there's only one level /// (so in sort mode) - fn write_line_size_with_bar<'w, W>( + fn write_line_size_with_bar<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, line: &TreeLine, total_size: FileSum, selected: bool, - ) -> Result<(), termimad::Error> - where - W: Write, - { - if let Some(s) = line.sum { + ) -> Result<usize, termimad::Error> { + Ok(if let Some(s) = line.sum { let pb = ProgressBar::new(s.part_of_size(total_size), 10); cond_bg!(size_style, self, selected, self.name_style(&line)); cond_bg!(sparse_style, self, selected, self.skin.sparse); cw.queue_string(&size_style, format!("{:>5}", s.to_size_string()))?; cw.queue_char(&sparse_style, if s.is_sparse() { 's' } else { ' ' })?; - cw.queue_string(&size_style, format!("{:<10} ", pb)) + cw.queue_string(&size_style, format!("{:<10}", pb))?; + 1 } else { - cw.queue_str(&self.skin.tree, "─────────────────") - } + 17 + }) } - fn write_line_git_status<'w, W>( + fn write_line_git_status<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, line: &TreeLine, - ) -> Result<(), termimad::Error> - where - W: Write, - { + selected: bool, + ) -> Result<usize, termimad::Error> { + let (style, char) = if !line.is_selectable() { - cw.queue_char(&self.skin.tree, ' ') + (&self.skin.tree, ' ') } else { match line.git_status.map(|s| s.status) { - Some(Status::CURRENT) => cw.queue_char(&self.skin.git_status_current, ' '), - Some(Status::WT_NEW) => cw.queue_char(&self.skin.git_status_new, 'N'), - Some(Status::CONFLICTED) => cw.queue_char(&self.skin.git_status_conflicted, 'C'), - Some(Status::WT_MODIFIED) => cw.queue_char(&self.skin.git_status_modified, 'M'), - Some(Status::IGNORED) => cw.queue_char(&self.skin.git_status_ignored, 'I'), - None => cw.queue_char(&self.skin.tree, ' '), - _ => cw.queue_char(&self.skin.git_status_other, '?'), + Some(Status::CURRENT) => (&self.skin.git_status_current, ' '), + Some(Status::WT_NEW) => (&self.skin.git_status_new, 'N'), + Some(Status::CONFLICTED) => (&self.skin.git_status_conflicted, 'C'), + Some(Status::WT_MODIFIED) => (&self.skin.git_status_modified, 'M'), + Some(Status::IGNORED) => (&self.skin.git_status_ignored, 'I'), + None => (&self.skin.tree, ' '), + _ => (&self.skin.git_status_other, '?'), } - } + }; + cond_bg!(git_style, self, selected, style); + cw.queue_char(git_style, char)?; + Ok(0) } - fn write_date<'w, W>( + fn write_date<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, seconds: i64, selected: bool, - ) -> Result<(), termimad::Error> - where - W: Write, - { + ) -> Result<usize, termimad::Error> { let date_time: DateTime<Local> = Local.timestamp(seconds, 0); cond_bg!(date_style, self, selected, self.skin.dates); - cw.queue_string(date_style, date_time.format(self.tree.options.date_time_format).to_string()) + cw.queue_string(date_style, date_time.format(self.tree.options.date_time_format).to_string())?; + Ok(1) } #[cfg(unix)] - fn write_mode<'w, W>( + fn write_mode<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, mode: Mode, selected: bool, - ) -> Result<(), termimad::Error> - where - W: Write, - { + ) -> Result<(), termimad::Error> { cond_bg!(n_style, self, selected, self.skin.perm__); cond_bg!(r_style, self, selected, self.skin.perm_r); cond_bg!(w_style, self, selected, self.skin.perm_w); @@ -236,18 +234,71 @@ impl<'s, 't> DisplayableTree<'s, 't> { Ok(()) } + #[cfg(unix)] + fn write_permissions<'w, W: Write>( + &self, + cw: &mut CropWriter<'w, W>, + line: &TreeLine, + user_group_max_lengths: (usize, usize), + selected: bool, + ) -> Result<usize, ProgramError> { + Ok(if line.is_selectable() { + self.write_mode(cw, line.mode(), selected)?; + let owner = permissions::user_name(line.metadata.uid()); + cond_bg!(owner_style, self, selected, self.skin.owner); + cw.queue_string( + &owner_style, + format!(" {:w$}", &owner, w = user_group_max_lengths.0), + )?; + let group = permissions::group_name(line.metadata.gid()); + cond_bg!(group_style, self, selected, self.skin.group); + cw.queue_string( + &group_style, + format!(" {:w$}", &group, w = user_group_max_lengths.1), + )?; + 1 + } else { + 9 + 1 + user_group_max_lengths.0 + 1 + user_group_max_lengths.1 + 1 + }) + } + + fn write_branch<'w, W: Write>( + &self, + cw: &mut CropWriter<'w, W>, + line_index: usize, + line: &TreeLine, + selected: bool, + ) -> Result<usize, ProgramError> { + cond_bg!(branch_style, self, selected, self.skin.tree); + for depth in 0..line.depth { + cw.queue_str( + &branch_style, + if line.left_branchs[depth as usize] { + if self.tree.has_branch(line_index + 1, depth as usize) { + if depth == line.depth - 1 { + "├──" + } else { + "│ " + } + } else { + "└──" + } + } else { + " " + }, + )?; + } + Ok(0) + } /// write the name or subpath, depending on the pattern_object - fn write_line_label<'w, W>( + fn write_line_label<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, line: &TreeLine, pattern_object: PatternObject, selected: bool, - ) -> Result<(), ProgramError> - where - W: Write, - { + ) -> Result<usize, ProgramError> { let style = match &line.line_type { TreeLineType::Dir => &self.skin.directory, TreeLineType::File => { @@ -297,18 +348,15 @@ impl<'s, 't> DisplayableTree<'s, 't> { } _ => {} } - Ok(()) + Ok(1) } - fn write_content_extract<'w, W>( + fn write_content_extract<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, extract: ContentMatch, selected: bool, - ) -> Result<(), ProgramError> - where - W: Write, - { + ) -> Result<(), ProgramError> { cond_bg!(extract_style, self, selected, self.skin.content_extract); cond_bg!(match_style, self, selected, self.skin.content_match); cw.queue_str(&extract_style, " ")?; @@ -322,14 +370,11 @@ impl<'s, 't> DisplayableTree<'s, 't> { Ok(()) } - pub fn write_root_line<'w, W>( + pub fn write_root_line<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, selected: bool, - ) -> Result<(), ProgramError> - where - W: Write, - { + ) -> Result<(), ProgramError> { cond_bg!(style, self, selected, self.skin.directory); let title = self.tree.lines[0].path.to_string_lossy(); cw.queue_str(&style, &title)?; @@ -351,14 +396,11 @@ impl<'s, 't> DisplayableTree<'s, 't> { } /// if in app, extend the background till the end of screen row - pub fn extend_line_bg<'w, W>( + pub fn extend_line_bg<'w, W: Write>( &self, cw: &mut CropWriter<'w, W>, selected: bool, - ) -> Result<(), ProgramError> - where - W: Write, - { + ) -> Result<(), ProgramError> { if self.in_app { if selected { cw.queue_bg(&self.skin.selected_line)?; @@ -371,10 +413,7 @@ impl<'s, 't> DisplayableTree<'s, 't> { } /// write the whole tree on the given `W` - pub fn write_on<W>(&self, f: &mut W) -> Result<(), ProgramError> - where - W: Write, - { + pub fn write_on<W: Write>(&self, f: &mut W) -> Result<(), ProgramError> { let tree = self.tree; #[cfg(unix)] let user_group_max_lengths = user_group_max_lengths(&tree); @@ -392,6 +431,15 @@ impl<'s, 't> DisplayableTree<'s, 't> { let pattern_object = tree.options.pattern.pattern.object(); self.write_root_line(&mut cw, tree.selection == 0)?; f.queue(SetBackgroundColor(Color::Reset))?; + + // we compute the length of the dates, depending on the format + let date_len = if tree.options.show_dates { + let date_time: DateTime<Local> = Local::now(); + date_time.format(tree.options.date_time_format).to_string().len() + } else { + 0 // we don't care + }; + for y in 1..self.area.height { if self.in_app { f.queue(cursor::MoveTo(self.area.left, y + self.area.top))?; @@ -408,72 +456,66 @@ impl<'s, 't> DisplayableTree<'s, 't> { if line_index < tree.lines.len() { let line = &tree.lines[line_index]; selected = self.in_app && line_index == tree.selection; - if !tree.git_status.is_none() { - self.write_line_git_status(cw, line)?; - } - for depth in 0..line.depth { - cw.queue_str( - &self.skin.tree, - if line.left_branchs[depth as usize] { - if self.tree.has_branch(line_index + 1, depth as usize) { - if depth == line.depth - 1 { - "├──" - } else { - "│ " - } + let mut in_branch = false; + for col in self.cols { + let void_len = match col { + + Col::Git if !tree.git_status.is_none() => { + self.write_line_git_status(cw, line, selected)? + } + + Col::Branch => { + in_branch = true; + self.write_branch(cw, line_index, line, selected)? + } + + #[cfg(unix)] + Col::Permission if tree.options.show_permissions => { + self.write_permissions(cw, line, user_group_max_lengths, selected)? + } + + Col::Date if tree.options.show_dates => { + if let Some(seconds) = line.sum.and_then(|sum| sum.to_valid_seconds()) { + self.write_date(cw, seconds, selected)? } else { - "└──" + date_len + 1 } - } else { - " " - }, - )?; - } - #[cfg(unix)] - { - if tree.options.show_permissions { - if line.is_selectable() { - self.write_mode(cw, line.mode(), selected)?; - let owner = permissions::user_name(line.metadata.uid()); - cond_bg!(owner_style, self, selected, self.skin.owner); - cw.queue_string( - &owner_style, - format!(" {:w$}", &owner, w = user_group_max_lengths.0,), - )?; - let group = permissions::group_name(line.metadata.gid()); - cond_bg!(group_style, self, selected, self.skin.group); - cw.queue_string( - &grou |