ricecoder_ide/
builtin_provider.rs

1//! Built-in language providers implementation
2//!
3//! This module implements the IdeProvider trait for built-in language support
4//! (Rust, TypeScript, Python) using basic pattern matching and analysis.
5
6use crate::error::IdeResult;
7use crate::provider::IdeProvider;
8use crate::types::*;
9use async_trait::async_trait;
10use tracing::debug;
11
12/// Built-in provider for Rust
13pub struct RustProvider;
14
15/// Built-in provider for TypeScript/JavaScript
16pub struct TypeScriptProvider;
17
18/// Built-in provider for Python
19pub struct PythonProvider;
20
21#[async_trait]
22impl IdeProvider for RustProvider {
23    async fn get_completions(&self, params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
24        debug!("Getting completions from built-in Rust provider");
25
26        let mut completions = Vec::new();
27
28        // Basic Rust keyword completions
29        if params.context.contains("fn ") {
30            completions.push(CompletionItem {
31                label: "fn".to_string(),
32                kind: CompletionItemKind::Keyword,
33                detail: Some("function declaration".to_string()),
34                documentation: Some("Declare a function".to_string()),
35                insert_text: "fn ${1:name}(${2:args}) {\n    ${3:body}\n}".to_string(),
36            });
37        }
38
39        if params.context.contains("let ") {
40            completions.push(CompletionItem {
41                label: "let".to_string(),
42                kind: CompletionItemKind::Keyword,
43                detail: Some("variable binding".to_string()),
44                documentation: Some("Bind a variable".to_string()),
45                insert_text: "let ${1:name} = ${2:value};".to_string(),
46            });
47        }
48
49        if params.context.contains("struct ") {
50            completions.push(CompletionItem {
51                label: "struct".to_string(),
52                kind: CompletionItemKind::Keyword,
53                detail: Some("struct definition".to_string()),
54                documentation: Some("Define a struct".to_string()),
55                insert_text: "struct ${1:Name} {\n    ${2:fields}\n}".to_string(),
56            });
57        }
58
59        if params.context.contains("impl ") {
60            completions.push(CompletionItem {
61                label: "impl".to_string(),
62                kind: CompletionItemKind::Keyword,
63                detail: Some("implementation block".to_string()),
64                documentation: Some("Implement methods for a type".to_string()),
65                insert_text: "impl ${1:Type} {\n    ${2:methods}\n}".to_string(),
66            });
67        }
68
69        Ok(completions)
70    }
71
72    async fn get_diagnostics(&self, params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
73        debug!("Getting diagnostics from built-in Rust provider");
74
75        let mut diagnostics = Vec::new();
76
77        // Basic Rust diagnostics
78        if params.source.contains("let ") && !params.source.contains("let _") {
79            // Check for unused variables
80            if let Some(var_name) = Self::extract_variable_name(&params.source) {
81                if !params.source.contains(&format!("_{}", var_name)) && !params.source.contains(&var_name[1..]) {
82                    diagnostics.push(Diagnostic {
83                        range: Range {
84                            start: Position {
85                                line: 0,
86                                character: 0,
87                            },
88                            end: Position {
89                                line: 0,
90                                character: 10,
91                            },
92                        },
93                        severity: DiagnosticSeverity::Warning,
94                        message: format!("unused variable: `{}`", var_name),
95                        source: "rust-builtin".to_string(),
96                    });
97                }
98            }
99        }
100
101        Ok(diagnostics)
102    }
103
104    async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
105        debug!("Getting hover from built-in Rust provider");
106        Ok(None)
107    }
108
109    async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
110        debug!("Getting definition from built-in Rust provider");
111        Ok(None)
112    }
113
114    fn is_available(&self, language: &str) -> bool {
115        language == "rust"
116    }
117
118    fn name(&self) -> &str {
119        "rust-builtin"
120    }
121}
122
123impl RustProvider {
124    /// Extract variable name from let binding
125    fn extract_variable_name(source: &str) -> Option<String> {
126        if let Some(start) = source.find("let ") {
127            let after_let = &source[start + 4..];
128            if let Some(end) = after_let.find([' ', '=', ':']) {
129                return Some(after_let[..end].trim().to_string());
130            }
131        }
132        None
133    }
134}
135
136#[async_trait]
137impl IdeProvider for TypeScriptProvider {
138    async fn get_completions(&self, params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
139        debug!("Getting completions from built-in TypeScript provider");
140
141        let mut completions = Vec::new();
142
143        // Basic TypeScript keyword completions
144        if params.context.contains("function ") || params.context.contains("const ") {
145            completions.push(CompletionItem {
146                label: "function".to_string(),
147                kind: CompletionItemKind::Keyword,
148                detail: Some("function declaration".to_string()),
149                documentation: Some("Declare a function".to_string()),
150                insert_text: "function ${1:name}(${2:args}) {\n    ${3:body}\n}".to_string(),
151            });
152        }
153
154        if params.context.contains("const ") {
155            completions.push(CompletionItem {
156                label: "const".to_string(),
157                kind: CompletionItemKind::Keyword,
158                detail: Some("constant binding".to_string()),
159                documentation: Some("Declare a constant".to_string()),
160                insert_text: "const ${1:name} = ${2:value};".to_string(),
161            });
162        }
163
164        if params.context.contains("interface ") {
165            completions.push(CompletionItem {
166                label: "interface".to_string(),
167                kind: CompletionItemKind::Keyword,
168                detail: Some("interface definition".to_string()),
169                documentation: Some("Define an interface".to_string()),
170                insert_text: "interface ${1:Name} {\n    ${2:properties}\n}".to_string(),
171            });
172        }
173
174        if params.context.contains("class ") {
175            completions.push(CompletionItem {
176                label: "class".to_string(),
177                kind: CompletionItemKind::Keyword,
178                detail: Some("class definition".to_string()),
179                documentation: Some("Define a class".to_string()),
180                insert_text: "class ${1:Name} {\n    ${2:members}\n}".to_string(),
181            });
182        }
183
184        Ok(completions)
185    }
186
187    async fn get_diagnostics(&self, params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
188        debug!("Getting diagnostics from built-in TypeScript provider");
189
190        let mut diagnostics = Vec::new();
191
192        // Basic TypeScript diagnostics
193        if params.source.contains("const ") && !params.source.contains("const _") {
194            // Check for unused variables
195            if let Some(var_name) = Self::extract_variable_name(&params.source) {
196                if !params.source.contains(&format!("_{}", var_name)) && !params.source.contains(&var_name[1..]) {
197                    diagnostics.push(Diagnostic {
198                        range: Range {
199                            start: Position {
200                                line: 0,
201                                character: 0,
202                            },
203                            end: Position {
204                                line: 0,
205                                character: 10,
206                            },
207                        },
208                        severity: DiagnosticSeverity::Warning,
209                        message: format!("'{}' is declared but its value is never used", var_name),
210                        source: "typescript-builtin".to_string(),
211                    });
212                }
213            }
214        }
215
216        Ok(diagnostics)
217    }
218
219    async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
220        debug!("Getting hover from built-in TypeScript provider");
221        Ok(None)
222    }
223
224    async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
225        debug!("Getting definition from built-in TypeScript provider");
226        Ok(None)
227    }
228
229    fn is_available(&self, language: &str) -> bool {
230        language == "typescript" || language == "javascript"
231    }
232
233    fn name(&self) -> &str {
234        "typescript-builtin"
235    }
236}
237
238impl TypeScriptProvider {
239    /// Extract variable name from const binding
240    fn extract_variable_name(source: &str) -> Option<String> {
241        if let Some(start) = source.find("const ") {
242            let after_const = &source[start + 6..];
243            if let Some(end) = after_const.find([' ', '=', ':']) {
244                return Some(after_const[..end].trim().to_string());
245            }
246        }
247        None
248    }
249}
250
251#[async_trait]
252impl IdeProvider for PythonProvider {
253    async fn get_completions(&self, params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
254        debug!("Getting completions from built-in Python provider");
255
256        let mut completions = Vec::new();
257
258        // Basic Python keyword completions
259        if params.context.contains("def ") {
260            completions.push(CompletionItem {
261                label: "def".to_string(),
262                kind: CompletionItemKind::Keyword,
263                detail: Some("function definition".to_string()),
264                documentation: Some("Define a function".to_string()),
265                insert_text: "def ${1:name}(${2:args}):\n    ${3:body}".to_string(),
266            });
267        }
268
269        if params.context.contains("class ") {
270            completions.push(CompletionItem {
271                label: "class".to_string(),
272                kind: CompletionItemKind::Keyword,
273                detail: Some("class definition".to_string()),
274                documentation: Some("Define a class".to_string()),
275                insert_text: "class ${1:Name}:\n    ${2:body}".to_string(),
276            });
277        }
278
279        if params.context.contains("if ") {
280            completions.push(CompletionItem {
281                label: "if".to_string(),
282                kind: CompletionItemKind::Keyword,
283                detail: Some("conditional statement".to_string()),
284                documentation: Some("Conditional execution".to_string()),
285                insert_text: "if ${1:condition}:\n    ${2:body}".to_string(),
286            });
287        }
288
289        if params.context.contains("for ") {
290            completions.push(CompletionItem {
291                label: "for".to_string(),
292                kind: CompletionItemKind::Keyword,
293                detail: Some("loop statement".to_string()),
294                documentation: Some("Iterate over a sequence".to_string()),
295                insert_text: "for ${1:item} in ${2:sequence}:\n    ${3:body}".to_string(),
296            });
297        }
298
299        Ok(completions)
300    }
301
302    async fn get_diagnostics(&self, params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
303        debug!("Getting diagnostics from built-in Python provider");
304
305        let mut diagnostics = Vec::new();
306
307        // Basic Python diagnostics
308        if params.source.contains("import ") {
309            // Check for unused imports
310            if let Some(module_name) = Self::extract_import_name(&params.source) {
311                if !params.source.contains(&module_name) || params.source.matches(&module_name).count() == 1 {
312                    diagnostics.push(Diagnostic {
313                        range: Range {
314                            start: Position {
315                                line: 0,
316                                character: 0,
317                            },
318                            end: Position {
319                                line: 0,
320                                character: 10,
321                            },
322                        },
323                        severity: DiagnosticSeverity::Warning,
324                        message: format!("'{}' imported but unused", module_name),
325                        source: "python-builtin".to_string(),
326                    });
327                }
328            }
329        }
330
331        Ok(diagnostics)
332    }
333
334    async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
335        debug!("Getting hover from built-in Python provider");
336        Ok(None)
337    }
338
339    async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
340        debug!("Getting definition from built-in Python provider");
341        Ok(None)
342    }
343
344    fn is_available(&self, language: &str) -> bool {
345        language == "python"
346    }
347
348    fn name(&self) -> &str {
349        "python-builtin"
350    }
351}
352
353impl PythonProvider {
354    /// Extract module name from import statement
355    fn extract_import_name(source: &str) -> Option<String> {
356        if let Some(start) = source.find("import ") {
357            let after_import = &source[start + 7..];
358            let end = after_import
359                .find([' ', '\n', ';'])
360                .unwrap_or(after_import.len());
361            if end > 0 {
362                return Some(after_import[..end].trim().to_string());
363            }
364        }
365        None
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[tokio::test]
374    async fn test_rust_provider_completions() {
375        let provider = RustProvider;
376        let params = CompletionParams {
377            language: "rust".to_string(),
378            file_path: "src/main.rs".to_string(),
379            position: Position {
380                line: 10,
381                character: 5,
382            },
383            context: "fn test".to_string(),
384        };
385
386        let result = provider.get_completions(&params).await;
387        assert!(result.is_ok());
388        assert!(!result.unwrap().is_empty());
389    }
390
391    #[tokio::test]
392    async fn test_rust_provider_is_available() {
393        let provider = RustProvider;
394        assert!(provider.is_available("rust"));
395        assert!(!provider.is_available("typescript"));
396    }
397
398    #[tokio::test]
399    async fn test_typescript_provider_completions() {
400        let provider = TypeScriptProvider;
401        let params = CompletionParams {
402            language: "typescript".to_string(),
403            file_path: "src/main.ts".to_string(),
404            position: Position {
405                line: 10,
406                character: 5,
407            },
408            context: "const test".to_string(),
409        };
410
411        let result = provider.get_completions(&params).await;
412        assert!(result.is_ok());
413        assert!(!result.unwrap().is_empty());
414    }
415
416    #[tokio::test]
417    async fn test_typescript_provider_is_available() {
418        let provider = TypeScriptProvider;
419        assert!(provider.is_available("typescript"));
420        assert!(provider.is_available("javascript"));
421        assert!(!provider.is_available("rust"));
422    }
423
424    #[tokio::test]
425    async fn test_python_provider_completions() {
426        let provider = PythonProvider;
427        let params = CompletionParams {
428            language: "python".to_string(),
429            file_path: "main.py".to_string(),
430            position: Position {
431                line: 10,
432                character: 5,
433            },
434            context: "def test".to_string(),
435        };
436
437        let result = provider.get_completions(&params).await;
438        assert!(result.is_ok());
439        assert!(!result.unwrap().is_empty());
440    }
441
442    #[tokio::test]
443    async fn test_python_provider_is_available() {
444        let provider = PythonProvider;
445        assert!(provider.is_available("python"));
446        assert!(!provider.is_available("rust"));
447    }
448
449    #[test]
450    fn test_rust_extract_variable_name() {
451        let source = "let my_var = 5;";
452        let name = RustProvider::extract_variable_name(source);
453        assert_eq!(name, Some("my_var".to_string()));
454    }
455
456    #[test]
457    fn test_typescript_extract_variable_name() {
458        let source = "const my_var = 5;";
459        let name = TypeScriptProvider::extract_variable_name(source);
460        assert_eq!(name, Some("my_var".to_string()));
461    }
462
463    #[test]
464    fn test_python_extract_import_name() {
465        let source = "import os";
466        let name = PythonProvider::extract_import_name(source);
467        assert_eq!(name, Some("os".to_string()));
468    }
469}