Forward many more parameters to fzf after --- (#294)

This commit is contained in:
Denis Isidoro 2020-03-19 22:01:50 -03:00 committed by GitHub
parent 64045e32ca
commit 10f0219792
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 105 additions and 95 deletions

13
Cargo.lock generated
View file

@ -245,13 +245,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "navi" name = "navi"
version = "2.2.0" version = "2.3.0"
dependencies = [ dependencies = [
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"raw_tty 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "raw_tty 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"shellwords 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -443,6 +444,15 @@ name = "semver-parser"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "shellwords"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.2.0" version = "1.2.0"
@ -698,6 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum shellwords 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "685f0e9b0efe23d26e60a780d8dcd3ac95e90975814de9bc6f48e5d609b5d0f5"
"checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073" "checksum structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "navi" name = "navi"
version = "2.2.0" version = "2.3.0"
authors = ["Denis Isidoro <denis_isidoro@live.com>"] authors = ["Denis Isidoro <denis_isidoro@live.com>"]
edition = "2018" edition = "2018"
description = "An interactive cheatsheet tool for the command-line" description = "An interactive cheatsheet tool for the command-line"
@ -23,6 +23,7 @@ lazy_static = "1.4.0"
dirs = "2.0.0" dirs = "2.0.0"
terminal_size = "0.1.10" terminal_size = "0.1.10"
walkdir = "2" walkdir = "2"
shellwords = "1.0.0"
[dependencies.git2] [dependencies.git2]
version = "0.10.0" version = "0.10.0"

View file

@ -202,9 +202,15 @@ $ image_id: docker images --- --column 3 --header-lines 1 --delimiter '\s\s+'
The supported parameters are: The supported parameters are:
- `--prevent-extra` *(experimental)*: limits the user to select one of the suggestions; - `--prevent-extra` *(experimental)*: limits the user to select one of the suggestions;
- `--column <number>`: extracts a single column from the selected result; - `--column <number>`: extracts a single column from the selected result;
- `--delimiter <regex>`: delimits columns + forwarded option to `fzf`;
- `--multi`: forwarded option to `fzf`; In addition, it's possible to forward the following parameters to `fzf`:
- `--header-lines <number>`: forwarded option to `fzf`; - `--header-lines <number>`;
- `--delimiter <regex>`;
- `--query <text>`;
- `--filter <text>`;
- `--header <text>`;
- `--preview <code>`;
- `--preview-window <text>`.
### Variable dependency ### Variable dependency

View file

@ -4,8 +4,8 @@ use crate::flows;
use crate::fzf; use crate::fzf;
use crate::handler; use crate::handler;
use crate::parser; use crate::parser;
use crate::structures::cheat::{Suggestion, SuggestionType, VariableMap}; use crate::structures::cheat::{Suggestion, VariableMap};
use crate::structures::fzf::Opts as FzfOpts; use crate::structures::fzf::{Opts as FzfOpts, SuggestionType};
use crate::structures::option; use crate::structures::option;
use crate::structures::option::Config; use crate::structures::option::Config;
use regex::Regex; use regex::Regex;
@ -73,7 +73,7 @@ fn prompt_with_suggestions(
for (key, value) in values.iter() { for (key, value) in values.iter() {
vars_cmd.push_str(format!("{}=\"{}\"; ", key, value).as_str()); vars_cmd.push_str(format!("{}=\"{}\"; ", key, value).as_str());
} }
let (suggestion_command, suggestion_options) = &suggestion; let (suggestion_command, suggestion_opts) = suggestion;
let command = format!("{} {}", vars_cmd, suggestion_command); let command = format!("{} {}", vars_cmd, suggestion_command);
let child = Command::new("bash") let child = Command::new("bash")
@ -85,18 +85,12 @@ fn prompt_with_suggestions(
let suggestions = String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap(); let suggestions = String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap();
let mut opts = FzfOpts { let opts = suggestion_opts.clone().unwrap_or_default();
let opts = FzfOpts {
autoselect: !config.no_autoselect, autoselect: !config.no_autoselect,
overrides: config.fzf_overrides_var.clone(), overrides: config.fzf_overrides_var.clone(),
prompt: Some(display::variable_prompt(varname)), prompt: Some(display::variable_prompt(varname)),
..Default::default() ..opts
};
if let Some(o) = &suggestion_options {
opts.suggestion_type = o.suggestion_type;
opts.header_lines = o.header_lines;
opts.column = o.column;
opts.delimiter = o.delimiter.clone();
}; };
let (output, _) = fzf::call(opts, |stdin| { let (output, _) = fzf::call(opts, |stdin| {
@ -162,9 +156,9 @@ fn with_new_lines(txt: String) -> String {
pub fn main(variant: Variant, config: Config, contains_key: bool) -> Result<(), Box<dyn Error>> { pub fn main(variant: Variant, config: Config, contains_key: bool) -> Result<(), Box<dyn Error>> {
let _ = display::WIDTHS; let _ = display::WIDTHS;
let (raw_selection, variables) = fzf::call(gen_core_fzf_opts(variant, &config), |stdin| { let opts = gen_core_fzf_opts(variant, &config);
Some(parser::read_all(&config, stdin)) let (raw_selection, variables) =
}); fzf::call(opts, |stdin| Some(parser::read_all(&config, stdin)));
let (key, tags, snippet) = extract_from_selections(&raw_selection[..], contains_key); let (key, tags, snippet) = extract_from_selections(&raw_selection[..], contains_key);

View file

@ -1,8 +1,7 @@
use crate::filesystem; use crate::filesystem;
use crate::fzf; use crate::fzf;
use crate::git; use crate::git;
use crate::structures::cheat::SuggestionType; use crate::structures::fzf::{Opts as FzfOpts, SuggestionType};
use crate::structures::fzf::Opts as FzfOpts;
use git2::Repository; use git2::Repository;
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
@ -66,14 +65,13 @@ pub fn add(uri: String) -> Result<(), Box<dyn Error>> {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let overrides = "--preview-window right:30%".to_string();
let opts = FzfOpts { let opts = FzfOpts {
suggestion_type: SuggestionType::MultipleSelections, suggestion_type: SuggestionType::MultipleSelections,
preview: Some(format!("cat '{}/{{}}'", tmp_path_str)), preview: Some(format!("cat '{}/{{}}'", tmp_path_str)),
header: Some( header: Some(
"Select the cheatsheets you want to import with <TAB> then hit <Enter>".to_string(), "Select the cheatsheets you want to import with <TAB> then hit <Enter>".to_string(),
), ),
overrides: Some(overrides), preview_window: Some("--preview-window right:30%".to_string()),
..Default::default() ..Default::default()
}; };

View file

@ -1,6 +1,6 @@
use crate::display; use crate::display;
use crate::structures::cheat::{SuggestionType, VariableMap}; use crate::structures::cheat::VariableMap;
use crate::structures::fzf::Opts; use crate::structures::fzf::{Opts, SuggestionType};
use std::process; use std::process;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -86,6 +86,10 @@ where
fzf_command.args(&["--prompt", &p]); fzf_command.args(&["--prompt", &p]);
} }
if let Some(pw) = opts.preview_window {
fzf_command.args(&["--preview-window", &pw]);
}
if opts.header_lines > 0 { if opts.header_lines > 0 {
fzf_command.args(&["--header-lines", format!("{}", opts.header_lines).as_str()]); fzf_command.args(&["--header-lines", format!("{}", opts.header_lines).as_str()]);
} }
@ -114,12 +118,12 @@ where
}; };
let stdin = child.stdin.as_mut().unwrap(); let stdin = child.stdin.as_mut().unwrap();
let result = stdin_fn(stdin); let result_map = stdin_fn(stdin);
let out = child.wait_with_output().unwrap(); let out = child.wait_with_output().unwrap();
let text = match out.status.code() { let text = match out.status.code() {
Some(0) | Some(1) => String::from_utf8(out.stdout).unwrap(), Some(0) | Some(1) | Some(2) => String::from_utf8(out.stdout).unwrap(),
Some(130) => process::exit(130), Some(130) => process::exit(130),
_ => { _ => {
let err = String::from_utf8(out.stderr) let err = String::from_utf8(out.stderr)
@ -128,14 +132,13 @@ where
} }
}; };
( let out = get_column(
get_column( parse_output_single(text, opts.suggestion_type),
parse_output_single(text, opts.suggestion_type), opts.column,
opts.column, opts.delimiter.as_deref(),
opts.delimiter.as_deref(), );
),
result, (out, result_map)
)
} }
fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> String { fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> String {

View file

@ -1,7 +1,8 @@
use crate::display; use crate::display;
use crate::filesystem; use crate::filesystem;
use crate::structures::cheat::{SuggestionOpts, SuggestionType, VariableMap}; use crate::structures::cheat::VariableMap;
use crate::structures::fnv::HashLine; use crate::structures::fnv::HashLine;
use crate::structures::fzf::{Opts as FzfOpts, SuggestionType};
use crate::structures::option::Config; use crate::structures::option::Config;
use crate::welcome; use crate::welcome;
use regex::Regex; use regex::Regex;
@ -9,47 +10,43 @@ use std::collections::HashSet;
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
fn remove_quotes(txt: &str) -> String { fn parse_opts(text: &str) -> FzfOpts {
txt.replace('"', "").replace('\'', "")
}
fn parse_opts(text: &str) -> SuggestionOpts {
let mut header_lines: u8 = 0;
let mut column: Option<u8> = None;
let mut multi = false; let mut multi = false;
let mut prevent_extra = false; let mut prevent_extra = false;
let mut delimiter: Option<String> = None; let mut opts = FzfOpts::default();
let parts_vec = shellwords::split(text).unwrap();
let mut parts = text.split(' '); let mut parts = parts_vec.into_iter();
while let Some(p) = parts.next() { while let Some(p) = parts.next() {
match p { match p.as_str() {
"--multi" => multi = true, "--multi" => multi = true,
"--prevent-extra" => prevent_extra = true, "--prevent-extra" => prevent_extra = true,
"--header" | "--headers" | "--header-lines" => { "--headers" | "--header-lines" => {
header_lines = remove_quotes(parts.next().unwrap()).parse::<u8>().unwrap() opts.header_lines = parts.next().unwrap().parse::<u8>().unwrap()
} }
"--column" => { "--column" => opts.column = Some(parts.next().unwrap().parse::<u8>().unwrap()),
column = Some(remove_quotes(parts.next().unwrap()).parse::<u8>().unwrap()) "--delimiter" => opts.delimiter = Some(parts.next().unwrap().to_string()),
} "--query" => opts.query = Some(parts.next().unwrap().to_string()),
"--delimiter" => delimiter = Some(remove_quotes(parts.next().unwrap()).to_string()), "--filter" => opts.filter = Some(parts.next().unwrap().to_string()),
"--preview" => opts.preview = Some(parts.next().unwrap().to_string()),
"--preview-window" => opts.preview_window = Some(parts.next().unwrap().to_string()),
"--header" => opts.header = Some(parts.next().unwrap().to_string()),
"--overrides" => opts.overrides = Some(parts.next().unwrap().to_string()),
_ => (), _ => (),
} }
} }
SuggestionOpts { let suggestion_type = match (multi, prevent_extra) {
header_lines, (true, _) => SuggestionType::MultipleSelections, // multi wins over allow-extra
column, (false, false) => SuggestionType::SingleRecommendation,
delimiter, (false, true) => SuggestionType::SingleSelection,
suggestion_type: match (multi, prevent_extra) { };
(true, _) => SuggestionType::MultipleSelections, // multi wins over allow-extra opts.suggestion_type = suggestion_type;
(false, false) => SuggestionType::SingleRecommendation,
(false, true) => SuggestionType::SingleSelection, opts
},
}
} }
fn parse_variable_line(line: &str) -> (&str, &str, Option<SuggestionOpts>) { fn parse_variable_line(line: &str) -> (&str, &str, Option<FzfOpts>) {
let re = Regex::new(r"^\$\s*([^:]+):(.*)").unwrap(); let re = Regex::new(r"^\$\s*([^:]+):(.*)").unwrap();
let caps = re.captures(line).unwrap(); let caps = re.captures(line).unwrap();
let variable = caps.get(1).unwrap().as_str().trim(); let variable = caps.get(1).unwrap().as_str().trim();
@ -200,11 +197,12 @@ mod tests {
assert_eq!(variable, "user"); assert_eq!(variable, "user");
assert_eq!( assert_eq!(
command_options, command_options,
Some(SuggestionOpts { Some(FzfOpts {
header_lines: 0, header_lines: 0,
column: None, column: None,
delimiter: None, delimiter: None,
suggestion_type: SuggestionType::SingleRecommendation suggestion_type: SuggestionType::SingleRecommendation,
..Default::default()
}) })
); );
} }
@ -220,11 +218,12 @@ mod tests {
read_file(path, &mut variables, &mut visited_lines, child_stdin); read_file(path, &mut variables, &mut visited_lines, child_stdin);
let expected_suggestion = ( let expected_suggestion = (
r#" echo -e "$(whoami)\nroot" "#.to_string(), r#" echo -e "$(whoami)\nroot" "#.to_string(),
Some(SuggestionOpts { Some(FzfOpts {
header_lines: 0, header_lines: 0,
column: None, column: None,
delimiter: None, delimiter: None,
suggestion_type: SuggestionType::SingleRecommendation, suggestion_type: SuggestionType::SingleRecommendation,
..Default::default()
}), }),
); );
let actual_suggestion = variables.get("ssh", "user"); let actual_suggestion = variables.get("ssh", "user");

View file

@ -1,29 +1,8 @@
use crate::structures::fnv::HashLine; use crate::structures::fnv::HashLine;
use crate::structures::fzf::Opts;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, PartialEq)] pub type Suggestion = (String, Option<Opts>);
pub struct SuggestionOpts {
pub header_lines: u8,
pub column: Option<u8>,
pub delimiter: Option<String>,
pub suggestion_type: SuggestionType,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SuggestionType {
/// fzf will not print any suggestions
Disabled,
/// fzf will only select one of the suggestions
SingleSelection,
/// fzf will select multiple suggestions
MultipleSelections,
/// fzf will select one of the suggestions or use the query
SingleRecommendation,
/// initial snippet selection
SnippetSelection,
}
pub type Suggestion = (String, Option<SuggestionOpts>);
fn gen_key(tags: &str, variable: &str) -> u64 { fn gen_key(tags: &str, variable: &str) -> u64 {
format!("{};{}", tags, variable).hash_line() format!("{};{}", tags, variable).hash_line()

View file

@ -1,10 +1,10 @@
use crate::structures::cheat::SuggestionType; #[derive(Debug, PartialEq, Clone)]
pub struct Opts { pub struct Opts {
pub query: Option<String>, pub query: Option<String>,
pub filter: Option<String>, pub filter: Option<String>,
pub prompt: Option<String>, pub prompt: Option<String>,
pub preview: Option<String>, pub preview: Option<String>,
pub preview_window: Option<String>,
pub autoselect: bool, pub autoselect: bool,
pub overrides: Option<String>, pub overrides: Option<String>,
pub header_lines: u8, pub header_lines: u8,
@ -21,6 +21,7 @@ impl Default for Opts {
filter: None, filter: None,
autoselect: true, autoselect: true,
preview: None, preview: None,
preview_window: None,
overrides: None, overrides: None,
header_lines: 0, header_lines: 0,
header: None, header: None,
@ -31,3 +32,17 @@ impl Default for Opts {
} }
} }
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SuggestionType {
/// fzf will not print any suggestions
Disabled,
/// fzf will only select one of the suggestions
SingleSelection,
/// fzf will select multiple suggestions
MultipleSelections,
/// fzf will select one of the suggestions or use the query
SingleRecommendation,
/// initial snippet selection
SnippetSelection,
}

View file

@ -32,6 +32,9 @@ echo "I like these examples: "$(printf '%s' "<examples>" | sed 's/^..*$/"&"/' |
# multiple replacements -> "foo" # multiple replacements -> "foo"
echo "<x> <y> <x> <z>" echo "<x> <y> <x> <z>"
# with preview
cat "<file>"
$ x: echo '1 2 3' | tr ' ' '\n' $ x: echo '1 2 3' | tr ' ' '\n'
$ y: echo 'a b c' | tr ' ' '\n' $ y: echo 'a b c' | tr ' ' '\n'
$ z: echo 'foo bar' | tr ' ' '\n' $ z: echo 'foo bar' | tr ' ' '\n'
@ -39,8 +42,9 @@ $ table_elem: echo -e '0 rust rust-lang.org\n1 clojure clojure.org' ---
$ table_elem2: echo -e '0;rust;rust-lang.org\n1;clojure;clojure.org' --- --column 2 --delimiter ';' $ table_elem2: echo -e '0;rust;rust-lang.org\n1;clojure;clojure.org' --- --column 2 --delimiter ';'
$ multi_col: ls -la | awk '{print $1, $9}' --- --column 2 --delimiter '\s' --multi $ multi_col: ls -la | awk '{print $1, $9}' --- --column 2 --delimiter '\s' --multi
$ langs: echo 'clojure rust javascript' | tr ' ' '\n' --- --multi $ langs: echo 'clojure rust javascript' | tr ' ' '\n' --- --multi
$ examples: echo -e 'foo bar\nlorem ipsum\ndolor sit' --- --multi $ examples: echo -e 'foo bar\nlorem ipsum\ndolor sit' --- --mult
$ multiword: echo -e 'foo bar\nlorem ipsum\ndolor sit\nbaz' $ multiword: echo -e 'foo bar\nlorem ipsum\ndolor sit\nbaz'i
$ file: ls . --- --preview 'cat {}' --preview-window '50%'
# this should be displayed # this should be displayed
echo hi echo hi