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