Repository: nushell/nushell (language: rust)

Issue:
A collection of issues related to `DotNuCompletion`
There are many remaining issues of `DotNuCompletion`, I'm not sure if they are known or not. And I'd like to discuss an elegant solution.

1. [x] - directories without a `mod.nu` in them are rejected, which IMO is overly restricted, cause of #14213 
2. [x] - for paths started with `~`, which the nu parser has no problem with, doesn't work in `DotNuCompletion`
3. [x] - additional space character when a directory entry is selected:  <img width="92" alt="image" src="https://github.com/user-attachments/assets/4c9e91de-ff86-4314-b7da-baaa5cc43fb7" />
4. [x] - nested path not supported
5. [x] - paths with space in them
6. [ ] - as in #14852 and many other completion related issues. I think we need a more robust method to locate the exact `pipeline_element` where the completion actually happens. @tmillr do you have any idea?


Discussion / hints:
And it should completes exposed commands/constants/modules after the file name:

```nushell
use foo.nu |_here
```
Those sounds like something we'd want to fix. I'd also want them to work with paths with spaces in them, if they don't already.

Fix patch:
diff --git a/crates/nu-cli/src/completions/dotnu_completions.rs b/crates/nu-cli/src/completions/dotnu_completions.rs
index c626e8b1d2afc..f06b85f5ed6c0 100644
--- a/crates/nu-cli/src/completions/dotnu_completions.rs
+++ b/crates/nu-cli/src/completions/dotnu_completions.rs
@@ -1,4 +1,5 @@
 use crate::completions::{file_path_completion, Completer, CompletionOptions};
+use nu_path::expand_tilde;
 use nu_protocol::{
     engine::{Stack, StateWorkingSet},
     Span,
@@ -28,7 +29,10 @@ impl Completer for DotNuCompletion {
         _pos: usize,
         options: &CompletionOptions,
     ) -> Vec<SemanticSuggestion> {
-        let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
+        let prefix_str = String::from_utf8_lossy(prefix);
+        let start_with_backquote = prefix_str.starts_with('`');
+        let end_with_backquote = prefix_str.ends_with('`');
+        let prefix_str = prefix_str.replace('`', "");
         let mut search_dirs: Vec<PathBuf> = vec![];
 
         // If prefix_str is only a word we want to search in the current dir
@@ -46,12 +50,8 @@ impl Completer for DotNuCompletion {
                 lib_dirs
                     .as_list()
                     .into_iter()
-                    .flat_map(|it| {
-                        it.iter().map(|x| {
-                            x.to_path()
-                                .expect("internal error: failed to convert lib path")
-                        })
-                    })
+                    .flat_map(|it| it.iter().filter_map(|x| x.to_path().ok()))
+                    .map(expand_tilde)
                     .collect()
             })
             .unwrap_or_default();
@@ -78,13 +78,12 @@ impl Completer for DotNuCompletion {
 
         // Fetch the files filtering the ones that ends with .nu
         // and transform them into suggestions
-
         let completions = file_path_completion(
             span,
             partial,
             &search_dirs
                 .iter()
-                .map(|d| d.to_str().unwrap_or_default())
+                .filter_map(|d| d.to_str())
                 .collect::<Vec<_>>(),
             options,
             working_set.permanent_state,
@@ -93,20 +92,35 @@ impl Completer for DotNuCompletion {
         completions
             .into_iter()
             // Different base dir, so we list the .nu files or folders
-            .filter(|it| it.path.ends_with(".nu") || it.path.ends_with(SEP))
+            .filter(|it| {
+                // for paths with spaces in them
+                let path = it.path.trim_end_matches('`');
+                path.ends_with(".nu") || path.ends_with(SEP)
+            })
             .map(|x| {
-                let append_whitespace = x.path.ends_with(".nu");
+                let append_whitespace =
+                    x.path.ends_with(".nu") && (!start_with_backquote || end_with_backquote);
                 // Re-calculate the span to replace
-                let span_offset = if base_dir == "." {
-                    0
-                } else {
-                    base_dir.len() + 1
-                } + prefix.iter().take_while(|c| **c == b'`').count();
+                let mut span_offset = 0;
+                let mut value = x.path.to_string();
+                // Complete only the last path component
+                if base_dir != "." {
+                    span_offset = base_dir.len() + 1
+                }
+                // Retain only one '`'
+                if start_with_backquote {
+                    value = value.trim_start_matches('`').to_string();
+                    span_offset += 1;
+                }
+                // Add the backquote back
+                if end_with_backquote && !value.ends_with('`') {
+                    value.push('`');
+                }
                 let end = x.span.end - offset;
                 let start = std::cmp::min(end, x.span.start - offset + span_offset);
                 SemanticSuggestion {
                     suggestion: Suggestion {
-                        value: x.path,
+                        value,
                         style: x.style,
                         span: reedline::Span { start, end },
                         append_whitespace,


Test patch:
diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs
index d878d312d20c6..ab9152172ee12 100644
--- a/crates/nu-cli/tests/completions/mod.rs
+++ b/crates/nu-cli/tests/completions/mod.rs
@@ -275,12 +275,30 @@ fn dotnu_completions() {
 
     // Test nested nu script
     #[cfg(windows)]
-    let completion_str = "use .\\dir_module\\sub_module\\".to_string();
+    let completion_str = "use `.\\dir_module\\".to_string();
     #[cfg(not(windows))]
-    let completion_str = "use ./dir_module/sub_module/".to_string();
+    let completion_str = "use `./dir_module/".to_string();
     let suggestions = completer.complete(&completion_str, completion_str.len());
 
-    match_suggestions(&vec!["sub.nu".into()], &suggestions);
+    match_suggestions(
+        &vec![
+            "mod.nu".into(),
+            #[cfg(windows)]
+            "sub module\\`".into(),
+            #[cfg(not(windows))]
+            "sub module/`".into(),
+        ],
+        &suggestions,
+    );
+
+    // Test nested nu script, with ending '`'
+    #[cfg(windows)]
+    let completion_str = "use `.\\dir_module\\sub module\\`".to_string();
+    #[cfg(not(windows))]
+    let completion_str = "use `./dir_module/sub module/`".to_string();
+    let suggestions = completer.complete(&completion_str, completion_str.len());
+
+    match_suggestions(&vec!["sub.nu`".into()], &suggestions);
 
     let expected = vec![
         "asdf.nu".into(),
diff --git a/tests/fixtures/dotnu_completions/dir_module/sub_module/sub.nu b/tests/fixtures/dotnu_completions/dir_module/sub module/sub.nu
similarity index 100%
rename from tests/fixtures/dotnu_completions/dir_module/sub_module/sub.nu
rename to tests/fixtures/dotnu_completions/dir_module/sub module/sub.nu