diff options
author | Canop <cano.petrole@gmail.com> | 2019-02-17 21:47:40 +0100 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2019-02-17 21:47:40 +0100 |
commit | 6534cf0a4a4aadc4d9881d8cb99a750d0e473340 (patch) | |
tree | 0c171b759457ebf9d79aa3ae92f578a4fef85ccb | |
parent | 06b2e9dfa7b31daec9a0ad1c858c8a570f76452b (diff) |
All colors of broot can be defined in config
-rw-r--r-- | documentation.md | 52 | ||||
-rw-r--r-- | img/20190217-custom-colors-help.png | bin | 0 -> 35463 bytes | |||
-rw-r--r-- | img/20190217-custom-colors-tree.png | bin | 0 -> 38191 bytes | |||
-rw-r--r-- | src/app_context.rs | 1 | ||||
-rw-r--r-- | src/browser_states.rs | 15 | ||||
-rw-r--r-- | src/errors.rs | 2 | ||||
-rw-r--r-- | src/help_states.rs | 28 | ||||
-rw-r--r-- | src/input.rs | 4 | ||||
-rw-r--r-- | src/screen_text.rs | 82 | ||||
-rw-r--r-- | src/screens.rs | 10 | ||||
-rw-r--r-- | src/skin.rs | 23 | ||||
-rw-r--r-- | src/skin_conf.rs | 19 | ||||
-rw-r--r-- | src/spinner.rs | 9 | ||||
-rw-r--r-- | src/status.rs | 4 | ||||
-rw-r--r-- | src/tree_views.rs | 50 |
15 files changed, 168 insertions, 131 deletions
diff --git a/documentation.md b/documentation.md index 99a1c04..7d60c3a 100644 --- a/documentation.md +++ b/documentation.md @@ -185,3 +185,55 @@ Example: ![dcd ruleset](img/20190122-dcd_rulset.png) +## Custom Colors + +You can change all colors by adding a `[skin]` section in your `conf.toml` file. + +For example: + + [skin] + status_normal_fg = "grayscale(18)" + status_normal_bg = "grayscale(3)" + status_error_fg = "red" + status_error_bg = "yellow" + tree_fg = "red" + selected_line_bg = "grayscale(7)" + permissions_fg = "grayscale(12)" + size_bar_full_bg = "red" + size_bar_void_bg = "black" + directory_fg = "lightyellow" + input_fg = "cyan" + flag_value_fg = "lightyellow" + table_border_fg = "red" + code_fg = "lightyellow" + +which would look like this: + +![custom colors tree](img/20190217-custom-colors-tree.png) + +![custom colors help](img/20190217-custom-colors-help.png) + +Complete list of keys (expected to change before the v1 of broot): + + char_match + code + directory + file + file_error + flag_label + flag_value + input + link + permissions + selected_line + size_bar_full + size_bar_void + size_text + spinner + status_error + status_normal + table_border + tree + unlisted + +Add `_fg` for a foreground color while `_bg` is for background colors. diff --git a/img/20190217-custom-colors-help.png b/img/20190217-custom-colors-help.png Binary files differnew file mode 100644 index 0000000..729effc --- /dev/null +++ b/img/20190217-custom-colors-help.png diff --git a/img/20190217-custom-colors-tree.png b/img/20190217-custom-colors-tree.png Binary files differnew file mode 100644 index 0000000..c09f41a --- /dev/null +++ b/img/20190217-custom-colors-tree.png diff --git a/src/app_context.rs b/src/app_context.rs index d9c4426..3ed3093 100644 --- a/src/app_context.rs +++ b/src/app_context.rs @@ -1,6 +1,5 @@ use crate::cli::AppLaunchArgs; use crate::verbs::VerbStore; -use crate::skin::Skin; /// The immutable container that can be passed around to provide /// the configuration things diff --git a/src/browser_states.rs b/src/browser_states.rs index 6869b43..6d279ef 100644 --- a/src/browser_states.rs +++ b/src/browser_states.rs @@ -5,7 +5,6 @@ use std::io::{self, Write}; use std::path::PathBuf; use std::result::Result; use std::time::Instant; -use termion::color; use crate::app::{AppState, AppStateCmdResult}; use crate::app_context::AppContext; @@ -312,19 +311,23 @@ impl AppState for BrowserState { let total_char_size = 9; write!( screen.stdout, - "{}{}{}{} h:{} gi:{}{}{}", + "{}{}{}{} h:{}{}{}{}{} gi:{}{}{}", termion::cursor::Goto(screen.w - total_char_size, screen.h), - color::Bg(color::AnsiValue::grayscale(1)), + screen.skin.flag_label.fg, + screen.skin.flag_label.bg, termion::clear::UntilNewline, - color::Fg(color::AnsiValue::grayscale(15)), + screen.skin.flag_value.fg, + screen.skin.flag_value.bg, if tree.options.show_hidden { 'y' } else { 'n' }, + screen.skin.flag_label.fg, + screen.skin.flag_label.bg, + screen.skin.flag_value.fg, + screen.skin.flag_value.bg, match tree.options.respect_git_ignore { OptionBool::Auto => 'a', OptionBool::Yes => 'y', OptionBool::No => 'n', }, - color::Bg(color::Reset), - color::Fg(color::Reset), )?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index df88266..6f0e8b7 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,8 +2,6 @@ use custom_error::custom_error; use std::io; -use crate::conf; - custom_error! {pub TreeBuildError NotADirectory { path: String } = "Not a directory: {}", FileNotFound { path: String } = "File not found: {}", diff --git a/src/help_states.rs b/src/help_states.rs index 1a7c1b4..fa396c6 100644 --- a/src/help_states.rs +++ b/src/help_states.rs @@ -8,6 +8,7 @@ use crate::commands::{Action, Command}; use crate::conf::Conf; use crate::screen_text::{Text, TextTable}; use crate::screens::{Screen, ScreenArea}; +use crate::skin::Skin; use crate::status::Status; use crate::task_sync::TaskLifetime; use crate::verbs::{PrefixSearchResult, Verb, VerbExecutor}; @@ -24,19 +25,6 @@ impl HelpState { state.resize_area(screen); state } - fn build_verbs_table(&self, text: &mut Text, con: &AppContext) { - let mut tbl: TextTable<Verb> = TextTable::new(); - tbl.add_col("name", &|verb| &verb.name); - tbl.add_col("shortcut", &|verb| { - if let Some(sk) = &verb.short_key { - &sk - } else { - "" - } - }); - tbl.add_col("description", &|verb: &Verb| &verb.description); - tbl.write(&con.verb_store.verbs, text); - } fn resize_area(&mut self, screen: &Screen) { self.area.bottom = screen.h - 2; self.area.width = screen.w; @@ -78,7 +66,7 @@ impl AppState for HelpState { } fn display(&mut self, screen: &mut Screen, con: &AppContext) -> io::Result<()> { - let mut text = Text::new(); + let mut text = Text::new(&screen.skin); text.md(""); text.md(r#" **broot** lets you explore directory trees and launch commands."#); text.md(r#" site: https://github.com/Canop/broot."#); @@ -90,7 +78,16 @@ impl AppState for HelpState { text.md(""); text.md(r#" To execute a verb, type a space or `:` then start of its name or shortcut."#); text.md(" Verbs:"); - self.build_verbs_table(&mut text, con); + let mut tbl: TextTable<Verb> = TextTable::new(&screen.skin); + tbl.add_col("name", &|verb| &verb.name); + tbl.add_col("shortcut", &|verb| { + if let Some(sk) = &verb.short_key { + &sk + } else { + "" + } + }); + tbl.write(&con.verb_store.verbs, &mut text); text.md(""); text.md(&format!( " Verb can be configured in {:?}.", @@ -109,6 +106,7 @@ impl AppState for HelpState { text.md(" When gitignore is auto, .gitignore rules are respected if"); text.md(" the displayed root is a git repository or in one."); self.area.content_length = text.height() as i32; + screen.reset_colors()?; text.write(screen, &self.area)?; Ok(()) } diff --git a/src/input.rs b/src/input.rs index 8a730f3..9503888 100644 --- a/src/input.rs +++ b/src/input.rs @@ -13,8 +13,10 @@ impl Input for Screen { fn write_input(&mut self, cmd: &Command) -> io::Result<()> { write!( self.stdout, - "{}{}{}{} {}", + "{}{}{}{}{}{} {}", termion::cursor::Goto(1, self.h), + self.skin.input.fg, + self.skin.input.bg, termion::clear::CurrentLine, cmd.raw, termion::style::Invert, diff --git a/src/screen_text.rs b/src/screen_text.rs index 97d5d13..10fbd96 100644 --- a/src/screen_text.rs +++ b/src/screen_text.rs @@ -5,9 +5,10 @@ use regex::Regex; use std::io::{self, Write}; -use termion::{color, style}; +use termion::style; use crate::screens::{Screen, ScreenArea}; +use crate::skin::Skin; // write a string, taking exactly w chars, either // by cutting or by padding @@ -18,38 +19,46 @@ fn append_pad(dest: &mut String, s: &str, w: usize) { } } -// do some simple markdown-like formatting to ease text generation: -// - bold: **bold text** -// - code: `some code` -fn md(s: &str) -> String { - lazy_static! { - static ref bold_regex: Regex = Regex::new(r"\*\*([^*]+)\*\*").unwrap(); - static ref bold_repl: String = format!("{}$1{}", style::Bold, style::Reset); - static ref code_regex: Regex = Regex::new(r"`([^`]+)`").unwrap(); - static ref code_repl: String = format!( - "{} $1 {}", - color::Bg(color::AnsiValue::grayscale(2)), - color::Bg(color::Reset) - ); - } - let s = bold_regex.replace_all(s, &*bold_repl as &str); // TODO how to avoid this complex casting ? - let s = code_regex.replace_all(&s, &*code_repl as &str); - s.to_string() -} - /// A Text is a vec of lines pub struct Text { lines: Vec<String>, + md_bold_repl: String, + md_code_repl: String, } impl Text { - pub fn new() -> Text { - Text { lines: Vec::new() } + pub fn new(skin: &Skin) -> Text { + let md_code_repl = format!( + "{}{} $1 {}{}", + skin.code.fg, + skin.code.bg, + skin.reset.fg, + skin.reset.bg, + ); + let md_bold_repl = format!("{}$1{}", style::Bold, style::Reset); + Text { + lines: Vec::new(), + md_bold_repl, + md_code_repl, + } } pub fn height(&self) -> usize { self.lines.len() } + // do some simple markdown-like formatting to ease text generation: + // - bold: **bold text** + // - code: `some code` + pub fn md_to_tty(&self, raw: &str) -> String { + lazy_static! { + static ref bold_regex: Regex = Regex::new(r"\*\*([^*]+)\*\*").unwrap(); + static ref code_regex: Regex = Regex::new(r"`([^`]+)`").unwrap(); + } + let s = bold_regex.replace_all(raw, &*self.md_bold_repl); + let s = code_regex.replace_all(&s, &*self.md_code_repl); + s.to_string() + } + // add a line from the line interpreted as "markdown" pub fn md(&mut self, line: &str) { - self.lines.push(md(line)); + self.lines.push(self.md_to_tty(line)); } pub fn push(&mut self, line: String) { self.lines.push(line); @@ -93,11 +102,20 @@ impl<'a, R> TextCol<'a, R> {} /// A small utility to format some data in a tabular way on screen pub struct TextTable<'a, R> { cols: Vec<TextCol<'a, R>>, + bar: String, // according to skin } impl<'a, R> TextTable<'a, R> { - pub fn new() -> TextTable<'a, R> { - TextTable { cols: Vec::new() } + pub fn new(skin: &Skin) -> TextTable<'a, R> { + let bar = format!( + " {}│{} ", + skin.table_border.fg, + skin.reset.fg, + ); + TextTable { + cols: Vec::new(), + bar, + } } pub fn add_col(&mut self, title: &str, extract: &'a Fn(&'a R) -> &str) { let width = title.len(); // initial value, will change @@ -119,18 +137,10 @@ impl<'a, R> TextTable<'a, R> { // Right now, to ease width computations, md transformation is done // only in the last column pub fn write(&mut self, rows: &'a [R], text: &mut Text) { - lazy_static! { - static ref bar: String = format!( - " {}│{} ", - color::Fg(color::AnsiValue::grayscale(8)), - color::Fg(color::Reset), - ) - .to_string(); - } self.compute_col_widths(&rows); let mut header = String::new(); for col in &self.cols { - header.push_str(&*bar); + header.push_str(&self.bar); // we're lazy here: // we add some bold, and add 4 for the width because we know the * won't // show up on screen. @@ -140,10 +150,10 @@ impl<'a, R> TextTable<'a, R> { for row in rows { let mut line = String::new(); for (i, col) in self.cols.iter().enumerate() { - line.push_str(&*bar); + line.push_str(&self.bar); let s = (col.extract)(row); if i == self.cols.len() - 1 { - line.push_str(&md(s)); + line.push_str(&text.md_to_tty(s)); } else { append_pad(&mut line, s, col.width); } diff --git a/src/screens.rs b/src/screens.rs index 66fad51..4b106e1 100644 --- a/src/screens.rs +++ b/src/screens.rs @@ -1,6 +1,7 @@ use std::io::{self, stdout, Write}; use termion::raw::{IntoRawMode, RawTerminal}; use termion::screen::AlternateScreen; +use termion::color; use crate::skin::{Skin, SkinEntry}; pub struct Screen { @@ -38,8 +39,13 @@ impl Screen { self.h = h; Ok(()) } - pub fn apply_skin_entry(&mut self, ske: &SkinEntry) -> io::Result<()> { - write!(self.stdout, "{}{}", ske.fg, ske.bg) + pub fn reset_colors(&mut self) -> io::Result<()> { + write!( + self.stdout, + "{}{}", + color::Fg(color::Reset), + color::Bg(color::Reset), + ) } } diff --git a/src/skin.rs b/src/skin.rs index 9aa29e1..40d9cec 100644 --- a/src/skin.rs +++ b/src/skin.rs @@ -1,6 +1,6 @@ use std::io::{self, Write}; -use crate::screens::{Screen, ScreenArea}; +use crate::screens::Screen; use std::collections::HashMap; use termion::color::{self, *}; @@ -11,9 +11,6 @@ pub struct SkinEntry { } impl SkinEntry { - pub fn apply(&self, screen: &mut Screen) -> io::Result<()> { - write!(screen.stdout, "{}{}", self.fg, self.bg) - } pub fn fgbg(&self) -> String { format!("{}{}", self.fg, self.bg) } @@ -48,12 +45,6 @@ macro_rules! Skin { } } -// TODO none instead of Reset -// -> and alert when a none is used -// TODO allow default in skin conf -// TODO fonction pour générer un nouveau fichier de skin ? -// TODO utiliser le même format exactement ici et dans toml ? -// TODO autoriser plusieurs skins avec des noms ? Skin! { status_normal: White, AnsiValue::grayscale(2) status_error: Red, AnsiValue::grayscale(2) @@ -63,6 +54,18 @@ Skin! { size_text: AnsiValue::grayscale(15), Reset size_bar_full: Reset, Magenta size_bar_void: Reset, AnsiValue::grayscale(2) + file: White, Reset + directory: LightBlue, Reset + char_match: Green, Reset + link: LightMagenta, Reset + file_error: Red, Reset + unlisted: AnsiValue::grayscale(13), Reset + input: White, Reset + flag_label: AnsiValue::grayscale(14), AnsiValue::grayscale(1) + flag_value: AnsiValue::grayscale(16), AnsiValue::grayscale(1) + code: Reset, AnsiValue::grayscale(2) + table_border: AnsiValue::grayscale(8), Reset + spinner: AnsiValue::grayscale(10), AnsiValue::grayscale(2) } diff --git a/src/skin_conf.rs b/src/skin_conf.rs index ba66795..94871e1 100644 --- a/src/skin_conf.rs +++ b/src/skin_conf.rs @@ -5,9 +5,8 @@ /// The code is ugly. Really. I know. /// I found no clean way to deal with termion colors. use std::result::Result; -use std::ascii::AsciiExt; use regex::Regex; -use termion::color::{self, *}; +use termion::color::*; use crate::errors::ConfError; fn parse_gray(raw: &str) -> Result<Option<u8>, ConfError> { @@ -92,19 +91,3 @@ make_parseurs! { Yellow, } -//pub fn parse_entry(raw: &str) -> Result<String, ConfError> { -// let mut tokens = raw.split_whitespace(); -// let fg = match tokens.next() { -// Some(c) => parse_fg(c)?, -// None => { -// return Err(ConfError::InvalidSkinEntry{reason:"Missing foreground in skin".to_string()}); -// } -// }; -// let bg = match tokens.next() { -// Some(c) => parse_bg(c)?, -// None => { -// return Err(ConfError::InvalidSkinEntry{reason:"Missing background in skin".to_string()}); -// } -// }; -// Ok(format!("{}{}", fg, bg)) -//} diff --git a/src/spinner.rs b/src/spinner.rs index d523b8a..8f0c6ac 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -2,7 +2,6 @@ //! Executed during the do_pending_tasks of the states use std::io::{self, Write}; -use termion::color; use crate::screens::Screen; @@ -15,13 +14,11 @@ impl Spinner for Screen { let y = self.h - 1; write!( self.stdout, - "{}{}{}{}{}{}", + "{}{}{}{}", termion::cursor::Goto(1, y), - color::Bg(color::AnsiValue::grayscale(2)), - color::Fg(color::AnsiValue::grayscale(10)), + self.skin.spinner.fg, + self.skin.spinner.bg, if spinning { "⌛" } else { " " }, - color::Bg(color::Reset), - color::Fg(color::Reset), )?; self.stdout.flush()?; Ok(()) diff --git a/src/status.rs b/src/status.rs index 34d570d..4c27ad3 100644 --- a/src/status.rs +++ b/src/status.rs @@ -23,8 +23,6 @@ impl Status for Screen { termion::cursor::Goto(2, y), self.skin.status_error.fg, self.skin.status_error.bg, - //color::Bg(color::AnsiValue::grayscale(2)), - //color::Fg(color::Red), termion::clear::CurrentLine, text, self.skin.reset.bg, @@ -42,11 +40,9 @@ impl Status for Screen { termion::cursor::Goto(2, y), self.skin.status_normal.fg, self.skin.status_normal.bg, - //color::Bg(color::AnsiValue::grayscale(2)), termion::clear::CurrentLine, text, self.skin.reset.bg, - //color::Bg(color::Reset), )?; self.stdout.flush()?; Ok(()) diff --git a/src/tree_views.rs b/src/tree_views.rs index 5fa4ed3..e0e40a8 100644 --- a/src/tree_views.rs +++ b/src/tree_views.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::io::{self, Write}; use std::sync::Mutex; -use termion::{color, style}; +use termion::style; use users::{Groups, Users, UsersCache}; use crate::file_sizes::Size; @@ -124,7 +124,6 @@ impl TreeView for Screen { write!(self.stdout, "{}▐", termion::cursor::Goto(self.w, y),)?; } } - //write!(self.stdout, "{}", color::Fg(color::Reset),)?; } self.stdout.flush()?; Ok(()) @@ -167,15 +166,15 @@ impl TreeView for Screen { write!( self.stdout, "{}{} ", - color::Bg(color::Reset), - color::Fg(color::Reset), + self.skin.reset.fg, + self.skin.reset.bg, ) } else { write!( self.stdout, "{}────────{} ", - color::Fg(color::AnsiValue::grayscale(5)), - color::Fg(color::Reset), + self.skin.tree.fg, + self.skin.reset.fg, ) } } @@ -186,15 +185,6 @@ impl TreeView for Screen { idx: usize, pattern: &Pattern, ) -> io::Result<()> { - lazy_static! { - static ref fg_reset: String = format!("{}", color::Fg(color::White)).to_string(); - static ref fg_dir: String = format!("{}", color::Fg(color::LightBlue)).to_string(); - static ref fg_err: String = format!("{}", color::Fg(color::Red)).to_string(); - static ref fg_link: String = format!("{}", color::Fg(color::LightMagenta)).to_string(); - static ref fg_match: String = format!("{}", color::Fg(color::Green)).to_string(); - static ref fg_reset_dir: String = format!("{}{}", &*fg_reset, &*fg_dir).to_string(); - static ref fg_reset_link: String = format!("{}{}", &*fg_reset, &*fg_link).to_string(); - } // TODO draw in red lines with has_error match &line.line_type { LineType::Dir => { @@ -203,7 +193,7 @@ impl TreeView for Screen { self.stdout, "{}{}{}", style::Bold, - &*fg_dir, + &self.skin.directory.fg, &line.path.to_string_lossy(), )?; } else { @@ -211,8 +201,8 @@ impl TreeView for Screen { self.stdout, "{}{}{}", style::Bold, - &*fg_dir, - decorated_name(&line.name, pattern, &*fg_match, &*fg_reset_dir), + &self.skin.directory.fg, + decorated_name(&line.name, pattern, &self.skin.char_match.fg, &self.skin.directory.fg), )?; if line.unlisted > 0 { write!(self.stdout, " …",)?; @@ -223,18 +213,18 @@ impl TreeView for Screen { write!( self.stdout, "{}{}", - &*fg_reset, - decorated_name(&line.name, pattern, &*fg_match, &*fg_reset), + &self.skin.file.fg, + decorated_name(&line.name, pattern, &self.skin.char_match.fg, &self.skin.file.fg), )?; } LineType::SymLinkToFile(target) => { write!( self.stdout, "{}{} {}->{} {}", - &*fg_link, - decorated_name(&line.name, pattern, &*fg_match, &*fg_reset_link), - if line.has_error { &*fg_err } else { &*fg_link }, - &*fg_reset, + &self.skin.link.fg, + decorated_name(&line.name, pattern, &self.skin.char_match.fg, &self.skin.link.fg), + if line.has_error { &self.skin.file_error.fg } else { &self.skin.link.fg }, + &self.skin.file.fg, &target, )?; } @@ -242,11 +232,11 @@ impl TreeView for Screen { write!( self.stdout, "{}{} {}->{}{} {}", - &*fg_link, - decorated_name(&line.name, pattern, &*fg_match, &*fg_reset_link), - if line.has_error { &*fg_err } else { &*fg_link }, + &self.skin.link.fg, + decorated_name(&line.name, pattern, &self.skin.char_match.fg, &self.skin.link.fg), + if line.has_error { &self.skin.file_error.fg } else { &self.skin.link.fg }, style::Bold, - &*fg_reset_dir, + &self.skin.directory.fg, &target, )?; } @@ -254,8 +244,8 @@ impl TreeView for Screen { write!( self.stdout, //"{}{}… {} unlisted", still not sure whether I want this '…' - "{}{} {} unlisted", - color::Fg(color::AnsiValue::grayscale(13)), + "{}{}{} unlisted", + self.skin.unlisted.fg, style::Italic, &line.unlisted, )?; |