1use std::collections::HashSet;
12
13use gruel_parser::ast::{
14 AssignTarget, Ast, BlockExpr, Expr, Function, Ident, Item, Method, Pattern, Statement,
15};
16use lasso::ThreadedRodeo;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum CompletionKind {
20 Function,
21 Struct,
22 Enum,
23 Interface,
24 Derive,
25 Constant,
26 Field,
27 EnumMember,
28 Variable,
29 Method,
30 Keyword,
31 Intrinsic,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct CompletionItem {
36 pub label: String,
37 pub kind: CompletionKind,
38 pub detail: Option<String>,
39}
40
41const KEYWORDS: &[&str] = &[
42 "fn",
43 "let",
44 "mut",
45 "struct",
46 "enum",
47 "interface",
48 "derive",
49 "const",
50 "pub",
51 "if",
52 "else",
53 "match",
54 "while",
55 "for",
56 "loop",
57 "in",
58 "break",
59 "continue",
60 "return",
61 "true",
62 "false",
63 "self",
64 "Self",
65];
66
67pub fn complete_at(
70 ast: &Ast,
71 interner: &ThreadedRodeo,
72 file_id: gruel_util::FileId,
73 byte: u32,
74 trigger: Option<char>,
75) -> Vec<CompletionItem> {
76 let mut items = Vec::new();
77 let mut seen: HashSet<String> = HashSet::new();
78 let mut push = |items: &mut Vec<CompletionItem>, item: CompletionItem| {
79 if seen.insert(item.label.clone()) {
80 items.push(item);
81 }
82 };
83
84 match trigger {
85 Some('@') => {
86 for def in gruel_intrinsics::INTRINSICS.iter() {
87 push(
88 &mut items,
89 CompletionItem {
90 label: format!("@{}", def.name),
91 kind: CompletionKind::Intrinsic,
92 detail: Some(def.summary.to_string()),
93 },
94 );
95 }
96 return items;
97 }
98 Some('.') => {
99 push_fields_and_methods(ast, interner, &mut items, &mut seen);
104 return items;
105 }
106 _ => {}
107 }
108
109 let enclosing = enclosing_function(ast, file_id, byte);
111 if let Some(f) = enclosing {
112 for p in &f.params {
113 push(
114 &mut items,
115 CompletionItem {
116 label: interner.resolve(&p.name.name).to_string(),
117 kind: CompletionKind::Variable,
118 detail: None,
119 },
120 );
121 }
122 collect_lets(&f.body, interner, &mut |label| {
123 push(
124 &mut items,
125 CompletionItem {
126 label,
127 kind: CompletionKind::Variable,
128 detail: None,
129 },
130 )
131 });
132 }
133
134 for item in &ast.items {
135 match item {
136 Item::Function(f) => push(
137 &mut items,
138 CompletionItem {
139 label: interner.resolve(&f.name.name).to_string(),
140 kind: CompletionKind::Function,
141 detail: None,
142 },
143 ),
144 Item::Struct(s) => push(
145 &mut items,
146 CompletionItem {
147 label: interner.resolve(&s.name.name).to_string(),
148 kind: CompletionKind::Struct,
149 detail: None,
150 },
151 ),
152 Item::Enum(e) => push(
153 &mut items,
154 CompletionItem {
155 label: interner.resolve(&e.name.name).to_string(),
156 kind: CompletionKind::Enum,
157 detail: None,
158 },
159 ),
160 Item::Interface(i) => push(
161 &mut items,
162 CompletionItem {
163 label: interner.resolve(&i.name.name).to_string(),
164 kind: CompletionKind::Interface,
165 detail: None,
166 },
167 ),
168 Item::Derive(d) => push(
169 &mut items,
170 CompletionItem {
171 label: interner.resolve(&d.name.name).to_string(),
172 kind: CompletionKind::Derive,
173 detail: None,
174 },
175 ),
176 Item::Const(c) => push(
177 &mut items,
178 CompletionItem {
179 label: interner.resolve(&c.name.name).to_string(),
180 kind: CompletionKind::Constant,
181 detail: None,
182 },
183 ),
184 _ => {}
185 }
186 }
187
188 for kw in KEYWORDS {
189 push(
190 &mut items,
191 CompletionItem {
192 label: (*kw).to_string(),
193 kind: CompletionKind::Keyword,
194 detail: None,
195 },
196 );
197 }
198
199 items
200}
201
202fn enclosing_function(ast: &Ast, file_id: gruel_util::FileId, byte: u32) -> Option<&Function> {
203 for item in &ast.items {
204 if let Item::Function(f) = item {
205 if f.span.file_id == file_id && byte >= f.span.start && byte <= f.span.end {
206 return Some(f);
207 }
208 }
209 }
210 None
211}
212
213fn collect_lets(expr: &Expr, interner: &ThreadedRodeo, push: &mut impl FnMut(String)) {
214 match expr {
215 Expr::Block(b) => collect_lets_block(b, interner, push),
216 _ => {}
217 }
218}
219
220fn collect_lets_block(b: &BlockExpr, interner: &ThreadedRodeo, push: &mut impl FnMut(String)) {
221 for stmt in &b.statements {
222 match stmt {
223 Statement::Let(l) => {
224 if let Pattern::Ident { name, .. } = &l.pattern {
225 push(interner.resolve(&name.name).to_string());
226 }
227 if let Expr::Block(_) = &*l.init {
228 collect_lets(&l.init, interner, push);
229 }
230 }
231 Statement::Assign(_) => {}
232 Statement::Expr(e) => collect_lets(e, interner, push),
233 }
234 }
235 collect_lets(&b.expr, interner, push);
236}
237
238fn push_fields_and_methods(
239 ast: &Ast,
240 interner: &ThreadedRodeo,
241 items: &mut Vec<CompletionItem>,
242 seen: &mut HashSet<String>,
243) {
244 let mut push = |items: &mut Vec<CompletionItem>, item: CompletionItem| {
245 if seen.insert(item.label.clone()) {
246 items.push(item);
247 }
248 };
249
250 for item in &ast.items {
251 match item {
252 Item::Struct(s) => {
253 for f in &s.fields {
254 push(
255 items,
256 CompletionItem {
257 label: interner.resolve(&f.name.name).to_string(),
258 kind: CompletionKind::Field,
259 detail: None,
260 },
261 );
262 }
263 for m in &s.methods {
264 push(
265 items,
266 CompletionItem {
267 label: interner.resolve(&m.name.name).to_string(),
268 kind: CompletionKind::Method,
269 detail: None,
270 },
271 );
272 }
273 }
274 Item::Enum(e) => {
275 for v in &e.variants {
276 push(
277 items,
278 CompletionItem {
279 label: interner.resolve(&v.name.name).to_string(),
280 kind: CompletionKind::EnumMember,
281 detail: None,
282 },
283 );
284 }
285 for m in &e.methods {
286 push(
287 items,
288 CompletionItem {
289 label: interner.resolve(&m.name.name).to_string(),
290 kind: CompletionKind::Method,
291 detail: None,
292 },
293 );
294 }
295 }
296 Item::Derive(d) => {
297 for m in &d.methods {
298 push(
299 items,
300 CompletionItem {
301 label: interner.resolve(&m.name.name).to_string(),
302 kind: CompletionKind::Method,
303 detail: None,
304 },
305 );
306 }
307 }
308 Item::Interface(i) => {
309 for sig in &i.methods {
310 push(
311 items,
312 CompletionItem {
313 label: interner.resolve(&sig.name.name).to_string(),
314 kind: CompletionKind::Method,
315 detail: None,
316 },
317 );
318 }
319 }
320 _ => {}
321 }
322 }
323}
324
325#[allow(dead_code)]
328fn _suppress() {
329 let _: Option<Ident> = None;
330 let _: Option<&Method> = None;
331 let _: Option<&dyn Fn(&AssignTarget) -> ()> = None;
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use gruel_compiler::{
338 FileId, PreviewFeatures, SourceFile, merge_symbols, parse_all_files_with_preview,
339 };
340
341 fn parse(source: &str) -> (Ast, ThreadedRodeo) {
342 let sources = vec![SourceFile::new("main.gruel", source, FileId::new(1))];
343 let parsed = parse_all_files_with_preview(&sources, &PreviewFeatures::default()).unwrap();
344 let merged = merge_symbols(parsed).unwrap();
345 (merged.ast, merged.interner)
346 }
347
348 #[test]
349 fn intrinsic_completion_after_at() {
350 let src = "fn main() -> i32 { 0 }";
351 let (ast, interner) = parse(src);
352 let items = complete_at(&ast, &interner, FileId::new(1), 19, Some('@'));
353 assert!(!items.is_empty());
354 assert!(items.iter().all(|i| i.label.starts_with('@')));
355 assert!(items.iter().any(|i| i.kind == CompletionKind::Intrinsic));
356 }
357
358 #[test]
359 fn dot_completion_surfaces_fields_and_methods() {
360 let src = r#"struct Point { x: i32, y: i32, fn sum(self) -> i32 { self.x + self.y } }
361fn main() -> i32 { 0 }"#;
362 let (ast, interner) = parse(src);
363 let items = complete_at(&ast, &interner, FileId::new(1), 30, Some('.'));
364 let labels: Vec<_> = items.iter().map(|i| i.label.as_str()).collect();
365 assert!(labels.contains(&"x"));
366 assert!(labels.contains(&"y"));
367 assert!(labels.contains(&"sum"));
368 }
369
370 #[test]
371 fn generic_context_includes_top_level_items() {
372 let src = "fn foo() -> i32 { 0 }\nstruct Bar { x: i32 }\nfn main() -> i32 { 0 }";
373 let (ast, interner) = parse(src);
374 let byte = src.find("0 }").unwrap() as u32;
376 let items = complete_at(&ast, &interner, FileId::new(1), byte, None);
377 let labels: Vec<_> = items.iter().map(|i| i.label.as_str()).collect();
378 assert!(labels.contains(&"foo"));
379 assert!(labels.contains(&"Bar"));
380 assert!(labels.contains(&"main"));
381 assert!(labels.contains(&"if"));
383 assert!(labels.contains(&"let"));
384 }
385
386 #[test]
387 fn generic_context_includes_locals_in_function() {
388 let src = "fn main() -> i32 { let answer = 42; 0 }";
389 let (ast, interner) = parse(src);
390 let byte = src.find("0 }").unwrap() as u32;
391 let items = complete_at(&ast, &interner, FileId::new(1), byte, None);
392 assert!(
393 items
394 .iter()
395 .any(|i| i.label == "answer" && i.kind == CompletionKind::Variable),
396 "expected `answer` in completion, got: {:?}",
397 items.iter().map(|i| &i.label).collect::<Vec<_>>()
398 );
399 }
400}