summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2019-02-14 20:27:41 +0100
committerCanop <cano.petrole@gmail.com>2019-02-14 20:27:41 +0100
commitee2a4c4ac9a0cf198d8b0a2e1f14183f688da4ae (patch)
tree7508c0ee2b4a8ceff01d04e53dcac89196ddd491
parent51661a79b1735b3c9f760a1c5adbdfb525450c48 (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.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/app.rs11
-rw-r--r--src/browser_states.rs20
-rw-r--r--src/browser_verbs.rs88
-rw-r--r--src/help_states.rs28
-rw-r--r--src/help_verbs.rs61
-rw-r--r--src/main.rs2
-rw-r--r--src/verbs.rs141
9 files changed, 217 insertions, 138 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c711b2f..35bb711 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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)",
diff --git a/Cargo.toml b/Cargo.toml
index 940ebcb..7417a69 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/app.rs b/src/app.rs
index 78250c2..5fce1b4 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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",