From 44d3be5e5aaa5c1cf66e151ebd8a2f7e3c399c16 Mon Sep 17 00:00:00 2001 From: Canop Date: Sat, 8 Jun 2019 09:27:21 +0200 Subject: configurable skin supported on help screen and in :print_tree screen output also bump version to 0.8, as it's the first deployed version based on crossterm&termimad and having some kind of windows support --- CHANGELOG.md | 4 + Cargo.lock | 8 +- Cargo.toml | 4 +- src/app.rs | 13 +- src/browser_states.rs | 45 +++---- src/cli.rs | 44 ++++--- src/command_parsing.rs | 34 +++--- src/commands.rs | 6 +- src/conf.rs | 61 ++++++++-- src/displayable_tree.rs | 75 +++++++----- src/external.rs | 125 +++++++++++--------- src/file_sizes/file_sizes_unix.rs | 112 +++++++++--------- src/file_sizes/file_sizes_windows.rs | 101 ++++++++-------- src/file_sizes/mod.rs | 2 +- src/flat_tree.rs | 20 ++-- src/help_content.rs | 14 +-- src/help_states.rs | 37 ++---- src/input.rs | 10 +- src/main.rs | 4 +- src/patterns.rs | 38 ++---- src/permissions/mod.rs | 1 - src/permissions/permissions_unix.rs | 35 +++--- src/screens.rs | 12 +- src/shell_bash.rs | 2 +- src/shell_fish.rs | 2 +- src/shell_install.rs | 68 ++++++----- src/skin.rs | 74 +++++++++--- src/skin_conf.rs | 36 ++++-- src/spinner.rs | 10 +- src/status.rs | 8 +- src/tree_build.rs | 6 +- src/verb_invocation.rs | 6 +- src/verb_store.rs | 138 ++++++++++------------ src/verbs.rs | 122 ++++++++++--------- test.txt | 40 ------- website/docs/community.md | 6 +- website/docs/documentation/configuration.md | 89 +++++++------- website/docs/documentation/installation.md | 5 +- website/docs/img/20190607-custom-colors-sizes.png | Bin 0 -> 43007 bytes website/docs/img/20190607-custom-colors-tree.png | Bin 0 -> 33307 bytes 40 files changed, 732 insertions(+), 685 deletions(-) delete mode 100644 test.txt create mode 100644 website/docs/img/20190607-custom-colors-sizes.png create mode 100644 website/docs/img/20190607-custom-colors-tree.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a084d7..f08a605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ + +### v0.8.0 - 2019-06-07 +Half broot has been rewritten to allow Windows compatibility. Termion has been replaced with crossterm. + ### v0.7.5 - 2019-04-03 - try to give arguments to verbs executed with --cmd diff --git a/Cargo.lock b/Cargo.lock index b32ca4d..f6324fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ [[package]] name = "broot" -version = "0.7.5" +version = "0.8.0" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -100,7 +100,7 @@ dependencies = [ "opener 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "termimad 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "termimad 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -617,7 +617,7 @@ dependencies = [ [[package]] name = "termimad" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossterm 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -795,7 +795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -"checksum termimad 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a8326aabc4d9ab6d6410c99fbd1a3bff0f89f2a7d315bbf765e2ea754e41eb45" +"checksum termimad 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c335380ef344bd1d9f20866ea45ba96319ef3191a43d38210c12a565a547848" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" diff --git a/Cargo.toml b/Cargo.toml index 0450d31..48036f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "broot" -version = "0.7.5" +version = "0.8.0" authors = ["dystroy "] repository = "https://github.com/Canop/broot" description = "Fuzzy Search + tree + cd" @@ -23,7 +23,7 @@ glob = "0.3" crossbeam = "0.7" opener = "0.4" crossterm = "0.9.5" -termimad = "0.3.4" +termimad = "0.3.5" #termimad = { path = "../termimad" } [target.'cfg(unix)'.dependencies] diff --git a/src/app.rs b/src/app.rs index 69cbd01..f13c699 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,17 +7,17 @@ //! - an operation which keeps the state //! - a request to quit broot //! - a request to launch an executable (thus leaving broot) +use crossterm::{InputEvent, TerminalInput}; use std::io::{self, Write}; use std::result::Result; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{mpsc, Arc}; use std::thread; -use crossterm::{TerminalInput, InputEvent}; use crate::app_context::AppContext; use crate::browser_states::BrowserState; -use crate::commands::Command; use crate::command_parsing::parse_command_sequence; +use crate::commands::Command; use crate::errors::ProgramError; use crate::errors::TreeBuildError; use crate::external::Launchable; @@ -65,11 +65,7 @@ pub trait AppState { screen: &mut Screen, con: &AppContext, ) -> io::Result; - fn refresh( - &mut self, - screen: &Screen, - con: &AppContext, - ) -> Command; + fn refresh(&mut self, screen: &Screen, con: &AppContext) -> Command; fn has_pending_tasks(&self) -> bool; fn do_pending_task(&mut self, screen: &mut Screen, tl: &TaskLifetime); fn display(&mut self, screen: &mut Screen, con: &AppContext) -> io::Result<()>; @@ -125,9 +121,9 @@ impl App { let has_task = self.state().has_pending_tasks(); if has_task { loop { + self.mut_state().display(screen, con)?; self.state().write_status(screen, &cmd, con)?; screen.write_spinner(true)?; - self.mut_state().display(screen, con)?; if tl.is_expired() { break; } @@ -139,6 +135,7 @@ impl App { screen.write_spinner(false)?; } self.mut_state().display(screen, con)?; + self.mut_state().write_status(screen, &cmd, con)?; Ok(()) } diff --git a/src/browser_states.rs b/src/browser_states.rs index 2eb0b29..928be19 100644 --- a/src/browser_states.rs +++ b/src/browser_states.rs @@ -1,4 +1,3 @@ - use std::io; use std::path::PathBuf; use std::result::Result; @@ -18,8 +17,8 @@ use crate::status::Status; use crate::task_sync::TaskLifetime; use crate::tree_build::TreeBuilder; use crate::tree_options::{OptionBool, TreeOptions}; -use crate::verbs::{VerbExecutor}; -use crate::verb_store::{PrefixSearchResult}; +use crate::verb_store::PrefixSearchResult; +use crate::verbs::VerbExecutor; /// An application state dedicated to displaying a tree. /// It's the first and main screen of broot. @@ -30,7 +29,6 @@ pub struct BrowserState { } impl BrowserState { - pub fn new( path: PathBuf, mut options: TreeOptions, @@ -82,11 +80,9 @@ impl BrowserState { None => &self.tree, } } - } impl AppState for BrowserState { - fn apply( &mut self, cmd: &mut Command, @@ -178,7 +174,9 @@ impl AppState for BrowserState { con.verb_store.verbs[cd_idx].to_cmd_result(&line.target(), &None, screen, con)? } Action::Verb(invocation) => match con.verb_store.search(&invocation.key) { - PrefixSearchResult::Match(verb) => self.execute_verb(verb, &invocation, screen, con)?, + PrefixSearchResult::Match(verb) => { + self.execute_verb(verb, &invocation, screen, con)? + } _ => AppStateCmdResult::verb_not_found(&invocation.key), }, Action::FuzzyPatternEdit(pat) => match pat.len() { @@ -204,10 +202,7 @@ impl AppState for BrowserState { } } Action::Help => { - AppStateCmdResult::NewState( - Box::new(HelpState::new(screen, con)), - Command::new() - ) + AppStateCmdResult::NewState(Box::new(HelpState::new(screen, con)), Command::new()) } Action::Refresh => AppStateCmdResult::RefreshState, Action::Quit => AppStateCmdResult::Quit, @@ -276,7 +271,7 @@ impl AppState for BrowserState { left: 0, top: 0, width: screen.w, - height: screen.h-2, + height: screen.h - 2, }, in_app: true, }; @@ -340,11 +335,7 @@ impl AppState for BrowserState { } } - fn refresh( - &mut self, - screen: &Screen, - _con: &AppContext, - ) -> Command { + fn refresh(&mut self, screen: &Screen, _con: &AppContext) -> Command { let page_height = BrowserState::page_height(screen) as usize; // refresh the base tree if let Err(e) = self.tree.refresh(page_height) { @@ -370,21 +361,23 @@ impl AppState for BrowserState { screen.goto(screen.w - total_char_size, screen.h); let terminal = crossterm::Terminal::new(); terminal.clear(crossterm::ClearType::UntilNewLine)?; - screen.write(&format!( + print!( "{}{} {}{}", screen.skin.flag_label.apply_to(" h:"), - screen.skin.flag_value.apply_to( - if tree.options.show_hidden { 'y' } else { 'n' } - ), + screen + .skin + .flag_value + .apply_to(if tree.options.show_hidden { 'y' } else { 'n' }), screen.skin.flag_label.apply_to(" gi:"), - screen.skin.flag_value.apply_to( - match tree.options.respect_git_ignore { + screen + .skin + .flag_value + .apply_to(match tree.options.respect_git_ignore { OptionBool::Auto => 'a', OptionBool::Yes => 'y', OptionBool::No => 'n', - } - ), - )); + }), + ); Ok(()) } } diff --git a/src/cli.rs b/src/cli.rs index dae85a8..ecf855f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,5 @@ /// this module manages reading and translating /// the arguments passed on launch of the application. - use crate::errors::{ProgramError, TreeBuildError}; use crate::tree_options::TreeOptions; use clap; @@ -9,15 +8,18 @@ use std::io; use std::path::PathBuf; use std::result::Result; +use crossterm::Color::*; +use termimad::{rgb, Alignment, MadSkin}; + pub struct AppLaunchArgs { pub root: PathBuf, // what should be the initial root pub file_export_path: Option, // where to write the produced path (if required with --out) - pub cmd_export_path: Option, // where to write the produced command (if required with --outcmd, or -oc) - pub tree_options: TreeOptions, // initial tree options - pub commands: Option, // commands passed as cli argument, still unparsed - pub install: bool, // installation is required - pub height: Option, // an optional height to replace the screen's one - pub no_style: bool, // whether to remove all styles (including colors) + pub cmd_export_path: Option, // where to write the produced command (if required with --outcmd, or -oc) + pub tree_options: TreeOptions, // initial tree options + pub commands: Option, // commands passed as cli argument, still unparsed + pub install: bool, // installation is required + pub height: Option, // an optional height to replace the screen's one + pub no_style: bool, // whether to remove all styles (including colors) } /// declare the possible CLI arguments, and gets the values @@ -64,7 +66,7 @@ fn get_cli_args<'a>() -> clap::ArgMatches<'a> { clap::Arg::with_name("height") .long("height") .help("height (if you don't want to fill the screen or for file export)") - .takes_value(true) + .takes_value(true), ) .arg( clap::Arg::with_name("install") @@ -141,9 +143,7 @@ pub fn read_lauch_args() -> Result { .value_of("commands") .and_then(|s| Some(s.to_owned())); let no_style = cli_args.is_present("no-style"); - let height = cli_args - .value_of("height") - .and_then(|s| s.parse().ok()); + let height = cli_args.value_of("height").and_then(|s| s.parse().ok()); Ok(AppLaunchArgs { root, file_export_path, @@ -156,11 +156,27 @@ pub fn read_lauch_args() -> Result { }) } -pub fn ask_authorization(question: &str) -> io::Result { - println!("{}", question); +/// wait for user input, return `true` if she +/// didn't answer 'n' +pub fn ask_authorization() -> io::Result { let answer = crossterm::input().read_line()?; Ok(match answer.as_ref() { - "n" => false, + "n" | "N" => false, _ => true, }) } + +pub fn mad_skin() -> MadSkin { + let mut skin = MadSkin::default(); + skin.set_headers_fg(rgb!(255, 187, 0)); + skin.bold.set_fg(Yellow); + skin.italic.set_fgbg(Magenta, rgb!(30, 30, 40)); + skin.scrollbar.set_track_fg(Rgb { + r: 30, + g: 30, + b: 40, + }); + skin.scrollbar.set_thumb_fg(Rgb { r: 67, g: 51, b: 0 }); + skin.code.align = Alignment::Center; + skin +} diff --git a/src/command_parsing.rs b/src/command_parsing.rs index 280442a..8014ee7 100644 --- a/src/command_parsing.rs +++ b/src/command_parsing.rs @@ -11,7 +11,7 @@ use crate::verb_store::PrefixSearchResult; #[derive(Debug)] enum CommandSequenceToken { Standard(String), // one or several words, not starting with a ':'. May be a filter or a verb argument - VerbKey(String), // a verb (the ':' isn't given) + VerbKey(String), // a verb (the ':' isn't given) } struct CommandSequenceTokenizer { chars: Vec, @@ -21,7 +21,7 @@ impl CommandSequenceTokenizer { pub fn from(sequence: &str) -> CommandSequenceTokenizer { CommandSequenceTokenizer { chars: sequence.chars().collect(), - pos: 0 + pos: 0, } } } @@ -49,13 +49,11 @@ impl Iterator for CommandSequenceTokenizer { } let token: String = self.chars[self.pos..end].iter().collect(); self.pos = end + 1; - Some( - if is_verb { - CommandSequenceToken::VerbKey(token) - } else { - CommandSequenceToken::Standard(token) - } - ) + Some(if is_verb { + CommandSequenceToken::VerbKey(token) + } else { + CommandSequenceToken::Standard(token) + }) } } @@ -70,7 +68,10 @@ impl Iterator for CommandSequenceTokenizer { /// The current parsing try to be the least possible flawed by /// giving verbs the biggest sequence of tokens accepted by their /// execution pattern. -pub fn parse_command_sequence(sequence: &str, con: &AppContext) -> Result, ProgramError> { +pub fn parse_command_sequence( + sequence: &str, + con: &AppContext, +) -> Result, ProgramError> { let mut tokenizer = CommandSequenceTokenizer::from(sequence); let mut commands: Vec = Vec::new(); let mut leftover: Option = None; @@ -84,12 +85,12 @@ pub fn parse_command_sequence(sequence: &str, con: &AppContext) -> Result { let verb = match con.verb_store.search(&key) { PrefixSearchResult::NoMatch => { - return Err(ProgramError::UnknownVerb{key}); + return Err(ProgramError::UnknownVerb { key }); } PrefixSearchResult::TooManyMatches => { - return Err(ProgramError::AmbiguousVerbKey{key}); + return Err(ProgramError::AmbiguousVerbKey { key }); } - PrefixSearchResult::Match(verb) => verb + PrefixSearchResult::Match(verb) => verb, }; let mut raw = format!(":{}", key); if let Some(args_regex) = &verb.args_parser { @@ -111,7 +112,7 @@ pub fn parse_command_sequence(sequence: &str, con: &AppContext) -> Result Result raw + CommandSequenceToken::Standard(raw) => raw, }; commands.push(Command::from(raw)); } if let Some(token) = leftover.take() { - commands.push(Command::from(match token{ + commands.push(Command::from(match token { CommandSequenceToken::Standard(raw) => raw, CommandSequenceToken::VerbKey(raw) => format!(":{}", raw), })); } Ok(commands) } - diff --git a/src/commands.rs b/src/commands.rs index bafd62d..967da1e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,15 +2,15 @@ //! in the input. It's independant of the state of the application //! (verbs arent checked at this point) -use regex::Regex; -use crossterm::{KeyEvent}; use crate::verb_invocation::VerbInvocation; +use crossterm::KeyEvent; +use regex::Regex; #[derive(Debug, Clone)] pub struct Command { pub raw: String, // what's visible in the input parts: CommandParts, // the parsed parts of the visible input - pub action: Action, // what's required, based on the last key (which may be not visible, like esc) + pub action: Action, // what's required, based on the last key (which may be not visible, like esc) } /// An intermediate parsed representation of the raw string diff --git a/src/conf.rs b/src/conf.rs index e3cd1cd..5f9b939 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,7 +1,8 @@ //! manage reading the verb shortcuts from the configuration file, //! initializing if if it doesn't yet exist -use crossterm::{Attribute}; +use crossterm::Attribute; +use crossterm::ObjectStyle; use directories::ProjectDirs; use std::collections::HashMap; use std::fs; @@ -9,7 +10,6 @@ use std::io; use std::path::{Path, PathBuf}; use std::result::Result; use toml::{self, Value}; -use crossterm::ObjectStyle; use crate::errors::ConfError; use crate::skin_conf; @@ -60,7 +60,6 @@ pub fn dir() -> PathBuf { } impl Conf { - pub fn default_location() -> PathBuf { dir().join("conf.toml") } @@ -118,9 +117,11 @@ impl Conf { let from_shell = bool_field(verb_value, "from_shell"); let leave_broot = bool_field(verb_value, "leave_broot"); if leave_broot == Some(false) && from_shell == Some(true) { - eprintln!("Invalid [[verbs]] entry in configuration"); - eprintln!("You can't simultaneously have leave_broot=false and from_shell=true"); - continue; + eprintln!("Invalid [[verbs]] entry in configuration"); + eprintln!( + "You can't simultaneously have leave_broot=false and from_shell=true" + ); + continue; } verbs.push(VerbConf { invocation, @@ -139,17 +140,18 @@ impl Conf { for (k, v) in entries_tbl.iter() { if let Some(s) = v.as_str() { match skin_conf::parse_object_style(s) { - Ok(ske) => { skin.insert(k.to_string(), ske); }, - Err(e) => { eprintln!("{}", e); } + Ok(ske) => { + skin.insert(k.to_string(), ske); + } + Err(e) => { + eprintln!("{}", e); + } } } } } - Ok(Conf { - verbs, - skin, - }) + Ok(Conf { verbs, skin }) } } @@ -190,4 +192,39 @@ name = "view" invocation = "view" execution = "less {file}" +##################### +# Skin + +# If you want to change the colors of broot, +# uncomment the following bloc and start messing +# with the various values +# +# [skin] +# tree = "rgb(89, 73, 101) none" +# file = "gray(21) none" +# directory = "rgb(255, 152, 0) none bold" +# exe = "rgb(17, 164, 181) none" +# link = "Magenta none" +# pruning = "rgb(89, 73, 101) none Italic" +# permissions = "gray(12) none " +# selected_line = "none gray(3)" +# size_bar = "black rgb(255, 152, 0)" +# size_no_bar = "gray(15) gray(2)" +# char_match = "yellow none" +# file_error = "Red none" +# flag_label = "gray(16) none" +# flag_value = "rgb(255, 152, 0) none bold" +# input = "White none" +# spinner = "gray(10) gray(2)" +# status_error = "Red gray(2)" +# status_normal = "gray(20) gray(2)" +# scrollbar_track = "rgb(80, 50, 0) none" +# scrollbar_thumb = "rgb(255, 187, 0) none" +# help_paragraph = "gray(20) none" +# help_bold = "rgb(255, 187, 0) none bold" +# help_italic = "Magenta rgb(30, 30, 40) italic" +# help_code = "gray(21) gray(3)" +# help_headers = "rgb(255, 187, 0) none" +# help_table_border = "rgb(170, 136, 0) none" +# "#; diff --git a/src/displayable_tree.rs b/src/displayable_tree.rs index 20c215a..710f09a 100644 --- a/src/displayable_tree.rs +++ b/src/displayable_tree.rs @@ -1,5 +1,5 @@ -use std::fmt; use crossterm::{ClearType, Terminal}; +use std::fmt; use crate::file_sizes::Size; use crate::flat_tree::{LineType, Tree, TreeLine}; @@ -26,9 +26,27 @@ pub struct DisplayableTree<'s, 't> { pub in_app: bool, // if true we show the selection and scrollbar } -impl DisplayableTree<'_, '_> { +impl<'s, 't> DisplayableTree<'s, 't> { + pub fn out_of_app(tree: &'t Tree, skin: &'s Skin, width: u16) -> DisplayableTree<'s, 't> { + DisplayableTree { + tree, + skin, + area: termimad::Area { + left: 0, + top: 0, + width, + height: tree.lines.len() as u16, + }, + in_app: false, + } + } - fn write_line_size(&self, f: &mut fmt::Formatter<'_>, line: &TreeLine, total_size: Size) -> fmt::Result { + fn write_line_size( + &self, + f: &mut fmt::Formatter<'_>, + line: &TreeLine, + total_size: Size, + ) -> fmt::Result { if let Some(s) = line.size { let dr: usize = s.discrete_ratio(total_size, 8) as usize; let s: Vec = s.to_string().chars().collect(); @@ -49,8 +67,10 @@ impl DisplayableTree<'_, '_> { } fn write_mode(&self, f: &mut fmt::Formatter<'_>, mode: u32) -> fmt::Result { - write!(f, "{}", self.skin.permissions.apply_to( - format!( + write!( + f, + "{}", + self.skin.permissions.apply_to(format!( "{}{}{}{}{}{}{}{}{}", if (mode & (1 << 8)) != 0 { 'r' } else { '-' }, if (mode & (1 << 7)) != 0 { 'w' } else { '-' }, @@ -61,8 +81,8 @@ impl DisplayableTree<'_, '_> { if (mode & (1 << 2)) != 0 { 'r' } else { '-' }, if (mode & (1 << 1)) != 0 { 'w' } else { '-' }, if (mode & 1) != 0 { 'x' } else { '-' }, - ) - )) + )) + ) } fn write_line_name( @@ -76,7 +96,11 @@ impl DisplayableTree<'_, '_> { let style = match &line.line_type { LineType::Dir => &self.skin.directory, LineType::File => { - if line.is_exe() { &self.skin.exe } else { &self.skin.file } + if line.is_exe() { + &self.skin.exe + } else { + &self.skin.file + } } LineType::SymLinkToFile(_) | LineType::SymLinkToDir(_) => &self.skin.link, LineType::Pruning => &self.skin.pruning, @@ -93,11 +117,7 @@ impl DisplayableTree<'_, '_> { write!( f, "{}", - pattern.style( - &line.name, - &style, - &self.skin.char_match, - ), + pattern.style(&line.name, &style, &self.skin.char_match,), )?; } match &line.line_type { @@ -111,7 +131,11 @@ impl DisplayableTree<'_, '_> { if line.has_error { self.skin.file_error.write(f, &target)?; } else { - let target_style = if line.is_dir() { &self.skin.directory } else { &self.skin.file }; + let target_style = if line.is_dir() { + &self.skin.directory + } else { + &self.skin.file + }; let mut target_style = target_style.clone(); if selected { if let Some(c) = self.skin.selected_line.bg_color { @@ -121,11 +145,10 @@ impl DisplayableTree<'_, '_> { target_style.write(f, &target)?; } } - _ => { } + _ => {} } Ok(()) } - } impl fmt::Display for DisplayableTree<'_, '_> { @@ -152,6 +175,9 @@ impl fmt::Display for DisplayableTree<'_, '_> { None }; for y in 0..self.area.height { + if self.in_app { + cursor.goto(0, y).unwrap(); + } let mut line_index = y as usize; if line_index > 0 { line_index += tree.scroll as usize; @@ -173,7 +199,7 @@ impl fmt::Display for DisplayableTree<'_, '_> { } } else { " " - } + }, )?; } if tree.options.show_sizes && line_index > 0 { @@ -183,19 +209,9 @@ impl fmt::Display for DisplayableTree<'_, '_> { if line.is_selectable() { self.write_mode(f, line.mode)?; let user = permissions::user_name(line.uid); - write!( - f, - " {:w$}", - &user, - w = max_user_len, - )?; + write!(f, " {:w$}", &user, w = max_user_len,)?; let group = permissions::group_name(line.gid); - write!( - f, - " {:w$} ", - &group, - w = max_group_len, - )?; + write!(f, " {:w$} ", &group, w = max_group_len,)?; } else { self.skin.tree.write(f, "──────────────")?; } @@ -224,4 +240,3 @@ impl fmt::Display for DisplayableTree<'_, '_> { Ok(()) } } - diff --git a/src/external.rs b/src/external.rs index 52bbadb..6dd3d83 100644 --- a/src/external.rs +++ b/src/external.rs @@ -1,10 +1,10 @@ -use std::env; +use opener; use regex::Regex; +use std::env; use std::fs::OpenOptions; -use std::io::{self, Cursor, Write}; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::Command; -use opener; use crate::app::AppStateCmdResult; use crate::app_context::AppContext; @@ -18,16 +18,25 @@ use crate::skin::Skin; /// A launchable can only be executed on end of life of broot. #[derive(Debug)] pub enum Launchable { - Printer { // just print something on stdout on end of broot + Printer { + // just print something on stdout on end of broot to_print: String, }, - Program { // execute an external program + TreePrinter { + // print the tree on end of broot + tree: Tree, + skin: Skin, + width: u16, + }, + Program { + // execute an external program exe: String, args: Vec, }, - SystemOpen { // open a path + SystemOpen { + // open a path path: PathBuf, - } + }, } /// If s starts by a '$', replace it by the environment variable of the same name @@ -41,13 +50,16 @@ fn resolve_env_variable(s: String) -> String { impl Launchable { pub fn opener(path: PathBuf) -> Launchable { - Launchable::SystemOpen { - path - } + Launchable::SystemOpen { path } } pub fn printer(to_print: String) -> Launchable { - Launchable::Printer { - to_print + Launchable::Printer { to_print } + } + pub fn tree_printer(tree: &Tree, screen: &Screen) -> Launchable { + Launchable::TreePrinter { + tree: tree.clone(), + skin: screen.skin.clone(), + width: screen.w, } } pub fn program(mut parts: Vec) -> io::Result { @@ -64,23 +76,26 @@ impl Launchable { pub fn execute(&self) -> Result<(), ProgramError> { match self { Launchable::Printer { to_print } => Ok(println!("{}", to_print)), + Launchable::TreePrinter { tree, skin, width } => { + let dp = DisplayableTree::out_of_app(&tree, &skin, *width); + print!("{}", dp); + Ok(()) + } Launchable::Program { exe, args } => { Command::new(&exe) - .args(args.iter()) - .spawn() - .and_then(|mut p| p.wait()) - .map_err(|source| ProgramError::LaunchError { - program: exe.clone(), - source, - })?; + .args(args.iter()) + .spawn() + .and_then(|mut p| p.wait()) + .map_err(|source| ProgramError::LaunchError { + program: exe.clone(), + source, + })?; Ok(()) } - Launchable::SystemOpen { path } => { - match opener::open(&path) { - Ok(_) => Ok(()), - Err(err) => Err(ProgramError::OpenError{err}), - } - } + Launchable::SystemOpen { path } => match opener::open(&path) { + Ok(_) => Ok(()), + Err(err) => Err(ProgramError::OpenError { err }), + }, } } } @@ -121,36 +136,34 @@ pub fn print_path(path: &Path, con: &AppContext) -> io::Result io::Result { +fn print_tree_to_file( + tree: &Tree, + screen: &mut Screen, + file_path: &str, +) -> io::Result { let no_style_skin = Skin::no_term(); - let dp = DisplayableTree { - tree, - skin: &no_style_skin, - area: termimad::Area { - left: 0, - top: 0, - width: screen.w, - height: tree.lines.len() as u16, - }, - in_app: false, - }; - Ok( - if let Some(ref output_path) = con.launch_args.file_export_path { - // an output path was provided, we write to it - let mut f = OpenOptions::new() - .create(true) - .append(true) - .open(output_path)?; - write!(f, "{}", dp)?; - AppStateCmdResult::Quit - } else { - // no output path provided. We write on stdout, but we must - // do it after app closing to have the normal terminal - let mut curs: Cursor> = Cursor::new(Vec::new()); - write!(&mut curs, "{}", dp)?; - AppStateCmdResult::Launch(Launchable::printer( - String::from_utf8(curs.into_inner()).unwrap() - )) - }, - ) + let dp = DisplayableTree::out_of_app(tree, &no_style_skin, screen.w); + let mut f = OpenOptions::new() + .create(true) + .append(true) + .open(file_path)?; + write!(f, "{}", dp)?; + Ok(AppStateCmdResult::Quit) +} + +pub fn print_tree( + tree: &Tree, + screen: &mut Screen, + con: &AppContext, +) -> io::Result { + if let Some(ref output_path) = con.launch_args.file_export_path { + // an output path was provided, we write to it + print_tree_to_file(tree, screen, output_path) + } else { + // no output path provided. We write on stdout, but we must + // do it after app closing to have the normal terminal + Ok(AppStateCmdResult::Launch(Launchable::tree_printer( + tree, screen, + ))) + } } diff --git a/src/file_sizes/file_sizes_unix.rs b/src/file_sizes/file_sizes_unix.rs index c03a6dc..c66ad82 100644 --- a/src/file_sizes/file_sizes_unix.rs +++ b/src/file_sizes/file_sizes_unix.rs @@ -1,77 +1,77 @@ use crate::task_sync::TaskLifetime; +use crossbeam::channel::unbounded; +use crossbeam::sync::WaitGroup; use std::collections::HashSet; use std::fs; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; +use std::sync::Arc; use std::sync::Mutex; use std::thread; -use crossbeam::channel::unbounded; -use crossbeam::sync::WaitGroup; -use std::sync::Arc; -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; use std::time::Duration; pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option { - let inodes = Arc::new(Mutex::new(HashSet::::new())); // to avoid counting twice an inode - let size = Arc::new(AtomicUsize::new(0)); + let inodes = Arc::new(Mutex::new(HashSet::::new())); // to avoid counting twice an inode + let size = Arc::new(AtomicUsize::new(0)); - // this MPMC channel contains the directory paths which must be handled - let (dirs_sender, dirs_receiver) = unbounded(); + // this MPMC channel contains the directory paths which must be handled + let (dirs_sender, dirs_receiver) = unbounded(); - // busy is the number of directories which are either being processed or queued - // We use this count to determine when threads can stop waiting for tasks - let busy = Arc::new(AtomicIsize::new(0)); - busy.fetch_add(1, Ordering::Relaxed); - dirs_sender.send(Some(PathBuf::from(path))).unwrap(); + // busy is the number of directories which are either being processed or queued + // We use this count to determine when threads can stop waiting for tasks + let busy = Arc::new(AtomicIsize::new(0)); + busy.fetch_add(1, Ordering::Relaxed); + dirs_sender.send(Some(PathBuf::from(path))).unwrap(); - let wg = WaitGroup::new(); - let period = Duration::from_micros(50); - for _ in 0..8 { - let size = Arc::clone(&size); - let busy = Arc::clone(&busy); - let wg = wg.clone(); - let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); - let tl = tl.clone(); - let inodes = inodes.clone(); - thread::spawn(move || { - loop { - let o = dirs_receiver.recv_timeout(period); - if let Ok(Some(open_dir)) = o { - if let Ok(entries) = fs::read_dir(&open_dir) { - for e in entries.flatten() { - if let Ok(md) = e.metadata() { - if md.is_dir() { - busy.fetch_add(1, Ordering::Relaxed); - dirs_sender.send(Some(e.path())).unwrap(); - } else if md.nlink() > 1 { - let mut inodes = inodes.lock().unwrap(); - if !inodes.insert(md.ino()) { - // it was already in the set - continue; // let's not add the size - } + let wg = WaitGroup::new(); + let period = Duration::from_micros(50); + for _ in 0..8 { + let size = Arc::clone(&size); + let busy = Arc::clone(&busy); + let wg = wg.clone(); + let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); + let tl = tl.clone(); + let inodes = inodes.clone(); + thread::spawn(move || { + loop { + let o = dirs_receiver.recv_timeout(period); + if let Ok(Some(open_dir)) = o { + if let Ok(entries) = fs::read_dir(&open_dir) { + for e in entries.flatten() { + if let Ok(md) = e.metadata() { + if md.is_dir() { + busy.fetch_add(1, Ordering::Relaxed); + dirs_sender.send(Some(e.path())).unwrap(); + } else if md.nlink() > 1 { + let mut inodes = inodes.lock().unwrap(); + if !inodes.insert(md.ino()) { + // it was already in the set + continue; // let's not add the size } - size.fetch_add(md.len() as usize, Ordering::Relaxed); } + size.fetch_add(md.len() as usize, Ordering::Relaxed); } } - busy.fetch_sub(1, Ordering::Relaxed); - dirs_sender.send(None).unwrap(); - } else if busy.load(Ordering::Relaxed) < 1 { - break; - } - if tl.is_expired() { - break; } + busy.fetch_sub(1, Ordering::Relaxed); + dirs_sender.send(None).unwrap(); + } else if busy.load(Ordering::Relaxed) < 1 { + break; + } + if tl.is_expired() { + break; } - drop(wg); - }); - } - wg.wait(); + } + drop(wg); + }); + } + wg.wait(); - if tl.is_expired() { - return None; - } - let size: usize = size.load(Ordering::Relaxed); - let size: u64 = size as u64; - Some(size) + if tl.is_expired() { + return None; + } + let size: usize = size.load(Ordering::Relaxed); + let size: u64 = size as u64; + Some(size) } diff --git a/src/file_sizes/file_sizes_windows.rs b/src/file_sizes/file_sizes_windows.rs index 0f893a1..0b7bf9f 100644 --- a/src/file_sizes/file_sizes_windows.rs +++ b/src/file_sizes/file_sizes_windows.rs @@ -1,69 +1,68 @@ use crate::task_sync::TaskLifetime; +use crossbeam::channel::unbounded; +use crossbeam::sync::WaitGroup; use std::fs; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; +use std::sync::Arc; use std::sync::Mutex; -use std::time::Instant; use std::thread; -use crossbeam::channel::unbounded; -use crossbeam::sync::WaitGroup; -use std::sync::Arc; -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; use std::time::Duration; +use std::time::Instant; pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option { - let size = Arc::new(AtomicUsize::new(0)); + let size = Arc::new(AtomicUsize::new(0)); - // this MPMC channel contains the directory paths which must be handled - let (dirs_sender, dirs_receiver) = unbounded(); + // this MPMC channel contains the directory paths which must be handled + let (dirs_sender, dirs_receiver) = unbounded(); - // busy is the number of directories which are either being processed or queued - // We use this count to determine when threads can stop waiting for tasks - let busy = Arc::new(AtomicIsize::new(0)); - busy.fetch_add(1, Ordering::Relaxed); - dirs_sender.send(Some(PathBuf::from(path))).unwrap(); + // busy is the number of directories which are either being processed or queued + // We use this count to determine when threads can stop waiting for tasks + let busy = Arc::new(AtomicIsize::new(0)); + busy.fetch_add(1, Ordering::Relaxed); + dirs_sender.send(Some(PathBuf::from(path))).unwrap(); - let wg = WaitGroup::new(); - let period = Duration::from_micros(50); - for _ in 0..8 { - let size = Arc::clone(&size); - let busy = Arc::clone(&busy); - let wg = wg.clone(); - let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); - let tl = tl.clone(); - thread::spawn(move || { - loop { - let o = dirs_receiver.recv_timeout(period); - if let Ok(Some(open_dir)) = o { - if let Ok(entries) = fs::read_dir(&open_dir) { - for e in entries.flatten() { - if let Ok(md) = e.metadata() { - if md.is_dir() { - busy.fetch_add(1, Ordering::Relaxed); - dirs_sender.send(Some(e.path())).unwrap(); - } - size.fetch_add(md.len() as usize, Ordering::Relaxed); + let wg = WaitGroup::new(); + let period = Duration::from_micros(50); + for _ in 0..8 { + let size = Arc::clone(&size); + let busy = Arc::clone(&busy); + let wg = wg.clone(); + let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); + let tl = tl.clone(); + thread::spawn(move || { + loop { + let o = dirs_receiver.recv_timeout(period); + if let Ok(Some(open_dir)) = o { + if let Ok(entries) = fs::read_dir(&open_dir) { + for e in entries.flatten() { + if let Ok(md) = e.metadata() { + if md.is_dir() { + busy.fetch_add(1, Ordering::Relaxed); + dirs_sender.send(Some(e.path())).unwrap(); } + size.fetch_add(md.len() as usize, Ordering::Relaxed); } } - busy.fetch_sub(1, Ordering::Relaxed); - dirs_sender.send(None).unwrap(); - } else if busy.load(Ordering::Relaxed) < 1 { - break; - } - if tl.is_expired() { - break; } + busy.fetch_sub(1, Ordering::Relaxed); + dirs_sender.send(None).unwrap(); + } else if busy.load(Ordering::Relaxed) < 1 { + break; + } + if tl.is_expired() { + break; } - drop(wg); - }); - } - wg.wait(); + } + drop(wg); + }); + } + wg.wait(); - if tl.is_expired() { - return None; - } - let size: usize = size.load(Ordering::Relaxed); - let size: u64 = size as u64; - Some(size) + if tl.is_expired() { + return None; + } + let size: usize = size.load(Ordering::Relaxed); + let size: u64 = size as u64; + Some(size) } - diff --git a/src/file_sizes/mod.rs b/src/file_sizes/mod.rs index 83d9111..43bd59c 100644 --- a/src/file_sizes/mod.rs +++ b/src/file_sizes/mod.rs @@ -4,9 +4,9 @@ /// Hard links are checked to avoid counting /// twice an inode. use crate::task_sync::TaskLifetime; +use std::collections::HashMap; use std::fs; use std::ops::AddAssign; -use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::Instant; diff --git a/src/flat_tree.rs b/src/flat_tree.rs index 416a5ec..4bf07c0 100644 --- a/src/flat_tree.rs +++ b/src/flat_tree.rs @@ -21,7 +21,7 @@ pub enum LineType { } /// a line in the representation of the file hierarchy -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TreeLine { pub left_branchs: Box<[bool]>, // a depth-sized array telling whether a branch pass pub depth: u16, @@ -38,7 +38,7 @@ pub struct TreeLine { pub gid: u32, // unix group id } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Tree { pub lines: Box<[TreeLine]>, pub selection: usize, // there's always a selection (starts with root, which is 0) @@ -143,16 +143,9 @@ impl PartialOrd for TreeLine { } impl Tree { - - pub fn refresh( - &mut self, - page_height: usize, - ) -> Result<(), errors::TreeBuildError> { - let builder = TreeBuilder::from( - self.root().to_path_buf(), - self.options.clone(), - page_height, - )?; + pub fn refresh(&mut self, page_height: usize) -> Result<(), errors::TreeBuildError> { + let builder = + TreeBuilder::from(self.root().to_path_buf(), self.options.clone(), page_height)?; debug!("remove 3"); let mut tree = builder.build(&TaskLifetime::unlimited()).unwrap(); // should not fail debug!("remove 4"); @@ -207,7 +200,8 @@ impl Tree { if unlisted > 0 && self.lines[end_index].nb_kept_children == 0 { self.lines[end_index].line_type = LineType::Pruning; self.lines[end_index].unlisted = unlisted + 1; - self.lines[end_index].name = format!("{} unlisted", unlisted+1).to_owned(); + self.lines[end_index].name = + format!("{} unlisted", unlisted + 1).to_owned(); self.lines[parent_index].unlisted = 0; } last_parent_index = parent_index; diff --git a/src/help_content.rs b/src/help_content.rs index ce8f61f..5217102 100644 --- a/src/help_content.rs +++ b/src/help_content.rs @@ -4,7 +4,9 @@ use crate::conf::Conf; /// build the markdown which will be displayed in the help page /// pub fn build_markdown(con: &AppContext) -> String { - let mut md = String::from(MD_HELP_INTRO); + let mut md = String::new(); + md.push_str(&format!("\n# broot v{}", env!("CARGO_PKG_VERSION"))); + md.push_str(MD_HELP_INTRO); append_verbs_table(&mut md, con); append_config_info(&mut md, con); md.push_str(MD_HELP_LAUNCH_ARGUMENTS); @@ -12,22 +14,19 @@ pub fn build_markdown(con: &AppContext) -> String { md } - const MD_HELP_INTRO: &'static str = r#" -# Help **broot** lets you explore directory trees and launch commands. -See https://dystroy.org/broot for a complete guide. +It's best used when launched as **br**. +See *https://dystroy.org/broot* for a complete guide. -**broot** is best used when launched as `br`. `` gets you back to the previous state. Typing some letters searches the tree and selects the most relevant file. To use a regular expression, use a slash eg `/j(ava|s)$`. +The **🡑** and **🡓** arrow keys can be used to change selection. To execute a verb, type a space or `:` then start of its name or shortcut. -## Verbs - "#; const MD_HELP_LAUNCH_ARGUMENTS: &'static str = r#" @@ -51,6 +50,7 @@ Flags are displayed at bottom right: "#; fn append_verbs_table(md: &mut String, con: &AppContext) { + md.push_str("## Verbs\n\n"); md.push_str("|-:\n"); md.push_str("|**name**|**shortcut**|**description**\n"); md.push_str("|-:|:-:|:-\n"); diff --git a/src/help_states.rs b/src/help_states.rs index da3418d..22a31b5 100644 --- a/src/help_states.rs +++ b/src/help_states.rs @@ -1,18 +1,17 @@ - use std::io; -use termimad::{Area, MadSkin, MadView}; +use termimad::{Area, MadView}; use crate::app::{AppState, AppStateCmdResult}; use crate::app_context::AppContext; use crate::commands::{Action, Command}; use crate::conf::Conf; -use crate::screens::{Screen}; +use crate::help_content; +use crate::screens::Screen; use crate::status::Status; use crate::task_sync::TaskLifetime; +use crate::verb_store::PrefixSearchResult; use crate::verbs::VerbExecutor; -use crate::verb_store::{PrefixSearchResult}; -use crate::help_content; /// an application state dedicated to help pub struct HelpState { @@ -20,32 +19,22 @@ pub struct HelpState { } impl HelpState { - - pub fn new(_screen: &Screen, con: &AppContext) -> HelpState { + pub fn new(screen: &Screen, con: &AppContext) -> HelpState { let mut area = Area::uninitialized(); area.top = 0; area.left = 0; let markdown = help_content::build_markdown(con); - let mut skin = MadSkin::default(); - let view = MadView::from( - markdown, - area, - skin - ); - HelpState { - view - } + let view = MadView::from(markdown, area, screen.skin.to_mad_skin()); + HelpState { view } } fn resize_area(&mut self, screen: &Screen) { let area = Area::new(0, 0, screen.w, screen.h - 3); self.view.resize(&area); } - } impl AppState for HelpState { - fn apply( &mut self, cmd: &mut Command, @@ -56,7 +45,9 @@ impl AppState for HelpState { Ok(match &cmd.action { Action::Back => AppStateCmdResult::PopState, Action::Verb(invocation) => match con.verb_store.search(&invocation.key) { - PrefixSearchResult::Match(verb) => self.execute_verb(verb, &invocation, screen, con)?, + PrefixSearchResult::Match(verb) => { + self.execute_verb(verb, &invocation, screen, con)? + } _ => AppStateCmdResult::verb_not_found(&invocation.key), }, Action::MoveSelection(dy) => { @@ -73,11 +64,7 @@ impl AppState for HelpState { }) } - fn refresh( - &mut self, - _screen: &Screen, - _con: &AppContext, - ) -> Command { + fn refresh(&mut self, _screen: &Screen, _con: &AppContext) -> Command { Command::new() } @@ -111,7 +98,7 @@ impl AppState for HelpState { &verb.invocation.key, &verb.description_for(Conf::default_location(), &invocation.args) ) - .to_string() + .to_string(), ) } } diff --git a/src/input.rs b/src/input.rs index 1d6242e..17d08ef 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,6 +1,4 @@ -/// displays the "input" at the bottom of the screen -/// (reading is managed in the app module) -use std::io::{self}; +use std::io; use crossterm::Attribute; @@ -8,6 +6,8 @@ use crate::commands::Command; use crate::screens::Screen; use crate::skin; +/// displays the "input" at the bottom of the screen +/// (reading is managed in the app module) pub trait Input { fn write_input(&mut self, cmd: &Command) -> io::Result<()>; } @@ -16,12 +16,12 @@ impl Input for Screen { fn write_input(&mut self, cmd: &Command) -> io::Result<()> { skin::reset(); self.goto_clear(1, self.h); - self.write(&format!( + print!( "{}{} {}", self.skin.input.apply_to(&cmd.raw), Attribute::Reverse, Attribute::NoInverse, - )); + ); Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 22ac1ce..af073b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ mod app_context; mod browser_states; mod browser_verbs; mod cli; -mod commands; mod command_parsing; +mod commands; mod conf; mod displayable_tree; mod errors; @@ -40,9 +40,9 @@ mod status; mod task_sync; mod tree_build; mod tree_options; -mod verbs; mod verb_invocation; mod verb_store; +mod verbs; use log::LevelFilter; use simplelog; diff --git a/src/patterns.rs b/src/patterns.rs index 9d6e6a9..03ee1f7 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -86,29 +86,6 @@ pub struct Match { pub pos: Vec, // positions of the matching chars } -impl Match { - // returns a new string made from candidate (which should be at the origin of the match) - // where the characters at positions pos (matching chars) are wrapped between - // prefix and postfix - pub fn wrap_matching_chars(&self, candidate: &str, prefix: &str, postfix: &str) -> String { - let mut pos_idx: usize = 0; - let mut decorated = String::new(); - for (cand_idx, cand_char) in candidate.chars().enumerate() { - if pos_idx < self.pos.len() && self.pos[pos_idx] == cand_idx { - decorated.push_str(prefix); - decorated.push(cand_char); - decorated.push_str(postfix); - pos_idx += 1; - } else { - decorated.push(cand_char); - } - } - decorated - } -} - - -// TODO move in another file pub struct MatchedString<'a> { pub pattern: &'a Pattern, pub string: &'a str, @@ -121,9 +98,9 @@ impl Pattern { &'a self, string: &'a str, base_style: &'a ObjectStyle, - match_style: &'a ObjectStyle + match_style: &'a ObjectStyle, ) -> MatchedString<'a> { - MatchedString{ + MatchedString { pattern: self, string, base_style, @@ -139,15 +116,18 @@ impl fmt::Display for MatchedString<'_> { let mut pos_idx: usize = 0; for (cand_idx, cand_char) in self.string.chars().enumerate() { if pos_idx < m.pos.len() && m.pos[pos_idx] == cand_idx { - write!(f, "{}", self.base_style.apply_to( - self.match_style.apply_to(cand_char) - ))?; + write!( + f, + "{}", + self.base_style + .apply_to(self.match_style.apply_to(cand_char)) + )?; pos_idx += 1; } else { write!(f, "{}", self.base_style.apply_to(cand_char))?; } } - return Ok(()) + return Ok(()); } } write!(f, "{}", self.base_style.apply_to(self.string)) diff --git a/src/permissions/mod.rs b/src/permissions/mod.rs index 7d087b0..7830a2a 100644 --- a/src/permissions/mod.rs +++ b/src/permissions/mod.rs @@ -1,4 +1,3 @@ - //////////////////// UNIX #[cfg(unix)] diff --git a/src/permissions/permissions_unix.rs b/src/permissions/permissions_unix.rs index bae53ae..90daca4 100644 --- a/src/permissions/permissions_unix.rs +++ b/src/permissions/permissions_unix.rs @@ -1,5 +1,4 @@ - -use std::collections::{HashMap}; +use std::collections::HashMap; use std::sync::Mutex; #[cfg(unix)] @@ -11,12 +10,15 @@ pub fn user_name(uid: u32) -> String { static ref USERS_CACHE_MUTEX: Mutex> = Mutex::new(HashMap::new()); } let mut users_cache = USERS_CACHE_MUTEX.lock().unwrap(); - users_cache.entry(uid).or_insert_with( - || users::get_user_by_uid(uid).map_or_else( - || "????".to_string(), - |u| u.name().to_string_lossy().to_string() - ) - ).to_string() + users_cache + .entry(uid) + .or_insert_with(|| { + users::get_user_by_uid(uid).map_or_else( + || "????".to_string(), + |u| u.name().to_string_lossy().to_string(), + ) + }) + .to_string() } #[cfg(unix)] @@ -25,16 +27,17 @@ pub fn group_name(gid: u32) -> String { static ref USERS_CACHE_MUTEX: Mutex> = Mutex::new(HashMap::new()); } let mut groups_cache = USERS_CACHE_MUTEX.lock().unwrap(); - groups_cache.entry(gid).or_insert_with( - || users::get_group_by_gid(gid).map_or_else( - || "????".to_string(), - |u| u.name().to_string_lossy().to_string() - ) - ).to_string() + groups_cache + .entry(gid) + .or_insert_with(|| { + users::get_group_by_gid(gid).map_or_else( + || "????".to_string(), + |u| u.name().to_string_lossy().to_string(), + ) + }) + .to_string() } - - /* mettre directement les string en cache diff --git a/src/screens.rs b/src/screens.rs index d39a40e..1573d30 100644 --- a/src/screens.rs +++ b/src/screens.rs @@ -1,4 +1,4 @@ -use std::io::{self}; +use std::io; use crossterm::{self, AlternateScreen, ClearType, TerminalCursor}; @@ -33,18 +33,12 @@ impl Screen { let terminal = crossterm::Terminal::new(); let (w, h) = terminal.terminal_size(); self.w = w; - self.h = h+1; + self.h = h + 1; if let Some(h) = con.launch_args.height { self.h = h; } Ok(()) } - pub fn write(&mut self, s: &str) { - let terminal = crossterm::Terminal::new(); - if let Err(e) = terminal.write(s) { - warn!("error in write: {:?}", e); - } - } // move the cursor to x,y // top left corner is (1, 1) pub fn goto_clear(&self, x: u16, y: u16) { @@ -54,7 +48,7 @@ impl Screen { pub fn goto(&self, x: u16, y: u16) { let cursor = TerminalCursor::new(); //debug!("goto x={}, y={}", x, y); - cursor.goto(x-1, y-1).unwrap(); + cursor.goto(x - 1, y - 1).unwrap(); } pub fn clear_line(&self) { let terminal = crossterm::Terminal::new(); diff --git a/src/shell_bash.rs b/src/shell_bash.rs index f135264..ee4c173 100644 --- a/src/shell_bash.rs +++ b/src/shell_bash.rs @@ -4,7 +4,7 @@ pub const BASH: ShellFamily<'static> = ShellFamily { name: "bash", sourcing_files: &[".bashrc", ".zshrc"], version: 1, - script: BASH_FUNC + script: BASH_FUNC, }; // This script has been tested on bash and zsh. diff --git a/src/shell_fish.rs b/src/shell_fish.rs index 3b09fee..83d4dd9 100644 --- a/src/shell_fish.rs +++ b/src/shell_fish.rs @@ -4,7 +4,7 @@ pub const FISH: ShellFamily<'static> = ShellFamily { name: "fish", sourcing_files: &[".config/fish/config.fish"], // idealy we should probably use XDG here... version: 1, - script: FISH_FUNC + script: FISH_FUNC, }; const FISH_FUNC: &str = r#" diff --git a/src/shell_install.rs b/src/shell_install.rs index a09f035..20d451f 100644 --- a/src/shell_install.rs +++ b/src/shell_install.rs @@ -26,7 +26,6 @@ use std::io::{self, BufRead, BufReader, Write}; use std::os; use std::path::Path; -use crossterm::Attribute; use directories::UserDirs; use crate::cli::{self, AppLaunchArgs}; @@ -34,17 +33,21 @@ use crate::conf; use crate::shell_bash::BASH; use crate::shell_fish::FISH; -const SHELL_FAMILIES: &[ShellFamily<'static>] = &[ BASH, FISH ]; +const SHELL_FAMILIES: &[ShellFamily<'static>] = &[BASH, FISH]; + +const MD_INSTALL_REQUEST: &'static str = r#" +**Broot** should be launched using a shell function (see *https://github.com/Canop/broot* for explanations). +The function is either missing, old or badly installed. +"#; pub struct ShellFamily<'a> { pub name: &'a str, - pub sourcing_files: &'a[&'a str], + pub sourcing_files: &'a [&'a str], pub version: usize, pub script: &'a str, } impl ShellFamily<'static> { - // make sure the script and symlink are installed // but don't touch the shellrc files // (i.e. this isn't enough to make the function available) @@ -102,6 +105,7 @@ impl ShellFamily<'static> { } } // it looks like the shell function is neither installed nor refused + let ms = cli::mad_skin(); let homedir_path = match UserDirs::new() { Some(user_dirs) => user_dirs.home_dir().to_path_buf(), None => { @@ -109,40 +113,42 @@ impl ShellFamily<'static> { return Ok(false); } }; - let rc_files: Vec<_> = self.sourcing_files + let rc_files: Vec<_> = self + .sourcing_files .iter() .map(|name| (name, homedir_path.join(name))) .filter(|t| t.1.exists()) .collect(); if rc_files.is_empty() { - warn!("no {} compatible shell config file found, no installation possible", self.name); + warn!( + "no {} compatible shell config file found, no installation possible", + self.name + ); if installation_required { - println!("No shell config found, we can't install the br function."); - println!("We were looking for the following file(s):"); + let mut md = + String::from("No shell config found, we