Skip to main content

palladium/lsp/
completion.rs

1// Code completion for Palladium LSP
2// "Intelligent suggestions for legendary code"
3
4use super::{LanguageServer, Position};
5use crate::ast::{Item, Program, Type};
6use serde::{Deserialize, Serialize};
7
8/// Completion item
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CompletionItem {
11    /// The label of this completion item
12    pub label: String,
13    /// The kind of this completion item
14    pub kind: Option<CompletionItemKind>,
15    /// A human-readable string with additional information
16    pub detail: Option<String>,
17    /// A human-readable string that represents a doc-comment
18    pub documentation: Option<String>,
19    /// The text to insert
20    pub insert_text: Option<String>,
21    /// The format of the insert text
22    pub insert_text_format: Option<InsertTextFormat>,
23    /// Additional text edits
24    pub additional_text_edits: Option<Vec<TextEdit>>,
25}
26
27/// Completion item kind
28#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
29#[repr(u8)]
30pub enum CompletionItemKind {
31    Text = 1,
32    Method = 2,
33    Function = 3,
34    Constructor = 4,
35    Field = 5,
36    Variable = 6,
37    Class = 7,
38    Interface = 8,
39    Module = 9,
40    Property = 10,
41    Unit = 11,
42    Value = 12,
43    Enum = 13,
44    Keyword = 14,
45    Snippet = 15,
46    Color = 16,
47    File = 17,
48    Reference = 18,
49    Folder = 19,
50    EnumMember = 20,
51    Constant = 21,
52    Struct = 22,
53    Event = 23,
54    Operator = 24,
55    TypeParameter = 25,
56}
57
58/// Insert text format
59#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
60#[repr(u8)]
61pub enum InsertTextFormat {
62    PlainText = 1,
63    Snippet = 2,
64}
65
66/// Text edit
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct TextEdit {
69    pub range: super::Range,
70    pub new_text: String,
71}
72
73/// Completion context
74pub struct CompletionContext {
75    /// The position where completion was triggered
76    pub position: Position,
77    /// The trigger character if any
78    pub trigger_character: Option<String>,
79    /// The current line text
80    pub line_text: String,
81    /// The word being typed
82    pub word: String,
83    /// Is this after a dot (method/field access)?
84    pub is_dot_access: bool,
85    /// Is this after :: (module/type access)?
86    pub is_module_access: bool,
87}
88
89impl LanguageServer {
90    /// Get completions at a position
91    pub fn get_completions(&self, uri: &str, position: Position) -> Vec<CompletionItem> {
92        let mut completions = Vec::new();
93
94        // Get document
95        let doc = match self.documents.get(uri) {
96            Some(doc) => doc,
97            None => return completions,
98        };
99
100        // Get completion context
101        let context = match self.get_completion_context(&doc.content, position) {
102            Some(ctx) => ctx,
103            None => return completions,
104        };
105
106        // Get completions based on context
107        if context.is_dot_access {
108            // Method/field completions
109            completions.extend(self.get_member_completions(&context, doc.ast.as_ref()));
110        } else if context.is_module_access {
111            // Module completions
112            completions.extend(self.get_module_completions(&context));
113        } else {
114            // General completions
115            completions.extend(self.get_general_completions(&context, doc.ast.as_ref()));
116        }
117
118        completions
119    }
120
121    /// Get completion context from position
122    fn get_completion_context(
123        &self,
124        content: &str,
125        position: Position,
126    ) -> Option<CompletionContext> {
127        let lines: Vec<&str> = content.lines().collect();
128        let line_text = lines.get(position.line as usize)?.to_string();
129
130        // Find the word being typed
131        let char_pos = position.character as usize;
132        let line_chars: Vec<char> = line_text.chars().collect();
133
134        // Find word start
135        let mut word_start = char_pos;
136        while word_start > 0 && line_chars.get(word_start - 1)?.is_alphanumeric() {
137            word_start -= 1;
138        }
139
140        let word = line_chars[word_start..char_pos].iter().collect();
141
142        // Check for trigger characters
143        let is_dot_access = word_start > 0 && line_chars.get(word_start - 1) == Some(&'.');
144        let is_module_access = word_start > 1
145            && line_chars.get(word_start - 2) == Some(&':')
146            && line_chars.get(word_start - 1) == Some(&':');
147
148        Some(CompletionContext {
149            position,
150            trigger_character: None,
151            line_text,
152            word,
153            is_dot_access,
154            is_module_access,
155        })
156    }
157
158    /// Get general completions (keywords, functions, variables)
159    fn get_general_completions(
160        &self,
161        context: &CompletionContext,
162        ast: Option<&Program>,
163    ) -> Vec<CompletionItem> {
164        let mut completions = Vec::new();
165
166        // Keywords
167        let keywords = vec![
168            ("fn", "Function declaration"),
169            ("let", "Variable declaration"),
170            ("mut", "Mutable variable"),
171            ("if", "If statement"),
172            ("else", "Else clause"),
173            ("while", "While loop"),
174            ("for", "For loop"),
175            ("return", "Return statement"),
176            ("struct", "Struct declaration"),
177            ("enum", "Enum declaration"),
178            ("trait", "Trait declaration"),
179            ("impl", "Implementation block"),
180            ("pub", "Public visibility"),
181            ("async", "Async function"),
182            ("await", "Await expression"),
183            ("match", "Match expression"),
184            ("break", "Break statement"),
185            ("continue", "Continue statement"),
186            ("const", "Constant declaration"),
187            ("type", "Type alias"),
188            ("use", "Import statement"),
189            ("mod", "Module declaration"),
190            ("Self", "Self type"),
191            ("self", "Self parameter"),
192            ("true", "Boolean true"),
193            ("false", "Boolean false"),
194        ];
195
196        for (keyword, detail) in keywords {
197            if keyword.starts_with(&context.word) {
198                completions.push(CompletionItem {
199                    label: keyword.to_string(),
200                    kind: Some(CompletionItemKind::Keyword),
201                    detail: Some(detail.to_string()),
202                    documentation: None,
203                    insert_text: Some(keyword.to_string()),
204                    insert_text_format: Some(InsertTextFormat::PlainText),
205                    additional_text_edits: None,
206                });
207            }
208        }
209
210        // Built-in types
211        let types = vec![
212            ("i32", "32-bit signed integer"),
213            ("i64", "64-bit signed integer"),
214            ("u32", "32-bit unsigned integer"),
215            ("u64", "64-bit unsigned integer"),
216            ("bool", "Boolean type"),
217            ("String", "UTF-8 string type"),
218            ("Vec", "Dynamic array type"),
219            ("HashMap", "Hash map type"),
220            ("Option", "Optional type"),
221            ("Result", "Result type"),
222        ];
223
224        for (type_name, detail) in types {
225            if type_name.starts_with(&context.word) {
226                completions.push(CompletionItem {
227                    label: type_name.to_string(),
228                    kind: Some(CompletionItemKind::Class),
229                    detail: Some(detail.to_string()),
230                    documentation: None,
231                    insert_text: Some(type_name.to_string()),
232                    insert_text_format: Some(InsertTextFormat::PlainText),
233                    additional_text_edits: None,
234                });
235            }
236        }
237
238        // Functions from AST
239        if let Some(ast) = ast {
240            for item in &ast.items {
241                match item {
242                    Item::Function(func) => {
243                        if func.name.starts_with(&context.word) {
244                            let params: Vec<String> = func
245                                .params
246                                .iter()
247                                .map(|p| format!("{}: {}", p.name, self.type_to_string(&p.ty)))
248                                .collect();
249
250                            let signature = format!(
251                                "fn {}({}){}",
252                                func.name,
253                                params.join(", "),
254                                func.return_type
255                                    .as_ref()
256                                    .map(|t| format!(" -> {}", self.type_to_string(t)))
257                                    .unwrap_or_default()
258                            );
259
260                            completions.push(CompletionItem {
261                                label: func.name.clone(),
262                                kind: Some(CompletionItemKind::Function),
263                                detail: Some(signature),
264                                documentation: None,
265                                insert_text: Some(format!("{}(", func.name)),
266                                insert_text_format: Some(InsertTextFormat::PlainText),
267                                additional_text_edits: None,
268                            });
269                        }
270                    }
271                    Item::Struct(struct_def) => {
272                        if struct_def.name.starts_with(&context.word) {
273                            completions.push(CompletionItem {
274                                label: struct_def.name.clone(),
275                                kind: Some(CompletionItemKind::Struct),
276                                detail: Some(format!("struct {}", struct_def.name)),
277                                documentation: None,
278                                insert_text: Some(struct_def.name.clone()),
279                                insert_text_format: Some(InsertTextFormat::PlainText),
280                                additional_text_edits: None,
281                            });
282                        }
283                    }
284                    Item::Enum(enum_def) => {
285                        if enum_def.name.starts_with(&context.word) {
286                            completions.push(CompletionItem {
287                                label: enum_def.name.clone(),
288                                kind: Some(CompletionItemKind::Enum),
289                                detail: Some(format!("enum {}", enum_def.name)),
290                                documentation: None,
291                                insert_text: Some(enum_def.name.clone()),
292                                insert_text_format: Some(InsertTextFormat::PlainText),
293                                additional_text_edits: None,
294                            });
295                        }
296                    }
297                    _ => {}
298                }
299            }
300        }
301
302        // Built-in functions
303        let builtins = vec![
304            ("print", "fn print(s: String)", "Print a string to stdout"),
305            (
306                "print_int",
307                "fn print_int(n: i64)",
308                "Print an integer to stdout",
309            ),
310            (
311                "string_len",
312                "fn string_len(s: String) -> i64",
313                "Get string length",
314            ),
315            (
316                "string_concat",
317                "fn string_concat(a: String, b: String) -> String",
318                "Concatenate strings",
319            ),
320            (
321                "int_to_string",
322                "fn int_to_string(n: i64) -> String",
323                "Convert integer to string",
324            ),
325            (
326                "string_to_int",
327                "fn string_to_int(s: String) -> Option<i64>",
328                "Parse integer from string",
329            ),
330        ];
331
332        for (name, signature, doc) in builtins {
333            if name.starts_with(&context.word) {
334                completions.push(CompletionItem {
335                    label: name.to_string(),
336                    kind: Some(CompletionItemKind::Function),
337                    detail: Some(signature.to_string()),
338                    documentation: Some(doc.to_string()),
339                    insert_text: Some(format!("{}(", name)),
340                    insert_text_format: Some(InsertTextFormat::PlainText),
341                    additional_text_edits: None,
342                });
343            }
344        }
345
346        completions
347    }
348
349    /// Get member completions (after dot)
350    fn get_member_completions(
351        &self,
352        context: &CompletionContext,
353        ast: Option<&Program>,
354    ) -> Vec<CompletionItem> {
355        let mut completions = Vec::new();
356        
357        // Extract the expression before the dot
358        let line_before_dot = &context.line_text[..context.position.character as usize - 1];
359        
360        // Simple heuristic: find the identifier before the dot
361        let expr_end = line_before_dot.rfind(|c: char| !c.is_alphanumeric() && c != '_');
362        let expr_start = expr_end.map(|i| i + 1).unwrap_or(0);
363        let var_name = &line_before_dot[expr_start..];
364        
365        // Look for struct types in AST
366        if let Some(ast) = ast {
367            // Check if var_name is a known struct type
368            for item in &ast.items {
369                if let Item::Struct(struct_def) = item {
370                    // Simple case: if var name matches struct name (constructor pattern)
371                    if var_name == struct_def.name || var_name.ends_with(&format!(" {}", struct_def.name)) {
372                        // Add field completions
373                        for (field_name, field_ty) in &struct_def.fields {
374                            completions.push(CompletionItem {
375                                label: field_name.clone(),
376                                kind: Some(CompletionItemKind::Field),
377                                detail: Some(format!("{}: {}", field_name, self.type_to_string(field_ty))),
378                                documentation: None,
379                                insert_text: Some(field_name.clone()),
380                                insert_text_format: Some(InsertTextFormat::PlainText),
381                                additional_text_edits: None,
382                            });
383                        }
384                    }
385                }
386            }
387            
388            // String methods
389            if var_name.contains("string") || var_name.contains("str") {
390                let string_methods = vec![
391                    ("len", "fn len() -> i64", "Get the length of the string"),
392                    ("is_empty", "fn is_empty() -> bool", "Check if string is empty"),
393                    ("to_uppercase", "fn to_uppercase() -> String", "Convert to uppercase"),
394                    ("to_lowercase", "fn to_lowercase() -> String", "Convert to lowercase"),
395                    ("trim", "fn trim() -> String", "Remove leading and trailing whitespace"),
396                    ("split", "fn split(sep: String) -> Vec<String>", "Split string by separator"),
397                    ("contains", "fn contains(s: String) -> bool", "Check if string contains substring"),
398                    ("starts_with", "fn starts_with(s: String) -> bool", "Check if string starts with prefix"),
399                    ("ends_with", "fn ends_with(s: String) -> bool", "Check if string ends with suffix"),
400                ];
401                
402                for (name, signature, doc) in string_methods {
403                    if name.starts_with(&context.word) {
404                        completions.push(CompletionItem {
405                            label: name.to_string(),
406                            kind: Some(CompletionItemKind::Method),
407                            detail: Some(signature.to_string()),
408                            documentation: Some(doc.to_string()),
409                            insert_text: Some(format!("{}(", name)),
410                            insert_text_format: Some(InsertTextFormat::PlainText),
411                            additional_text_edits: None,
412                        });
413                    }
414                }
415            }
416            
417            // Array methods
418            if var_name.contains('[') || var_name.contains("array") || var_name.contains("vec") {
419                let array_methods = vec![
420                    ("len", "fn len() -> i64", "Get the length of the array"),
421                    ("is_empty", "fn is_empty() -> bool", "Check if array is empty"),
422                    ("push", "fn push(item: T)", "Add an element to the end"),
423                    ("pop", "fn pop() -> Option<T>", "Remove and return the last element"),
424                    ("get", "fn get(index: i64) -> Option<T>", "Get element at index"),
425                    ("first", "fn first() -> Option<T>", "Get the first element"),
426                    ("last", "fn last() -> Option<T>", "Get the last element"),
427                    ("sort", "fn sort()", "Sort the array in place"),
428                    ("reverse", "fn reverse()", "Reverse the array in place"),
429                ];
430                
431                for (name, signature, doc) in array_methods {
432                    if name.starts_with(&context.word) {
433                        completions.push(CompletionItem {
434                            label: name.to_string(),
435                            kind: Some(CompletionItemKind::Method),
436                            detail: Some(signature.to_string()),
437                            documentation: Some(doc.to_string()),
438                            insert_text: Some(if name == "push" || name == "get" {
439                                format!("{}(", name)
440                            } else {
441                                format!("{}()", name)
442                            }),
443                            insert_text_format: Some(InsertTextFormat::PlainText),
444                            additional_text_edits: None,
445                        });
446                    }
447                }
448            }
449        }
450        
451        completions
452    }
453
454    /// Get module completions (after ::)
455    fn get_module_completions(&self, context: &CompletionContext) -> Vec<CompletionItem> {
456        let mut completions = Vec::new();
457        
458        // Extract the module path before ::
459        let line_before_colons = &context.line_text[..context.position.character as usize - 2];
460        
461        // Find the module/type name
462        let expr_end = line_before_colons.rfind(|c: char| !c.is_alphanumeric() && c != '_');
463        let expr_start = expr_end.map(|i| i + 1).unwrap_or(0);
464        let module_name = &line_before_colons[expr_start..];
465        
466        // Standard library modules
467        if module_name == "std" {
468            let std_modules = vec![
469                ("io", "Input/output functionality"),
470                ("fs", "File system operations"),
471                ("collections", "Data structures"),
472                ("string", "String utilities"),
473                ("math", "Mathematical functions"),
474                ("vec", "Vector operations"),
475                ("hashmap", "Hash map implementation"),
476                ("result", "Result type utilities"),
477                ("option", "Option type utilities"),
478            ];
479            
480            for (name, doc) in std_modules {
481                if name.starts_with(&context.word) {
482                    completions.push(CompletionItem {
483                        label: name.to_string(),
484                        kind: Some(CompletionItemKind::Module),
485                        detail: Some(format!("mod {}", name)),
486                        documentation: Some(doc.to_string()),
487                        insert_text: Some(name.to_string()),
488                        insert_text_format: Some(InsertTextFormat::PlainText),
489                        additional_text_edits: None,
490                    });
491                }
492            }
493        }
494        
495        // Result type methods
496        if module_name == "Result" {
497            let result_methods = vec![
498                ("Ok", "Result::Ok(T) -> Result<T, E>", "Construct a successful result"),
499                ("Err", "Result::Err(E) -> Result<T, E>", "Construct an error result"),
500            ];
501            
502            for (name, signature, doc) in result_methods {
503                if name.starts_with(&context.word) {
504                    completions.push(CompletionItem {
505                        label: name.to_string(),
506                        kind: Some(CompletionItemKind::Constructor),
507                        detail: Some(signature.to_string()),
508                        documentation: Some(doc.to_string()),
509                        insert_text: Some(format!("{}(", name)),
510                        insert_text_format: Some(InsertTextFormat::PlainText),
511                        additional_text_edits: None,
512                    });
513                }
514            }
515        }
516        
517        // Option type methods
518        if module_name == "Option" {
519            let option_methods = vec![
520                ("Some", "Option::Some(T) -> Option<T>", "Construct a Some value"),
521                ("None", "Option::None -> Option<T>", "Construct a None value"),
522            ];
523            
524            for (name, signature, doc) in option_methods {
525                if name.starts_with(&context.word) {
526                    completions.push(CompletionItem {
527                        label: name.to_string(),
528                        kind: Some(CompletionItemKind::Constructor),
529                        detail: Some(signature.to_string()),
530                        documentation: Some(doc.to_string()),
531                        insert_text: Some(if name == "Some" {
532                            format!("{}(", name)
533                        } else {
534                            name.to_string()
535                        }),
536                        insert_text_format: Some(InsertTextFormat::PlainText),
537                        additional_text_edits: None,
538                    });
539                }
540            }
541        }
542        
543        // Enum variants from AST
544        if let Some(doc) = self.documents.get(&context.position.line.to_string()) {
545            if let Some(ast) = &doc.ast {
546                for item in &ast.items {
547                    if let Item::Enum(enum_def) = item {
548                        if enum_def.name == module_name {
549                            for variant in &enum_def.variants {
550                                if variant.name.starts_with(&context.word) {
551                                    let has_fields = match &variant.data {
552                                        crate::ast::EnumVariantData::Unit => false,
553                                        crate::ast::EnumVariantData::Tuple(_) => true,
554                                        crate::ast::EnumVariantData::Struct(_) => true,
555                                    };
556                                    completions.push(CompletionItem {
557                                        label: variant.name.clone(),
558                                        kind: Some(CompletionItemKind::EnumMember),
559                                        detail: Some(format!("{}::{}", enum_def.name, variant.name)),
560                                        documentation: None,
561                                        insert_text: Some(if has_fields {
562                                            format!("{}(", variant.name)
563                                        } else {
564                                            variant.name.clone()
565                                        }),
566                                        insert_text_format: Some(InsertTextFormat::PlainText),
567                                        additional_text_edits: None,
568                                    });
569                                }
570                            }
571                        }
572                    }
573                }
574            }
575        }
576        
577        completions
578    }
579
580    /// Convert type to string
581    #[allow(clippy::only_used_in_recursion)]
582    pub fn type_to_string(&self, ty: &Type) -> String {
583        match ty {
584            Type::I32 => "i32".to_string(),
585            Type::I64 => "i64".to_string(),
586            Type::U32 => "u32".to_string(),
587            Type::U64 => "u64".to_string(),
588            Type::Bool => "bool".to_string(),
589            Type::String => "String".to_string(),
590            Type::Unit => "()".to_string(),
591            Type::Custom(name) => name.clone(),
592            Type::Array(elem, size) => format!("[{}; {}]", self.type_to_string(elem), size),
593            Type::Reference { mutable, inner, .. } => {
594                if *mutable {
595                    format!("&mut {}", self.type_to_string(inner))
596                } else {
597                    format!("&{}", self.type_to_string(inner))
598                }
599            }
600            Type::Future { output } => format!("Future<{}>", self.type_to_string(output)),
601            Type::Generic { name, args } => {
602                if args.is_empty() {
603                    name.clone()
604                } else {
605                    let arg_strs: Vec<String> = args.iter().map(|arg| arg.to_string()).collect();
606                    format!("{}<{}>", name, arg_strs.join(", "))
607                }
608            }
609            Type::TypeParam(name) => name.clone(),
610        }
611    }
612}