pub mod support; use std::{ fs::{read_dir, FileType, ReadDir}, path::{PathBuf, MAIN_SEPARATOR}, sync::Arc, }; use nu_cli::NuCompleter; use nu_engine::eval_block; use nu_parser::parse; use nu_path::expand_tilde; use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData}; use nu_std::load_standard_library; use reedline::{Completer, Suggestion}; use rstest::{fixture, rstest}; use support::{ completions_helpers::{ new_dotnu_engine, new_external_engine, new_partial_engine, new_quote_engine, }, file, folder, match_suggestions, match_suggestions_by_string, new_engine, }; // Match a list of suggestions with the content of a directory. // This helper is for DotNutCompletion, so actually it only retrieves // *.nu files and subdirectories. pub fn match_dir_content_for_dotnu(dir: ReadDir, suggestions: &[Suggestion]) { let actual_dir_entries: Vec<_> = dir.filter_map(|c| c.ok()).collect(); let type_name_pairs: Vec<(FileType, String)> = actual_dir_entries .into_iter() .filter_map(|t| t.file_type().ok().zip(t.file_name().into_string().ok())) .collect(); let mut simple_dir_entries: Vec<&str> = type_name_pairs .iter() .filter_map(|(t, n)| { if t.is_dir() || n.ends_with(".nu") { Some(n.as_str()) } else { None } }) .collect(); simple_dir_entries.sort(); let mut pure_suggestions: Vec<&str> = suggestions .iter() .map(|s| { // The file names in suggestions contain some extra characters, // we clean them to compare more exactly with read_dir result. s.value .as_str() .trim_end_matches('`') .trim_end_matches('/') .trim_end_matches('\\') .trim_start_matches('`') .trim_start_matches("~/") .trim_start_matches("~\\") }) .collect(); pure_suggestions.sort(); assert_eq!(simple_dir_entries, pure_suggestions); } #[fixture] fn completer() -> NuCompleter { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); // Add record value as example let record = "def tst [--mod -s] {}"; 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() -> NuCompleter { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); // Add record value as example let record = r#"def animals [] { ["cat", "dog", "eel" ] } 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 extern_completer() -> NuCompleter { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); // Add record value as example let record = r#" def animals [] { [ "cat", "dog", "eel" ] } extern spam [ animal: string@animals --foo (-f): string@animals -b: string@animals ] "#; 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)) } fn custom_completer_with_options( global_opts: &str, completer_opts: &str, completions: &[&str], ) -> NuCompleter { let (_, _, mut engine, mut stack) = new_engine(); 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()); NuCompleter::new(Arc::new(engine), Arc::new(stack)) } #[fixture] fn custom_completer() -> NuCompleter { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); // Add record value as example let record = r#" let external_completer = {|spans| $spans } $env.config.completions.external = { enable: true max_results: 100 completer: $external_completer } "#; 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)) } /// Use fuzzy completions but sort in alphabetical order #[fixture] fn fuzzy_alpha_sort_completer() -> NuCompleter { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); let config = r#" $env.config.completions.algorithm = "fuzzy" $env.config.completions.sort = "alphabetical" "#; assert!(support::merge_input(config.as_bytes(), &mut engine, &mut stack).is_ok()); // Instantiate a new completer NuCompleter::new(Arc::new(engine), Arc::new(stack)) } #[test] fn variables_dollar_sign_with_variablecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "$ "; let suggestions = completer.complete(target_dir, target_dir.len()); assert_eq!(9, suggestions.len()); } #[rstest] fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) { let suggestions = completer.complete("tst --", 6); let expected: Vec<_> = vec!["--help", "--mod"]; // dbg!(&expected, &suggestions); match_suggestions(&expected, &suggestions); } #[rstest] fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) { let suggestions = completer.complete("tst -", 5); let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; match_suggestions(&expected, &suggestions); } #[rstest] fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) { let suggestions = completer_strings.complete("my-c ", 4); let expected: Vec<_> = vec!["my-command"]; match_suggestions(&expected, &suggestions); } #[rstest] fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) { let suggestions = completer_strings.complete("my-command ", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn variables_customcompletion_subcommands_with_customcompletion_2( mut completer_strings: NuCompleter, ) { let suggestions = completer_strings.complete("my-command ", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } /// $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", "Foo Abcdef"]; 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", "Abcdef", "Foo Abcdef"]; match_suggestions(&expected, &suggestions); // Custom options should make matching case insensitive let suggestions = completer.complete("my-command acd", 14); match_suggestions(&expected, &suggestions); } #[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", "foo", "abcfoo"]; match_suggestions(&expected, &suggestions); } /// Fallback to file completions if custom completer returns null #[test] fn customcompletions_fallback() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#" def comp [] { null } def my-command [arg: string@comp] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "my-command test"; let suggestions = completer.complete(completion_str, completion_str.len()); let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")]; match_suggestions_by_string(&expected, &suggestions); } /// Custom function arguments mixed with subcommands #[test] fn custom_arguments_and_subcommands() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#" def foo [i: directory] {} def "foo test bar" [] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "foo test"; let suggestions = completer.complete(completion_str, completion_str.len()); // including both subcommand and directory completions let expected = ["foo test bar".into(), folder("test_a"), folder("test_b")]; match_suggestions_by_string(&expected, &suggestions); } /// Custom function flags mixed with subcommands #[test] fn custom_flags_and_subcommands() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#" def foo [--test: directory] {} def "foo --test bar" [] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "foo --test"; let suggestions = completer.complete(completion_str, completion_str.len()); // including both flag and directory completions let expected: Vec<_> = vec!["foo --test bar", "--test"]; match_suggestions(&expected, &suggestions); } /// If argument type is something like int/string, complete only subcommands #[test] fn custom_arguments_vs_subcommands() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#" def foo [i: string] {} def "foo test bar" [] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "foo test"; let suggestions = completer.complete(completion_str, completion_str.len()); // including only subcommand completions let expected: Vec<_> = vec!["foo test bar"]; match_suggestions(&expected, &suggestions); } /// External command only if starts with `^` #[test] fn external_commands_only() { let engine = new_external_engine(); let mut completer = NuCompleter::new( Arc::new(engine), Arc::new(nu_protocol::engine::Stack::new()), ); let completion_str = "ls; ^sleep"; let suggestions = completer.complete(completion_str, completion_str.len()); #[cfg(windows)] let expected: Vec<_> = vec!["sleep.exe"]; #[cfg(not(windows))] let expected: Vec<_> = vec!["sleep"]; match_suggestions(&expected, &suggestions); let completion_str = "sleep"; let suggestions = completer.complete(completion_str, completion_str.len()); #[cfg(windows)] let expected: Vec<_> = vec!["sleep", "sleep.exe"]; #[cfg(not(windows))] let expected: Vec<_> = vec!["sleep", "^sleep"]; match_suggestions(&expected, &suggestions); } /// Which completes both internals and externals #[test] fn which_command_completions() { let engine = new_external_engine(); let mut completer = NuCompleter::new( Arc::new(engine), Arc::new(nu_protocol::engine::Stack::new()), ); // flags let completion_str = "which --all"; let suggestions = completer.complete(completion_str, completion_str.len()); let expected: Vec<_> = vec!["--all"]; match_suggestions(&expected, &suggestions); // commands let completion_str = "which sleep"; let suggestions = completer.complete(completion_str, completion_str.len()); #[cfg(windows)] let expected: Vec<_> = vec!["sleep", "sleep.exe"]; #[cfg(not(windows))] let expected: Vec<_> = vec!["sleep", "^sleep"]; match_suggestions(&expected, &suggestions); } /// Suppress completions for invalid values #[test] fn customcompletions_invalid() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#" def comp [] { 123 } def my-command [arg: string@comp] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "my-command foo"; let suggestions = completer.complete(completion_str, completion_str.len()); assert!(suggestions.is_empty()); } #[test] fn dont_use_dotnu_completions() { // Create a new engine let (_, _, engine, stack) = new_dotnu_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test nested nu script let completion_str = "go work use `./dir_module/"; let suggestions = completer.complete(completion_str, completion_str.len()); // including a plaintext file let expected: Vec<_> = vec![ "./dir_module/mod.nu", "./dir_module/plain.txt", "`./dir_module/sub module/`", ]; match_suggestions(&expected, &suggestions); } #[test] fn dotnu_completions() { // Create a new engine let (_, _, engine, stack) = new_dotnu_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Flags should still be working let completion_str = "overlay use --"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["--help", "--prefix", "--reload"], &suggestions); // Test nested nu script #[cfg(windows)] let completion_str = "use `.\\dir_module\\"; #[cfg(not(windows))] let completion_str = "use `./dir_module/"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions( &vec![ "mod.nu", #[cfg(windows)] "sub module\\`", #[cfg(not(windows))] "sub module/`", ], &suggestions, ); // Test nested nu script, with ending '`' #[cfg(windows)] let completion_str = "use `.\\dir_module\\sub module\\`"; #[cfg(not(windows))] let completion_str = "use `./dir_module/sub module/`"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["sub.nu`"], &suggestions); let mut expected = vec![ "asdf.nu", "bar.nu", "bat.nu", "baz.nu", #[cfg(windows)] "dir_module\\", #[cfg(not(windows))] "dir_module/", "foo.nu", #[cfg(windows)] "lib-dir1\\", #[cfg(not(windows))] "lib-dir1/", #[cfg(windows)] "lib-dir2\\", #[cfg(not(windows))] "lib-dir2/", #[cfg(windows)] "lib-dir3\\", #[cfg(not(windows))] "lib-dir3/", "spam.nu", "xyzzy.nu", ]; // Test source completion let completion_str = "source-env "; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); // Test use completion expected.push("std"); expected.push("std-rfc"); let completion_str = "use "; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); // Test overlay use completion let completion_str = "overlay use "; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); // Test special paths #[cfg(windows)] { let completion_str = "use \\"; let dir_content = read_dir("\\").unwrap(); let suggestions = completer.complete(completion_str, completion_str.len()); match_dir_content_for_dotnu(dir_content, &suggestions); } let completion_str = "use /"; let suggestions = completer.complete(completion_str, completion_str.len()); let dir_content = read_dir("/").unwrap(); match_dir_content_for_dotnu(dir_content, &suggestions); let completion_str = "use ~"; let dir_content = read_dir(expand_tilde("~")).unwrap(); let suggestions = completer.complete(completion_str, completion_str.len()); match_dir_content_for_dotnu(dir_content, &suggestions); } #[test] fn dotnu_stdlib_completions() { let (_, _, mut engine, stack) = new_dotnu_engine(); assert!(load_standard_library(&mut engine).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "export use std/ass"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["assert"], &suggestions); let completion_str = "use `std-rfc/cli"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["clip"], &suggestions); let completion_str = "use \"std"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["\"std", "\"std-rfc"], &suggestions); let completion_str = "overlay use \'std-rfc/cli"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["clip"], &suggestions); } #[test] fn exportable_completions() { let (_, _, mut engine, mut stack) = new_dotnu_engine(); let code = r#"export module "🤔🐘" { export const foo = "🤔🐘"; }"#; assert!(support::merge_input(code.as_bytes(), &mut engine, &mut stack).is_ok()); assert!(load_standard_library(&mut engine).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "use std null"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["null-device", "null_device"], &suggestions); let completion_str = "export use std/assert eq"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["equal"], &suggestions); let completion_str = "use std/assert \"not eq"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["'not equal'"], &suggestions); let completion_str = "use std-rfc/clip ['prefi"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["prefix"], &suggestions); let completion_str = "use std/math [E, `TAU"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["TAU"], &suggestions); let completion_str = "use 🤔🐘 'foo"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["foo"], &suggestions); } #[test] fn dotnu_completions_const_nu_lib_dirs() { let (_, _, engine, stack) = new_dotnu_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // file in `lib-dir1/`, set by `const NU_LIB_DIRS` let completion_str = "use xyzz"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["xyzzy.nu"], &suggestions); // file in `lib-dir2/`, set by `$env.NU_LIB_DIRS` let completion_str = "use asdf"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["asdf.nu"], &suggestions); // file in `lib-dir3/`, set by both, should not replicate let completion_str = "use spam"; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&vec!["spam.nu"], &suggestions); // if `./` specified by user, file in `lib-dir*` should be ignored #[cfg(windows)] { let completion_str = "use .\\asdf"; let suggestions = completer.complete(completion_str, completion_str.len()); assert!(suggestions.is_empty()); } let completion_str = "use ./asdf"; let suggestions = completer.complete(completion_str, completion_str.len()); assert!(suggestions.is_empty()); } #[test] #[ignore] fn external_completer_trailing_space() { // https://github.com/nushell/nushell/issues/6378 let block = "{|spans| $spans}"; let input = "gh alias "; let suggestions = run_external_completion(block, input); assert_eq!(3, suggestions.len()); assert_eq!("gh", suggestions.first().unwrap().value); assert_eq!("alias", suggestions.get(1).unwrap().value); assert_eq!("", suggestions.get(2).unwrap().value); } #[test] fn external_completer_no_trailing_space() { let block = "{|spans| $spans}"; let input = "gh alias"; let suggestions = run_external_completion(block, input); assert_eq!(2, suggestions.len()); assert_eq!("gh", suggestions.first().unwrap().value); assert_eq!("alias", suggestions.get(1).unwrap().value); } #[test] fn external_completer_pass_flags() { let block = "{|spans| $spans}"; let input = "gh api --"; let suggestions = run_external_completion(block, input); assert_eq!(3, suggestions.len()); assert_eq!("gh", suggestions.first().unwrap().value); assert_eq!("api", suggestions.get(1).unwrap().value); assert_eq!("--", suggestions.get(2).unwrap().value); } /// Fallback to file completions when external completer returns null #[test] fn external_completer_fallback() { let block = "{|spans| null}"; let input = "foo test"; let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")]; let suggestions = run_external_completion(block, input); match_suggestions_by_string(&expected, &suggestions); } /// Fallback to external completions for flags of `sudo` #[test] fn external_completer_sudo() { let block = "{|spans| ['--background']}"; let input = "sudo --back"; let expected = vec!["--background"]; let suggestions = run_external_completion(block, input); match_suggestions(&expected, &suggestions); } /// Suppress completions when external completer returns invalid value #[test] fn external_completer_invalid() { let block = "{|spans| 123}"; let input = "foo "; let suggestions = run_external_completion(block, input); assert!(suggestions.is_empty()); } #[test] fn file_completions() { // Create a new engine let (dir, dir_str, engine, stack) = new_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("cp {dir_str}{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("another")), file(dir.join("custom_completion.nu")), folder(dir.join("directory_completion")), file(dir.join("nushell")), folder(dir.join("test_a")), file(dir.join("test_a_symlink")), folder(dir.join("test_b")), file(dir.join(".hidden_file")), folder(dir.join(".hidden_folder")), ]; #[cfg(windows)] { let separator = '/'; let target_dir = format!("cp {dir_str}{separator}"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for the current folder even with parts before the autocomplet let target_dir = format!("cp somefile.txt {dir_str}{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("another")), file(dir.join("custom_completion.nu")), folder(dir.join("directory_completion")), file(dir.join("nushell")), folder(dir.join("test_a")), file(dir.join("test_a_symlink")), folder(dir.join("test_b")), file(dir.join(".hidden_file")), folder(dir.join(".hidden_folder")), ]; #[cfg(windows)] { let separator = '/'; let target_dir = format!("cp somefile.txt {dir_str}{separator}"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for a file let target_dir = format!("cp {}", folder(dir.join("another"))); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [file(dir.join("another").join("newfile"))]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for hidden files let target_dir = format!("ls {}", file(dir.join(".hidden_folder").join("."))); let suggestions = completer.complete(&target_dir, target_dir.len()); let expected_paths = [file(dir.join(".hidden_folder").join(".hidden_subfile"))]; #[cfg(windows)] { let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder"))); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn custom_command_rest_any_args_file_completions() { // Create a new engine let (dir, dir_str, mut engine, mut stack) = new_engine(); let command = r#"def list [ ...args: any ] {}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("list {dir_str}{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("another")), file(dir.join("custom_completion.nu")), folder(dir.join("directory_completion")), file(dir.join("nushell")), folder(dir.join("test_a")), file(dir.join("test_a_symlink")), folder(dir.join("test_b")), file(dir.join(".hidden_file")), folder(dir.join(".hidden_folder")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for the current folder even with parts before the autocomplet let target_dir = format!("list somefile.txt {dir_str}{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("another")), file(dir.join("custom_completion.nu")), folder(dir.join("directory_completion")), file(dir.join("nushell")), folder(dir.join("test_a")), file(dir.join("test_a_symlink")), folder(dir.join("test_b")), file(dir.join(".hidden_file")), folder(dir.join(".hidden_folder")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for a file let target_dir = format!("list {}", folder(dir.join("another"))); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [file(dir.join("another").join("newfile"))]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for hidden files let target_dir = format!("list {}", file(dir.join(".hidden_folder").join("."))); let suggestions = completer.complete(&target_dir, target_dir.len()); let expected_paths = [file(dir.join(".hidden_folder").join(".hidden_subfile"))]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[cfg(windows)] #[test] fn file_completions_with_mixed_separators() { // Create a new engine let (dir, dir_str, engine, stack) = new_dotnu_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Create Expected values let expected_paths: Vec<_> = vec![ file(dir.join("lib-dir1").join("bar.nu")), file(dir.join("lib-dir1").join("baz.nu")), file(dir.join("lib-dir1").join("xyzzy.nu")), ]; let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace(MAIN_SEPARATOR, "/")) .collect(); let target_dir = format!("ls {dir_str}/lib-dir1/"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_slash_paths, &suggestions); let target_dir = format!("cp {dir_str}\\lib-dir1/"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}/lib-dir1\\/"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}\\lib-dir1\\/"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}\\lib-dir1\\"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_paths, &suggestions); let target_dir = format!("ls {dir_str}/lib-dir1\\"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_paths, &suggestions); let target_dir = format!("ls {dir_str}/lib-dir1/\\"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_paths, &suggestions); let target_dir = format!("ls {dir_str}\\lib-dir1/\\"); let suggestions = completer.complete(&target_dir, target_dir.len()); match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn partial_completions() { // Create a new engine let (dir, _, engine, stack) = new_partial_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for a folder's name let target_dir = format!("cd {}", file(dir.join("pa"))); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("partial")), folder(dir.join("partial-a")), folder(dir.join("partial-b")), folder(dir.join("partial-c")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completions for the files whose name begin with "h" // and are present under directories whose names begin with "pa" let dir_str = file(dir.join("pa").join("h")); let target_dir = format!("cp {dir_str}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ file(dir.join("partial").join("hello.txt")), file(dir.join("partial-a").join("have_ext.exe")), file(dir.join("partial-a").join("have_ext.txt")), file(dir.join("partial-a").join("hello")), file(dir.join("partial-a").join("hola")), file(dir.join("partial-b").join("hello_b")), file(dir.join("partial-b").join("hi_b")), file(dir.join("partial-c").join("hello_c")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completion for all files under directories whose names begin with "pa" let dir_str = folder(dir.join("pa")); let target_dir = format!("ls {dir_str}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ file(dir.join("partial").join("hello.txt")), file(dir.join("partial-a").join("anotherfile")), file(dir.join("partial-a").join("have_ext.exe")), file(dir.join("partial-a").join("have_ext.txt")), file(dir.join("partial-a").join("hello")), file(dir.join("partial-a").join("hola")), file(dir.join("partial-b").join("hello_b")), file(dir.join("partial-b").join("hi_b")), file(dir.join("partial-c").join("hello_c")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completion for a single file let dir_str = file(dir.join("fi").join("so")); let target_dir = format!("rm {dir_str}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [file(dir.join("final_partial").join("somefile"))]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completion where there is a sneaky `..` in the path let dir_str = file(dir.join("par").join("..").join("fi").join("so")); let target_dir = format!("rm {dir_str}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ file( dir.join("partial") .join("..") .join("final_partial") .join("somefile"), ), file( dir.join("partial-a") .join("..") .join("final_partial") .join("somefile"), ), file( dir.join("partial-b") .join("..") .join("final_partial") .join("somefile"), ), file( dir.join("partial-c") .join("..") .join("final_partial") .join("somefile"), ), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completion for all files under directories whose names begin with "pa" let file_str = file(dir.join("partial-a").join("have")); let target_file = format!("rm {file_str}"); let suggestions = completer.complete(&target_file, target_file.len()); // Create the expected values let expected_paths = [ file(dir.join("partial-a").join("have_ext.exe")), file(dir.join("partial-a").join("have_ext.txt")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); // Test completion for all files under directories whose names begin with "pa" let file_str = file(dir.join("partial-a").join("have_ext.")); let file_dir = format!("rm {file_str}"); let suggestions = completer.complete(&file_dir, file_dir.len()); // Create the expected values let expected_paths = [ file(dir.join("partial-a").join("have_ext.exe")), file(dir.join("partial-a").join("have_ext.txt")), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn partial_completion_with_dot_expansions() { let (dir, _, engine, stack) = new_partial_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let dir_str = file( dir.join("par") .join("...") .join("par") .join("fi") .join("so"), ); let target_dir = format!("rm {dir_str}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ file( dir.join("partial") .join("...") .join("partial_completions") .join("final_partial") .join("somefile"), ), file( dir.join("partial-a") .join("...") .join("partial_completions") .join("final_partial") .join("somefile"), ), file( dir.join("partial-b") .join("...") .join("partial_completions") .join("final_partial") .join("somefile"), ), file( dir.join("partial-c") .join("...") .join("partial_completions") .join("final_partial") .join("somefile"), ), ]; // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn command_ls_with_filecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "ls "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions); let target_dir = "ls custom_completion."; let suggestions = completer.complete(target_dir, target_dir.len()); let expected_paths: Vec<_> = vec!["custom_completion.nu"]; match_suggestions(&expected_paths, &suggestions); } #[test] fn command_open_with_filecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "open "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions); let target_dir = "open custom_completion."; let suggestions = completer.complete(target_dir, target_dir.len()); let expected_paths: Vec<_> = vec!["custom_completion.nu"]; match_suggestions(&expected_paths, &suggestions); } #[test] fn command_rm_with_globcompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "rm "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[test] fn command_cp_with_globcompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "cp "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[test] fn command_save_with_filecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "save "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[test] fn command_touch_with_filecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "touch "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[test] fn command_watch_with_filecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "watch "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[rstest] fn subcommand_completions() { let (_, _, mut engine, mut stack) = new_engine(); let commands = r#" $env.config.completions.algorithm = "fuzzy" def foo-test-command [] {} def "foo-test-command bar" [] {} def "foo-test-command aagap bcr" [] {} def "food bar" [] {} "#; assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack).is_ok()); let mut subcommand_completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let prefix = "fod br"; let suggestions = subcommand_completer.complete(prefix, prefix.len()); match_suggestions( &vec![ "food bar", "foo-test-command bar", "foo-test-command aagap bcr", ], &suggestions, ); let prefix = "foot bar"; let suggestions = subcommand_completer.complete(prefix, prefix.len()); match_suggestions(&vec!["foo-test-command bar"], &suggestions); } #[test] fn file_completion_quoted() { let (_, _, engine, stack) = new_quote_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "open "; let suggestions = completer.complete(target_dir, target_dir.len()); let test_dir_folder = format!("`{}`", folder("test dir")); let expected_paths: Vec<_> = vec![ "`--help`", "`-42`", "`-inf`", "`4.2`", "\'[a] bc.txt\'", "`te st.txt`", "`te#st.txt`", "`te'st.txt`", "`te(st).txt`", test_dir_folder.as_str(), ]; match_suggestions(&expected_paths, &suggestions); let dir: PathBuf = "test dir".into(); let target_dir = format!("open '{}'", folder(dir.clone())); let suggestions = completer.complete(&target_dir, target_dir.len()); let expected_paths = [ format!("`{}`", file(dir.join("double quote"))), format!("`{}`", file(dir.join("single quote"))), ]; match_suggestions_by_string(&expected_paths, &suggestions) } #[test] fn flag_completions() { // Create a new engine let (_, _, engine, stack) = new_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the 'ls' flags let suggestions = completer.complete("ls -", 4); assert_eq!(18, suggestions.len()); let expected: Vec<_> = vec![ "--all", "--directory", "--du", "--full-paths", "--help", "--long", "--mime-type", "--short-names", "--threads", "-a", "-D", "-d", "-f", "-h", "-l", "-m", "-s", "-t", ]; // Match results match_suggestions(&expected, &suggestions); } #[test] fn attribute_completions() { // Create a new engine let (_, _, engine, stack) = new_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the 'ls' flags let suggestions = completer.complete("@", 1); // Only checking for the builtins and not the std attributes let expected: Vec<_> = vec!["category", "example", "search-terms"]; // Match results match_suggestions(&expected, &suggestions); } #[test] fn attributable_completions() { // Create a new engine let (_, _, engine, stack) = new_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the 'ls' flags let suggestions = completer.complete("@example; ", 10); let expected: Vec<_> = vec!["def", "export def", "export extern", "extern"]; // Match results match_suggestions(&expected, &suggestions); } #[test] fn folder_with_directorycompletions() { // Create a new engine let (dir, dir_str, engine, stack) = new_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder(dir.join("another")), folder(dir.join("directory_completion")), folder(dir.join("test_a")), folder(dir.join("test_b")), folder(dir.join(".hidden_folder")), ]; #[cfg(windows)] { let target_dir = format!("cd {dir_str}/"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn folder_with_directorycompletions_with_dots() { // Create a new engine let (dir, _, engine, stack) = new_engine(); let dir_str = dir .join("directory_completion") .join("folder_inside_folder") .into_os_string() .into_string() .unwrap(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}..{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("folder_inside_folder"), )]; #[cfg(windows)] { let target_dir = format!("cd {dir_str}/../"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn folder_with_directorycompletions_with_three_trailing_dots() { // Create a new engine let (dir, _, engine, stack) = new_engine(); let dir_str = dir .join("directory_completion") .join("folder_inside_folder") .into_os_string() .into_string() .unwrap(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}...{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths = [ folder( dir.join("directory_completion") .join("folder_inside_folder") .join("...") .join("another"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("...") .join("directory_completion"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("...") .join("test_a"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("...") .join("test_b"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("...") .join(".hidden_folder"), ), ]; #[cfg(windows)] { let target_dir = format!("cd {dir_str}/.../"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn folder_with_directorycompletions_do_not_collapse_dots() { // Create a new engine let (dir, _, engine, stack) = new_engine(); let dir_str = dir .join("directory_completion") .join("folder_inside_folder") .into_os_string() .into_string() .unwrap(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for the current folder let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}..{MAIN_SEPARATOR}..{MAIN_SEPARATOR}"); let suggestions = completer.complete(&target_dir, target_dir.len()); // Create the expected values let expected_paths: Vec<_> = vec![ folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("..") .join("another"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("..") .join("directory_completion"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("..") .join("test_a"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("..") .join("test_b"), ), folder( dir.join("directory_completion") .join("folder_inside_folder") .join("..") .join("..") .join(".hidden_folder"), ), ]; #[cfg(windows)] { let target_dir = format!("cd {dir_str}/../../"); let slash_suggestions = completer.complete(&target_dir, target_dir.len()); let expected_slash_paths: Vec<_> = expected_paths .iter() .map(|s| s.replace('\\', "/")) .collect(); match_suggestions_by_string(&expected_slash_paths, &slash_suggestions); } // Match the results match_suggestions_by_string(&expected_paths, &suggestions); } #[test] fn variables_completions() { // Create a new engine let (_, _, mut engine, mut stack) = new_engine(); // Add record value as example let record = "let actor = { name: 'Tom Hardy', age: 44 }"; assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack).is_ok()); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Test completions for $nu let suggestions = completer.complete("$nu.", 4); assert_eq!(19, suggestions.len()); let expected: Vec<_> = vec![ "cache-dir", "config-path", "current-exe", "data-dir", "default-config-dir", "env-path", "history-enabled", "history-path", "home-path", "is-interactive", "is-login", "loginshell-path", "os-info", "pid", "plugin-path", "startup-time", "temp-path", "user-autoload-dirs", "vendor-autoload-dirs", ]; // Match results match_suggestions(&expected, &suggestions); // Test completions for $nu.h (filter) let suggestions = completer.complete("$nu.h", 5); assert_eq!(3, suggestions.len()); let expected: Vec<_> = vec!["history-enabled", "history-path", "home-path"]; // Match results match_suggestions(&expected, &suggestions); // Test completions for $nu.os-info let suggestions = completer.complete("$nu.os-info.", 12); assert_eq!(4, suggestions.len()); let expected: Vec<_> = vec!["arch", "family", "kernel_version", "name"]; // Match results match_suggestions(&expected, &suggestions); // Test completions for custom var let suggestions = completer.complete("$actor.", 7); assert_eq!(2, suggestions.len()); let expected: Vec<_> = vec!["age", "name"]; // Match results match_suggestions(&expected, &suggestions); // Test completions for custom var (filtering) let suggestions = completer.complete("$actor.n", 8); assert_eq!(1, suggestions.len()); let expected: Vec<_> = vec!["name"]; // Match results match_suggestions(&expected, &suggestions); // Test completions for $env let suggestions = completer.complete("$env.", 5); assert_eq!(3, suggestions.len()); #[cfg(windows)] let expected: Vec<_> = vec!["Path", "PWD", "TEST"]; #[cfg(not(windows))] let expected: Vec<_> = vec!["PATH", "PWD", "TEST"]; // Match results match_suggestions(&expected, &suggestions); // Test completions for $env let suggestions = completer.complete("$env.T", 6); assert_eq!(1, suggestions.len()); let expected: Vec<_> = vec!["TEST"]; // Match results match_suggestions(&expected, &suggestions); let suggestions = completer.complete("$", 1); let expected: Vec<_> = vec!["$actor", "$env", "$in", "$nu"]; match_suggestions(&expected, &suggestions); } #[test] fn record_cell_path_completions() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#"let foo = {a: [1 {a: 2}]}; const bar = {a: [1 {a: 2}]}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let expected: Vec<_> = vec!["a"]; let completion_str = "$foo."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let completion_str = "$foo.a.1."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let completion_str = "$bar."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let completion_str = "$bar.a.1."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let completion_str = "{a: [1 {a: 2}]}.a.1."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); } #[test] fn table_cell_path_completions() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#"let foo = [{a:{b:1}}, {a:{b:2}}]; const bar = [[a b]; [1 2]]"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let expected: Vec<_> = vec!["a"]; let completion_str = "$foo."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let expected: Vec<_> = vec!["b"]; let completion_str = "$foo.a."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); let expected: Vec<_> = vec!["a", "b"]; let completion_str = "$bar."; let suggestions = completer.complete(completion_str, completion_str.len()); match_suggestions(&expected, &suggestions); } #[test] fn alias_of_command_and_flags() { let (_, _, mut engine, mut stack) = new_engine(); // Create an alias let alias = r#"alias ll = ls -l"#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let suggestions = completer.complete("ll t", 4); #[cfg(windows)] let expected_paths: Vec<_> = vec!["test_a\\", "test_a_symlink", "test_b\\"]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec!["test_a/", "test_a_symlink", "test_b/"]; match_suggestions(&expected_paths, &suggestions) } #[test] fn alias_of_basic_command() { let (_, _, mut engine, mut stack) = new_engine(); // Create an alias let alias = r#"alias ll = ls "#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let suggestions = completer.complete("ll t", 4); #[cfg(windows)] let expected_paths: Vec<_> = vec!["test_a\\", "test_a_symlink", "test_b\\"]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec!["test_a/", "test_a_symlink", "test_b/"]; match_suggestions(&expected_paths, &suggestions) } #[test] fn alias_of_another_alias() { let (_, _, mut engine, mut stack) = new_engine(); // Create an alias let alias = r#"alias ll = ls -la"#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); // Create the second alias let alias = r#"alias lf = ll -f"#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let suggestions = completer.complete("lf t", 4); #[cfg(windows)] let expected_paths: Vec<_> = vec!["test_a\\", "test_a_symlink", "test_b\\"]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec!["test_a/", "test_a_symlink", "test_b/"]; match_suggestions(&expected_paths, &suggestions) } fn run_external_completion(completer: &str, input: &str) -> Vec { let completer = format!("$env.config.completions.external.completer = {completer}"); // Create a new engine let (_, _, mut engine_state, mut stack) = new_engine(); let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); let block = parse(&mut working_set, None, completer.as_bytes(), false); assert!(working_set.parse_errors.is_empty()); (block, working_set.render()) }; assert!(engine_state.merge_delta(delta).is_ok()); assert!( eval_block::(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok() ); // Merge environment into the permanent state assert!(engine_state.merge_env(&mut stack).is_ok()); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine_state), Arc::new(stack)); completer.complete(input, input.len()) } #[test] fn unknown_command_completion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = "thiscommanddoesnotexist "; let suggestions = completer.complete(target_dir, target_dir.len()); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions) } #[rstest] fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) { let suggestions = completer.complete("tst -h", 5); let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; match_suggestions(&expected, &suggestions); } #[rstest] fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) { let suggestions = completer_strings.complete("my-command c", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) { let suggestions = completer_strings.complete("my-command c | ls", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) { let suggestions = completer.complete("tst -h | ls", 5); let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; match_suggestions(&expected, &suggestions); } #[test] fn filecompletions_triggers_after_cursor() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let suggestions = completer.complete("cp test_c", 3); #[cfg(windows)] let expected_paths: Vec<_> = vec![ "another\\", "custom_completion.nu", "directory_completion\\", "nushell", "test_a\\", "test_a_symlink", "test_b\\", ".hidden_file", ".hidden_folder\\", ]; #[cfg(not(windows))] let expected_paths: Vec<_> = vec![ "another/", "custom_completion.nu", "directory_completion/", "nushell", "test_a/", "test_a_symlink", "test_b/", ".hidden_file", ".hidden_folder/", ]; match_suggestions(&expected_paths, &suggestions); } #[rstest] fn extern_custom_completion_positional(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam ", 5); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam --foo=", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam --foo ", 11); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam -f ", 8); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam -b ", 8); let expected: Vec<_> = vec!["cat", "dog", "eel"]; match_suggestions(&expected, &suggestions); } #[rstest] fn extern_complete_flags(mut extern_completer: NuCompleter) { let suggestions = extern_completer.complete("spam -", 6); let expected: Vec<_> = vec!["--foo", "-b", "-f"]; match_suggestions(&expected, &suggestions); } #[rstest] fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("cmd foo bar", 8); let expected: Vec<_> = vec!["cmd", "foo", ""]; match_suggestions(&expected, &suggestions); } #[rstest] fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("cmd foo bar", 8); let expected: Vec<_> = vec!["cmd", "foo", ""]; match_suggestions(&expected, &suggestions); } #[rstest] fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("cmd foo bar", 11); let expected: Vec<_> = vec!["cmd", "foo", "bar"]; match_suggestions(&expected, &suggestions); } #[rstest] fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("cmd foo bar ", 12); let expected: Vec<_> = vec!["cmd", "foo", "bar", ""]; match_suggestions(&expected, &suggestions); } #[rstest] fn sort_fuzzy_completions_in_alphabetical_order(mut fuzzy_alpha_sort_completer: NuCompleter) { let suggestions = fuzzy_alpha_sort_completer.complete("ls nu", 5); // Even though "nushell" is a better match, it should come second because // the completions should be sorted in alphabetical order match_suggestions(&vec!["custom_completion.nu", "nushell"], &suggestions); } #[test] fn exact_match() { let (dir, _, engine, stack) = new_partial_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let target_dir = format!("open {}", folder(dir.join("pArTiAL"))); let suggestions = completer.complete(&target_dir, target_dir.len()); // Since it's an exact match, only 'partial' should be suggested, not // 'partial-a' and stuff. Implemented in #13302 match_suggestions( &vec![file(dir.join("partial").join("hello.txt")).as_str()], &suggestions, ); } #[ignore = "was reverted, still needs fixing"] #[rstest] fn alias_offset_bug_7648() { let (_, _, mut engine, mut stack) = new_engine(); // Create an alias let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Issue #7648 // Nushell crashes when an alias name is shorter than the alias command // and the alias command is a external command // This happens because of offset is not correct. // This crashes before PR #7779 let _suggestions = completer.complete("e", 1); } #[ignore = "was reverted, still needs fixing"] #[rstest] fn alias_offset_bug_7754() { let (_, _, mut engine, mut stack) = new_engine(); // Create an alias let alias = r#"alias ll = ls -l"#; assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); // Issue #7754 // Nushell crashes when an alias name is shorter than the alias command // and the alias command contains pipes. // This crashes before PR #7756 let _suggestions = completer.complete("ll -a | c", 9); } #[rstest] fn nested_block(mut completer: NuCompleter) { let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; let suggestions = completer.complete("somecmd | lines | each { tst - }", 30); match_suggestions(&expected, &suggestions); let suggestions = completer.complete("somecmd | lines | each { tst -}", 30); match_suggestions(&expected, &suggestions); } #[rstest] fn incomplete_nested_block(mut completer: NuCompleter) { let suggestions = completer.complete("somecmd | lines | each { tst -", 30); let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; match_suggestions(&expected, &suggestions); } #[rstest] fn deeply_nested_block(mut completer: NuCompleter) { let suggestions = completer.complete( "somecmd | lines | each { print ([each (print) (tst -)]) }", 52, ); let expected: Vec<_> = vec!["--help", "--mod", "-h", "-s"]; match_suggestions(&expected, &suggestions); } #[rstest] fn operator_completions(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("1 ", 2); // == != > < >= <= in not-in // + - * / // mod ** // 5 bit-xxx assert_eq!(20, suggestions.len()); let suggestions = custom_completer.complete("1 bit-s", 7); let expected: Vec<_> = vec!["bit-shl", "bit-shr"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("'str' ", 6); // == != > < >= <= in not-in // has not-has starts-with ends-with // =~ !~ like not-like ++ assert_eq!(17, suggestions.len()); let suggestions = custom_completer.complete("'str' +", 7); let expected: Vec<_> = vec!["++"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("1ms ", 4); // == != > < >= <= in not-in // + - * / // mod assert_eq!(14, suggestions.len()); let suggestions = custom_completer.complete("1ms /", 5); let expected: Vec<_> = vec!["/", "//"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("..2 ", 4); // == != in not-in has not-has assert_eq!(6, suggestions.len()); let suggestions = custom_completer.complete("..2 h", 5); let expected: Vec<_> = vec!["has"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("[[];[]] ", 8); // == != in not-in has not-has ++ assert_eq!(7, suggestions.len()); let suggestions = custom_completer.complete("[[];[]] h", 9); let expected: Vec<_> = vec!["has"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("(date now) ", 11); // == != > < >= <= in not-in assert_eq!(8, suggestions.len()); let suggestions = custom_completer.complete("(date now) <", 12); let expected: Vec<_> = vec!["<", "<="]; match_suggestions(&expected, &suggestions); // default operators for all types let expected: Vec<_> = vec!["!=", "==", "in", "not-in"]; let suggestions = custom_completer.complete("{1} ", 4); match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("null ", 5); match_suggestions(&expected, &suggestions); } #[rstest] fn cell_path_operator_completions(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("[1].0 ", 6); // == != > < >= <= in not-in // + - * / // mod ** // 5 bit-xxx assert_eq!(20, suggestions.len()); let suggestions = custom_completer.complete("[1].0 bit-s", 11); let expected: Vec<_> = vec!["bit-shl", "bit-shr"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("{'foo': [1, 1kb]}.foo.1 ", 24); // == != > < >= <= in not-in // + - * / // mod assert_eq!(14, suggestions.len()); let suggestions = custom_completer.complete("{'foo': [1, 1kb]}.foo.1 mo", 26); let expected: Vec<_> = vec!["mod"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("const f = {'foo': [1, '1']}; $f.foo.1 ", 38); // == != > < >= <= in not-in // has not-has starts-with ends-with // =~ !~ like not-like ++ assert_eq!(17, suggestions.len()); let suggestions = custom_completer.complete("const f = {'foo': [1, '1']}; $f.foo.1 ++", 40); let expected: Vec<_> = vec!["++"]; match_suggestions(&expected, &suggestions); } #[rstest] fn assignment_operator_completions(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("mut foo = ''; $foo ", 19); // == != > < >= <= in not-in // has not-has starts-with ends-with // =~ !~ like not-like ++ // = ++= assert_eq!(19, suggestions.len()); let suggestions = custom_completer.complete("mut foo = ''; $foo ++", 21); let expected: Vec<_> = vec!["++", "++="]; match_suggestions(&expected, &suggestions); // == != > < >= <= in not-in // = let suggestions = custom_completer.complete("mut foo = date now; $foo ", 25); assert_eq!(9, suggestions.len()); let suggestions = custom_completer.complete("mut foo = date now; $foo =", 26); let expected: Vec<_> = vec!["=", "=="]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("mut foo = date now; $foo ", 25); // == != > < >= <= in not-in // = assert_eq!(9, suggestions.len()); let suggestions = custom_completer.complete("mut foo = date now; $foo =", 26); let expected: Vec<_> = vec!["=", "=="]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("mut foo = 1ms; $foo ", 20); // == != > < >= <= in not-in // + - * / // mod // = += -= *= /= assert_eq!(19, suggestions.len()); let suggestions = custom_completer.complete("mut foo = 1ms; $foo +", 21); let expected: Vec<_> = vec!["+", "+="]; match_suggestions(&expected, &suggestions); // default operators for all mutables let expected: Vec<_> = vec!["!=", "=", "==", "in", "not-in"]; let suggestions = custom_completer.complete("mut foo = null; $foo ", 21); match_suggestions(&expected, &suggestions); // $env should be considered mutable let suggestions = custom_completer.complete("$env.config.keybindings ", 24); // == != in not-in // has not-has ++= // = ++= assert_eq!(9, suggestions.len()); let expected: Vec<_> = vec!["++", "++="]; let suggestions = custom_completer.complete("$env.config.keybindings +", 25); match_suggestions(&expected, &suggestions); } #[test] fn cellpath_assignment_operator_completions() { let (_, _, mut engine, mut stack) = new_engine(); let command = r#"mut foo = {'foo': [1, '1']}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "$foo.foo.1 "; let suggestions = completer.complete(completion_str, completion_str.len()); // == != > < >= <= in not-in // has not-has starts-with ends-with // =~ !~ like not-like ++ // = ++= assert_eq!(19, suggestions.len()); let completion_str = "$foo.foo.1 ++"; let suggestions = completer.complete(completion_str, completion_str.len()); let expected: Vec<_> = vec!["++", "++="]; match_suggestions(&expected, &suggestions); let (_, _, mut engine, mut stack) = new_engine(); let command = r#"mut foo = {'foo': [1, (date now)]}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let completion_str = "$foo.foo.1 "; let suggestions = completer.complete(completion_str, completion_str.len()); // == != > < >= <= in not-in // = assert_eq!(9, suggestions.len()); let completion_str = "$foo.foo.1 ="; let suggestions = completer.complete(completion_str, completion_str.len()); let expected: Vec<_> = vec!["=", "=="]; match_suggestions(&expected, &suggestions); } // TODO: type inference #[ignore] #[rstest] fn type_inferenced_operator_completions(mut custom_completer: NuCompleter) { let suggestions = custom_completer.complete("let f = {'foo': [1, '1']}; $f.foo.1 ", 36); // == != > < >= <= in not-in // has not-has starts-with ends-with // =~ !~ like not-like ++ assert_eq!(17, suggestions.len()); let suggestions = custom_completer.complete("const f = {'foo': [1, '1']}; $f.foo.1 ++", 38); let expected: Vec<_> = vec!["++"]; match_suggestions(&expected, &suggestions); let suggestions = custom_completer.complete("mut foo = [(date now)]; $foo.0 ", 31); // == != > < >= <= in not-in // = assert_eq!(9, suggestions.len()); let suggestions = custom_completer.complete("mut foo = [(date now)]; $foo.0 =", 32); let expected: Vec<_> = vec!["=", "=="]; match_suggestions(&expected, &suggestions); }