summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2019-01-21 21:19:43 +0100
committerCanop <cano.petrole@gmail.com>2019-01-21 21:19:43 +0100
commit3e5faa68e74e8c206e91d06d03447c16e2c20b74 (patch)
tree4715dd80e15351f390cbfa7eb93bf089937f6530
parent8aa96fb62ef7622b2db8473c10fe7bb99c3c163c (diff)
--cmd program argument to pass a sequence of commands
Right not I use it for tests & benchmarks
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md5
-rw-r--r--documentation.md27
-rw-r--r--src/app.rs143
-rw-r--r--src/commands.rs18
-rw-r--r--src/main.rs23
7 files changed, 157 insertions, 63 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f5bba1b..513ff30 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.4.6"
+version = "0.4.7"
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 aa5774e..287e530 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "broot"
-version = "0.4.6"
+version = "0.4.7"
authors = ["dystroy <denys.seguret@gmail.com>"]
repository = "https://github.com/Canop/broot"
description = "Fuzzy Search + tree + cd"
diff --git a/README.md b/README.md
index a29710a..2d152f2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,6 @@
An interactive tree view, a fuzzy search, a balanced BFS descent and customizable commands.
-[Documentation](documentation.md)
### Get an overview of a directory, even a big one:
@@ -50,6 +49,10 @@ broot tries to select the most relevant file. You can still go from one match to
Just find the file you want to edit with a few keystrokes, type `:e`, then `<enter>` (you should define your prefered editor, see [documentation](documentation.md#verbs)).
+### More...
+
+See the complete [Documentation](documentation.md).
+
## Installation
### From Source
diff --git a/documentation.md b/documentation.md
index 912a9ea..6008bd7 100644
--- a/documentation.md
+++ b/documentation.md
@@ -135,3 +135,30 @@ In the default configuration, it's mapped to `s` and can be activated at launch
When broot starts, it checks for a configuration file in the standard location defined by your OS and creates one if there's none.
You can see this location by opening the help with ̀`?`. You can also open it directly from the help screen by typing `:o`.
+
+## Passing commands as program argument
+
+*Note: this feature is experimental and will probably change.*
+
+Commands to be executed can be passed using the `--cmd` argument, separated with a space.
+
+### Direcly search
+
+ broot --cmd miaou /
+
+This opens broot and immediately search for "miaou" in `/` as if it were typed in broot's input.
+
+### Go to the most relevant directory
+
+ broot --cmd ":p miaou :g"
+
+This opens broot, goes to the parent directory, searches for "miaou", then opens the selected directory (staying in broot).
+
+### cd to a directory
+
+ br --cmd "roulette :c" ~
+
+This launches broot using the `br` shell function in your home directory, searches for "roulette", then cd to the relevant directory (leaving broot).
+
+
+
diff --git a/src/app.rs b/src/app.rs
index 281ec10..b93ba7e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -55,19 +55,25 @@ pub trait AppState {
}
pub struct App {
- pub states: Vec<Box<dyn AppState>>, // stack: the last one is current
+ states: Vec<Box<dyn AppState>>, // stack: the last one is current
+ quitting: bool,
+ launch_at_end: Option<Launchable>, // what must be launched after end
}
impl App {
pub fn new() -> App {
- App { states: Vec::new() }
+ App {
+ states: Vec::new(),
+ quitting: false,
+ launch_at_end: None,
+ }
}
pub fn push(&mut self, new_state: Box<dyn AppState>) {
self.states.push(new_state);
}
- pub fn mut_state(&mut self) -> &mut Box<dyn AppState> {
+ fn mut_state(&mut self) -> &mut Box<dyn AppState> {
match self.states.last_mut() {
Some(s) => s,
None => {
@@ -75,7 +81,7 @@ impl App {
}
}
}
- pub fn state(&self) -> &Box<dyn AppState> {
+ fn state(&self) -> &Box<dyn AppState> {
match self.states.last() {
Some(s) => s,
None => {
@@ -84,6 +90,8 @@ impl App {
}
}
+ /// execute all the pending tasks until there's none remaining or
+ /// the allowed lifetime is expired (usually when the user typed a new key)
fn do_pending_tasks(&mut self, cmd: &Command, screen: &mut Screen, con: &AppContext, tl: TaskLifetime) -> io::Result<()> {
let has_task = self.state().has_pending_tasks();
if has_task {
@@ -105,8 +113,52 @@ impl App {
Ok(())
}
+ /// apply a command, and returns a command, which may be the same (modified or not)
+ /// or a new one.
+ /// This normally mutates self
+ fn apply_command(&mut self, cmd: Command, screen: &mut Screen, con: &AppContext) -> io::Result<Command> {
+ let mut cmd = cmd;
+ debug!("action: {:?}", &cmd.action);
+ screen.write_input(&cmd)?;
+ self.state().write_flags(screen, con)?;
+ match self.mut_state().apply(&mut cmd, con)? {
+ AppStateCmdResult::Quit => {
+ debug!("cmd result quit");
+ self.quitting = true;
+ }
+ AppStateCmdResult::Launch(launchable) => {
+ self.launch_at_end = Some(launchable);
+ self.quitting = true;
+ }
+ AppStateCmdResult::NewState(boxed_state) => {
+ self.push(boxed_state);
+ cmd = cmd.pop_verb();
+ self.state().write_status(screen, &cmd, con)?;
+ }
+ AppStateCmdResult::PopState => {
+ if self.states.len() == 1 {
+ debug!("quitting on last pop state");
+ self.quitting = true;
+ } else {
+ self.states.pop();
+ cmd = Command::new();
+ self.state().write_status(screen, &cmd, con)?;
+ }
+ }
+ AppStateCmdResult::DisplayError(txt) => {
+ screen.write_status_err(&txt)?;
+ }
+ AppStateCmdResult::Keep => {
+ self.state().write_status(screen, &cmd, con)?;
+ }
+ }
+ screen.write_input(&cmd)?;
+ self.state().write_flags(screen, con)?;
+ Ok(cmd)
+ }
+
/// This is the main loop of the application
- pub fn run(mut self, con: &AppContext) -> io::Result<Option<Launchable>> {
+ pub fn run(mut self, con: &AppContext, input_commands: Vec<Command>) -> io::Result<Option<Launchable>> {
let (w, h) = termion::terminal_size()?;
let mut screen = Screen::new(w, h)?;
write!(
@@ -115,15 +167,30 @@ impl App {
termion::clear::All,
termion::cursor::Hide
)?;
- let stdin = stdin();
- let keys = stdin.keys();
+
+ // if some commands were passed to the application
+ // we execute them before even starting listening for keys
+ for cmd in input_commands {
+ let cmd = self.apply_command(cmd, &mut screen, con)?;
+ self.do_pending_tasks(
+ &cmd,
+ &mut screen,
+ con,
+ TaskLifetime::unlimited(),
+ )?;
+ if self.quitting {
+ return Ok(self.launch_at_end);
+ }
+ }
+
+ // we listen for keys in a separate thread so that we can go on listening
+ // when a long search is running, and interrupt it if needed
+ let keys = stdin().keys();
let (tx_keys, rx_keys) = mpsc::channel();
let (tx_quit, rx_quit) = mpsc::channel();
let cmd_count = Arc::new(AtomicUsize::new(0));
let key_count = Arc::clone(&cmd_count);
thread::spawn(move || {
- // we listen for keys in a separate thread so that we can go on listening
- // when a long search is running, and interrupt it if needed
for c in keys {
key_count.fetch_add(1, Ordering::SeqCst);
// we send the command to the receiver in the
@@ -139,21 +206,12 @@ impl App {
}
}
});
+
let mut cmd = Command::new();
screen.write_input(&cmd)?;
screen.write_status_text("Hit <esc> to quit, '?' for help, or type some letters to search")?;
self.state().write_flags(&mut screen, con)?;
- let mut quit = false;
- let mut to_launch: Option<Launchable> = None;
loop {
- if !quit {
- self.do_pending_tasks(
- &cmd,
- &mut screen,
- con,
- TaskLifetime::new(&cmd_count),
- )?;
- }
let c = match rx_keys.recv() {
Ok(c) => c,
Err(_) => {
@@ -163,44 +221,17 @@ impl App {
}
};
cmd.add_key(c?);
- debug!("action: {:?}", &cmd.action);
- screen.write_input(&cmd)?;
- self.state().write_flags(&mut screen, con)?;
- match self.mut_state().apply(&mut cmd, con)? {
- AppStateCmdResult::Quit => {
- debug!("cmd result quit");
- quit = true;
- }
- AppStateCmdResult::Launch(launchable) => {
- to_launch = Some(launchable);
- quit = true;
- }
- AppStateCmdResult::NewState(boxed_state) => {
- self.push(boxed_state);
- cmd = cmd.pop_verb();
- self.state().write_status(&mut screen, &cmd, con)?;
- }
- AppStateCmdResult::PopState => {
- if self.states.len() == 1 {
- debug!("quitting on last pop state");
- quit = true;
- } else {
- self.states.pop();
- cmd = Command::new();
- self.state().write_status(&mut screen, &cmd, con)?;
- }
- }
- AppStateCmdResult::DisplayError(txt) => {
- screen.write_status_err(&txt)?;
- }
- AppStateCmdResult::Keep => {
- self.state().write_status(&mut screen, &cmd, con)?;
- }
+ cmd = self.apply_command(cmd, &mut screen, con)?;
+ tx_quit.send(self.quitting).unwrap();
+ if !self.quitting {
+ self.do_pending_tasks(
+ &cmd,
+ &mut screen,
+ con,
+ TaskLifetime::new(&cmd_count),
+ )?;
}
- screen.write_input(&cmd)?;
- self.state().write_flags(&mut screen, con)?;
- tx_quit.send(quit).unwrap();
}
- Ok(to_launch)
+ Ok(self.launch_at_end)
}
}
diff --git a/src/commands.rs b/src/commands.rs
index 38167c9..935de53 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -101,6 +101,24 @@ impl Command {
}
c
}
+ // build a command from a string
+ // Note that this isn't used (or usable) for interpretation
+ // of the in-app user input. It's meant for interpretation
+ // of a file or from a sequence of commands passed as argument
+ // of the program.
+ // A ':', even if at the end, is assumed to mean that the
+ // command must be executed (it's equivalent to the user
+ // typing `enter` in the app
+ // This specific syntax isn't definitive
+ pub fn from(raw: String) -> Command {
+ let parts = CommandParts::from(&raw);
+ let action = Action::from(&parts, raw.contains(":"));
+ Command {
+ raw,
+ parts,
+ action,
+ }
+ }
pub fn add_key(&mut self, key: Key) {
match key {
Key::Char('\t') => {
diff --git a/src/main.rs b/src/main.rs
index 14ff5fe..2646a22 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -38,6 +38,7 @@ use toml;
use crate::app::App;
use crate::app_context::AppContext;
use crate::browser_states::BrowserState;
+use crate::commands::Command;
use crate::conf::Conf;
use crate::errors::ProgramError;
use crate::external::Launchable;
@@ -45,7 +46,7 @@ use crate::task_sync::TaskLifetime;
use crate::tree_options::TreeOptions;
use crate::verbs::VerbStore;
-const VERSION: &str = "0.4.6";
+const VERSION: &str = "0.4.7";
// declare the possible CLI arguments, and gets the values
fn get_cli_args<'a>() -> clap::ArgMatches<'a> {
@@ -53,7 +54,17 @@ fn get_cli_args<'a>() -> clap::ArgMatches<'a> {
.version(VERSION)
.author("dystroy <denys.seguret@gmail.com>")
.about("Balanced tree view + fuzzy search + BFS + customizable launcher")
- .arg(clap::Arg::with_name("root").help("sets the root directory"))
+ .arg(
+ clap::Arg::with_name("root")
+ .help("sets the root directory")
+ )
+ .arg(
+ clap::Arg::with_name("commands")
+ .short("c")
+ .long("cmd")
+ .takes_value(true)
+ .help("commands to execute (space separated, experimental)"),
+ )
.arg(
clap::Arg::with_name("only-folders")
.short("f")
@@ -160,15 +171,19 @@ fn run() -> Result<Option<Launchable>, ProgramError> {
.value_of("output_path")
.and_then(|s| Some(s.to_owned())),
};
-
debug!("output path: {:?}", &con.output_path);
+ let input_commands: Vec<Command> = match cli_args.value_of("commands") {
+ Some(str) => str.split(' ').map(|s| Command::from(s.to_string())).collect(),
+ None => Vec::new(),
+ };
+
Ok(
match BrowserState::new(path.clone(), tree_options, &TaskLifetime::unlimited()) {
Some(bs) => {
let mut app = App::new();
app.push(Box::new(bs));
- app.run(&con)?
+ app.run(&con, input_commands)?
}
_ => None, // should not happen, as the lifetime is "unlimited"
},