diff options
author | Canop <cano.petrole@gmail.com> | 2020-10-07 18:38:07 +0200 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2020-10-07 18:38:07 +0200 |
commit | 4b6a90a86d62902bb2795b5b721635fdaa87faa5 (patch) | |
tree | d01a6d18389f42e02aebb8458c8a0ef5e682489f | |
parent | 53e07a4ebb94ae613b552af1d6e3d0ff7ee5cdff (diff) |
change the syntax of `cols_order` in conf
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | src/command/panel_input.rs | 1 | ||||
-rw-r--r-- | src/conf/conf.rs | 19 | ||||
-rw-r--r-- | src/conf/default_conf.rs | 23 | ||||
-rw-r--r-- | src/conf/toml.rs | 49 | ||||
-rw-r--r-- | src/display/col.rs | 51 | ||||
-rw-r--r-- | src/display/crop_writer.rs | 18 | ||||
-rw-r--r-- | src/display/displayable_tree.rs | 16 | ||||
-rw-r--r-- | src/display/filling.rs | 63 | ||||
-rw-r--r-- | src/display/mod.rs | 22 | ||||
-rw-r--r-- | src/errors.rs | 1 | ||||
-rw-r--r-- | src/hex/hex_view.rs | 4 | ||||
-rw-r--r-- | src/preview/preview_state.rs | 4 | ||||
-rw-r--r-- | src/syntactic/syntactic_view.rs | 4 | ||||
-rw-r--r-- | src/verb/verb_conf.rs | 8 | ||||
-rw-r--r-- | website/docs/conf_file.md | 39 |
16 files changed, 218 insertions, 106 deletions
@@ -1460,8 +1460,6 @@ dependencies = [ [[package]] name = "termimad" version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695a09c526e20acf459e522670afc5486eeb1c9cf2823515ed4ad4f8f48c8f30" dependencies = [ "crossbeam", "crossterm", diff --git a/src/command/panel_input.rs b/src/command/panel_input.rs index f8e3af0..c8081a7 100644 --- a/src/command/panel_input.rs +++ b/src/command/panel_input.rs @@ -57,6 +57,7 @@ impl PanelInput { /// consume the event to /// - maybe change the input /// - build a command + /// then redraw the input field pub fn on_event( &mut self, w: &mut W, diff --git a/src/conf/conf.rs b/src/conf/conf.rs index 8c34d2b..9a36b58 100644 --- a/src/conf/conf.rs +++ b/src/conf/conf.rs @@ -87,7 +87,12 @@ impl Conf { /// (i.e. this function is called before or after the terminal alternation) pub fn read_file(&mut self, filepath: &Path) -> Result<(), ConfError> { let data = fs::read_to_string(filepath)?; - let root: Value = data.parse::<Value>()?; + let root: toml::value::Table = match data.parse::<Value>()? { + Value::Table(tbl) => tbl, + _ => { + return Err(ConfError::Invalid {}); + } + }; // reading default flags if let Some(s) = string_field(&root, "default_flags") { // it's additive because another config file may have @@ -104,9 +109,15 @@ impl Conf { self.disable_mouse_capture = !mouse_capture; } // cols order - self.cols_order = string_field(&root, "cols_order") - .map(|s| Col::parse_cols(&s)) - .transpose()?; + if let Some(s) = string_field(&root, "cols_order") { + // old format, with each char being a col, for example + // `cols_order = "gbpdscn"` + self.cols_order = Some(Col::parse_cols_single_str(&s)?); + } else if let Some(arr) = string_array_field(&root, "cols_order") { + // new format, where each col is a string, for example + // `cols_order = ["branch", "size" ..., "name"]` + self.cols_order = Some(Col::parse_cols(&arr)?); + } // 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 6375e83..bfccebb 100644 --- a/src/conf/default_conf.rs +++ b/src/conf/default_conf.rs @@ -52,17 +52,20 @@ default_flags = "" ############################################################### # 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, if specified, must be a permutation of the following +# array. You should keep the name at the end as it has a variable +# length. # -# cols_order = "gbdscn" +# cols_order = [ +# "mark", +# "git", +# "branch", +# "permission", +# "date", +# "size", +# "count", +# "name", +# ] ############################################################### # True Colors diff --git a/src/conf/toml.rs b/src/conf/toml.rs index 2827910..f734352 100644 --- a/src/conf/toml.rs +++ b/src/conf/toml.rs @@ -1,24 +1,47 @@ - use { - toml::Value, + toml::{ + value::Table, + Value, + }, }; -pub fn string_field(value: &Value, field_name: &str) -> Option<String> { - if let Value::Table(tbl) = value { - if let Some(fv) = tbl.get(field_name) { - if let Some(s) = fv.as_str() { - return Some(s.to_string()); + +/// try to read the field_name property of the given value, as a string +pub fn string_field(tbl: &Table, field_name: &str) -> Option<String> { + tbl.get(field_name) + .and_then(|fv| fv.as_str()) + .map(|s| s.to_string()) +} + +/// try to read the field_name property of the given value, as an array of strings. +/// Return None if the type isn't compatible +pub fn string_array_field(tbl: &Table, field_name: &str) -> Option<Vec<String>> { + if let Some(fv) = tbl.get(field_name) { + if let Value::Array(arr_val) = fv { + let mut arr = Vec::new(); + for v in arr_val { + match v.as_str() { + Some(s) => { + arr.push(s.to_string()); + } + None => { + return None; // non matching value + } + } } + return Some(arr); } } None + } -pub fn bool_field(value: &Value, field_name: &str) -> Option<bool> { - if let Value::Table(tbl) = value { - if let Some(Value::Boolean(b)) = tbl.get(field_name) { - return Some(*b); - } +/// try to read the field_name property of the given value, as a boolean +/// (only read it if it's a proper toml boolean, doesn't try to do hazardous +/// string to bool conversions) +pub fn bool_field(tbl: &Table, field_name: &str) -> Option<bool> { + match tbl.get(field_name) { + Some(Value::Boolean(b)) => Some(*b), + _ => None, } - None } diff --git a/src/display/col.rs b/src/display/col.rs index 54f1f9f..ec0425e 100644 --- a/src/display/col.rs +++ b/src/display/col.rs @@ -2,6 +2,7 @@ use { crate::{ errors::ConfError, }, + std::str::FromStr, }; // number of columns in enum @@ -35,23 +36,27 @@ pub enum Col { Name, } -impl Col { - pub fn parse(c: char) -> Result<Self, ConfError> { - Ok(match c { - 'm' => Self::Mark, - 'g' => Self::Git, - 'b' => Self::Branch, - 'd' => Self::Date, - 's' => Self::Size, - 'c' => Self::Count, - 'n' => Self::Name, - _ => { - return Err(ConfError::InvalidCols { - details: format!("column not recognized : {}", c), - }); - } - }) +impl FromStr for Col { + type Err = ConfError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = s.to_lowercase(); + match s.as_ref() { + "m" | "mark" => Ok(Self::Mark), + "g" | "git" => Ok(Self::Git), + "b" | "branch" => Ok(Self::Branch), + "p" | "permission" => Ok(Self::Permission), + "d" | "date" => Ok(Self::Date), + "s" | "size" => Ok(Self::Size), + "c" | "count" => Ok(Self::Count), + "n" | "name" => Ok(Self::Name), + _ => Err(ConfError::InvalidCols { + details: format!("column not recognized : {}", s), + }) + } } +} + +impl Col { pub fn index_in(self, cols: &Cols) -> Option<usize> { for (idx, col) in cols.iter().enumerate() { if *col==self { @@ -62,22 +67,28 @@ impl Col { } /// 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> { + pub fn parse_cols(arr: &Vec<String>) -> Result<Cols, ConfError> { let mut cols = DEFAULT_COLS; - for (idx, c) in s.chars().enumerate() { + for (idx, s) in arr.iter().enumerate() { if idx >= COLS_COUNT { return Err(ConfError::InvalidCols { - details: format!("too long: {:?}", s), + details: format!("too long: {:?}", arr), }); } // we swap the cols, to ensure both keeps being present - let col = Col::parse(c)?; + let col = Col::from_str(s)?; let dest_idx = col.index_in(&cols).unwrap(); // can't be none by construct cols[dest_idx] = cols[idx]; cols[idx] = col; } + debug!("cols from conf = {:?}", cols); Ok(cols) } + /// 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_single_str(s: &str) -> Result<Cols, ConfError> { + Self::parse_cols(&s.chars().map(|c| String::from(c)).collect()) + } } pub type Cols = [Col;COLS_COUNT]; diff --git a/src/display/crop_writer.rs b/src/display/crop_writer.rs index 9fb431a..927b7dd 100644 --- a/src/display/crop_writer.rs +++ b/src/display/crop_writer.rs @@ -1,4 +1,5 @@ use { + super::Filling, crossterm::{ QueueableCommand, style::Print, @@ -108,19 +109,12 @@ where pub fn queue_bg(&mut self, cs: &CompoundStyle) -> Result<()> { cs.queue_bg(self.w) } - pub fn fill(&mut self, cs: &CompoundStyle, filling: &'static str) -> Result<()> { + pub fn fill(&mut self, cs: &CompoundStyle, filling: &'static Filling) -> Result<()> { self.repeat(cs, filling, self.allowed) } - pub fn repeat(&mut self, cs: &CompoundStyle, filling: &'static str, mut len: usize) -> Result<()> { - loop { - let slice_len = len.min(self.allowed).min(filling.len()); - if slice_len == 0 { - break; - } - cs.queue_str(self.w, &filling[0..slice_len])?; - self.allowed -= slice_len; - len -= slice_len; - } - Ok(()) + pub fn repeat(&mut self, cs: &CompoundStyle, filling: &'static Filling, mut len: usize) -> Result<()> { + len = len.min(self.allowed); + self.allowed -= len; + filling.queue_styled(self.w, cs, len) } } diff --git a/src/display/displayable_tree.rs b/src/display/displayable_tree.rs index c31b2dd..03b1922 100644 --- a/src/display/displayable_tree.rs +++ b/src/display/displayable_tree.rs @@ -4,7 +4,7 @@ use { Cols, CropWriter, GitStatusDisplay, - LONG_SPACE, LONG_BRANCH, + SPACE_FILLING, BRANCH_FILLING, MatchedString, }, crate::{ @@ -351,7 +351,7 @@ impl<'s, 't> DisplayableTree<'s, 't> { } else { &self.skin.default }; - cw.fill(style, LONG_SPACE)?; + cw.fill(style, &SPACE_FILLING)?; } Ok(()) } @@ -463,13 +463,13 @@ impl<'s, 't> DisplayableTree<'s, 't> { } }; // void: intercol & replacing missing cells - let (void_base_style, void) = if in_branch && void_len > 2 { - (&self.skin.tree, LONG_BRANCH) + if in_branch && void_len > 2 { + cond_bg!(void_style, self, selected, &self.skin.tree); + cw.repeat(void_style, &BRANCH_FILLING, void_len)?; } else { - (&self.skin.default, LONG_SPACE) - }; - cond_bg!(void_style, self, selected, void_base_style); - cw.repeat(void_style, void, void_len)?; + cond_bg!(void_style, self, selected, &self.skin.default); + cw.repeat(void_style, &SPACE_FILLING, void_len)?; + } } if cw.allowed > 8 && pattern_object.content { diff --git a/src/display/filling.rs b/src/display/filling.rs new file mode 100644 index 0000000..705977b --- /dev/null +++ b/src/display/filling.rs @@ -0,0 +1,63 @@ + +use { + crossterm::{ + QueueableCommand, + style::Print, + }, + termimad::{ + CompoundStyle, + Result, + }, +}; + +const FILLING_STRING_CHAR_LEN: usize = 1000; + +pub struct Filling { + filling_string: String, + char_size: usize, +} + +impl Filling { + // TODO as soon as const fn are capable enough, this should be const + // to allow normal static fillings + pub fn from_char(filling_char: char) -> Self { + let char_size = String::from(filling_char).len(); + let mut filling_string = String::with_capacity(char_size*FILLING_STRING_CHAR_LEN); + for _ in 0..FILLING_STRING_CHAR_LEN { + filling_string.push(filling_char); + } + Self { + filling_string, + char_size, + } + } + pub fn queue_unstyled<W>( + &self, + w: &mut W, + mut len: usize, + ) -> Result<()> + where W: std::io::Write + { + while len > 0 { + let sl = len.min(FILLING_STRING_CHAR_LEN); + w.queue(Print(&self.filling_string[0..sl*self.char_size]))?; + len -= sl; + } + Ok(()) + } + pub fn queue_styled<W>( + &self, + w: &mut W, + cs: &CompoundStyle, + mut len: usize, + ) -> Result<()> + where W: std::io::Write + { + while len > 0 { + let sl = len.min(FILLING_STRING_CHAR_LEN); + cs.queue_str(w, &self.filling_string[0..sl*self.char_size])?; + len -= sl; + } + Ok(()) + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs index da1e588..ac8e70d 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -24,8 +24,9 @@ mod areas; mod col; mod crop_writer; mod displayable_tree; -pub mod flags_display; +mod filling; mod git_status_display; +pub mod flags_display; pub mod status_line; mod matched_string; mod screen; @@ -38,6 +39,7 @@ pub use { col::{Col, Cols, DEFAULT_COLS}, crop_writer::CropWriter, displayable_tree::DisplayableTree, + filling::*, git_status_display::GitStatusDisplay, matched_string::MatchedString, screen::Screen, @@ -49,7 +51,6 @@ use { crossterm::{ style::{ Color, - Print, SetBackgroundColor, }, QueueableCommand, @@ -61,9 +62,11 @@ pub use { permissions::PermWriter, }; -pub static LONG_SPACE: &str = " "; -pub static LONG_BRANCH: &str = "───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"; +lazy_static! { + pub static ref SPACE_FILLING: Filling = Filling::from_char(' '); + pub static ref BRANCH_FILLING: Filling = Filling::from_char('─'); +} /// if true then the status of a panel covers the whole width /// of the terminal (over the other panels) @@ -80,17 +83,10 @@ pub fn writer() -> W { pub fn fill_bg( w: &mut W, - mut len: usize, + len: usize, bg: Color, ) -> Result<(), ProgramError> { w.queue(SetBackgroundColor(bg))?; - loop { - let slice_len = len.min(len).min(LONG_SPACE.len()); - if slice_len == 0 { - break; - } - w.queue(Print(&LONG_SPACE[0..slice_len]))?; - len -= slice_len; - } + SPACE_FILLING.queue_unstyled(w, len)?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index f31bee9..9bd7fe6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -35,6 +35,7 @@ custom_error! {pub TreeBuildError custom_error! {pub ConfError Io {source: io::Error} = "unable to read from the file: {}", Toml {source: toml::de::Error} = "unable to parse TOML: {}", + Invalid = "unexpected conf structure", // not expected MissingField {txt: String} = "missing field in conf", InvalidVerbInvocation {invocation: String} = "invalid verb invocation: {}", InvalidVerbConf {details: String} = "invalid verb conf: {}", diff --git a/src/hex/hex_view.rs b/src/hex/hex_view.rs index b645ff3..75005a8 100644 --- a/src/hex/hex_view.rs +++ b/src/hex/hex_view.rs @@ -5,7 +5,7 @@ use { }, crate::{ command::{ScrollCommand}, - display::{CropWriter, LONG_SPACE, Screen, W}, + display::{CropWriter, SPACE_FILLING, Screen, W}, errors::ProgramError, skin::PanelSkin, }, @@ -203,7 +203,7 @@ impl HexView { } } } - cw.fill(&styles.default, LONG_SPACE)?; + cw.fill(&styles.default, &SPACE_FILLING)?; if is_thumb(y, scrollbar) { w.queue(SetForegroundColor(scrollbar_fg))?; w.queue(Print('▐'))?; diff --git a/src/preview/preview_state.rs b/src/preview/preview_state.rs index 4cff8db..68ef09e 100644 --- a/src/preview/preview_state.rs +++ b/src/preview/preview_state.rs @@ -3,7 +3,7 @@ use { crate::{ app::*, command::{Command, ScrollCommand, TriggerType}, - display::{CropWriter, LONG_SPACE, Screen, W}, + display::{CropWriter, SPACE_FILLING, Screen, W}, errors::ProgramError, flag::Flag, pattern::InputPattern, @@ -217,7 +217,7 @@ impl AppState for PreviewState { cw.allowed as u16, 1, ); - cw.fill(&styles.default, LONG_SPACE)?; + cw.fill(&styles.default, &SPACE_FILLING)?; let preview = self.filtered_preview.as_mut().unwrap_or(&mut self.preview); preview.display_info(w, screen, panel_skin, &info_area)?; preview.display(w, screen, panel_skin, &self.preview_area, con) diff --git a/src/syntactic/syntactic_view.rs b/src/syntactic/syntactic_view.rs index aa707f4..b2aa8ff 100644 --- a/src/syntactic/syntactic_view.rs +++ b/src/syntactic/syntactic_view.rs @@ -3,7 +3,7 @@ use { crate::{ app::{AppContext, LineNumber}, command::{ScrollCommand}, - display::{CropWriter, LONG_SPACE, Screen, W}, + display::{CropWriter, SPACE_FILLING, Screen, W}, errors::ProgramError, pattern::{InputPattern, NameMatch}, skin::PanelSkin, @@ -372,7 +372,7 @@ impl SyntacticView { } cw.fill( if selected { &styles.selected_line } else { &styles.preview }, - LONG_SPACE, + &SPACE_FILLING, )?; w.queue(SetBackgroundColor(bg))?; if is_thumb(y, scrollbar) { diff --git a/src/verb/verb_conf.rs b/src/verb/verb_conf.rs index c7e7776..585e674 100644 --- a/src/verb/verb_conf.rs +++ b/src/verb/verb_conf.rs @@ -16,6 +16,14 @@ use { impl TryFrom<&Value> for Verb { type Error = ConfError; fn try_from(verb_value: &Value) -> Result<Self, Self::Error> { + let verb_value = match verb_value { + Value::Table(tbl) => tbl, + _ => { + return Err(ConfError::InvalidVerbConf { + details: "unexpected verb conf structure".to_string(), + }); + } + }; let invocation = string_field(verb_value, "invocation"); let key = string_field(verb_value, "key") .map(|s| keys::parse_key(&s)) diff --git a/website/docs/conf_file.md b/website/docs/conf_file.md index de8d767..518210f 100644 --- a/website/docs/conf_file.md +++ b/website/docs/conf_file.md @@ -81,30 +81,33 @@ show_selection_mark = true # Columns order -You may change the order of file attributes in file lists. +You may change the order of file attributes in file lists: -The `cols_order` property, if specified, must be a permutation of `"gbpdscn"` where every char denotes a column: +* mark: a small triangle flagging the selected line +* git : Git file info +* branch : shows the depth and parent in the tree +* permission : mode, user, group +* date : last modification date +* size : ISO size (and size bar when sorting) +* count : number of files in directories +* name : file name -* 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 - -The default value is - -```toml -cols_order = "gscpdbn" -``` -If you prefer to have the branches left of the tree (as was the default in broot prior 0.18.1) you can use +For example, if you prefer to have the branches left of the tree (as was the default in broot prior 0.18.1) you can use ```toml -cols_order = "gbpdscn" +cols_order = [ + "mark", + "git", + "branch", + "permission", + "date", + "size", + "count", + "name", +] ``` -The `n` column should be kept at end as it's the only one with a variable size. +The name should be kept at end as it's the only one with a variable size. # Colors by file extension |