diff options
author | Canop <cano.petrole@gmail.com> | 2019-02-14 20:27:41 +0100 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2019-02-14 20:27:41 +0100 |
commit | ee2a4c4ac9a0cf198d8b0a2e1f14183f688da4ae (patch) | |
tree | 7508c0ee2b4a8ceff01d04e53dcac89196ddd491 | |
parent | 51661a79b1735b3c9f760a1c5adbdfb525450c48 (diff) |
complete verbs handling in help screen
For some verbs execution is done by first coming
back to the last browser screen.
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/app.rs | 11 | ||||
-rw-r--r-- | src/browser_states.rs | 20 | ||||
-rw-r--r-- | src/browser_verbs.rs | 88 | ||||
-rw-r--r-- | src/help_states.rs | 28 | ||||
-rw-r--r-- | src/help_verbs.rs | 61 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/verbs.rs | 141 |
9 files changed, 217 insertions, 138 deletions
@@ -31,7 +31,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "broot" -version = "0.6.0" +version = "0.6.1" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "custom_error 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1,6 +1,6 @@ [package] name = "broot" -version = "0.6.0" +version = "0.6.1" authors = ["dystroy <denys.seguret@gmail.com>"] repository = "https://github.com/Canop/broot" description = "Fuzzy Search + tree + cd" @@ -33,6 +33,7 @@ pub enum AppStateCmdResult { Launch(Launchable), DisplayError(String), NewState(Box<dyn AppState>), + PopStateAndReapply, // the state asks the command be executed on a previous state PopState, } @@ -170,6 +171,16 @@ impl App { self.state().write_status(screen, &cmd, con)?; } } + AppStateCmdResult::PopStateAndReapply => { + if self.states.len() == 1 { + debug!("quitting on last pop state"); + self.quitting = true; + } else { + self.states.pop(); + debug!("about to reapply {:?}", &cmd); + return self.apply_command(cmd, screen, con); + } + } AppStateCmdResult::DisplayError(txt) => { screen.write_status_err(&txt)?; } diff --git a/src/browser_states.rs b/src/browser_states.rs index 715c7c5..6869b43 100644 --- a/src/browser_states.rs +++ b/src/browser_states.rs @@ -266,14 +266,20 @@ impl AppState for BrowserState { PrefixSearchResult::NoMatch => { screen.write_status_err("No matching verb (':?' for the list of verbs)") } - PrefixSearchResult::Match(verb) => screen.write_status_text( - &format!( - "Hit <enter> to {} : {}", - &verb.name, - verb.description_for(&self) + PrefixSearchResult::Match(verb) => { + let line = match &self.filtered_tree { + Some(tree) => tree.selected_line(), + None => self.tree.selected_line(), + }; + screen.write_status_text( + &format!( + "Hit <enter> to {} : {}", + &verb.name, + verb.description_for(line.target()) + ) + .to_string(), ) - .to_string(), - ), + } PrefixSearchResult::TooManyMatches => screen.write_status_text( // TODO show what verbs start with the currently edited verb key "Type a verb then <enter> to execute it (':?' for the list of verbs)", diff --git a/src/browser_verbs.rs b/src/browser_verbs.rs new file mode 100644 index 0000000..ae1c3d6 --- /dev/null +++ b/src/browser_verbs.rs @@ -0,0 +1,88 @@ +use std::fs::OpenOptions; +use std::io::{self, Write}; + +use crate::verbs::{Verb, VerbExecutor}; +use crate::app::AppStateCmdResult; +use crate::app_context::AppContext; +use crate::browser_states::BrowserState; +use crate::external::Launchable; +use crate::help_states::HelpState; +use crate::screens::Screen; +use crate::task_sync::TaskLifetime; +use crate::tree_options::{OptionBool, TreeOptions}; + +impl VerbExecutor for BrowserState { + fn execute_verb( + &self, + verb: &Verb, + screen: &Screen, + con: &AppContext, + ) -> io::Result<AppStateCmdResult> { + let tree = match &self.filtered_tree { + Some(tree) => &tree, + None => &self.tree, + }; + let line = &tree.selected_line(); + Ok(match verb.exec_pattern.as_ref() { + ":back" => AppStateCmdResult::PopState, + ":focus" => { + AppStateCmdResult::from_optional_state(BrowserState::new( + tree.selected_line().path.clone(), + tree.options.clone(), + screen, + &TaskLifetime::unlimited(), + )) + } + ":help" => AppStateCmdResult::NewState(Box::new(HelpState::new(screen))), + ":open" => AppStateCmdResult::Launch(Launchable::opener(&line.target())?), + ":parent" => match &line.target().parent() { + Some(path) => { + AppStateCmdResult::from_optional_state(BrowserState::new( + path.to_path_buf(), + tree.options.clone(), + screen, + &TaskLifetime::unlimited(), + )) + } + None => AppStateCmdResult::DisplayError("no parent found".to_string()), + }, + ":print_path" => { + if let Some(ref output_path) = con.launch_args.file_export_path { + // an output path was provided, we write to it + let f = OpenOptions::new().append(true).open(output_path)?; + writeln!(&f, "{}", line.target().to_string_lossy())?; + 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 launchable = Launchable::from(vec![line.target().to_string_lossy().to_string()])?; + launchable.just_print = true; + AppStateCmdResult::Launch(launchable) + } + } + ":toggle_files" => { + self.with_new_options(screen, &|o: &mut TreeOptions| o.only_folders ^= true) + } + ":toggle_hidden" => self.with_new_options(screen, &|o| o.show_hidden ^= true), + ":toggle_git_ignore" => self.with_new_options(screen, &|options| { + options.respect_git_ignore = match options.respect_git_ignore { + OptionBool::Auto => { + if tree.nb_gitignored > 0 { + OptionBool::No + } else { + OptionBool::Yes + } + } + OptionBool::Yes => OptionBool::No, + OptionBool::No => OptionBool::Yes, + }; + }), + ":toggle_perm" => self.with_new_options(screen, &|o| o.show_permissions ^= true), + ":toggle_sizes" => self.with_new_options(screen, &|o| o.show_sizes ^= true), + ":toggle_trim_root" => self.with_new_options(screen, &|o| o.trim_root ^= true), + ":quit" => AppStateCmdResult::Quit, + _ => verb.to_cmd_result(&line.target(), con)?, + }) + } +} + diff --git a/src/help_states.rs b/src/help_states.rs index 66a626e..1a7c1b4 100644 --- a/src/help_states.rs +++ b/src/help_states.rs @@ -116,10 +116,32 @@ impl AppState for HelpState { fn write_status( &self, screen: &mut Screen, - _cmd: &Command, - _con: &AppContext, + cmd: &Command, + con: &AppContext, ) -> io::Result<()> { - screen.write_status_text("Hit <esc> to get back to the tree, or `:o` to open the conf file") + match &cmd.action { + Action::VerbEdit(verb_key) => { + match con.verb_store.search(&verb_key) { + PrefixSearchResult::NoMatch => { + screen.write_status_err("No matching verb)") + } + PrefixSearchResult::Match(verb) => screen.write_status_text( + &format!( + "Hit <enter> to {} : {}", + &verb.name, + &verb.description_for(Conf::default_location()) + ) + .to_string(), + ), + PrefixSearchResult::TooManyMatches => screen.write_status_text( + "Type a verb then <enter> to execute it", + ), + } + } + _ => { + screen.write_status_text("Hit <esc> to get back to the tree, or a space to start a verb") + } + } } fn write_flags(&self, _screen: &mut Screen, _con: &AppContext) -> io::Result<()> { diff --git a/src/help_verbs.rs b/src/help_verbs.rs new file mode 100644 index 0000000..3741246 --- /dev/null +++ b/src/help_verbs.rs @@ -0,0 +1,61 @@ +use std::fs::OpenOptions; +use std::io::{self, Write}; + +use crate::verbs::{Verb, VerbExecutor}; +use crate::app::AppStateCmdResult; +use crate::app_context::AppContext; +use crate::browser_states::BrowserState; +use crate::conf::{self, Conf}; +use crate::external::Launchable; +use crate::help_states::HelpState; +use crate::screens::Screen; +use crate::task_sync::TaskLifetime; +use crate::tree_options::TreeOptions; + +impl VerbExecutor for HelpState { + fn execute_verb( + &self, + verb: &Verb, + screen: &Screen, + con: &AppContext, + ) -> io::Result<AppStateCmdResult> { + Ok(match verb.exec_pattern.as_ref() { + ":back" => AppStateCmdResult::PopState, + ":focus" | ":parent" => { + AppStateCmdResult::from_optional_state(BrowserState::new( + conf::dir(), + TreeOptions::new(), + screen, + &TaskLifetime::unlimited(), + )) + } + ":help" => AppStateCmdResult::Keep, + ":open" => AppStateCmdResult::Launch(Launchable::opener(&Conf::default_location())?), + ":print_path" => { + let path = Conf::default_location().to_string_lossy().to_string(); + if let Some(ref output_path) = con.launch_args.file_export_path { + // an output path was provided, we write to it + let f = OpenOptions::new().append(true).open(output_path)?; + writeln!(&f, "{}", path)?; + 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 launchable = Launchable::from(vec![path])?; + launchable.just_print = true; + AppStateCmdResult::Launch(launchable) + } + } + ":quit" => AppStateCmdResult::Quit, + _ => { + if verb.exec_pattern.starts_with(":toggle") { + AppStateCmdResult::PopStateAndReapply + } else { + AppStateCmdResult::Launch(Launchable::from( + verb.exec_token(&Conf::default_location()), + )?) + } + } + }) + } +} diff --git a/src/main.rs b/src/main.rs index 15d8dd6..6a1263d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate log; mod app; mod app_context; mod browser_states; +mod browser_verbs; mod cli; mod commands; mod conf; @@ -19,6 +20,7 @@ mod flat_tree; mod fuzzy_patterns; mod git_ignore; mod help_states; +mod help_verbs; mod input; mod patterns; mod regex_patterns; diff --git a/src/verbs.rs b/src/verbs.rs index 9fc66a4..19e7d6f 100644 --- a/src/verbs.rs +++ b/src/verbs.rs @@ -1,20 +1,16 @@ -use regex::Regex; /// Verbs are the engines of broot commands, and apply /// - to the selected file (if user-defined, then must contain {file} or {directory}) /// - to the current app state use std::fs::OpenOptions; use std::io::{self, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; +use regex::Regex; use crate::app::AppStateCmdResult; use crate::app_context::AppContext; -use crate::browser_states::BrowserState; use crate::conf::Conf; use crate::external::Launchable; -use crate::help_states::HelpState; use crate::screens::Screen; -use crate::task_sync::TaskLifetime; -use crate::tree_options::{OptionBool, TreeOptions}; #[derive(Debug, Clone)] pub struct Verb { @@ -63,14 +59,10 @@ impl Verb { from_shell: false, } } - pub fn description_for(&self, state: &BrowserState) -> String { - let line = match &state.filtered_tree { - Some(tree) => tree.selected_line(), - None => state.tree.selected_line(), - }; - let mut path = line.target(); + pub fn description_for(&self, mut path: PathBuf) -> String { + //let mut path = path; if self.exec_pattern == ":cd" { - if !line.is_dir() { + if !path.is_dir() { path = path.parent().unwrap().to_path_buf(); } format!("cd {}", path.to_string_lossy()) @@ -80,7 +72,7 @@ impl Verb { self.exec_token(&path).join(" ") } } - fn exec_token(&self, path: &Path) -> Vec<String> { + pub fn exec_token(&self, path: &Path) -> Vec<String> { self.exec_pattern .split_whitespace() .map(|t| { @@ -125,7 +117,12 @@ impl Verb { } } -/// hold all the available verbs: built-in ones and those coming from configuration +/// Provide access to the verbs: +/// - the built-in ones +/// - the user defined ones +/// When the user types some keys, we select a verb +/// - if the input exactly matches a shortcut or the name +/// - if only one verb starts with the input pub struct VerbStore { pub verbs: Vec<Verb>, } @@ -139,108 +136,6 @@ pub trait VerbExecutor { ) -> io::Result<AppStateCmdResult>; } -impl VerbExecutor for HelpState { - fn execute_verb( - &self, - verb: &Verb, - _screen: &Screen, - _con: &AppContext, - ) -> io::Result<AppStateCmdResult> { - Ok(match verb.exec_pattern.as_ref() { - ":open" => AppStateCmdResult::Launch(Launchable::opener(&Conf::default_location())?), - ":quit" => AppStateCmdResult::Quit, - _ => { - if verb.exec_pattern.starts_with(':') { - AppStateCmdResult::Keep - } else { - AppStateCmdResult::Launch(Launchable::from( - verb.exec_token(&Conf::default_location()), - )?) - } - } - }) - } -} - -impl VerbExecutor for BrowserState { - fn execute_verb( - &self, - verb: &Verb, - screen: &Screen, - con: &AppContext, - ) -> io::Result<AppStateCmdResult> { - let tree = match &self.filtered_tree { - Some(tree) => &tree, - None => &self.tree, - }; - let line = &tree.selected_line(); - Ok(match verb.exec_pattern.as_ref() { - ":back" => AppStateCmdResult::PopState, - ":focus" => { - let path = tree.selected_line().path.clone(); - let options = tree.options.clone(); - AppStateCmdResult::from_optional_state(BrowserState::new( - path, - options, - screen, - &TaskLifetime::unlimited(), - )) - } - ":help" => AppStateCmdResult::NewState(Box::new(HelpState::new(screen))), - ":open" => AppStateCmdResult::Launch(Launchable::opener(&line.target())?), - ":parent" => match &line.target().parent() { - Some(path) => { - let path = path.to_path_buf(); - let options = self.tree.options.clone(); - AppStateCmdResult::from_optional_state(BrowserState::new( - path, - options, - screen, - &TaskLifetime::unlimited(), - )) - } - None => AppStateCmdResult::DisplayError("no parent found".to_string()), - }, - ":print_path" => { - if let Some(ref output_path) = con.launch_args.file_export_path { - // an output path was provided, we write to it - let f = OpenOptions::new().append(true).open(output_path)?; - writeln!(&f, "{}", line.target().to_string_lossy())?; - 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 launchable = - Launchable::from(vec![line.target().to_string_lossy().to_string()])?; - launchable.just_print = true; - AppStateCmdResult::Launch(launchable) - } - } - ":toggle_files" => { - self.with_new_options(screen, &|o: &mut TreeOptions| o.only_folders ^= true) - } - ":toggle_hidden" => self.with_new_options(screen, &|o| o.show_hidden ^= true), - ":toggle_git_ignore" => self.with_new_options(screen, &|options| { - options.respect_git_ignore = match options.respect_git_ignore { - OptionBool::Auto => { - if tree.nb_gitignored > 0 { - OptionBool::No - } else { - OptionBool::Yes - } - } - OptionBool::Yes => OptionBool::No, - OptionBool::No => OptionBool::Yes, - }; - }), - ":toggle_perm" => self.with_new_options(screen, &|o| o.show_permissions ^= true), - ":toggle_sizes" => self.with_new_options(screen, &|o| o.show_sizes ^= true), - ":toggle_trim_root" => self.with_new_options(screen, &|o| o.trim_root ^= true), - ":quit" => AppStateCmdResult::Quit, - _ => verb.to_cmd_result(&line.target(), con)?, - }) - } -} #[derive(Debug, Clone, PartialEq)] pub enum PrefixSearchResult<T> { @@ -249,12 +144,6 @@ pub enum PrefixSearchResult<T> { TooManyMatches, } -/// Provide access to the verbs: -/// - the built-in ones -/// - the user defined ones -/// When the user types some keys, we select a verb -/// - if the input exactly matches a shortcut or the name -/// - if only one verb starts with the input impl VerbStore { pub fn new() -> VerbStore { VerbStore { @@ -273,13 +162,13 @@ impl VerbStore { "cd".to_string(), None, // no real need for a shortcut as it's mapped to alt-enter "cd {directory}".to_string(), - "change directory and quit (mapped to `<alt><enter>`)".to_string(), + "change directory and quit (mapped to `<alt><enter>` in tree)".to_string(), true, // needs to be launched from the parent shell )); self.verbs.push(Verb::create_built_in( "focus", Some("goto".to_string()), - "display a directory (mapped to `<enter>`)", + "display {directory} (mapped to `<enter>` in tree)", )); self.verbs.push(Verb::create_built_in( "help", @@ -289,7 +178,7 @@ impl VerbStore { self.verbs.push(Verb::create_built_in( "open", None, - "open a file according to OS settings (mapped to `<enter>`)", + "open a file according to OS settings (mapped to `<enter>` in tree)", )); self.verbs.push(Verb::create_built_in( "parent", |