nu_cli/completions/
base.rs1use crate::completions::completer::Context;
2use nu_color_config::NuStyle;
3use nu_protocol::{DynamicSuggestion, IntoValue, Record, Span, SuggestionKind, Value};
4use reedline::Suggestion;
5
6pub trait Completer {
7 fn fetch(&mut self, ctx: &Context) -> Fetched;
9}
10
11#[derive(Debug, Default)]
14pub enum Fetched {
15 Pure(Vec<SemanticSuggestion>),
17 Cacheable(Vec<SemanticSuggestion>),
19 Declined,
21 #[default]
23 Absent,
24}
25
26impl Fetched {
27 pub(crate) fn into_suggestions(self) -> Vec<SemanticSuggestion> {
29 match self {
30 Self::Pure(suggestions) | Self::Cacheable(suggestions) => suggestions,
31 Self::Declined | Self::Absent => Vec::new(),
32 }
33 }
34
35 pub(crate) fn is_cacheable(&self) -> bool {
37 matches!(self, Self::Cacheable(_) | Self::Declined)
38 }
39
40 pub(crate) fn needs_fallback(&self) -> bool {
42 matches!(self, Self::Declined | Self::Absent)
43 }
44
45 pub(crate) fn caching(self) -> Self {
47 match self {
48 Self::Pure(suggestions) => Self::Cacheable(suggestions),
49 Self::Absent => Self::Declined,
50 already => already,
51 }
52 }
53}
54
55pub(crate) fn to_reedline_span(span: Span, offset: usize) -> reedline::Span {
59 reedline::Span::new(
60 span.start.saturating_sub(offset),
61 span.end.saturating_sub(offset),
62 )
63}
64
65#[derive(Debug, Default, PartialEq)]
66pub struct SemanticSuggestion {
67 pub suggestion: Suggestion,
68 pub kind: Option<SuggestionKind>,
69}
70
71impl SemanticSuggestion {
72 pub fn from_dynamic_suggestion(
73 suggestion: DynamicSuggestion,
74 span: reedline::Span,
75 style: Option<nu_ansi_term::Style>,
76 ) -> Self {
77 SemanticSuggestion {
78 suggestion: Suggestion {
79 value: suggestion.value,
80 display_override: suggestion.display_override,
81 description: suggestion.description,
82 extra: suggestion.extra,
83 append_whitespace: suggestion.append_whitespace,
84 match_indices: suggestion.match_indices,
85 style,
86 span,
87 },
88 kind: suggestion.kind,
89 }
90 }
91}
92
93impl IntoValue for SemanticSuggestion {
94 fn into_value(self, span: Span) -> Value {
95 let mut record = Record::new();
96 record.insert("value", Value::string(self.suggestion.value, span));
97
98 if let Some(span_rec) = span_record(self.suggestion.span, span) {
99 record.insert("span", span_rec);
100 }
101
102 if let Some(display) = self.suggestion.display_override {
103 record.insert("display_override", Value::string(display, span));
104 }
105
106 if let Some(style) = self.suggestion.style.map(NuStyle::from) {
107 record.insert("style", style.into_value(span));
108 }
109
110 if let Some(description) = self.suggestion.description {
111 record.insert("description", description.into_value(span));
112 }
113
114 if let Some(kind) = self.kind {
115 let (kind_str, ty) = match kind {
116 SuggestionKind::Command(ty, _) => ("command", Some(ty.to_string())),
117 SuggestionKind::Value(ty) => ("value", Some(ty.to_string())),
118 SuggestionKind::CellPath => ("cell-path", None),
119 SuggestionKind::Directory => ("directory", None),
120 SuggestionKind::File => ("file", None),
121 SuggestionKind::Flag => ("flag", None),
122 SuggestionKind::Module => ("module", None),
123 SuggestionKind::Operator => ("operator", None),
124 SuggestionKind::Variable => ("variable", None),
125 };
126 record.insert("kind", kind_str.into_value(span));
127
128 record.insert(
130 "type",
131 ty.map_or_else(|| Value::nothing(span), |ty| ty.into_value(span)),
132 );
133 }
134
135 Value::record(record, span)
136 }
137}
138
139fn span_record(span: reedline::Span, src_span: Span) -> Option<Value> {
140 let (Ok(start), Ok(end)) = (span.start.try_into(), span.end.try_into()) else {
141 log::error!("failed to convert span to i64s");
142 return None;
143 };
144
145 Some(Value::record(
146 Record::from_iter([
147 ("start".into(), Value::int(start, src_span)),
148 ("end".into(), Value::int(end, src_span)),
149 ]),
150 src_span,
151 ))
152}
153
154impl From<Suggestion> for SemanticSuggestion {
155 fn from(suggestion: Suggestion) -> Self {
156 Self {
157 suggestion,
158 ..Default::default()
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
170 fn fallback_variants_carry_no_suggestions() {
171 assert!(Fetched::Declined.into_suggestions().is_empty());
172 assert!(Fetched::Absent.into_suggestions().is_empty());
173 }
174}