From 0088a4db473194c0b6632da561167bbef2ebe57a Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdx@users.noreply.github.com> Date: Sat, 10 Feb 2024 02:58:57 -0600 Subject: [PATCH] wip --- cli/src/cli/complete_word.rs | 66 ++++++++++++++++++++++++++++-------- cli/tests/complete_word.rs | 10 ++++++ examples/basic.usage.kdl | 1 + src/parse/cmd.rs | 17 ++++++++++ 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/cli/src/cli/complete_word.rs b/cli/src/cli/complete_word.rs index cf7bf77..5c71ab6 100644 --- a/cli/src/cli/complete_word.rs +++ b/cli/src/cli/complete_word.rs @@ -38,6 +38,15 @@ pub struct CompleteWord { impl CompleteWord { pub fn run(&self) -> miette::Result<()> { let spec = generate::file_or_spec(&self.file, &self.spec)?; + let choices = self.complete_word(&spec)?; + for c in &choices { + println!("{}", c); + } + + Ok(()) + } + + fn complete_word(&self, spec: &Spec) -> miette::Result> { let cword = self.cword.unwrap_or(self.words.len().max(1) - 1); let ctoken = self .ctoken @@ -45,7 +54,6 @@ impl CompleteWord { .or(self.words.get(cword)) .cloned() .unwrap_or_default(); - let words: VecDeque<_> = self.words.iter().take(cword).cloned().collect(); trace!( @@ -53,19 +61,23 @@ impl CompleteWord { words.iter().join(" ") ); - let parsed = parse(&spec, words)?; - - let mut choices = vec![]; - choices.extend(complete_subcommands(parsed.cmd, &ctoken)); - if let Some(arg) = parsed.cmd.args.get(parsed.args.len()) { - choices.extend(complete_arg(&spec, arg, &ctoken)?); - } - - for c in &choices { - println!("{}", c); - } - - Ok(()) + let parsed = parse(spec, words)?; + let choices = if !parsed.cmd.subcommands.is_empty() { + complete_subcommands(parsed.cmd, &ctoken) + } else if ctoken == "-" { + let shorts = complete_short_flag_names(parsed.cmd, ""); + let longs = complete_long_flag_names(parsed.cmd, ""); + shorts.into_iter().chain(longs).collect() + } else if ctoken.starts_with("--") { + complete_long_flag_names(parsed.cmd, &ctoken) + } else if ctoken.starts_with('-') { + complete_short_flag_names(parsed.cmd, &ctoken) + } else if let Some(arg) = parsed.cmd.args.get(parsed.args.len()) { + complete_arg(spec, arg, &ctoken)? + } else { + vec![] + }; + Ok(choices) } } @@ -141,7 +153,6 @@ fn parse(spec: &Spec, mut words: VecDeque) -> miette::Result) -> miette::Result Vec { + trace!("complete_subcommands: {ctoken}"); let mut choices = vec![]; for subcommand in cmd.subcommands.values() { if subcommand.hide { @@ -169,7 +181,30 @@ fn complete_subcommands(cmd: &SpecCommand, ctoken: &str) -> Vec { .collect() } +fn complete_long_flag_names(cmd: &SpecCommand, ctoken: &str) -> Vec { + trace!("complete_long_flag_names: {ctoken}"); + let ctoken = ctoken.strip_prefix("--").unwrap_or(ctoken); + cmd.list_visible_long_flags() + .into_iter() + .filter(|c| c.starts_with(ctoken)) + .map(|c| format!("--{}", c)) + .sorted() + .collect() +} + +fn complete_short_flag_names(cmd: &SpecCommand, ctoken: &str) -> Vec { + trace!("complete_short_flag_names: {ctoken}"); + let cur = ctoken.chars().nth(1); + cmd.list_visible_short_flags() + .into_iter() + .filter(|c| cur.is_none() || cur == Some(**c)) + .map(|c| format!("-{}", c)) + .sorted() + .collect() +} + fn complete_arg(spec: &Spec, arg: &SpecArg, ctoken: &str) -> miette::Result> { + trace!("complete_arg: {} {ctoken}", &arg.name); let name = arg.name.to_lowercase(); if let Ok(cwd) = env::current_dir() { @@ -198,6 +233,7 @@ fn complete_path( ctoken: &str, filter: impl Fn(&Path) -> bool, ) -> miette::Result> { + trace!("complete_path: {ctoken}"); let path = PathBuf::from(ctoken); let mut dir = path.parent().unwrap_or(&path).to_path_buf(); if dir.is_relative() { diff --git a/cli/tests/complete_word.rs b/cli/tests/complete_word.rs index 8565bbf..1a9732c 100644 --- a/cli/tests/complete_word.rs +++ b/cli/tests/complete_word.rs @@ -21,12 +21,22 @@ fn complete_word_cword() { #[test] fn complete_word_long_flag() { + assert_cmd(&["--", "plugins", "install", "--"]).stdout("--dir\n--global\n"); + assert_cmd(&["--", "plugins", "install", "--g"]).stdout("--global\n"); assert_cmd(&["--", "plugins", "install", "--global", "pl"]) .stdout(predicate::str::contains("plugin-2")); } +// #[test] +// fn complete_word_long_flag_val() { +// assert_cmd(&["--", "plugins", "install", "--dir", "mydir"]) +// .stdout(predicate::str::contains("plugin-2")); +// } + #[test] fn complete_word_short_flag() { + assert_cmd(&["--", "plugins", "install", "-"]).stdout("-d\n-g\n--dir\n--global\n"); + assert_cmd(&["--", "plugins", "install", "-g"]).stdout("-g\n"); assert_cmd(&["--", "plugins", "install", "-g", "pl"]) .stdout(predicate::str::contains("plugin-2")); } diff --git a/examples/basic.usage.kdl b/examples/basic.usage.kdl index 5c2b7a9..38a1301 100644 --- a/examples/basic.usage.kdl +++ b/examples/basic.usage.kdl @@ -3,6 +3,7 @@ cmd "plugins" { arg "plugin" arg "version" flag "-g --global" + flag "-d --dir " } } diff --git a/src/parse/cmd.rs b/src/parse/cmd.rs index 9b6a226..90f7fa6 100644 --- a/src/parse/cmd.rs +++ b/src/parse/cmd.rs @@ -207,6 +207,23 @@ impl SpecCommand { self.subcommands.insert(name, cmd); } } + + pub fn list_visible_long_flags(&self) -> Vec { + self.flags + .iter() + .filter(|f| !f.hide) + .flat_map(|f| f.long.iter()) + .map(|s| s.to_string()) + .collect() + } + + pub fn list_visible_short_flags(&self) -> Vec<&char> { + self.flags + .iter() + .filter(|f| !f.hide) + .flat_map(|f| f.short.iter()) + .collect() + } } impl From<&SpecCommand> for KdlNode {