tinymist_query/analysis/completion/
type.rs

1//! Completion by types.
2
3use super::*;
4
5pub(crate) struct TypeCompletionWorker<'a, 'b, 'c, 'd> {
6    pub base: &'d mut CompletionPair<'a, 'b, 'c>,
7    pub filter: &'d dyn Fn(&Ty) -> bool,
8}
9
10impl TypeCompletionWorker<'_, '_, '_, '_> {
11    fn snippet_completion(&mut self, label: &str, apply: &str, detail: &str) {
12        if !(self.filter)(&Ty::Any) {
13            return;
14        }
15
16        self.base.snippet_completion(label, apply, detail);
17    }
18
19    pub fn type_completion(&mut self, infer_type: &Ty, docs: Option<&str>) -> Option<()> {
20        // Prevent duplicate completions from appearing.
21        if !self.base.worker.seen_types.insert(infer_type.clone()) {
22            return Some(());
23        }
24
25        crate::log_debug_ct!("type_completion: {infer_type:?}");
26
27        match infer_type {
28            Ty::Any => return None,
29            Ty::Pattern(_) => return None,
30            Ty::Args(_) => return None,
31            Ty::Func(_) => return None,
32            Ty::With(_) => return None,
33            Ty::Select(_) => return None,
34            Ty::Var(_) => return None,
35            Ty::Unary(_) => return None,
36            Ty::Binary(_) => return None,
37            Ty::If(_) => return None,
38            Ty::Union(u) => {
39                for info in u.as_ref() {
40                    self.type_completion(info, docs);
41                }
42            }
43            Ty::Let(bounds) => {
44                for ut in bounds.ubs.iter() {
45                    self.type_completion(ut, docs);
46                }
47                for lt in bounds.lbs.iter() {
48                    self.type_completion(lt, docs);
49                }
50            }
51            Ty::Tuple(..) | Ty::Array(..) => {
52                if !(self.filter)(infer_type) {
53                    return None;
54                }
55                self.snippet_completion("()", "(${})", "An array.");
56            }
57            Ty::Dict(..) => {
58                if !(self.filter)(infer_type) {
59                    return None;
60                }
61                self.snippet_completion("()", "(${})", "A dictionary.");
62            }
63            Ty::Boolean(_b) => {
64                if !(self.filter)(infer_type) {
65                    return None;
66                }
67                self.snippet_completion("false", "false", "No / Disabled.");
68                self.snippet_completion("true", "true", "Yes / Enabled.");
69            }
70            Ty::Builtin(v) => {
71                if !(self.filter)(infer_type) {
72                    return None;
73                }
74                self.builtin_type_completion(v, docs);
75            }
76            Ty::Value(v) => {
77                if !(self.filter)(infer_type) {
78                    return None;
79                }
80                let docs = v.syntax.as_ref().map(|s| s.doc.as_ref()).or(docs);
81
82                if let Value::Type(ty) = &v.val {
83                    self.type_completion(&Ty::Builtin(BuiltinTy::Type(*ty)), docs);
84                } else if v.val.ty() == Type::of::<NoneValue>() {
85                    self.type_completion(&Ty::Builtin(BuiltinTy::None), docs);
86                } else if v.val.ty() == Type::of::<AutoValue>() {
87                    self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
88                } else {
89                    self.base.value_completion(None, &v.val, true, docs);
90                }
91            }
92            Ty::Param(param) => {
93                // todo: variadic
94
95                let docs = docs.or_else(|| param.docs.as_deref());
96                if param.attrs.positional {
97                    self.type_completion(&param.ty, docs);
98                }
99                if !param.attrs.named {
100                    return Some(());
101                }
102
103                let field = &param.name;
104                if self.base.worker.seen_field(field.clone()) {
105                    return Some(());
106                }
107                if !(self.filter)(infer_type) {
108                    return None;
109                }
110
111                let mut rev_stream = self.base.cursor.before.chars().rev();
112                let ch = rev_stream.find(|ch| !typst::syntax::is_id_continue(*ch));
113                // skip label/ref completion.
114                // todo: more elegant way
115                if matches!(ch, Some('<' | '@')) {
116                    return Some(());
117                }
118
119                self.base.push_completion(Completion {
120                    kind: CompletionKind::Field,
121                    label: field.into(),
122                    apply: Some(eco_format!("{}: ${{}}", field)),
123                    label_details: param.ty.describe(),
124                    detail: docs.map(Into::into),
125                    command: self
126                        .base
127                        .worker
128                        .ctx
129                        .analysis
130                        .trigger_on_snippet_with_param_hint(true)
131                        .map(From::from),
132                    ..Completion::default()
133                });
134            }
135        };
136
137        Some(())
138    }
139
140    pub fn builtin_type_completion(&mut self, v: &BuiltinTy, docs: Option<&str>) -> Option<()> {
141        match v {
142            BuiltinTy::None => self.snippet_completion("none", "none", "Nothing."),
143            BuiltinTy::Auto => {
144                self.snippet_completion("auto", "auto", "A smart default.");
145            }
146            BuiltinTy::Clause => return None,
147            BuiltinTy::Undef => return None,
148            BuiltinTy::Space => return None,
149            BuiltinTy::Break => return None,
150            BuiltinTy::Continue => return None,
151            BuiltinTy::Content(..) => return None,
152            BuiltinTy::Infer => return None,
153            BuiltinTy::FlowNone => return None,
154            BuiltinTy::Tag(..) => return None,
155            BuiltinTy::Module(..) => return None,
156
157            BuiltinTy::Path(preference) => {
158                let items = self.base.complete_path(preference);
159                self.base
160                    .worker
161                    .completions
162                    .extend(items.into_iter().flatten());
163            }
164            BuiltinTy::Args => return None,
165            BuiltinTy::Stroke => {
166                self.snippet_completion("stroke()", "stroke(${})", "Stroke type.");
167                self.snippet_completion("()", "(${})", "Stroke dictionary.");
168                self.type_completion(&Ty::Builtin(BuiltinTy::Color), docs);
169                self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
170            }
171            BuiltinTy::Color => {
172                self.snippet_completion("luma()", "luma(${v})", "A custom grayscale color.");
173                self.snippet_completion(
174                    "rgb()",
175                    "rgb(${r}, ${g}, ${b}, ${a})",
176                    "A custom RGBA color.",
177                );
178                self.snippet_completion(
179                    "cmyk()",
180                    "cmyk(${c}, ${m}, ${y}, ${k})",
181                    "A custom CMYK color.",
182                );
183                self.snippet_completion(
184                    "oklab()",
185                    "oklab(${l}, ${a}, ${b}, ${alpha})",
186                    "A custom Oklab color.",
187                );
188                self.snippet_completion(
189                    "oklch()",
190                    "oklch(${l}, ${chroma}, ${hue}, ${alpha})",
191                    "A custom Oklch color.",
192                );
193                self.snippet_completion(
194                    "color.linear-rgb()",
195                    "color.linear-rgb(${r}, ${g}, ${b}, ${a})",
196                    "A custom linear RGBA color.",
197                );
198                self.snippet_completion(
199                    "color.hsv()",
200                    "color.hsv(${h}, ${s}, ${v}, ${a})",
201                    "A custom HSVA color.",
202                );
203                self.snippet_completion(
204                    "color.hsl()",
205                    "color.hsl(${h}, ${s}, ${l}, ${a})",
206                    "A custom HSLA color.",
207                );
208            }
209            BuiltinTy::TextSize => return None,
210            BuiltinTy::TextLang => {
211                for (&key, desc) in rust_iso639::ALL_MAP.entries() {
212                    let detail =
213                        eco_format!("An ISO 639-1/2/3 language code, {}.", desc.get_name());
214                    self.base.push_completion(Completion {
215                        kind: CompletionKind::Syntax,
216                        label: key.to_lowercase().into(),
217                        apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
218                        detail: Some(detail),
219                        label_details: Some(desc.get_name()),
220                        ..Completion::default()
221                    });
222                }
223            }
224            BuiltinTy::TextRegion => {
225                for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() {
226                    let detail =
227                        eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.get_name());
228                    self.base.push_completion(Completion {
229                        kind: CompletionKind::Syntax,
230                        label: key.to_lowercase().into(),
231                        apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
232                        detail: Some(detail),
233                        label_details: Some(desc.get_name()),
234                        ..Completion::default()
235                    });
236                }
237            }
238            BuiltinTy::Dir => {}
239            BuiltinTy::TextFont => {
240                self.base.font_completions();
241            }
242            BuiltinTy::TextFeature => {
243                self.base.font_feature_completions();
244            }
245            BuiltinTy::Margin => {
246                self.snippet_completion("()", "(${})", "Margin dictionary.");
247                self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
248            }
249            BuiltinTy::Inset => {
250                self.snippet_completion("()", "(${})", "Inset dictionary.");
251                self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
252            }
253            BuiltinTy::Outset => {
254                self.snippet_completion("()", "(${})", "Outset dictionary.");
255                self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
256            }
257            BuiltinTy::Radius => {
258                self.snippet_completion("()", "(${})", "Radius dictionary.");
259                self.type_completion(&Ty::Builtin(BuiltinTy::Length), docs);
260            }
261            BuiltinTy::Length => {
262                self.snippet_completion("pt", "${1}pt", "Point length unit.");
263                self.snippet_completion("mm", "${1}mm", "Millimeter length unit.");
264                self.snippet_completion("cm", "${1}cm", "Centimeter length unit.");
265                self.snippet_completion("in", "${1}in", "Inch length unit.");
266                self.snippet_completion("em", "${1}em", "Em length unit.");
267                self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
268            }
269            BuiltinTy::Float => {
270                self.snippet_completion(
271                    "exponential notation",
272                    "${1}e${0}",
273                    "Exponential notation",
274                );
275            }
276            BuiltinTy::Label => {
277                self.base.label_completions(false);
278            }
279            BuiltinTy::CiteLabel => {
280                self.base.label_completions(true);
281            }
282            BuiltinTy::RefLabel => {
283                self.base.ref_completions();
284            }
285            BuiltinTy::TypeType(ty) | BuiltinTy::Type(ty) => {
286                if *ty == Type::of::<NoneValue>() {
287                    let docs = docs.or(Some("Nothing."));
288                    self.type_completion(&Ty::Builtin(BuiltinTy::None), docs);
289                } else if *ty == Type::of::<AutoValue>() {
290                    let docs = docs.or(Some("A smart default."));
291                    self.type_completion(&Ty::Builtin(BuiltinTy::Auto), docs);
292                } else if *ty == Type::of::<bool>() {
293                    self.snippet_completion("false", "false", "No / Disabled.");
294                    self.snippet_completion("true", "true", "Yes / Enabled.");
295                } else if *ty == Type::of::<Color>() {
296                    self.type_completion(&Ty::Builtin(BuiltinTy::Color), docs);
297                } else if *ty == Type::of::<Label>() {
298                    self.base.label_completions(false)
299                } else if *ty == Type::of::<Func>() {
300                    self.snippet_completion(
301                        "function",
302                        "(${params}) => ${output}",
303                        "A custom function.",
304                    );
305                } else if let Ok(cons) = ty.constructor() {
306                    let docs = docs.or(cons.docs()).unwrap_or(ty.docs());
307                    self.base.value_completion(
308                        Some(ty.short_name().into()),
309                        &Value::Func(cons),
310                        true,
311                        Some(docs),
312                    );
313                } else if ty.scope().iter().any(|(_, b)| {
314                    if let Value::Func(f) = b.read() {
315                        let pos = f
316                            .params()
317                            .and_then(|params| params.iter().find(|s| s.required));
318                        pos.is_none_or(|pos| pos.name != "self")
319                    } else {
320                        true
321                    }
322                }) {
323                    let docs = docs.unwrap_or(ty.docs());
324                    self.base.push_completion(Completion {
325                        kind: CompletionKind::Syntax,
326                        label: ty.short_name().into(),
327                        apply: Some(eco_format!("${{{ty}}}")),
328                        detail: Some(docs.into()),
329                        ..Completion::default()
330                    });
331                }
332                // Otherwise, if the type doesn't have constructor and
333                // associated scope, we do nothing here. For example,
334                // - complete `content` doesn't make much sense.
335                // - complete `color` is okay because it has associated
336                //   constructors in scope.`
337            }
338            BuiltinTy::Element(elem) => {
339                self.base.value_completion(
340                    Some(elem.name().into()),
341                    &Value::Func((*elem).into()),
342                    true,
343                    docs,
344                );
345            }
346        };
347
348        Some(())
349    }
350}
351
352// desc.name()
353
354trait GetName {
355    fn get_name(&self) -> EcoString;
356}
357
358#[cfg(not(target_arch = "wasm32"))]
359impl GetName for rust_iso639::LanguageCode<'_> {
360    fn get_name(&self) -> EcoString {
361        self.name.into()
362    }
363}
364
365#[cfg(not(all(direct_wasm, target_arch = "wasm32")))]
366impl GetName for rust_iso3166::CountryCode {
367    fn get_name(&self) -> EcoString {
368        self.name.into()
369    }
370}
371
372#[cfg(target_arch = "wasm32")]
373impl GetName for rust_iso639::LanguageCode {
374    fn get_name(&self) -> EcoString {
375        self.name().into()
376    }
377}
378
379#[cfg(all(direct_wasm, target_arch = "wasm32"))]
380impl GetName for rust_iso3166::CountryCode {
381    fn get_name(&self) -> EcoString {
382        self.name().into()
383    }
384}