tinymist_query/analysis/completion/
field_access.rs

1//! Completion for field access on nodes.
2
3use typst::syntax::ast::MathTextKind;
4
5use crate::analysis::completion::typst_specific::ValueCompletionInfo;
6
7use super::*;
8impl CompletionPair<'_, '_, '_> {
9    /// Add completions for all dot targets on a node.
10    pub fn doc_access_completions(&mut self, target: &LinkedNode) -> Option<()> {
11        self.value_dot_access_completions(target)
12            .or_else(|| self.type_dot_access_completions(target))
13    }
14
15    /// Add completions for all fields on a type.
16    fn type_dot_access_completions(&mut self, target: &LinkedNode) -> Option<()> {
17        let mode = self.cursor.leaf_mode();
18
19        if matches!(mode, InterpretMode::Math) {
20            return None;
21        }
22
23        self.type_field_access_completions(target);
24        Some(())
25    }
26
27    /// Add completions for all fields on a type.
28    fn type_field_access_completions(&mut self, target: &LinkedNode) -> Option<()> {
29        let ty = self
30            .worker
31            .ctx
32            .post_type_of_node(target.clone())
33            .filter(|ty| !matches!(ty, Ty::Any));
34        crate::log_debug_ct!("type_field_access_completions_on: {target:?} -> {ty:?}");
35        let mut defines = Defines {
36            types: self.worker.ctx.type_check(&self.cursor.source),
37            defines: Default::default(),
38            docs: Default::default(),
39        };
40        ty?.iface_surface(
41            true,
42            &mut CompletionScopeChecker {
43                check_kind: ScopeCheckKind::FieldAccess,
44                defines: &mut defines,
45                ctx: self.worker.ctx,
46            },
47        );
48
49        self.def_completions(defines, true);
50        Some(())
51    }
52
53    /// Add completions for all fields on a value.
54    fn value_dot_access_completions(&mut self, target: &LinkedNode) -> Option<()> {
55        let (value, styles) = self.worker.ctx.analyze_expr(target).into_iter().next()?;
56
57        let mode = self.cursor.leaf_mode();
58        let valid_field_access_syntax =
59            !matches!(mode, InterpretMode::Math) || is_valid_math_field_access(target);
60        let valid_postfix_target =
61            !matches!(mode, InterpretMode::Math) || is_valid_math_postfix(target);
62
63        if !valid_field_access_syntax && !valid_postfix_target {
64            return None;
65        }
66
67        if valid_field_access_syntax {
68            self.value_field_access_completions(&value, mode);
69        }
70        if valid_postfix_target {
71            self.postfix_completions(target, Ty::Value(InsTy::new(value.clone())));
72        }
73
74        match value {
75            Value::Symbol(symbol) => {
76                self.symbol_var_completions(&symbol, None);
77
78                if valid_postfix_target {
79                    self.ufcs_completions(target);
80                }
81            }
82            Value::Content(content) => {
83                if valid_field_access_syntax {
84                    for (name, value) in content.fields() {
85                        self.value_completion(Some(name.into()), &value, false, None);
86                    }
87                }
88                if valid_postfix_target {
89                    self.ufcs_completions(target);
90                }
91            }
92            Value::Dict(dict) if valid_field_access_syntax => {
93                for (name, value) in dict.iter() {
94                    self.value_completion(Some(name.clone().into()), value, false, None);
95                }
96            }
97            Value::Func(func) if valid_field_access_syntax => {
98                // Autocomplete get rules.
99                if let Some((elem, styles)) = func.element().zip(styles.as_ref()) {
100                    for param in elem.params().iter().filter(|param| !param.required) {
101                        if let Some(value) = elem
102                            .field_id(param.name)
103                            .map(|id| elem.field_from_styles(id, StyleChain::new(styles)))
104                        {
105                            self.value_completion(
106                                Some(param.name.into()),
107                                &value.unwrap(),
108                                false,
109                                None,
110                            );
111                        }
112                    }
113                }
114            }
115            _ => {}
116        }
117
118        Some(())
119    }
120
121    fn value_field_access_completions(&mut self, value: &Value, mode: InterpretMode) {
122        let elem_parens = !matches!(mode, InterpretMode::Math);
123        for (name, bind) in value.ty().scope().iter() {
124            if matches!(mode, InterpretMode::Math) && is_func(bind.read()) {
125                continue;
126            }
127
128            self.value_completion_(
129                bind.read(),
130                ValueCompletionInfo {
131                    label: Some(name.clone()),
132                    parens: elem_parens,
133                    docs: None,
134                    label_details: None,
135                    bound_self: true,
136                },
137            );
138        }
139
140        if let Some(scope) = value.scope() {
141            for (name, bind) in scope.iter() {
142                if matches!(mode, InterpretMode::Math) && is_func(bind.read()) {
143                    continue;
144                }
145
146                self.value_completion_(
147                    bind.read(),
148                    ValueCompletionInfo {
149                        label: Some(name.clone()),
150                        parens: elem_parens,
151                        docs: None,
152                        label_details: None,
153                        bound_self: false,
154                    },
155                );
156            }
157        }
158
159        for &field in fields_on(value.ty()) {
160            // Complete the field name along with its value. Notes:
161            // 1. No parentheses since function fields cannot currently be called
162            // with method syntax;
163            // 2. We can unwrap the field's value since it's a field belonging to
164            // this value's type, so accessing it should not fail.
165            self.value_completion_(
166                &value.field(field, ()).unwrap(),
167                ValueCompletionInfo {
168                    label: Some(field.into()),
169                    parens: false,
170                    docs: None,
171                    label_details: None,
172                    bound_self: true,
173                },
174            );
175        }
176    }
177}
178
179fn is_func(read: &Value) -> bool {
180    matches!(read, Value::Func(func) if func.element().is_none())
181}
182
183fn is_valid_math_field_access(target: &SyntaxNode) -> bool {
184    if let Some(field_access) = target.cast::<ast::FieldAccess>() {
185        return is_valid_math_field_access(field_access.target().to_untyped());
186    }
187    if matches!(target.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) {
188        return true;
189    }
190
191    false
192}
193
194fn is_valid_math_postfix(target: &SyntaxNode) -> bool {
195    fn bad_punc_text(punc: char) -> bool {
196        punc.is_ascii_punctuation() || punc.is_ascii_whitespace()
197    }
198
199    if let Some(target) = target.cast::<ast::MathText>() {
200        return match target.get() {
201            MathTextKind::Character(ch) => !bad_punc_text(ch),
202            MathTextKind::Number(..) => true,
203        };
204    }
205
206    if let Some(target) = target.cast::<ast::Text>() {
207        let target = target.get();
208        return !target.is_empty() && target.chars().all(|ch| !bad_punc_text(ch));
209    }
210
211    true
212}