tinymist_query/analysis/completion/
mode.rs

1//! Completion by [`crate::syntax::InterpretMode`].
2
3use super::*;
4impl CompletionPair<'_, '_, '_> {
5    /// Complete in comments. Or rather, don't!
6    pub fn complete_comments(&mut self) -> bool {
7        let text = self.cursor.leaf.get().text();
8        // check if next line defines a function
9        if (text == "///" || text == "/// ")
10            // hash node
11            && let Some(hash_node) = self.cursor.leaf.next_leaf()
12            // let node
13            && let Some(let_node) = hash_node.next_leaf()
14            && let Some(let_closure) = let_node.next_leaf()
15            && matches!(let_closure.parent_kind(), Some(SyntaxKind::Closure))
16            && let Some(closure) = let_closure.parent()
17            && let Some(closure) = closure.cast::<ast::Expr>()
18            && let ast::Expr::Closure(c) = closure
19        {
20            // Only completes if the next line is a function definition
21            let rng = self.cursor.leaf.offset()..hash_node.offset();
22            let text_between = &self.cursor.source.text()[rng];
23            let mut line_count = 0;
24            for ch in text_between.chars() {
25                if ch == '\n' {
26                    line_count += 1;
27                }
28                if line_count > 1 {
29                    return false;
30                }
31            }
32
33            let mut doc_snippet: String = if text == "///" {
34                " $0\n///".to_string()
35            } else {
36                "$0\n///".to_string()
37            };
38            let mut i = 0;
39            for param in c.params().children() {
40                // TODO: Properly handle Pos and Spread argument
41                let param: &EcoString = match param {
42                    Param::Pos(p) => match p {
43                        ast::Pattern::Normal(ast::Expr::Ident(ident)) => ident.get(),
44                        _ => &"_".into(),
45                    },
46                    Param::Named(n) => n.name().get(),
47                    Param::Spread(s) => {
48                        if let Some(ident) = s.sink_ident() {
49                            &eco_format!("{}", ident.get())
50                        } else {
51                            &EcoString::new()
52                        }
53                    }
54                };
55                log::info!("param: {param}, index: {i}");
56                doc_snippet += &format!("\n/// - {param} (${}): ${}", i + 1, i + 2);
57                i += 2;
58            }
59            doc_snippet += &format!("\n/// -> ${}", i + 1);
60            self.push_completion(Completion {
61                label: "Document function".into(),
62                apply: Some(doc_snippet.into()),
63                ..Completion::default()
64            });
65        }
66
67        true
68    }
69
70    /// Complete in markup mode.
71    pub fn complete_markup(&mut self) -> bool {
72        let parent_raw =
73            node_ancestors(&self.cursor.leaf).find(|node| matches!(node.kind(), SyntaxKind::Raw));
74
75        // Behind a half-completed binding: "#let x = |" or `#let f(x) = |`.
76        if let Some(prev) = self.cursor.leaf.prev_leaf()
77            && matches!(prev.kind(), SyntaxKind::Eq | SyntaxKind::Arrow)
78            && matches!(
79                prev.parent_kind(),
80                Some(SyntaxKind::LetBinding | SyntaxKind::Closure)
81            )
82        {
83            self.cursor.from = self.cursor.cursor;
84            self.code_completions(false);
85            return true;
86        }
87
88        // Behind a half-completed context block: "#context |".
89        if let Some(prev) = self.cursor.leaf.prev_leaf()
90            && prev.kind() == SyntaxKind::Context
91        {
92            self.cursor.from = self.cursor.cursor;
93            self.code_completions(false);
94            return true;
95        }
96
97        // Directly after a raw block.
98        if let Some(parent_raw) = parent_raw {
99            let mut s = Scanner::new(self.cursor.text);
100            s.jump(parent_raw.offset());
101            if s.eat_if("```") {
102                s.eat_while('`');
103                let start = s.cursor();
104                if s.eat_if(is_id_start) {
105                    s.eat_while(is_id_continue);
106                }
107                if s.cursor() == self.cursor.cursor {
108                    self.cursor.from = start;
109                    self.raw_completions();
110                }
111                return true;
112            }
113        }
114
115        // Anywhere: "|".
116        if !is_triggered_by_punc(self.worker.trigger_character) && self.worker.explicit {
117            self.cursor.from = self.cursor.cursor;
118            self.snippet_completions(Some(InterpretMode::Markup), None);
119            return true;
120        }
121
122        false
123    }
124
125    /// Complete in math mode.
126    pub fn complete_math(&mut self) -> bool {
127        // Behind existing atom or identifier: "$a|$" or "$abc|$".
128        if !is_triggered_by_punc(self.worker.trigger_character)
129            && matches!(
130                self.cursor.leaf.kind(),
131                SyntaxKind::Text | SyntaxKind::MathIdent | SyntaxKind::MathText
132            )
133        {
134            self.cursor.from = self.cursor.leaf.offset();
135            self.scope_completions(true);
136            self.snippet_completions(Some(InterpretMode::Math), None);
137            return true;
138        }
139
140        // Anywhere: "$|$".
141        if !is_triggered_by_punc(self.worker.trigger_character) && self.worker.explicit {
142            self.cursor.from = self.cursor.cursor;
143            self.scope_completions(true);
144            self.snippet_completions(Some(InterpretMode::Math), None);
145            return true;
146        }
147
148        false
149    }
150
151    /// Complete in code mode.
152    pub fn complete_code(&mut self) -> bool {
153        // Start of an interpolated identifier: "#|".
154        if self.cursor.leaf.kind() == SyntaxKind::Hash {
155            self.cursor.from = self.cursor.cursor;
156            self.code_completions(true);
157
158            return true;
159        }
160
161        // Start of an interpolated identifier: "#pa|".
162        if self.cursor.leaf.kind() == SyntaxKind::Ident {
163            self.cursor.from = self.cursor.leaf.offset();
164            self.code_completions(is_hash_expr(&self.cursor.leaf));
165            return true;
166        }
167
168        // Behind a half-completed context block: "context |".
169        if let Some(prev) = self.cursor.leaf.prev_leaf()
170            && prev.kind() == SyntaxKind::Context
171        {
172            self.cursor.from = self.cursor.cursor;
173            self.code_completions(false);
174            return true;
175        }
176
177        // An existing identifier: "{ pa| }".
178        if self.cursor.leaf.kind() == SyntaxKind::Ident
179            && !matches!(
180                self.cursor.leaf.parent_kind(),
181                Some(SyntaxKind::FieldAccess)
182            )
183        {
184            self.cursor.from = self.cursor.leaf.offset();
185            self.code_completions(false);
186            return true;
187        }
188
189        // Anywhere: "{ | }".
190        // But not within or after an expression.
191        // ctx.explicit &&
192        if self.cursor.leaf.kind().is_trivia()
193            || (matches!(
194                self.cursor.leaf.kind(),
195                SyntaxKind::LeftParen | SyntaxKind::LeftBrace
196            ) || (matches!(self.cursor.leaf.kind(), SyntaxKind::Colon)
197                && self.cursor.leaf.parent_kind() == Some(SyntaxKind::ShowRule)))
198        {
199            self.cursor.from = self.cursor.cursor;
200            self.code_completions(false);
201            return true;
202        }
203
204        false
205    }
206
207    /// Add completions for expression snippets.
208    fn code_completions(&mut self, hash: bool) {
209        // todo: filter code completions
210        // matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Type(_) |
211        // Value::Module(_))
212        self.scope_completions(true);
213
214        self.snippet_completions(Some(InterpretMode::Code), None);
215
216        if !hash {
217            self.snippet_completion(
218                "function",
219                "(${params}) => ${output}",
220                "Creates an unnamed function.",
221            );
222        }
223    }
224}