From e0a85678e112759f56f9e07d04a29f2eed6eb348 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 11 Dec 2023 07:03:49 +0100 Subject: complete/fish: improve shell completions for fish - Stop using `-n __fish_use_subcommand`. This had the effect of ignoring options if a positional argument has already been given, but that's not how ripgrep works. - Only suggest negation options if the option they're negating is passed (e.g., only complete `--no-pcre2` if `--pcre2` is present). The zsh completions already do this. - Take into account whether an option takes an argument. If an option is not a switch then it won't suggest further options until the argument is given, e.g. `-C` won't suggest options but `-i` will. - Suggest correct arguments for options. We already completed a fixed set of choices where available, but now we go further: - Filenames are only suggested for options that take filenames. - `--pre` and `--hostname-bin` suggest binaries from `$PATH`. - `-t`/`--type`/&c use `--type-list` for suggestions, like in zsh, with a preview of the glob patterns. - `--encoding` uses a hardcoded list extracted from the zsh completions. This has been refactored into a separate file, and the range globs (`{1..5}`) replaced by comma globs (`{1,2,3,4,5}`) since those work in both shells. I verified that this produces the same list as before in zsh, and the same list in fish (albeit in a different order). PR #2684 --- CHANGELOG.md | 10 +++++ crates/core/flags/complete/encodings.sh | 29 ++++++++++++++ crates/core/flags/complete/fish.rs | 70 +++++++++++++++++++++------------ crates/core/flags/complete/mod.rs | 2 + crates/core/flags/complete/rg.zsh | 26 +----------- crates/core/flags/complete/zsh.rs | 2 +- crates/core/flags/defs.rs | 23 +++++++++++ crates/core/flags/mod.rs | 21 +++++++++- 8 files changed, 131 insertions(+), 52 deletions(-) create mode 100644 crates/core/flags/complete/encodings.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 366f904e..e6d56e7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +14.1.0 (TBD) +============ +This is a minor release with a few small new features and bug fixes. + +Feature enhancements: + +* [FEATURE #2684](https://github.com/BurntSushi/ripgrep/issues/2684): + Improve completions for the `fish` shell. + + 14.0.3 (2023-11-28) =================== This is a patch release with a bug fix for the `--sortr` flag. diff --git a/crates/core/flags/complete/encodings.sh b/crates/core/flags/complete/encodings.sh new file mode 100644 index 00000000..14fc4a85 --- /dev/null +++ b/crates/core/flags/complete/encodings.sh @@ -0,0 +1,29 @@ +# This is impossible to read, but these encodings rarely if ever change, so +# it probably does not matter. They are derived from the list given here: +# https://encoding.spec.whatwg.org/#concept-encoding-get +# +# The globbing here works in both fish and zsh (though they expand it in +# different orders). It may work in other shells too. + +{{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean} +logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian} +866 ibm{819,866} csibm866 +big5{,-hkscs} {cn-,cs}big5 x-x-big5 +cp{819,866,125{0,1,2,3,4,5,6,7,8}} x-cp125{0,1,2,3,4,5,6,7,8} +csiso2022{jp,kr} csiso8859{6,8}{e,i} +csisolatin{1,2,3,4,5,6,9} csisolatin{arabic,cyrillic,greek,hebrew} +ecma-{114,118} asmo-708 elot_928 sun_eu_greek +euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese +{,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312 +iso-2022-{cn,cn-ext,jp,kr} +iso8859{,-}{1,2,3,4,5,6,7,8,9,10,11,13,14,15} +iso-8859-{1,2,3,4,5,6,7,8,9,10,11,{6,8}-{e,i},13,14,15,16} iso_8859-{1,2,3,4,5,6,7,8,9,15} +iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989 +iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157} +koi{,8,8-r,8-ru,8-u,8_r} cskoi8r +ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987 +latin{1,2,3,4,5,6} l{1,2,3,4,5,6,9} +shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932 +utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8 +windows-{31j,874,949,125{0,1,2,3,4,5,6,7,8}} dos-874 tis-620 ansi_x3.4-1968 +x-user-defined auto none diff --git a/crates/core/flags/complete/fish.rs b/crates/core/flags/complete/fish.rs index e55d72e3..f8f7133b 100644 --- a/crates/core/flags/complete/fish.rs +++ b/crates/core/flags/complete/fish.rs @@ -2,17 +2,13 @@ Provides completions for ripgrep's CLI for the fish shell. */ -use crate::flags::defs::FLAGS; +use crate::flags::{defs::FLAGS, CompletionType}; -const TEMPLATE: &'static str = - "complete -c rg -n '__fish_use_subcommand' !SHORT! !LONG! !DOC!\n"; -const TEMPLATE_CHOICES: &'static str = - "complete -c rg -n '__fish_use_subcommand' !SHORT! !LONG! !DOC! -r -f -a '!CHOICES!'\n"; +const TEMPLATE: &'static str = "complete -c rg !SHORT! -l !LONG! -d '!DOC!'"; +const TEMPLATE_NEGATED: &'static str = + "complete -c rg -l !NEGATED! -n '__fish_contains_opt !SHORT! !LONG!' -d '!DOC!'\n"; /// Generate completions for Fish. -/// -/// Note that these completions are based on what was produced for ripgrep <=13 -/// using Clap 2.x. Improvements on this are welcome. pub(crate) fn generate() -> String { let mut out = String::new(); for flag in FLAGS.iter() { @@ -20,25 +16,49 @@ pub(crate) fn generate() -> String { None => "".to_string(), Some(byte) => format!("-s {}", char::from(byte)), }; - let long = format!("-l '{}'", flag.name_long().replace("'", "\\'")); - let doc = format!("-d '{}'", flag.doc_short().replace("'", "\\'")); - let template = if flag.doc_choices().is_empty() { - TEMPLATE.to_string() - } else { - TEMPLATE_CHOICES - .replace("!CHOICES!", &flag.doc_choices().join(" ")) - }; - out.push_str( - &template - .replace("!SHORT!", &short) - .replace("!LONG!", &long) - .replace("!DOC!", &doc), - ); + let long = flag.name_long(); + let doc = flag.doc_short().replace("'", "\\'"); + let mut completion = TEMPLATE + .replace("!SHORT!", &short) + .replace("!LONG!", &long) + .replace("!DOC!", &doc); + + match flag.completion_type() { + CompletionType::Filename => { + completion.push_str(" -r -F"); + } + CompletionType::Executable => { + completion.push_str(" -r -f -a '(__fish_complete_command)'"); + } + CompletionType::Filetype => { + completion.push_str( + " -r -f -a '(rg --type-list | string replace : \\t)'", + ); + } + CompletionType::Encoding => { + completion.push_str(" -r -f -a '"); + completion.push_str(super::ENCODINGS); + completion.push_str("'"); + } + CompletionType::Other if !flag.doc_choices().is_empty() => { + completion.push_str(" -r -f -a '"); + completion.push_str(&flag.doc_choices().join(" ")); + completion.push_str("'"); + } + CompletionType::Other if !flag.is_switch() => { + completion.push_str(" -r -f"); + } + CompletionType::Other => (), + } + + completion.push('\n'); + out.push_str(&completion); + if let Some(negated) = flag.name_negated() { - let long = format!("-l '{}'", negated.replace("'", "\\'")); out.push_str( - &TEMPLATE - .replace("!SHORT!", "") + &TEMPLATE_NEGATED + .replace("!NEGATED!", &negated) + .replace("!SHORT!", &short) .replace("!LONG!", &long) .replace("!DOC!", &doc), ); diff --git a/crates/core/flags/complete/mod.rs b/crates/core/flags/complete/mod.rs index 7d2fb606..b531bf12 100644 --- a/crates/core/flags/complete/mod.rs +++ b/crates/core/flags/complete/mod.rs @@ -2,6 +2,8 @@ Modules for generating completions for various shells. */ +static ENCODINGS: &'static str = include_str!("encodings.sh"); + pub(super) mod bash; pub(super) mod fish; pub(super) mod powershell; diff --git a/crates/core/flags/complete/rg.zsh b/crates/core/flags/complete/rg.zsh index 7ca89008..e9731a16 100644 --- a/crates/core/flags/complete/rg.zsh +++ b/crates/core/flags/complete/rg.zsh @@ -413,32 +413,8 @@ _rg_encodings() { local -a expl local -aU _encodings - # This is impossible to read, but these encodings rarely if ever change, so it - # probably doesn't matter. They are derived from the list given here: - # https://encoding.spec.whatwg.org/#concept-encoding-get _encodings=( - {{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean} - logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian} - 866 ibm{819,866} csibm866 - big5{,-hkscs} {cn-,cs}big5 x-x-big5 - cp{819,866,125{0..8}} x-cp125{0..8} - csiso2022{jp,kr} csiso8859{6,8}{e,i} - csisolatin{{1..6},9} csisolatin{arabic,cyrillic,greek,hebrew} - ecma-{114,118} asmo-708 elot_928 sun_eu_greek - euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese - {,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312 - iso-2022-{cn,cn-ext,jp,kr} - iso8859{,-}{{1..11},13,14,15} - iso-8859-{{1..11},{6,8}-{e,i},13,14,15,16} iso_8859-{{1..9},15} - iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989 - iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157} - koi{,8,8-r,8-ru,8-u,8_r} cskoi8r - ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987 - latin{1..6} l{{1..6},9} - shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932 - utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8 - windows-{31j,874,949,125{0..8}} dos-874 tis-620 ansi_x3.4-1968 - x-user-defined auto none +!ENCODINGS! ) _wanted encodings expl encoding compadd -a "$@" - _encodings diff --git a/crates/core/flags/complete/zsh.rs b/crates/core/flags/complete/zsh.rs index 59876f18..7475c0c1 100644 --- a/crates/core/flags/complete/zsh.rs +++ b/crates/core/flags/complete/zsh.rs @@ -19,5 +19,5 @@ long as it meets criteria 3 and 4 above. /// Generate completions for zsh. pub(crate) fn generate() -> String { - include_str!("rg.zsh").to_string() + include_str!("rg.zsh").replace("!ENCODINGS!", super::ENCODINGS.trim_end()) } diff --git a/crates/core/flags/defs.rs b/crates/core/flags/defs.rs index a0ace969..0abf0c92 100644 --- a/crates/core/flags/defs.rs +++ b/crates/core/flags/defs.rs @@ -34,6 +34,8 @@ use crate::flags::{ #[cfg(test)] use crate::flags::parse::parse_low_raw; +use super::CompletionType; + /// A list of all flags in ripgrep via implementations of `Flag`. /// /// The order of these flags matter. It determines the order of the flags in @@ -1582,6 +1584,9 @@ The encoding detection that ripgrep uses can be reverted to its automatic mode via the \flag-negate{encoding} flag. " } + fn completion_type(&self) -> CompletionType { + CompletionType::Encoding + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { let value = match v { @@ -1977,6 +1982,9 @@ When \flag{file} or \flag{regexp} is used, then ripgrep treats all positional arguments as files or directories to search. " } + fn completion_type(&self) -> CompletionType { + CompletionType::Filename + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { let path = PathBuf::from(v.unwrap_value()); @@ -2808,6 +2816,9 @@ to calling \fBgethostname\fP. On Windows, this corresponds to calling ripgrep uses your system's hostname for producing hyperlinks. "# } + fn completion_type(&self) -> CompletionType { + CompletionType::Executable + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { let path = PathBuf::from(v.unwrap_value()); @@ -3141,6 +3152,9 @@ If you are looking for a way to include or exclude files and directories directly on the command line, then use \flag{glob} instead. " } + fn completion_type(&self) -> CompletionType { + CompletionType::Filename + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { let path = PathBuf::from(v.unwrap_value()); @@ -5410,6 +5424,9 @@ format, then \fBpzstd\fP is used to decompress the contents to stdout. This overrides the \flag{search-zip} flag. "# } + fn completion_type(&self) -> CompletionType { + CompletionType::Executable + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { let path = match v { @@ -6781,6 +6798,9 @@ any rules found in ignore files. To see the list of available file types, use the \flag{type-list} flag. "# } + fn completion_type(&self) -> CompletionType { + CompletionType::Filetype + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { args.type_changes.push(TypeChange::Select { @@ -7000,6 +7020,9 @@ will only search files that are unrecognized by its type definitions. To see the list of available file types, use the \flag{type-list} flag. "# } + fn completion_type(&self) -> CompletionType { + CompletionType::Filetype + } fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> { args.type_changes.push(TypeChange::Negate { diff --git a/crates/core/flags/mod.rs b/crates/core/flags/mod.rs index e2b0ccb8..54aec572 100644 --- a/crates/core/flags/mod.rs +++ b/crates/core/flags/mod.rs @@ -70,7 +70,7 @@ mod parse; /// value. Flags that accept multiple values are an unsupported abberation. trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static { /// Returns true if this flag is a switch. When a flag is a switch, the - /// CLI parser will look for a value after the flag is seen. + /// CLI parser will not look for a value after the flag is seen. fn is_switch(&self) -> bool; /// A short single byte name for this flag. This returns `None` by default, @@ -150,6 +150,10 @@ trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static { &[] } + fn completion_type(&self) -> CompletionType { + CompletionType::Other + } + /// Given the parsed value (which might just be a switch), this should /// update the state in `args` based on the value given for this flag. /// @@ -228,6 +232,21 @@ impl Category { } } +/// The kind of argument a flag accepts, to be used for shell completions. +#[derive(Clone, Copy, Debug)] +enum CompletionType { + /// No special category. is_switch() and doc_choices() may apply. + Other, + /// A path to a file. + Filename, + /// A command in $PATH. + Executable, + /// The name of a file type, as used by e.g. --type. + Filetype, + /// The name of an encoding_rs encoding, as used by --encoding. + Encoding, +} + /// Represents a value parsed from the command line. /// /// This doesn't include the corresponding flag, but values come in one of -- cgit v1.2.3