Skip to content

Commit

Permalink
Send LSP Completion Item Kind
Browse files Browse the repository at this point in the history
This commit fills in the completion item kind into the
textDocument/completion response so that LSP client can present more
information to the user.
  • Loading branch information
schrieveslaach committed Dec 30, 2023
1 parent 21b3eee commit 0426c2a
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 164 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/nu-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fancy-regex = "0.12"
fuzzy-matcher = "0.3"
is_executable = "1.0"
log = "0.4"
lsp-types = "0.95.0"
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
once_cell = "1.18"
percent-encoding = "2"
Expand Down
30 changes: 25 additions & 5 deletions crates/nu-cli/src/completions/base.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::completions::{CompletionOptions, SortBy};
use lsp_types::CompletionItemKind;
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
use reedline::Suggestion;

Expand All @@ -13,31 +14,50 @@ pub trait Completer {
offset: usize,
pos: usize,
options: &CompletionOptions,
) -> Vec<Suggestion>;
) -> Vec<SuggestionWithLspAnnotations>;

fn get_sort_by(&self) -> SortBy {
SortBy::Ascending
}

fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
fn sort(
&self,
items: Vec<SuggestionWithLspAnnotations>,
prefix: Vec<u8>,
) -> Vec<SuggestionWithLspAnnotations> {
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
let mut filtered_items = items;

// Sort items
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 => {}
};

filtered_items
}
}

#[derive(Default, PartialEq)]
pub struct SuggestionWithLspAnnotations {
pub suggestion: Suggestion,
pub kind: Option<CompletionItemKind>,
}

impl From<Suggestion> for SuggestionWithLspAnnotations {
fn from(suggestion: Suggestion) -> Self {
Self {
suggestion,
..Default::default()
}
}
}
55 changes: 33 additions & 22 deletions crates/nu-cli/src/completions/command_completions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::completions::base::SuggestionWithLspAnnotations;
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
use lsp_types::CompletionItemKind;
use nu_parser::FlatShape;
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
Expand Down Expand Up @@ -83,20 +85,23 @@ impl CommandCompletion {
offset: usize,
find_externals: bool,
match_algorithm: MatchAlgorithm,
) -> Vec<Suggestion> {
) -> Vec<SuggestionWithLspAnnotations> {
let partial = working_set.get_span_contents(span);

let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);

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,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true,
.map(move |x| SuggestionWithLspAnnotations {
suggestion: Suggestion {
value: String::from_utf8_lossy(&x.0).to_string(),
description: x.1,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true,
},
kind: Some(CompletionItemKind::FUNCTION),
})
.collect::<Vec<_>>();

Expand All @@ -107,25 +112,31 @@ impl CommandCompletion {
let results_external = self
.external_command_completion(&partial, match_algorithm)
.into_iter()
.map(move |x| Suggestion {
value: x,
description: None,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true,
.map(move |x| SuggestionWithLspAnnotations {
suggestion: Suggestion {
value: x,
description: None,
extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true,
},
kind: Some(CompletionItemKind::FUNCTION),
});

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 {
if results_strings.contains(&external.value) {
results.push(Suggestion {
value: format!("^{}", external.value),
description: None,
extra: None,
span: external.span,
append_whitespace: true,
if results_strings.contains(&external.suggestion.value) {
results.push(SuggestionWithLspAnnotations {
suggestion: Suggestion {
value: format!("^{}", external.suggestion.value),
description: None,
extra: None,
span: external.suggestion.span,
append_whitespace: true,
},
kind: Some(CompletionItemKind::FUNCTION),
})
} else {
results.push(external)
Expand All @@ -148,7 +159,7 @@ impl Completer for CommandCompletion {
offset: usize,
pos: usize,
options: &CompletionOptions,
) -> Vec<Suggestion> {
) -> Vec<SuggestionWithLspAnnotations> {
let last = self
.flattened
.iter()
Expand Down
78 changes: 52 additions & 26 deletions crates/nu-cli/src/completions/completer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::base::SuggestionWithLspAnnotations;
use crate::completions::{
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
Expand Down Expand Up @@ -27,6 +28,14 @@ impl NuCompleter {
}
}

pub fn fetch_completions_at(
&mut self,
line: &str,
pos: usize,
) -> Vec<SuggestionWithLspAnnotations> {
self.completion_helper(line, pos)
}

// Process the completion for a given completer
fn process_completion<T: Completer>(
&self,
Expand All @@ -36,7 +45,7 @@ impl NuCompleter {
new_span: Span,
offset: usize,
pos: usize,
) -> Vec<Suggestion> {
) -> Vec<SuggestionWithLspAnnotations> {
let config = self.engine_state.get_config();

let options = CompletionOptions {
Expand All @@ -61,7 +70,7 @@ impl NuCompleter {
spans: &[String],
offset: usize,
span: Span,
) -> Option<Vec<Suggestion>> {
) -> Option<Vec<SuggestionWithLspAnnotations>> {
let stack = self.stack.clone();
let block = self.engine_state.get_block(block_id);
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
Expand Down Expand Up @@ -107,7 +116,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<SuggestionWithLspAnnotations> {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start();
let initial_line = line.to_string();
Expand Down Expand Up @@ -214,7 +223,10 @@ impl NuCompleter {
if let Some(external_result) = self
.external_completion(block_id, &spans, offset, new_span)
{
return external_result;
return external_result
.into_iter()
.map(SuggestionWithLspAnnotations::from)
.collect();
}
}
}
Expand Down Expand Up @@ -355,7 +367,10 @@ impl NuCompleter {
block_id, &spans, offset, new_span,
) {
if !external_result.is_empty() {
return external_result;
return external_result
.into_iter()
.map(SuggestionWithLspAnnotations::from)
.collect();
}
}
}
Expand Down Expand Up @@ -391,6 +406,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()
}
}

Expand Down Expand Up @@ -448,33 +466,41 @@ pub fn map_value_completions<'a>(
list: impl Iterator<Item = &'a Value>,
span: Span,
offset: usize,
) -> Vec<Suggestion> {
) -> Vec<SuggestionWithLspAnnotations> {
list.filter_map(move |x| {
// Match for string values
if let Ok(s) = x.as_string() {
return Some(Suggestion {
value: s,
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
return Some(SuggestionWithLspAnnotations {
suggestion: Suggestion {
value: s,
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
},
append_whitespace: false,
// TODO: can we assume that this is kind VALUE?
kind: None,
});
}

// Match for record values
if let Ok(record) = x.as_record() {
let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
let mut suggestion = SuggestionWithLspAnnotations {
suggestion: Suggestion {
value: String::from(""), // Initialize with empty string
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
},
append_whitespace: false,
// TODO: can we assume that this is kind RECORD or STRUCT?
kind: None,
};

// Iterate the cols looking for `value` and `description`
Expand All @@ -484,7 +510,7 @@ pub fn map_value_completions<'a>(
// Convert the value to string
if let Ok(val_str) = it.1.as_string() {
// Update the suggestion value
suggestion.value = val_str;
suggestion.suggestion.value = val_str;
}
}

Expand All @@ -493,7 +519,7 @@ pub fn map_value_completions<'a>(
// Convert the value to string
if let Ok(desc_str) = it.1.as_string() {
// Update the suggestion value
suggestion.description = Some(desc_str);
suggestion.suggestion.description = Some(desc_str);
}
}
});
Expand Down Expand Up @@ -550,13 +576,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(),
Expand Down
Loading

0 comments on commit 0426c2a

Please sign in to comment.