ricecoder_ide/
generic_provider.rs1use crate::error::IdeResult;
8use crate::provider::IdeProvider;
9use crate::types::*;
10use async_trait::async_trait;
11use std::collections::HashSet;
12use tracing::debug;
13
14pub struct GenericProvider;
16
17impl Default for GenericProvider {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl GenericProvider {
24 pub fn new() -> Self {
26 GenericProvider
27 }
28
29 fn extract_words(text: &str) -> Vec<String> {
31 text.split(|c: char| !c.is_alphanumeric() && c != '_')
32 .filter(|w| !w.is_empty())
33 .map(|w| w.to_string())
34 .collect()
35 }
36
37 fn get_unique_words(text: &str) -> Vec<String> {
39 let words = Self::extract_words(text);
40 let mut unique: Vec<String> = words.into_iter().collect::<HashSet<_>>().into_iter().collect();
41 unique.sort();
42 unique
43 }
44
45 fn has_syntax_error(source: &str) -> bool {
47 let open_braces = source.matches('{').count();
48 let close_braces = source.matches('}').count();
49 let open_parens = source.matches('(').count();
50 let close_parens = source.matches(')').count();
51 let open_brackets = source.matches('[').count();
52 let close_brackets = source.matches(']').count();
53
54 open_braces != close_braces || open_parens != close_parens || open_brackets != close_brackets
55 }
56}
57
58#[async_trait]
59impl IdeProvider for GenericProvider {
60 async fn get_completions(&self, params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
61 debug!("Getting completions from generic text-based provider");
62
63 let mut completions = Vec::new();
64
65 let words = Self::get_unique_words(¶ms.context);
67
68 for word in words.iter().take(10) {
69 completions.push(CompletionItem {
71 label: word.clone(),
72 kind: CompletionItemKind::Text,
73 detail: Some("word suggestion".to_string()),
74 documentation: None,
75 insert_text: word.clone(),
76 });
77 }
78
79 Ok(completions)
80 }
81
82 async fn get_diagnostics(&self, params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
83 debug!("Getting diagnostics from generic text-based provider");
84
85 let mut diagnostics = Vec::new();
86
87 if Self::has_syntax_error(¶ms.source) {
89 diagnostics.push(Diagnostic {
90 range: Range {
91 start: Position {
92 line: 0,
93 character: 0,
94 },
95 end: Position {
96 line: 0,
97 character: 10,
98 },
99 },
100 severity: DiagnosticSeverity::Error,
101 message: "Mismatched brackets or parentheses".to_string(),
102 source: "generic".to_string(),
103 });
104 }
105
106 for (line_num, line) in params.source.lines().enumerate() {
108 if line.ends_with(' ') || line.ends_with('\t') {
109 diagnostics.push(Diagnostic {
110 range: Range {
111 start: Position {
112 line: line_num as u32,
113 character: (line.len() - 1) as u32,
114 },
115 end: Position {
116 line: line_num as u32,
117 character: line.len() as u32,
118 },
119 },
120 severity: DiagnosticSeverity::Hint,
121 message: "Trailing whitespace".to_string(),
122 source: "generic".to_string(),
123 });
124 }
125 }
126
127 Ok(diagnostics)
128 }
129
130 async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
131 debug!("Getting hover from generic text-based provider");
132 Ok(None)
134 }
135
136 async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
137 debug!("Getting definition from generic text-based provider");
138 Ok(None)
140 }
141
142 fn is_available(&self, _language: &str) -> bool {
143 true
145 }
146
147 fn name(&self) -> &str {
148 "generic"
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_extract_words() {
158 let text = "hello world test_var";
159 let words = GenericProvider::extract_words(text);
160 assert_eq!(words.len(), 3);
161 assert!(words.contains(&"hello".to_string()));
162 assert!(words.contains(&"world".to_string()));
163 assert!(words.contains(&"test_var".to_string()));
164 }
165
166 #[test]
167 fn test_get_unique_words() {
168 let text = "hello world hello test";
169 let words = GenericProvider::get_unique_words(text);
170 assert_eq!(words.len(), 3);
171 assert!(words.contains(&"hello".to_string()));
172 assert!(words.contains(&"world".to_string()));
173 assert!(words.contains(&"test".to_string()));
174 }
175
176 #[test]
177 fn test_has_syntax_error_balanced() {
178 let source = "{ ( [ ] ) }";
179 assert!(!GenericProvider::has_syntax_error(source));
180 }
181
182 #[test]
183 fn test_has_syntax_error_unbalanced_braces() {
184 let source = "{ ( [ ] ) ";
185 assert!(GenericProvider::has_syntax_error(source));
186 }
187
188 #[test]
189 fn test_has_syntax_error_unbalanced_parens() {
190 let source = "{ ( [ ] }";
191 assert!(GenericProvider::has_syntax_error(source));
192 }
193
194 #[tokio::test]
195 async fn test_generic_provider_completions() {
196 let provider = GenericProvider;
197 let params = CompletionParams {
198 language: "unknown".to_string(),
199 file_path: "file.txt".to_string(),
200 position: Position {
201 line: 0,
202 character: 0,
203 },
204 context: "hello world test".to_string(),
205 };
206
207 let result = provider.get_completions(¶ms).await;
208 assert!(result.is_ok());
209 let completions = result.unwrap();
210 assert!(!completions.is_empty());
211 assert!(completions.iter().any(|c| c.label == "hello"));
212 }
213
214 #[tokio::test]
215 async fn test_generic_provider_diagnostics_syntax_error() {
216 let provider = GenericProvider;
217 let params = DiagnosticsParams {
218 language: "unknown".to_string(),
219 file_path: "file.txt".to_string(),
220 source: "{ ( [ ] ) ".to_string(),
221 };
222
223 let result = provider.get_diagnostics(¶ms).await;
224 assert!(result.is_ok());
225 let diagnostics = result.unwrap();
226 assert!(!diagnostics.is_empty());
227 assert!(diagnostics[0].severity == DiagnosticSeverity::Error);
228 }
229
230 #[tokio::test]
231 async fn test_generic_provider_diagnostics_trailing_whitespace() {
232 let provider = GenericProvider;
233 let params = DiagnosticsParams {
234 language: "unknown".to_string(),
235 file_path: "file.txt".to_string(),
236 source: "hello world \ntest".to_string(),
237 };
238
239 let result = provider.get_diagnostics(¶ms).await;
240 assert!(result.is_ok());
241 let diagnostics = result.unwrap();
242 assert!(!diagnostics.is_empty());
243 assert!(diagnostics.iter().any(|d| d.message.contains("Trailing whitespace")));
244 }
245
246 #[tokio::test]
247 async fn test_generic_provider_is_available() {
248 let provider = GenericProvider;
249 assert!(provider.is_available("rust"));
250 assert!(provider.is_available("typescript"));
251 assert!(provider.is_available("unknown"));
252 }
253
254 #[tokio::test]
255 async fn test_generic_provider_hover() {
256 let provider = GenericProvider;
257 let params = HoverParams {
258 language: "unknown".to_string(),
259 file_path: "file.txt".to_string(),
260 position: Position {
261 line: 0,
262 character: 0,
263 },
264 };
265
266 let result = provider.get_hover(¶ms).await;
267 assert!(result.is_ok());
268 assert!(result.unwrap().is_none());
269 }
270
271 #[tokio::test]
272 async fn test_generic_provider_definition() {
273 let provider = GenericProvider;
274 let params = DefinitionParams {
275 language: "unknown".to_string(),
276 file_path: "file.txt".to_string(),
277 position: Position {
278 line: 0,
279 character: 0,
280 },
281 };
282
283 let result = provider.get_definition(¶ms).await;
284 assert!(result.is_ok());
285 assert!(result.unwrap().is_none());
286 }
287}