Skip to content

Commit

Permalink
complete/fish: Make fish completion more intelligent
Browse files Browse the repository at this point in the history
- 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<tab>` won't suggest options but `-i<tab>`
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 BurntSushi#2684
  • Loading branch information
blyxxyz authored and BurntSushi committed Jan 6, 2024
1 parent 56c7ad1 commit af4ed71
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 52 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
29 changes: 29 additions & 0 deletions crates/core/flags/complete/encodings.sh
Original file line number Diff line number Diff line change
@@ -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
70 changes: 45 additions & 25 deletions crates/core/flags/complete/fish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,63 @@
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() {
let short = match flag.name_short() {
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),
);
Expand Down
2 changes: 2 additions & 0 deletions crates/core/flags/complete/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 1 addition & 25 deletions crates/core/flags/complete/rg.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/core/flags/complete/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
23 changes: 23 additions & 0 deletions crates/core/flags/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 20 additions & 1 deletion crates/core/flags/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit af4ed71

Please sign in to comment.