tinymist_query/analysis/completion/
scope.rs

1//! Completion of definitions in scope.
2
3use typst::foundations::{Array, Dict};
4
5use crate::ty::SigWithTy;
6
7use super::*;
8
9#[derive(BindTyCtx)]
10#[bind(types)]
11pub(crate) struct Defines {
12    pub types: Arc<TypeInfo>,
13    pub defines: BTreeMap<EcoString, Ty>,
14    pub docs: BTreeMap<EcoString, EcoString>,
15}
16
17impl Defines {
18    pub fn insert(&mut self, name: EcoString, item: Ty) {
19        if name.is_empty() {
20            return;
21        }
22
23        if let std::collections::btree_map::Entry::Vacant(entry) = self.defines.entry(name.clone())
24        {
25            entry.insert(item);
26        }
27    }
28
29    pub fn insert_ty(&mut self, ty: Ty, name: &EcoString) {
30        self.insert(name.clone(), ty);
31    }
32
33    pub fn insert_scope(&mut self, scope: &Scope) {
34        // filter(Some(value)) &&
35        for (name, bind) in scope.iter() {
36            if !self.defines.contains_key(name) {
37                self.insert(name.clone(), Ty::Value(InsTy::new(bind.read().clone())));
38            }
39        }
40    }
41}
42
43impl CompletionPair<'_, '_, '_> {
44    /// Add completions for definitions that are available at the cursor.
45    pub fn scope_completions(&mut self, parens: bool) {
46        let Some(defines) = self.scope_defs() else {
47            return;
48        };
49
50        self.def_completions(defines, parens);
51    }
52
53    #[typst_macros::time]
54    pub fn scope_defs(&mut self) -> Option<Defines> {
55        let mut defines = Defines {
56            types: self.worker.ctx.type_check(&self.cursor.source),
57            defines: Default::default(),
58            docs: Default::default(),
59        };
60
61        let mode = self.cursor.leaf_mode();
62
63        previous_decls(self.cursor.leaf.clone(), |node| -> Option<()> {
64            match node {
65                PreviousDecl::Ident(ident) => {
66                    let ty = self
67                        .worker
68                        .ctx
69                        .type_of_span(ident.span())
70                        .unwrap_or(Ty::Any);
71                    defines.insert_ty(ty, ident.get());
72                }
73                PreviousDecl::ImportSource(src) => {
74                    let ty = analyze_import_source(self.worker.ctx, &defines.types, src)?;
75                    let name = ty.name().as_ref().into();
76                    defines.insert_ty(ty, &name);
77                }
78                // todo: cache completion items
79                PreviousDecl::ImportAll(mi) => {
80                    let ty = analyze_import_source(self.worker.ctx, &defines.types, mi.source())?;
81                    ty.iface_surface(
82                        true,
83                        &mut CompletionScopeChecker {
84                            check_kind: ScopeCheckKind::Import,
85                            defines: &mut defines,
86                            ctx: self.worker.ctx,
87                        },
88                    );
89                }
90            }
91            None
92        });
93
94        let in_math = matches!(mode, InterpretMode::Math);
95
96        let lib = self.worker.world().library();
97        let scope = if in_math { &lib.math } else { &lib.global }
98            .scope()
99            .clone();
100        defines.insert_scope(&scope);
101
102        defines.insert(
103            EcoString::inline("std"),
104            Ty::Value(InsTy::new(lib.std.read().clone())),
105        );
106
107        Some(defines)
108    }
109
110    /// Add completions for definitions.
111    pub fn def_completions(&mut self, defines: Defines, parens: bool) {
112        let default_docs = defines.docs;
113        let defines = defines.defines;
114
115        let mode = self.cursor.leaf_mode();
116        let surrounding_syntax = self.cursor.surrounding_syntax;
117
118        let mut kind_checker = CompletionKindChecker {
119            symbols: HashSet::default(),
120            functions: HashSet::default(),
121        };
122
123        let filter = |checker: &CompletionKindChecker| {
124            match surrounding_syntax {
125                SurroundingSyntax::Regular => true,
126                SurroundingSyntax::StringContent => false,
127                SurroundingSyntax::ImportList | SurroundingSyntax::ParamList => false,
128                // SurroundingSyntax::Selector => 'selector: {
129                //     for func in &checker.functions {
130                //         if func.element().is_some() {
131                //             break 'selector true;
132                //         }
133                //     }
134
135                //     false
136                // }
137                // SurroundingSyntax::ShowTransform => !checker.functions.is_empty(),
138                SurroundingSyntax::Selector | SurroundingSyntax::ShowTransform => true,
139                SurroundingSyntax::SetRule => 'set_rule: {
140                    // todo: user defined elements
141                    for func in &checker.functions {
142                        if let Some(elem) = func.element()
143                            && elem.params().iter().any(|param| param.settable)
144                        {
145                            break 'set_rule true;
146                        }
147                    }
148
149                    false
150                }
151            }
152        };
153
154        // we don't check literal type here for faster completion
155        for (name, ty) in &defines {
156            if name.is_empty() {
157                continue;
158            }
159
160            kind_checker.check(ty);
161            if !filter(&kind_checker) {
162                continue;
163            }
164
165            // todo: describe all chars
166            if let Some(sym) = kind_checker.symbols.iter().min_by_key(|s| s.get()) {
167                self.symbol_completions(name.clone(), sym);
168                continue;
169            }
170
171            let docs = default_docs.get(name).cloned();
172
173            let label_details = ty.describe().or_else(|| Some("any".into()));
174
175            crate::log_debug_ct!("scope completions!: {name} {ty:?} {label_details:?}");
176            let detail = docs.or_else(|| label_details.clone());
177
178            if !kind_checker.functions.is_empty() {
179                let fn_feat =
180                    FnCompletionFeat::default().check(kind_checker.functions.iter().copied());
181                crate::log_debug_ct!("fn_feat: {name} {ty:?} -> {fn_feat:?}");
182                self.func_completion(mode, fn_feat, name.clone(), label_details, detail, parens);
183                continue;
184            }
185
186            let kind = type_to_completion_kind(ty);
187            self.push_completion(Completion {
188                kind,
189                label: name.clone(),
190                label_details,
191                detail,
192                ..Completion::default()
193            });
194        }
195    }
196}
197
198fn analyze_import_source(ctx: &LocalContext, types: &TypeInfo, s: ast::Expr) -> Option<Ty> {
199    if let Some(res) = types.type_of_span(s.span())
200        && !matches!(res.value(), Some(Value::Str(..)))
201    {
202        return Some(types.simplify(res, false));
203    }
204
205    let m = ctx.module_by_syntax(s.to_untyped())?;
206    Some(Ty::Value(InsTy::new_at(m, s.span())))
207}
208
209pub(crate) enum ScopeCheckKind {
210    Import,
211    FieldAccess,
212}
213
214#[derive(BindTyCtx)]
215#[bind(defines)]
216pub(crate) struct CompletionScopeChecker<'a> {
217    pub check_kind: ScopeCheckKind,
218    pub defines: &'a mut Defines,
219    pub ctx: &'a mut LocalContext,
220}
221
222impl CompletionScopeChecker<'_> {
223    fn is_only_importable(&self) -> bool {
224        matches!(self.check_kind, ScopeCheckKind::Import)
225    }
226
227    fn is_field_access(&self) -> bool {
228        matches!(self.check_kind, ScopeCheckKind::FieldAccess)
229    }
230
231    fn type_methods(&mut self, bound_self: Option<Ty>, ty: Type) {
232        for name in fields_on(ty) {
233            self.defines.insert((*name).into(), Ty::Any);
234        }
235        let bound_self = bound_self.map(|this| SigTy::unary(this, Ty::Any));
236        for (name, bind) in ty.scope().iter() {
237            let val = bind.read().clone();
238            let has_self = bound_self.is_some()
239                && (if let Value::Func(func) = &val {
240                    let first_pos = func
241                        .params()
242                        .and_then(|params| params.iter().find(|p| p.required));
243                    first_pos.is_some_and(|p| p.name == "self")
244                } else {
245                    false
246                });
247            let ty = Ty::Value(InsTy::new(val));
248            let ty = if has_self {
249                if let Some(bound_self) = bound_self.as_ref() {
250                    Ty::With(SigWithTy::new(ty.into(), bound_self.clone()))
251                } else {
252                    ty
253                }
254            } else {
255                ty
256            };
257
258            self.defines.insert(name.into(), ty);
259        }
260    }
261}
262
263impl IfaceChecker for CompletionScopeChecker<'_> {
264    fn check(
265        &mut self,
266        iface: Iface,
267        _ctx: &mut crate::ty::IfaceCheckContext,
268        _pol: bool,
269    ) -> Option<()> {
270        match iface {
271            // dict is not importable
272            Iface::Dict(d) if !self.is_only_importable() => {
273                for (name, term) in d.interface() {
274                    self.defines.insert(name.as_ref().into(), term.clone());
275                }
276            }
277            Iface::Value { val, .. } if !self.is_only_importable() => {
278                for (name, value) in val.iter() {
279                    let term = Ty::Value(InsTy::new(value.clone()));
280                    self.defines.insert(name.clone().into(), term);
281                }
282            }
283            Iface::Content { val, .. } if self.is_field_access() => {
284                // 255 is the magic "label"
285                let styles = StyleChain::default();
286                for field_id in 0u8..254u8 {
287                    let Some(field_name) = val.field_name(field_id) else {
288                        continue;
289                    };
290                    let param_info = val.params().iter().find(|p| p.name == field_name);
291                    let param_docs = param_info.map(|p| p.docs.into());
292                    let ty_from_param = param_info.map(|f| Ty::from_cast_info(&f.input));
293
294                    let ty_from_style = val
295                        .field_from_styles(field_id, styles)
296                        .ok()
297                        .map(|v| Ty::Builtin(BuiltinTy::Type(v.ty())));
298
299                    let field_ty = match (ty_from_param, ty_from_style) {
300                        (Some(param), None) => Some(param),
301                        (Some(opt), Some(_)) | (None, Some(opt)) => Some(Ty::from_types(
302                            [opt, Ty::Builtin(BuiltinTy::None)].into_iter(),
303                        )),
304                        (None, None) => None,
305                    };
306
307                    self.defines
308                        .insert(field_name.into(), field_ty.unwrap_or(Ty::Any));
309
310                    if let Some(docs) = param_docs {
311                        self.defines.docs.insert(field_name.into(), docs);
312                    }
313                }
314            }
315            Iface::Type { val, at } if self.is_field_access() => {
316                self.type_methods(Some(at.clone()), *val);
317            }
318            Iface::TypeType { val, .. } if self.is_field_access() => {
319                self.type_methods(None, *val);
320            }
321            Iface::Func { .. } if self.is_field_access() => {
322                self.type_methods(Some(iface.to_type()), Type::of::<Func>());
323            }
324            Iface::Array { .. } | Iface::Tuple { .. } if self.is_field_access() => {
325                self.type_methods(Some(iface.to_type()), Type::of::<Array>());
326            }
327            Iface::Dict { .. } if self.is_field_access() => {
328                self.type_methods(Some(iface.to_type()), Type::of::<Dict>());
329            }
330            Iface::Content { val, .. } => {
331                self.defines.insert_scope(val.scope());
332            }
333            // todo: distingusish TypeType and Type
334            Iface::TypeType { val, .. } | Iface::Type { val, .. } => {
335                self.defines.insert_scope(val.scope());
336            }
337            Iface::Func { val, .. } => {
338                if let Some(s) = val.scope() {
339                    self.defines.insert_scope(s);
340                }
341            }
342            Iface::Module { val, .. } => {
343                let ti = self.ctx.type_check_by_id(val);
344                if !ti.valid {
345                    self.defines
346                        .insert_scope(self.ctx.module_by_id(val).ok()?.scope());
347                } else {
348                    for (name, ty) in ti.exports.iter() {
349                        // todo: Interned -> EcoString here
350                        let ty = ti.simplify(ty.clone(), false);
351                        self.defines.insert(name.as_ref().into(), ty);
352                    }
353                }
354            }
355            Iface::ModuleVal { val, .. } => {
356                self.defines.insert_scope(val.scope());
357            }
358            Iface::Array { .. } | Iface::Tuple { .. } | Iface::Dict(..) | Iface::Value { .. } => {}
359        }
360        None
361    }
362}