1use std::sync::Arc;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum CompletionKind {
11 #[default]
12 Text,
13 Method,
14 Function,
15 Constructor,
16 Field,
17 Variable,
18 Class,
19 Interface,
20 Module,
21 Property,
22 Unit,
23 Value,
24 Enum,
25 Keyword,
26 Snippet,
27 Color,
28 File,
29 Reference,
30 Folder,
31 EnumMember,
32 Constant,
33 Struct,
34 Event,
35 Operator,
36 TypeParameter,
37}
38
39#[derive(Debug, Clone)]
41pub struct CompletionItem {
42 pub insert_text: String,
44 pub label: String,
46 pub detail: Option<String>,
48 pub documentation: Option<String>,
50 pub source: &'static str,
52 pub kind: CompletionKind,
54 pub sort_priority: u32,
56 pub filter_text: Option<String>,
58 pub score: u32,
60 pub match_indices: Vec<u32>,
62}
63
64impl CompletionItem {
65 #[must_use]
67 pub fn new(insert_text: impl Into<String>, source: &'static str) -> Self {
68 let text = insert_text.into();
69 Self {
70 label: text.clone(),
71 insert_text: text,
72 detail: None,
73 documentation: None,
74 source,
75 kind: CompletionKind::Text,
76 sort_priority: 100,
77 filter_text: None,
78 score: 0,
79 match_indices: Vec::new(),
80 }
81 }
82
83 #[must_use]
85 pub fn with_label(mut self, label: impl Into<String>) -> Self {
86 self.label = label.into();
87 self
88 }
89
90 #[must_use]
92 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
93 self.detail = Some(detail.into());
94 self
95 }
96
97 #[must_use]
99 pub fn with_documentation(mut self, doc: impl Into<String>) -> Self {
100 self.documentation = Some(doc.into());
101 self
102 }
103
104 #[must_use]
106 pub const fn with_kind(mut self, kind: CompletionKind) -> Self {
107 self.kind = kind;
108 self
109 }
110
111 #[must_use]
113 pub const fn with_priority(mut self, priority: u32) -> Self {
114 self.sort_priority = priority;
115 self
116 }
117
118 #[must_use]
120 pub fn with_filter_text(mut self, text: impl Into<String>) -> Self {
121 self.filter_text = Some(text.into());
122 self
123 }
124
125 #[must_use]
127 pub fn filter_text(&self) -> &str {
128 self.filter_text.as_deref().unwrap_or(&self.label)
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct CompletionContext {
135 pub buffer_id: usize,
137 pub file_path: Option<String>,
139 pub cursor_row: u32,
141 pub cursor_col: u32,
143 pub line: String,
145 pub prefix: String,
147 pub word_start_col: u32,
149 pub trigger_char: Option<char>,
151}
152
153impl CompletionContext {
154 #[must_use]
156 #[allow(clippy::missing_const_for_fn)] pub fn new(
158 buffer_id: usize,
159 cursor_row: u32,
160 cursor_col: u32,
161 line: String,
162 prefix: String,
163 word_start_col: u32,
164 ) -> Self {
165 Self {
166 buffer_id,
167 file_path: None,
168 cursor_row,
169 cursor_col,
170 line,
171 prefix,
172 word_start_col,
173 trigger_char: None,
174 }
175 }
176
177 #[must_use]
179 pub fn with_file_path(mut self, path: impl Into<String>) -> Self {
180 self.file_path = Some(path.into());
181 self
182 }
183
184 #[must_use]
186 pub const fn with_trigger_char(mut self, ch: char) -> Self {
187 self.trigger_char = Some(ch);
188 self
189 }
190}
191
192pub trait CompletionProvider: Send + Sync {
197 fn is_available(&self, ctx: &CompletionContext) -> bool;
199
200 fn request_completion(&self, ctx: CompletionContext);
205
206 fn dismiss(&self);
208
209 fn is_active(&self) -> bool;
211}
212
213pub trait CompletionFactory: Send + Sync {
218 fn get_provider(&self) -> Arc<dyn CompletionProvider>;
223}
224
225pub type SharedCompletionFactory = Arc<dyn CompletionFactory>;
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_completion_kind_default() {
234 let kind = CompletionKind::default();
235 assert_eq!(kind, CompletionKind::Text);
236 }
237
238 #[test]
239 fn test_completion_kind_variants() {
240 let kinds = [
242 CompletionKind::Text,
243 CompletionKind::Method,
244 CompletionKind::Function,
245 CompletionKind::Constructor,
246 CompletionKind::Field,
247 CompletionKind::Variable,
248 CompletionKind::Class,
249 CompletionKind::Interface,
250 CompletionKind::Module,
251 CompletionKind::Property,
252 CompletionKind::Unit,
253 CompletionKind::Value,
254 CompletionKind::Enum,
255 CompletionKind::Keyword,
256 CompletionKind::Snippet,
257 CompletionKind::Color,
258 CompletionKind::File,
259 CompletionKind::Reference,
260 CompletionKind::Folder,
261 CompletionKind::EnumMember,
262 CompletionKind::Constant,
263 CompletionKind::Struct,
264 CompletionKind::Event,
265 CompletionKind::Operator,
266 CompletionKind::TypeParameter,
267 ];
268 assert_eq!(kinds.len(), 25);
269 }
270
271 #[test]
272 fn test_completion_item_new() {
273 let item = CompletionItem::new("test_text", "test_source");
274
275 assert_eq!(item.insert_text, "test_text");
276 assert_eq!(item.label, "test_text");
277 assert_eq!(item.source, "test_source");
278 assert_eq!(item.kind, CompletionKind::Text);
279 assert_eq!(item.sort_priority, 100);
280 assert!(item.detail.is_none());
281 assert!(item.documentation.is_none());
282 assert!(item.filter_text.is_none());
283 assert_eq!(item.score, 0);
284 }
285
286 #[test]
287 fn test_completion_item_with_label() {
288 let item = CompletionItem::new("insert", "source").with_label("Display Label");
289
290 assert_eq!(item.insert_text, "insert");
291 assert_eq!(item.label, "Display Label");
292 }
293
294 #[test]
295 fn test_completion_item_with_detail() {
296 let item = CompletionItem::new("func", "source").with_detail("fn() -> i32");
297
298 assert_eq!(item.detail, Some("fn() -> i32".to_string()));
299 }
300
301 #[test]
302 fn test_completion_item_with_documentation() {
303 let item = CompletionItem::new("func", "source")
304 .with_documentation("This function does something useful.");
305
306 assert_eq!(item.documentation, Some("This function does something useful.".to_string()));
307 }
308
309 #[test]
310 fn test_completion_item_with_kind() {
311 let item = CompletionItem::new("func", "source").with_kind(CompletionKind::Function);
312
313 assert_eq!(item.kind, CompletionKind::Function);
314 }
315
316 #[test]
317 fn test_completion_item_with_priority() {
318 let item = CompletionItem::new("item", "source").with_priority(50);
319
320 assert_eq!(item.sort_priority, 50);
321 }
322
323 #[test]
324 fn test_completion_item_with_filter_text() {
325 let item = CompletionItem::new("item", "source").with_filter_text("custom_filter");
326
327 assert_eq!(item.filter_text, Some("custom_filter".to_string()));
328 }
329
330 #[test]
331 fn test_completion_item_filter_text_accessor() {
332 let item1 = CompletionItem::new("label_text", "source");
334 assert_eq!(item1.filter_text(), "label_text");
335
336 let item2 = CompletionItem::new("label", "source").with_filter_text("filter");
338 assert_eq!(item2.filter_text(), "filter");
339 }
340
341 #[test]
342 fn test_completion_item_builder_chain() {
343 let item = CompletionItem::new("complete", "lsp")
344 .with_label("complete()")
345 .with_detail("fn complete() -> Result<()>")
346 .with_documentation("Completes the operation")
347 .with_kind(CompletionKind::Function)
348 .with_priority(10)
349 .with_filter_text("comp");
350
351 assert_eq!(item.insert_text, "complete");
352 assert_eq!(item.label, "complete()");
353 assert_eq!(item.detail, Some("fn complete() -> Result<()>".to_string()));
354 assert_eq!(item.documentation, Some("Completes the operation".to_string()));
355 assert_eq!(item.kind, CompletionKind::Function);
356 assert_eq!(item.sort_priority, 10);
357 assert_eq!(item.filter_text, Some("comp".to_string()));
358 assert_eq!(item.source, "lsp");
359 }
360
361 #[test]
362 fn test_completion_context_new() {
363 let ctx =
364 CompletionContext::new(42, 10, 15, "let x = some".to_string(), "some".to_string(), 8);
365
366 assert_eq!(ctx.buffer_id, 42);
367 assert_eq!(ctx.cursor_row, 10);
368 assert_eq!(ctx.cursor_col, 15);
369 assert_eq!(ctx.line, "let x = some");
370 assert_eq!(ctx.prefix, "some");
371 assert_eq!(ctx.word_start_col, 8);
372 assert!(ctx.file_path.is_none());
373 assert!(ctx.trigger_char.is_none());
374 }
375
376 #[test]
377 fn test_completion_context_with_file_path() {
378 let ctx = CompletionContext::new(0, 0, 0, String::new(), String::new(), 0)
379 .with_file_path("/path/to/file.rs");
380
381 assert_eq!(ctx.file_path, Some("/path/to/file.rs".to_string()));
382 }
383
384 #[test]
385 fn test_completion_context_with_trigger_char() {
386 let ctx =
387 CompletionContext::new(0, 0, 0, String::new(), String::new(), 0).with_trigger_char('.');
388
389 assert_eq!(ctx.trigger_char, Some('.'));
390 }
391
392 #[test]
393 fn test_completion_context_full_builder() {
394 let ctx = CompletionContext::new(1, 5, 10, "text".to_string(), "pre".to_string(), 7)
395 .with_file_path("main.rs")
396 .with_trigger_char(':');
397
398 assert_eq!(ctx.buffer_id, 1);
399 assert_eq!(ctx.cursor_row, 5);
400 assert_eq!(ctx.cursor_col, 10);
401 assert_eq!(ctx.line, "text");
402 assert_eq!(ctx.prefix, "pre");
403 assert_eq!(ctx.word_start_col, 7);
404 assert_eq!(ctx.file_path, Some("main.rs".to_string()));
405 assert_eq!(ctx.trigger_char, Some(':'));
406 }
407
408 #[test]
409 fn test_completion_item_clone() {
410 let item = CompletionItem::new("test", "source")
411 .with_label("label")
412 .with_detail("detail");
413
414 let cloned = item.clone();
415
416 assert_eq!(cloned.insert_text, item.insert_text);
417 assert_eq!(cloned.label, item.label);
418 assert_eq!(cloned.detail, item.detail);
419 }
420
421 #[test]
422 fn test_completion_context_clone() {
423 let ctx = CompletionContext::new(1, 2, 3, "line".to_string(), "p".to_string(), 0)
424 .with_file_path("/file");
425
426 let cloned = ctx.clone();
427
428 assert_eq!(cloned.buffer_id, ctx.buffer_id);
429 assert_eq!(cloned.file_path, ctx.file_path);
430 }
431
432 #[test]
433 fn test_completion_item_debug() {
434 let item = CompletionItem::new("test", "source");
435 let debug_str = format!("{item:?}");
436
437 assert!(debug_str.contains("CompletionItem"));
438 assert!(debug_str.contains("test"));
439 }
440
441 #[test]
442 fn test_completion_context_debug() {
443 let ctx = CompletionContext::new(0, 0, 0, String::new(), String::new(), 0);
444 let debug_str = format!("{ctx:?}");
445
446 assert!(debug_str.contains("CompletionContext"));
447 }
448
449 #[test]
450 fn test_completion_kind_debug() {
451 let kind = CompletionKind::Function;
452 let debug_str = format!("{kind:?}");
453
454 assert!(debug_str.contains("Function"));
455 }
456
457 #[test]
458 fn test_completion_kind_copy() {
459 let kind1 = CompletionKind::Method;
460 let kind2 = kind1; assert_eq!(kind1, kind2);
463 }
464
465 #[test]
466 fn test_completion_kind_eq() {
467 assert_eq!(CompletionKind::Text, CompletionKind::Text);
468 assert_ne!(CompletionKind::Text, CompletionKind::Method);
469 }
470}