Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

complete/fish: Make fish completion more intelligent #2684

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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