1use crate::error::IdeResult;
7use crate::provider::IdeProvider;
8use crate::types::*;
9use async_trait::async_trait;
10use tracing::debug;
11
12pub struct RustProvider;
14
15pub struct TypeScriptProvider;
17
18pub 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 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 if params.source.contains("let ") && !params.source.contains("let _") {
79 if let Some(var_name) = Self::extract_variable_name(¶ms.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 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 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 if params.source.contains("const ") && !params.source.contains("const _") {
194 if let Some(var_name) = Self::extract_variable_name(¶ms.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 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 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 if params.source.contains("import ") {
309 if let Some(module_name) = Self::extract_import_name(¶ms.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 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(¶ms).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(¶ms).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(¶ms).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}