Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send LSP Completion Item Kind #11443

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 27 additions & 5 deletions crates/nu-cli/src/completions/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,53 @@ 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;

// 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(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()
}
}
}
67 changes: 41 additions & 26 deletions crates/nu-cli/src/completions/command_completions.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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)>,
Expand Down Expand Up @@ -83,21 +88,24 @@ 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);

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<_>>();

Expand All @@ -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,
.map(move |x| SemanticSuggestion {
suggestion: Suggestion {
value: x,
description: None,
style: None,
extra: None,
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.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,
style: None,
extra: None,
span: external.span,
append_whitespace: true,
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)
Expand All @@ -151,7 +166,7 @@ impl Completer for CommandCompletion {
offset: usize,
pos: usize,
options: &CompletionOptions,
) -> Vec<Suggestion> {
) -> Vec<SemanticSuggestion> {
let last = self
.flattened
.iter()
Expand Down
47 changes: 31 additions & 16 deletions crates/nu-cli/src/completions/completer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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())),
});
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down
21 changes: 14 additions & 7 deletions crates/nu-cli/src/completions/custom_completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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)
Expand All @@ -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()
}