From 787f292ca70547f3e15c80cdac5488a670858785 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:52:31 -0500 Subject: [PATCH] Custom completions: Inherit case_sensitive option from $env.config (#14738) # Description Currently, if a custom completer returns a record containing an `options` field, but these options don't specify `case_sensitive`, `case_sensitive` will be true. This PR instead makes the default value whatever the user specified in `$env.config.completions.case_sensitive`. The match algorithm option already does this. `positional` is also inherited from the global config, although user's can't actually specify that one themselves in `$env.config` (I'm planning on getting rid of `positional` in a separate PR). # User-Facing Changes For those making custom completions, if they need matching to be done case-sensitively and: - their completer returns a record rather than a list, - and the record contains an `options` field, - and the `options` field is a record, - and the record doesn't contain a `case_sensitive` option, then they will need to specify `case_sensitive: true` in their custom completer's options. Otherwise, if the user sets `$env.config.completions.case_sensitive = false`, their custom completer will also use case-insensitive matching. Others shouldn't have to make any changes. # Tests + Formatting Updated tests to check if `case_sensitive`. Basically rewrote them, actually. I figured it'd be better to make a single helper function that takes completer options and completion suggestions and generates a completer from that rather than having multiple fixtures providing different completers. # After Submitting Probably needs to be noted in the release notes, but I don't think the [docs](https://www.nushell.sh/book/custom_completions.html#options-for-custom-completions) need to be updated. --- .../src/completions/custom_completions.rs | 42 +++--- crates/nu-cli/tests/completions/mod.rs | 123 ++++++++++-------- 2 files changed, 89 insertions(+), 76 deletions(-) diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 4f19cd0eb0..2a9c621192 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -66,7 +66,7 @@ impl Completer for CustomCompletion { PipelineData::empty(), ); - let mut custom_completion_options = None; + let mut completion_options = completion_options.clone(); let mut should_sort = true; // Parse result @@ -89,25 +89,24 @@ impl Completer for CustomCompletion { should_sort = sort; } - custom_completion_options = Some(CompletionOptions { - case_sensitive: options - .get("case_sensitive") - .and_then(|val| val.as_bool().ok()) - .unwrap_or(true), - positional: options - .get("positional") - .and_then(|val| val.as_bool().ok()) - .unwrap_or(completion_options.positional), - match_algorithm: match options.get("completion_algorithm") { - Some(option) => option - .coerce_string() - .ok() - .and_then(|option| option.try_into().ok()) - .unwrap_or(completion_options.match_algorithm), - None => completion_options.match_algorithm, - }, - sort: completion_options.sort, - }); + if let Some(case_sensitive) = options + .get("case_sensitive") + .and_then(|val| val.as_bool().ok()) + { + completion_options.case_sensitive = case_sensitive; + } + if let Some(positional) = + options.get("positional").and_then(|val| val.as_bool().ok()) + { + completion_options.positional = positional; + } + if let Some(algorithm) = options + .get("completion_algorithm") + .and_then(|option| option.coerce_string().ok()) + .and_then(|option| option.try_into().ok()) + { + completion_options.match_algorithm = algorithm; + } } completions @@ -117,8 +116,7 @@ impl Completer for CustomCompletion { }) .unwrap_or_default(); - let options = custom_completion_options.unwrap_or(completion_options.clone()); - let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options); + let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), completion_options); if should_sort { for sugg in suggestions { diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 4ff1776d0f..a75ffe1d4c 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -62,50 +62,29 @@ fn extern_completer() -> NuCompleter { NuCompleter::new(Arc::new(engine), Arc::new(stack)) } -#[fixture] -fn completer_strings_with_options() -> NuCompleter { - // Create a new engine +fn custom_completer_with_options( + global_opts: &str, + completer_opts: &str, + completions: &[&str], +) -> NuCompleter { let (_, _, mut engine, mut stack) = new_engine(); - // Add record value as example - let record = r#" - # To test that the config setting has no effect on the custom completions - $env.config.completions.algorithm = "fuzzy" - def animals [] { - { - # Very rare and totally real animals - completions: ["Abcdef", "Foo Abcdef", "Acd Bar" ], - options: { - completion_algorithm: "prefix", - positional: false, - case_sensitive: false, - } - } - } - def my-command [animal: string@animals] { print $animal }"#; - assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack).is_ok()); - - // Instantiate a new completer - NuCompleter::new(Arc::new(engine), Arc::new(stack)) -} - -#[fixture] -fn completer_strings_no_sort() -> NuCompleter { - // Create a new engine - let (_, _, mut engine, mut stack) = new_engine(); - let command = r#" - def animals [] { - { - completions: ["zzzfoo", "foo", "not matched", "abcfoo" ], - options: { - completion_algorithm: "fuzzy", - sort: false, - } - } - } - def my-command [animal: string@animals] { print $animal }"#; + let command = format!( + r#" + {} + def comp [] {{ + {{ completions: [{}], options: {{ {} }} }} + }} + def my-command [arg: string@comp] {{}}"#, + global_opts, + completions + .iter() + .map(|comp| format!("'{}'", comp)) + .collect::>() + .join(", "), + completer_opts, + ); assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); - // Instantiate a new completer NuCompleter::new(Arc::new(engine), Arc::new(stack)) } @@ -217,23 +196,59 @@ fn variables_customcompletion_subcommands_with_customcompletion_2( match_suggestions(&expected, &suggestions); } -#[rstest] -fn customcompletions_substring_matching(mut completer_strings_with_options: NuCompleter) { - let suggestions = completer_strings_with_options.complete("my-command Abcd", 15); +/// $env.config should be overridden by the custom completer's options +#[test] +fn customcompletions_override_options() { + let mut completer = custom_completer_with_options( + r#"$env.config.completions.algorithm = "fuzzy" + $env.config.completions.case_sensitive = false"#, + r#"completion_algorithm: "prefix", + positional: false, + case_sensitive: true, + sort: true"#, + &["Foo Abcdef", "Abcdef", "Acd Bar"], + ); + + // positional: false should make it do substring matching + // sort: true should force sorting let expected: Vec = vec!["Abcdef".into(), "Foo Abcdef".into()]; + let suggestions = completer.complete("my-command Abcd", 15); + match_suggestions(&expected, &suggestions); + + // Custom options should make case-sensitive + let suggestions = completer.complete("my-command aBcD", 15); + assert!(suggestions.is_empty()); +} + +/// $env.config should be inherited by the custom completer's options +#[test] +fn customcompletions_inherit_options() { + let mut completer = custom_completer_with_options( + r#"$env.config.completions.algorithm = "fuzzy" + $env.config.completions.case_sensitive = false"#, + "", + &["Foo Abcdef", "Abcdef", "Acd Bar"], + ); + + // Make sure matching is fuzzy + let suggestions = completer.complete("my-command Acd", 14); + let expected: Vec = vec!["Acd Bar".into(), "Abcdef".into(), "Foo Abcdef".into()]; + match_suggestions(&expected, &suggestions); + + // Custom options should make matching case insensitive + let suggestions = completer.complete("my-command acd", 14); match_suggestions(&expected, &suggestions); } -#[rstest] -fn customcompletions_case_insensitive(mut completer_strings_with_options: NuCompleter) { - let suggestions = completer_strings_with_options.complete("my-command foo", 14); - let expected: Vec = vec!["Foo Abcdef".into()]; - match_suggestions(&expected, &suggestions); -} - -#[rstest] -fn customcompletions_no_sort(mut completer_strings_no_sort: NuCompleter) { - let suggestions = completer_strings_no_sort.complete("my-command foo", 14); +#[test] +fn customcompletions_no_sort() { + let mut completer = custom_completer_with_options( + "", + r#"completion_algorithm: "fuzzy", + sort: false"#, + &["zzzfoo", "foo", "not matched", "abcfoo"], + ); + let suggestions = completer.complete("my-command foo", 14); let expected: Vec = vec!["zzzfoo".into(), "foo".into(), "abcfoo".into()]; match_suggestions(&expected, &suggestions); }