Skip to main content

nu_cli/completions/
base.rs

1use crate::completions::CompletionOptions;
2use nu_color_config::NuStyle;
3use nu_protocol::{
4    DynamicSuggestion, IntoValue, Record, Span, SuggestionKind, Value,
5    engine::{Stack, StateWorkingSet},
6};
7use reedline::Suggestion;
8
9pub trait Completer {
10    /// Fetch, filter, and sort completions
11    #[allow(clippy::too_many_arguments)]
12    fn fetch(
13        &mut self,
14        working_set: &StateWorkingSet,
15        stack: &Stack,
16        prefix: impl AsRef<str>,
17        span: Span,
18        offset: usize,
19        options: &CompletionOptions,
20    ) -> Vec<SemanticSuggestion>;
21}
22
23#[derive(Debug, Default, PartialEq)]
24pub struct SemanticSuggestion {
25    pub suggestion: Suggestion,
26    pub kind: Option<SuggestionKind>,
27}
28
29impl SemanticSuggestion {
30    pub fn from_dynamic_suggestion(
31        suggestion: DynamicSuggestion,
32        span: reedline::Span,
33        style: Option<nu_ansi_term::Style>,
34    ) -> Self {
35        SemanticSuggestion {
36            suggestion: Suggestion {
37                value: suggestion.value,
38                display_override: suggestion.display_override,
39                description: suggestion.description,
40                extra: suggestion.extra,
41                append_whitespace: suggestion.append_whitespace,
42                match_indices: suggestion.match_indices,
43                style,
44                span,
45            },
46            kind: suggestion.kind,
47        }
48    }
49}
50
51impl IntoValue for SemanticSuggestion {
52    fn into_value(self, span: Span) -> Value {
53        let mut record = Record::new();
54        record.insert("value", Value::string(self.suggestion.value, span));
55
56        if let Some(span_rec) = span_record(self.suggestion.span, span) {
57            record.insert("span", span_rec);
58        }
59
60        if let Some(display) = self.suggestion.display_override {
61            record.insert("display_override", Value::string(display, span));
62        }
63
64        if let Some(style) = self.suggestion.style.map(NuStyle::from) {
65            record.insert("style", style.into_value(span));
66        }
67
68        if let Some(description) = self.suggestion.description {
69            record.insert("description", description.into_value(span));
70        }
71
72        if let Some(kind) = self.kind {
73            let (kind_str, ty) = match kind {
74                SuggestionKind::Command(ty, _) => ("command", Some(ty.to_string())),
75                SuggestionKind::Value(ty) => ("value", Some(ty.to_string())),
76                SuggestionKind::CellPath => ("cell-path", None),
77                SuggestionKind::Directory => ("directory", None),
78                SuggestionKind::File => ("file", None),
79                SuggestionKind::Flag => ("flag", None),
80                SuggestionKind::Module => ("module", None),
81                SuggestionKind::Operator => ("operator", None),
82                SuggestionKind::Variable => ("variable", None),
83            };
84            record.insert("kind", kind_str.into_value(span));
85
86            if let Some(ty) = ty {
87                record.insert("type", ty.into_value(span));
88            }
89        }
90
91        Value::record(record, span)
92    }
93}
94
95fn span_record(span: reedline::Span, src_span: Span) -> Option<Value> {
96    let (Ok(start), Ok(end)) = (span.start.try_into(), span.end.try_into()) else {
97        log::error!("failed to convert span to i64s");
98        return None;
99    };
100
101    Some(Value::record(
102        Record::from_iter([
103            ("start".into(), Value::int(start, src_span)),
104            ("end".into(), Value::int(end, src_span)),
105        ]),
106        src_span,
107    ))
108}
109
110impl From<Suggestion> for SemanticSuggestion {
111    fn from(suggestion: Suggestion) -> Self {
112        Self {
113            suggestion,
114            ..Default::default()
115        }
116    }
117}