Skip to content

Commit

Permalink
fix(completion): dotnu_completion for nested folders/scripts (#14978)
Browse files Browse the repository at this point in the history
# Description

Fixes #14213 
and some of #14956

<img width="314" alt="image"
src="https://github.com/user-attachments/assets/e79d0882-da90-4592-8af5-7ab0c1d2f96a"
/>

# User-Facing Changes

# Tests + Formatting

# After Submitting
  • Loading branch information
blindFS authored Feb 1, 2025
1 parent 4540f38 commit 63fa6a6
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 58 deletions.
1 change: 1 addition & 0 deletions crates/nu-cli/src/completions/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct SemanticSuggestion {
pub enum SuggestionKind {
Command(nu_protocol::engine::CommandType),
Type(nu_protocol::Type),
Module,
}

impl From<Suggestion> for SemanticSuggestion {
Expand Down
3 changes: 0 additions & 3 deletions crates/nu-cli/src/completions/completion_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ pub struct FileSuggestion {
pub span: nu_protocol::Span,
pub path: String,
pub style: Option<Style>,
pub cwd: PathBuf,
}

/// # Parameters
Expand Down Expand Up @@ -261,7 +260,6 @@ pub fn complete_item(
if should_collapse_dots {
p = collapse_ndots(p);
}
let cwd = p.cwd.clone();
let path = original_cwd.apply(p, path_separator);
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(
Expand All @@ -277,7 +275,6 @@ pub fn complete_item(
span,
path: escape_path(path, want_directory),
style,
cwd,
}
})
.collect()
Expand Down
97 changes: 42 additions & 55 deletions crates/nu-cli/src/completions/dotnu_completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use nu_protocol::{
Span,
};
use reedline::Suggestion;
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
use std::path::{is_separator, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};

use super::SemanticSuggestion;
use super::{SemanticSuggestion, SuggestionKind};

#[derive(Clone, Default)]
pub struct DotNuCompletion {}
Expand All @@ -29,19 +29,16 @@ impl Completer for DotNuCompletion {
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
let mut search_dirs: Vec<String> = vec![];
let mut search_dirs: Vec<PathBuf> = vec![];

// If prefix_str is only a word we want to search in the current dir
let (base, partial) = prefix_str
.rsplit_once(is_separator)
.unwrap_or((".", &prefix_str));
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
let mut partial = partial.to_string();
// On windows, this standardizes paths to use \
let mut is_current_folder = false;

// Fetch the lib dirs
let lib_dirs: Vec<String> = working_set
let lib_dirs: Vec<PathBuf> = working_set
.find_variable(b"$NU_LIB_DIRS")
.and_then(|vid| working_set.get_variable(vid).const_val.as_ref())
.or(working_set.get_env_var("NU_LIB_DIRS"))
Expand All @@ -55,36 +52,27 @@ impl Completer for DotNuCompletion {
.expect("internal error: failed to convert lib path")
})
})
.map(|it| {
it.into_os_string()
.into_string()
.expect("internal error: failed to convert OS path")
})
.collect()
})
.unwrap_or_default();

// Check if the base_dir is a folder
// rsplit_once removes the separator
let cwd = working_set.permanent_state.cwd(None);
if base_dir != "." {
// Add the base dir into the directories to be searched
search_dirs.push(base_dir.clone());

// Reset the partial adding the basic dir back
// in order to make the span replace work properly
let mut base_dir_partial = base_dir;
base_dir_partial.push_str(&partial);

partial = base_dir_partial;
// Search in base_dir as well as lib_dirs
if let Ok(mut cwd) = cwd {
cwd.push(&base_dir);
search_dirs.push(cwd.into_std_path_buf());
}
search_dirs.extend(lib_dirs.into_iter().map(|mut dir| {
dir.push(&base_dir);
dir
}));
} else {
// Fetch the current folder
#[allow(deprecated)]
let current_folder = working_set.permanent_state.current_work_dir();
is_current_folder = true;

// Add the current folder and the lib dirs into the
// directories to be searched
search_dirs.push(current_folder);
if let Ok(cwd) = cwd {
search_dirs.push(cwd.into_std_path_buf());
}
search_dirs.extend(lib_dirs);
}

Expand All @@ -93,40 +81,39 @@ impl Completer for DotNuCompletion {

let completions = file_path_completion(
span,
&partial,
&search_dirs.iter().map(|d| d.as_str()).collect::<Vec<_>>(),
partial,
&search_dirs
.iter()
.map(|d| d.to_str().unwrap_or_default())
.collect::<Vec<_>>(),
options,
working_set.permanent_state,
stack,
);
completions
.into_iter()
.filter(move |it| {
// Different base dir, so we list the .nu files or folders
if !is_current_folder {
it.path.ends_with(".nu") || it.path.ends_with(SEP)
// Different base dir, so we list the .nu files or folders
.filter(|it| it.path.ends_with(".nu") || it.path.ends_with(SEP))
.map(|x| {
let append_whitespace = x.path.ends_with(".nu");
// Re-calculate the span to replace
let span_offset = if base_dir == "." {
0
} else {
// Lib dirs, so we filter only the .nu files or directory modules
if it.path.ends_with(SEP) {
Path::new(&it.cwd).join(&it.path).join("mod.nu").exists()
} else {
it.path.ends_with(".nu")
}
}
})
.map(move |x| SemanticSuggestion {
suggestion: Suggestion {
value: x.path,
style: x.style,
span: reedline::Span {
start: x.span.start - offset,
end: x.span.end - offset,
base_dir.len() + 1
} + prefix.iter().take_while(|c| **c == b'`').count();
let end = x.span.end - offset;
let start = std::cmp::min(end, x.span.start - offset + span_offset);
SemanticSuggestion {
suggestion: Suggestion {
value: x.path,
style: x.style,
span: reedline::Span { start, end },
append_whitespace,
..Suggestion::default()
},
append_whitespace: true,
..Suggestion::default()
},
// TODO????
kind: None,
kind: Some(SuggestionKind::Module),
}
})
.collect::<Vec<_>>()
}
Expand Down
21 changes: 21 additions & 0 deletions crates/nu-cli/tests/completions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,15 @@ fn dotnu_completions() {
// Instantiate a new completer
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));

// Test nested nu script
#[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(),
"bar.nu".into(),
Expand All @@ -283,6 +292,18 @@ fn dotnu_completions() {
#[cfg(not(windows))]
"dir_module/".into(),
"foo.nu".into(),
#[cfg(windows)]
"lib-dir1\\".into(),
#[cfg(not(windows))]
"lib-dir1/".into(),
#[cfg(windows)]
"lib-dir2\\".into(),
#[cfg(not(windows))]
"lib-dir2/".into(),
#[cfg(windows)]
"lib-dir3\\".into(),
#[cfg(not(windows))]
"lib-dir3/".into(),
"spam.nu".into(),
"xyzzy.nu".into(),
];
Expand Down
2 changes: 2 additions & 0 deletions crates/nu-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ impl LanguageServer {
.map(|kind| match kind {
SuggestionKind::Type(t) => t.to_string(),
SuggestionKind::Command(cmd) => cmd.to_string(),
SuggestionKind::Module => "".to_string(),
})
.map(|s| CompletionItemLabelDetails {
detail: None,
Expand Down Expand Up @@ -715,6 +716,7 @@ impl LanguageServer {
nu_protocol::engine::CommandType::Builtin => Some(CompletionItemKind::FUNCTION),
_ => None,
},
SuggestionKind::Module => Some(CompletionItemKind::MODULE),
})
}
}
Expand Down
Empty file.

0 comments on commit 63fa6a6

Please sign in to comment.