1use 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)]
16pub struct Fetched {
17 pub(crate) suggestions: Vec<SemanticSuggestion>,
18 pub(crate) cacheable: bool,
19 pub(crate) need_fallback: bool,
20}
21
22impl Fetched {
23 pub(crate) fn pure(suggestions: Vec<SemanticSuggestion>) -> Self {
25 Self {
26 suggestions,
27 cacheable: false,
28 need_fallback: false,
29 }
30 }
31
32 pub(crate) fn cacheable(suggestions: Vec<SemanticSuggestion>) -> Self {
34 Self {
35 suggestions,
36 cacheable: true,
37 need_fallback: false,
38 }
39 }
40
41 pub(crate) fn fallback() -> Self {
44 Self {
45 suggestions: vec![],
46 cacheable: true,
47 need_fallback: true,
48 }
49 }
50
51 pub(crate) fn absent() -> Self {
53 Self {
54 suggestions: vec![],
55 cacheable: false,
56 need_fallback: true,
57 }
58 }
59
60 pub(crate) fn caching(mut self) -> Self {
63 self.cacheable = true;
64 self
65 }
66}
67
68pub(crate) fn to_reedline_span(span: Span, offset: usize) -> reedline::Span {
72 reedline::Span::new(
73 span.start.saturating_sub(offset),
74 span.end.saturating_sub(offset),
75 )
76}
77
78#[derive(Debug, Default, PartialEq)]
79pub struct SemanticSuggestion {
80 pub suggestion: Suggestion,
81 pub kind: Option<SuggestionKind>,
82}
83
84impl SemanticSuggestion {
85 pub fn from_dynamic_suggestion(
86 suggestion: DynamicSuggestion,
87 span: reedline::Span,
88 style: Option<nu_ansi_term::Style>,
89 ) -> Self {
90 SemanticSuggestion {
91 suggestion: Suggestion {
92 value: suggestion.value,
93 display_override: suggestion.display_override,
94 description: suggestion.description,
95 extra: suggestion.extra,
96 append_whitespace: suggestion.append_whitespace,
97 match_indices: suggestion.match_indices,
98 style,
99 span,
100 },
101 kind: suggestion.kind,
102 }
103 }
104}
105
106impl IntoValue for SemanticSuggestion {
107 fn into_value(self, span: Span) -> Value {
108 let mut record = Record::new();
109 record.insert("value", Value::string(self.suggestion.value, span));
110
111 if let Some(span_rec) = span_record(self.suggestion.span, span) {
112 record.insert("span", span_rec);
113 }
114
115 if let Some(display) = self.suggestion.display_override {
116 record.insert("display_override", Value::string(display, span));
117 }
118
119 if let Some(style) = self.suggestion.style.map(NuStyle::from) {
120 record.insert("style", style.into_value(span));
121 }
122
123 if let Some(description) = self.suggestion.description {
124 record.insert("description", description.into_value(span));
125 }
126
127 if let Some(kind) = self.kind {
128 let (kind_str, ty) = match kind {
129 SuggestionKind::Command(ty, _) => ("command", Some(ty.to_string())),
130 SuggestionKind::Value(ty) => ("value", Some(ty.to_string())),
131 SuggestionKind::CellPath => ("cell-path", None),
132 SuggestionKind::Directory => ("directory", None),
133 SuggestionKind::File => ("file", None),
134 SuggestionKind::Flag => ("flag", None),
135 SuggestionKind::Module => ("module", None),
136 SuggestionKind::Operator => ("operator", None),
137 SuggestionKind::Variable => ("variable", None),
138 };
139 record.insert("kind", kind_str.into_value(span));
140
141 if let Some(ty) = ty {
142 record.insert("type", ty.into_value(span));
143 }
144 }
145
146 Value::record(record, span)
147 }
148}
149
150fn span_record(span: reedline::Span, src_span: Span) -> Option<Value> {
151 let (Ok(start), Ok(end)) = (span.start.try_into(), span.end.try_into()) else {
152 log::error!("failed to convert span to i64s");
153 return None;
154 };
155
156 Some(Value::record(
157 Record::from_iter([
158 ("start".into(), Value::int(start, src_span)),
159 ("end".into(), Value::int(end, src_span)),
160 ]),
161 src_span,
162 ))
163}
164
165impl From<Suggestion> for SemanticSuggestion {
166 fn from(suggestion: Suggestion) -> Self {
167 Self {
168 suggestion,
169 ..Default::default()
170 }
171 }
172}