tinymist_query/analysis/completion/
scope.rs1use 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 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 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 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 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 | SurroundingSyntax::ShowTransform => true,
139 SurroundingSyntax::SetRule => 'set_rule: {
140 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 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 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 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 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 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 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}