mirror of
https://github.com/nushell/nushell.git
synced 2024-12-22 23:23:12 +01:00
Send LSP Completion Item Kind (#11443)
# Description This commit fills in the completion item kind into the `textDocument/completion` response so that LSP client can present more information to the user. It is an improvement in the context of #10794 # User-Facing Changes Improved information display in editor's intelli-sense menu ![output](https://github.com/nushell/nushell/assets/16558417/991dc0a9-45d1-4718-8f22-29002d687b93)
This commit is contained in:
parent
d1a8992590
commit
e7bdd08a04
@ -13,13 +13,13 @@ pub trait Completer {
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion>;
|
||||
) -> Vec<SemanticSuggestion>;
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::Ascending
|
||||
}
|
||||
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut filtered_items = items;
|
||||
|
||||
@ -27,13 +27,13 @@ pub trait Completer {
|
||||
match self.get_sort_by() {
|
||||
SortBy::LevenshteinDistance => {
|
||||
filtered_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
SortBy::Ascending => {
|
||||
filtered_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
filtered_items.sort_by(|a, b| a.suggestion.value.cmp(&b.suggestion.value));
|
||||
}
|
||||
SortBy::None => {}
|
||||
};
|
||||
@ -41,3 +41,25 @@ pub trait Completer {
|
||||
filtered_items
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct SemanticSuggestion {
|
||||
pub suggestion: Suggestion,
|
||||
pub kind: Option<SuggestionKind>,
|
||||
}
|
||||
|
||||
// TODO: think about name: maybe suggestion context?
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SuggestionKind {
|
||||
Command(nu_protocol::engine::CommandType),
|
||||
Type(nu_protocol::Type),
|
||||
}
|
||||
|
||||
impl From<Suggestion> for SemanticSuggestion {
|
||||
fn from(suggestion: Suggestion) -> Self {
|
||||
Self {
|
||||
suggestion,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||
use crate::{
|
||||
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
||||
SuggestionKind,
|
||||
};
|
||||
use nu_parser::FlatShape;
|
||||
use nu_protocol::{
|
||||
engine::{CachedFile, EngineState, StateWorkingSet},
|
||||
@ -7,6 +10,8 @@ use nu_protocol::{
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SemanticSuggestion;
|
||||
|
||||
pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
@ -83,7 +88,7 @@ impl CommandCompletion {
|
||||
offset: usize,
|
||||
find_externals: bool,
|
||||
match_algorithm: MatchAlgorithm,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let partial = working_set.get_span_contents(span);
|
||||
|
||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||
@ -91,13 +96,16 @@ impl CommandCompletion {
|
||||
let mut results = working_set
|
||||
.find_commands_by_predicate(filter_predicate, true)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||
description: x.1,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||
description: x.1,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
},
|
||||
kind: Some(SuggestionKind::Command(x.2)),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@ -108,27 +116,34 @@ impl CommandCompletion {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let results_strings: Vec<String> =
|
||||
results.clone().into_iter().map(|x| x.value).collect();
|
||||
|
||||
for external in results_external {
|
||||
if results_strings.contains(&external.value) {
|
||||
results.push(Suggestion {
|
||||
value: format!("^{}", external.value),
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: external.span,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
},
|
||||
// TODO: is there a way to create a test?
|
||||
kind: None,
|
||||
});
|
||||
|
||||
let results_strings: Vec<String> =
|
||||
results.iter().map(|x| x.suggestion.value.clone()).collect();
|
||||
|
||||
for external in results_external {
|
||||
if results_strings.contains(&external.suggestion.value) {
|
||||
results.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: format!("^{}", external.suggestion.value),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: external.suggestion.span,
|
||||
append_whitespace: true,
|
||||
},
|
||||
kind: external.kind,
|
||||
})
|
||||
} else {
|
||||
results.push(external)
|
||||
@ -151,7 +166,7 @@ impl Completer for CommandCompletion {
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let last = self
|
||||
.flattened
|
||||
.iter()
|
||||
|
@ -14,6 +14,8 @@ use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::base::{SemanticSuggestion, SuggestionKind};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuCompleter {
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -28,6 +30,10 @@ impl NuCompleter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_completions_at(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
|
||||
// Process the completion for a given completer
|
||||
fn process_completion<T: Completer>(
|
||||
&self,
|
||||
@ -37,7 +43,7 @@ impl NuCompleter {
|
||||
new_span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let options = CompletionOptions {
|
||||
@ -62,7 +68,7 @@ impl NuCompleter {
|
||||
spans: &[String],
|
||||
offset: usize,
|
||||
span: Span,
|
||||
) -> Option<Vec<Suggestion>> {
|
||||
) -> Option<Vec<SemanticSuggestion>> {
|
||||
let block = self.engine_state.get_block(block_id);
|
||||
let mut callee_stack = self
|
||||
.stack
|
||||
@ -107,7 +113,7 @@ impl NuCompleter {
|
||||
None
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
// TODO: Callers should be trimming the line themselves
|
||||
@ -397,6 +403,9 @@ impl NuCompleter {
|
||||
impl ReedlineCompleter for NuCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
.into_iter()
|
||||
.map(|s| s.suggestion)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,20 +463,23 @@ pub fn map_value_completions<'a>(
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
list.filter_map(move |x| {
|
||||
// Match for string values
|
||||
if let Ok(s) = x.coerce_string() {
|
||||
return Some(Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
return Some(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
},
|
||||
append_whitespace: false,
|
||||
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||
});
|
||||
}
|
||||
|
||||
@ -516,7 +528,10 @@ pub fn map_value_completions<'a>(
|
||||
}
|
||||
});
|
||||
|
||||
return Some(suggestion);
|
||||
return Some(SemanticSuggestion {
|
||||
suggestion,
|
||||
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
@ -568,13 +583,13 @@ mod completer_tests {
|
||||
// Test whether the result begins with the expected value
|
||||
result
|
||||
.iter()
|
||||
.for_each(|x| assert!(x.value.starts_with(begins_with)));
|
||||
.for_each(|x| assert!(x.suggestion.value.starts_with(begins_with)));
|
||||
|
||||
// Test whether the result contains all the expected values
|
||||
assert_eq!(
|
||||
result
|
||||
.iter()
|
||||
.map(|x| expected_values.contains(&x.value.as_str()))
|
||||
.map(|x| expected_values.contains(&x.suggestion.value.as_str()))
|
||||
.filter(|x| *x)
|
||||
.count(),
|
||||
expected_values.len(),
|
||||
|
@ -7,10 +7,10 @@ use nu_protocol::{
|
||||
PipelineData, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use reedline::Suggestion;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::base::SemanticSuggestion;
|
||||
use super::completer::map_value_completions;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
@ -42,7 +42,7 @@ impl Completer for CustomCompletion {
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
completion_options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
// Line position
|
||||
let line_pos = pos - offset;
|
||||
|
||||
@ -145,15 +145,22 @@ impl Completer for CustomCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) -> Vec<Suggestion> {
|
||||
fn filter(
|
||||
prefix: &[u8],
|
||||
items: Vec<SemanticSuggestion>,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
items
|
||||
.into_iter()
|
||||
.filter(|it| match options.match_algorithm {
|
||||
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||
(true, true) => it.suggestion.value.as_bytes().starts_with(prefix),
|
||||
(true, false) => it
|
||||
.suggestion
|
||||
.value
|
||||
.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||
(false, positional) => {
|
||||
let value = it.value.to_folded_case();
|
||||
let value = it.suggestion.value.to_folded_case();
|
||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
||||
if positional {
|
||||
value.starts_with(&prefix)
|
||||
@ -164,7 +171,7 @@ fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) ->
|
||||
},
|
||||
MatchAlgorithm::Fuzzy => options
|
||||
.match_algorithm
|
||||
.matches_u8(it.value.as_bytes(), prefix),
|
||||
.matches_u8(it.suggestion.value.as_bytes(), prefix),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ use reedline::Suggestion;
|
||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SemanticSuggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirectoryCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -35,7 +37,7 @@ impl Completer for DirectoryCompletion {
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
|
||||
|
||||
// Filter only the folders
|
||||
@ -48,16 +50,20 @@ impl Completer for DirectoryCompletion {
|
||||
&self.stack,
|
||||
)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
},
|
||||
append_whitespace: false,
|
||||
// TODO????
|
||||
kind: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -65,7 +71,7 @@ impl Completer for DirectoryCompletion {
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Sort items
|
||||
@ -75,15 +81,16 @@ impl Completer for DirectoryCompletion {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
// Ignore trailing slashes in folder names when sorting
|
||||
a.value
|
||||
a.suggestion
|
||||
.value
|
||||
.trim_end_matches(SEP)
|
||||
.cmp(b.value.trim_end_matches(SEP))
|
||||
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
||||
});
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
@ -91,11 +98,11 @@ impl Completer for DirectoryCompletion {
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
let item_path = Path::new(&item.suggestion.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
|
@ -9,6 +9,8 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::SemanticSuggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DotNuCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -33,7 +35,7 @@ impl Completer for DotNuCompletion {
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
||||
let mut search_dirs: Vec<String> = vec![];
|
||||
|
||||
@ -93,7 +95,7 @@ impl Completer for DotNuCompletion {
|
||||
|
||||
// Fetch the files filtering the ones that ends with .nu
|
||||
// and transform them into suggestions
|
||||
let output: Vec<Suggestion> = search_dirs
|
||||
let output: Vec<SemanticSuggestion> = search_dirs
|
||||
.into_iter()
|
||||
.flat_map(|search_dir| {
|
||||
let completions = file_path_completion(
|
||||
@ -119,16 +121,20 @@ impl Completer for DotNuCompletion {
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
},
|
||||
append_whitespace: true,
|
||||
// TODO????
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -12,6 +12,8 @@ use reedline::Suggestion;
|
||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SemanticSuggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
@ -36,7 +38,7 @@ impl Completer for FileCompletion {
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let AdjustView {
|
||||
prefix,
|
||||
span,
|
||||
@ -53,16 +55,20 @@ impl Completer for FileCompletion {
|
||||
&self.stack,
|
||||
)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
},
|
||||
append_whitespace: false,
|
||||
// TODO????
|
||||
kind: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -70,7 +76,7 @@ impl Completer for FileCompletion {
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Sort items
|
||||
@ -80,15 +86,16 @@ impl Completer for FileCompletion {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
// Ignore trailing slashes in folder names when sorting
|
||||
a.value
|
||||
a.suggestion
|
||||
.value
|
||||
.trim_end_matches(SEP)
|
||||
.cmp(b.value.trim_end_matches(SEP))
|
||||
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
||||
});
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
@ -96,11 +103,11 @@ impl Completer for FileCompletion {
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
let item_path = Path::new(&item.suggestion.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
|
@ -7,6 +7,8 @@ use nu_protocol::{
|
||||
|
||||
use reedline::Suggestion;
|
||||
|
||||
use super::SemanticSuggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlagCompletion {
|
||||
expression: Expression,
|
||||
@ -27,7 +29,7 @@ impl Completer for FlagCompletion {
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
// Check if it's a flag
|
||||
if let Expr::Call(call) = &self.expression.expr {
|
||||
let decl = working_set.get_decl(call.decl_id);
|
||||
@ -43,16 +45,20 @@ impl Completer for FlagCompletion {
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
},
|
||||
append_whitespace: true,
|
||||
// TODO????
|
||||
kind: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -66,16 +72,20 @@ impl Completer for FlagCompletion {
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
},
|
||||
append_whitespace: true,
|
||||
// TODO????
|
||||
kind: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ mod file_completions;
|
||||
mod flag_completions;
|
||||
mod variable_completions;
|
||||
|
||||
pub use base::Completer;
|
||||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||
pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||
|
@ -9,7 +9,7 @@ use reedline::Suggestion;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::MatchAlgorithm;
|
||||
use super::{MatchAlgorithm, SemanticSuggestion, SuggestionKind};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let mut output = vec![];
|
||||
let builtins = ["$nu", "$in", "$env"];
|
||||
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
||||
@ -75,7 +75,7 @@ impl Completer for VariableCompletion {
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
@ -92,13 +92,16 @@ impl Completer for VariableCompletion {
|
||||
env_var.0.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -121,7 +124,7 @@ impl Completer for VariableCompletion {
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
@ -144,7 +147,7 @@ impl Completer for VariableCompletion {
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
suggestion.suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
@ -163,13 +166,17 @@ impl Completer for VariableCompletion {
|
||||
builtin.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
// TODO is there a way to get the VarId to get the type???
|
||||
kind: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -186,13 +193,18 @@ impl Completer for VariableCompletion {
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(
|
||||
working_set.get_variable(*v.1).ty.clone(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -208,13 +220,18 @@ impl Completer for VariableCompletion {
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(
|
||||
working_set.get_variable(*v.1).ty.clone(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -232,21 +249,25 @@ fn nested_suggestions(
|
||||
val: Value,
|
||||
sublevels: Vec<Vec<u8>>,
|
||||
current_span: reedline::Span,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output: Vec<Suggestion> = vec![];
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
let mut output: Vec<SemanticSuggestion> = vec![];
|
||||
let value = recursive_value(val, sublevels);
|
||||
|
||||
let kind = SuggestionKind::Type(value.get_type());
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
// Add all the columns as completion
|
||||
for (col, _) in val.into_iter() {
|
||||
output.push(Suggestion {
|
||||
value: col,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: col,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
@ -255,13 +276,16 @@ fn nested_suggestions(
|
||||
Value::LazyRecord { val, .. } => {
|
||||
// Add all the columns as completion
|
||||
for column_name in val.column_names() {
|
||||
output.push(Suggestion {
|
||||
value: column_name.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: column_name.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
@ -269,13 +293,16 @@ fn nested_suggestions(
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
for column_name in get_columns(vals.as_slice()) {
|
||||
output.push(Suggestion {
|
||||
value: column_name,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: column_name,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ mod util;
|
||||
mod validation;
|
||||
|
||||
pub use commands::add_cli_context;
|
||||
pub use completions::{FileCompletion, NuCompleter};
|
||||
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use eval_cmds::evaluate_commands;
|
||||
pub use eval_file::evaluate_file;
|
||||
|
@ -11,18 +11,18 @@ use std::{
|
||||
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
||||
use lsp_types::{
|
||||
request::{Completion, GotoDefinition, HoverRequest, Request},
|
||||
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
|
||||
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
|
||||
OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url,
|
||||
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location,
|
||||
MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit,
|
||||
Url,
|
||||
};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_cli::NuCompleter;
|
||||
use nu_cli::{NuCompleter, SuggestionKind};
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
DeclId, Span, Value, VarId,
|
||||
};
|
||||
use reedline::Completer;
|
||||
use ropey::Rope;
|
||||
|
||||
mod diagnostics;
|
||||
@ -559,7 +559,8 @@ impl LanguageServer {
|
||||
|
||||
let location =
|
||||
Self::lsp_position_to_location(¶ms.text_document_position.position, rope_of_file);
|
||||
let results = completer.complete(&rope_of_file.to_string()[..location], location);
|
||||
let results =
|
||||
completer.fetch_completions_at(&rope_of_file.to_string()[..location], location);
|
||||
if results.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@ -568,17 +569,18 @@ impl LanguageServer {
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let mut start = params.text_document_position.position;
|
||||
start.character -= (r.span.end - r.span.start) as u32;
|
||||
start.character -= (r.suggestion.span.end - r.suggestion.span.start) as u32;
|
||||
|
||||
CompletionItem {
|
||||
label: r.value.clone(),
|
||||
detail: r.description,
|
||||
label: r.suggestion.value.clone(),
|
||||
detail: r.suggestion.description,
|
||||
kind: Self::lsp_completion_item_kind(r.kind),
|
||||
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
|
||||
range: Range {
|
||||
start,
|
||||
end: params.text_document_position.position,
|
||||
},
|
||||
new_text: r.value,
|
||||
new_text: r.suggestion.value,
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
@ -587,12 +589,28 @@ impl LanguageServer {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn lsp_completion_item_kind(
|
||||
suggestion_kind: Option<SuggestionKind>,
|
||||
) -> Option<CompletionItemKind> {
|
||||
suggestion_kind.and_then(|suggestion_kind| match suggestion_kind {
|
||||
SuggestionKind::Type(t) => match t {
|
||||
nu_protocol::Type::String => Some(CompletionItemKind::VARIABLE),
|
||||
_ => None,
|
||||
},
|
||||
SuggestionKind::Command(c) => match c {
|
||||
nu_protocol::engine::CommandType::Keyword => Some(CompletionItemKind::KEYWORD),
|
||||
nu_protocol::engine::CommandType::Builtin => Some(CompletionItemKind::FUNCTION),
|
||||
_ => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_json_diff::assert_json_eq;
|
||||
use assert_json_diff::{assert_json_eq, assert_json_include};
|
||||
use lsp_types::{
|
||||
notification::{
|
||||
DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
|
||||
@ -1078,7 +1096,8 @@ mod tests {
|
||||
"start": { "character": 5, "line": 2 },
|
||||
"end": { "character": 9, "line": 2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": 6
|
||||
}
|
||||
])
|
||||
);
|
||||
@ -1115,7 +1134,8 @@ mod tests {
|
||||
"end": { "line": 0, "character": 8 },
|
||||
},
|
||||
"newText": "config nu"
|
||||
}
|
||||
},
|
||||
"kind": 3
|
||||
}
|
||||
])
|
||||
);
|
||||
@ -1152,7 +1172,45 @@ mod tests {
|
||||
"end": { "line": 0, "character": 14 },
|
||||
},
|
||||
"newText": "str trim"
|
||||
}
|
||||
},
|
||||
"kind": 3
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_keyword() {
|
||||
let (client_connection, _recv) = initialize_language_server();
|
||||
|
||||
let mut script = fixtures();
|
||||
script.push("lsp");
|
||||
script.push("completion");
|
||||
script.push("keyword.nu");
|
||||
let script = Url::from_file_path(script).unwrap();
|
||||
|
||||
open_unchecked(&client_connection, script.clone());
|
||||
|
||||
let resp = complete(&client_connection, script, 0, 2);
|
||||
let result = if let Message::Response(response) = resp {
|
||||
response.result
|
||||
} else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
assert_json_include!(
|
||||
actual: result,
|
||||
expected: serde_json::json!([
|
||||
{
|
||||
"label": "def",
|
||||
"textEdit": {
|
||||
"newText": "def",
|
||||
"range": {
|
||||
"start": { "character": 0, "line": 0 },
|
||||
"end": { "character": 2, "line": 0 }
|
||||
}
|
||||
},
|
||||
"kind": 14
|
||||
}
|
||||
])
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ use crate::{ast::Call, Alias, BlockId, Example, IoStream, PipelineData, ShellErr
|
||||
|
||||
use super::{EngineState, Stack, StateWorkingSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CommandType {
|
||||
Builtin,
|
||||
Custom,
|
||||
|
@ -4,7 +4,8 @@ use lru::LruCache;
|
||||
use super::cached_file::CachedFile;
|
||||
use super::{usage::build_usage, usage::Usage, StateDelta};
|
||||
use super::{
|
||||
Command, EnvVars, OverlayFrame, ScopeFrame, Stack, Variable, Visibility, DEFAULT_OVERLAY_NAME,
|
||||
Command, CommandType, EnvVars, OverlayFrame, ScopeFrame, Stack, Variable, Visibility,
|
||||
DEFAULT_OVERLAY_NAME,
|
||||
};
|
||||
use crate::ast::Block;
|
||||
use crate::debugger::{Debugger, NoopDebugger};
|
||||
@ -733,7 +734,7 @@ impl EngineState {
|
||||
&self,
|
||||
predicate: impl Fn(&[u8]) -> bool,
|
||||
ignore_deprecated: bool,
|
||||
) -> Vec<(Vec<u8>, Option<String>)> {
|
||||
) -> Vec<(Vec<u8>, Option<String>, CommandType)> {
|
||||
let mut output = vec![];
|
||||
|
||||
for overlay_frame in self.active_overlays(&[]).rev() {
|
||||
@ -743,7 +744,11 @@ impl EngineState {
|
||||
if ignore_deprecated && command.signature().category == Category::Removed {
|
||||
continue;
|
||||
}
|
||||
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
||||
output.push((
|
||||
decl.0.clone(),
|
||||
Some(command.usage().to_string()),
|
||||
command.command_type(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::cached_file::CachedFile;
|
||||
use super::CommandType;
|
||||
use super::{
|
||||
usage::build_usage, Command, EngineState, OverlayFrame, StateDelta, Variable, VirtualPath,
|
||||
Visibility, PWD_ENV,
|
||||
@ -708,7 +709,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||
&self,
|
||||
predicate: impl Fn(&[u8]) -> bool,
|
||||
ignore_deprecated: bool,
|
||||
) -> Vec<(Vec<u8>, Option<String>)> {
|
||||
) -> Vec<(Vec<u8>, Option<String>, CommandType)> {
|
||||
let mut output = vec![];
|
||||
|
||||
for scope_frame in self.delta.scope.iter().rev() {
|
||||
@ -721,7 +722,11 @@ impl<'a> StateWorkingSet<'a> {
|
||||
if ignore_deprecated && command.signature().category == Category::Removed {
|
||||
continue;
|
||||
}
|
||||
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
||||
output.push((
|
||||
decl.0.clone(),
|
||||
Some(command.usage().to_string()),
|
||||
command.command_type(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
tests/fixtures/lsp/completion/keyword.nu
vendored
Normal file
1
tests/fixtures/lsp/completion/keyword.nu
vendored
Normal file
@ -0,0 +1 @@
|
||||
de
|
Loading…
Reference in New Issue
Block a user