reovim_plugin_completion/
registry.rs1use std::{future::Future, pin::Pin, sync::Arc};
7
8use reovim_core::completion::{CompletionContext, CompletionItem};
9
10pub trait SourceSupport: Send + Sync + 'static {
34 fn source_id(&self) -> &'static str;
36
37 fn priority(&self) -> u32 {
41 100
42 }
43
44 fn is_available(&self, _ctx: &CompletionContext) -> bool {
48 true
49 }
50
51 fn complete<'a>(
55 &'a self,
56 ctx: &'a CompletionContext,
57 content: &'a str,
58 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>>;
59
60 fn resolve<'a>(
64 &'a self,
65 item: &'a CompletionItem,
66 ) -> Pin<Box<dyn Future<Output = CompletionItem> + Send + 'a>> {
67 let item = item.clone();
68 Box::pin(async move { item })
69 }
70
71 fn execute(&self, _item: &CompletionItem) {}
75
76 fn trigger_characters(&self) -> Option<&[char]> {
80 None
81 }
82}
83
84#[derive(Default)]
88pub struct SourceRegistry {
89 sources: Vec<Arc<dyn SourceSupport>>,
90}
91
92impl SourceRegistry {
93 #[must_use]
95 pub fn new() -> Self {
96 Self {
97 sources: Vec::new(),
98 }
99 }
100
101 pub fn register(&mut self, source: Arc<dyn SourceSupport>) {
105 tracing::info!(
106 source_id = source.source_id(),
107 priority = source.priority(),
108 "Registered completion source"
109 );
110
111 let priority = source.priority();
113 let pos = self
114 .sources
115 .iter()
116 .position(|s| s.priority() > priority)
117 .unwrap_or(self.sources.len());
118 self.sources.insert(pos, source);
119 }
120
121 #[must_use]
123 pub fn sources(&self) -> &[Arc<dyn SourceSupport>] {
124 &self.sources
125 }
126
127 pub fn available_sources(&self, ctx: &CompletionContext) -> Vec<Arc<dyn SourceSupport>> {
129 self.sources
130 .iter()
131 .filter(|s| s.is_available(ctx))
132 .cloned()
133 .collect()
134 }
135
136 #[must_use]
138 pub fn get(&self, source_id: &str) -> Option<Arc<dyn SourceSupport>> {
139 self.sources
140 .iter()
141 .find(|s| s.source_id() == source_id)
142 .cloned()
143 }
144
145 #[must_use]
147 pub fn all_trigger_characters(&self) -> Vec<char> {
148 self.sources
149 .iter()
150 .filter_map(|s| s.trigger_characters())
151 .flatten()
152 .copied()
153 .collect()
154 }
155
156 #[must_use]
158 pub fn len(&self) -> usize {
159 self.sources.len()
160 }
161
162 #[must_use]
164 pub fn is_empty(&self) -> bool {
165 self.sources.is_empty()
166 }
167}
168
169impl std::fmt::Debug for SourceRegistry {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 f.debug_struct("SourceRegistry")
172 .field(
173 "sources",
174 &self
175 .sources
176 .iter()
177 .map(|s| s.source_id())
178 .collect::<Vec<_>>(),
179 )
180 .finish()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 struct TestSource {
190 id: &'static str,
191 priority: u32,
192 trigger_chars: Option<&'static [char]>,
193 available: bool,
194 }
195
196 impl TestSource {
197 fn new(id: &'static str) -> Self {
198 Self {
199 id,
200 priority: 100,
201 trigger_chars: None,
202 available: true,
203 }
204 }
205
206 fn with_priority(mut self, priority: u32) -> Self {
207 self.priority = priority;
208 self
209 }
210
211 fn with_trigger_chars(mut self, chars: &'static [char]) -> Self {
212 self.trigger_chars = Some(chars);
213 self
214 }
215
216 fn unavailable(mut self) -> Self {
217 self.available = false;
218 self
219 }
220 }
221
222 impl SourceSupport for TestSource {
223 fn source_id(&self) -> &'static str {
224 self.id
225 }
226
227 fn priority(&self) -> u32 {
228 self.priority
229 }
230
231 fn is_available(&self, _ctx: &CompletionContext) -> bool {
232 self.available
233 }
234
235 fn trigger_characters(&self) -> Option<&[char]> {
236 self.trigger_chars
237 }
238
239 fn complete<'a>(
240 &'a self,
241 _ctx: &'a CompletionContext,
242 _content: &'a str,
243 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>> {
244 Box::pin(async move { vec![CompletionItem::new("test", self.id)] })
245 }
246 }
247
248 fn make_context() -> CompletionContext {
249 CompletionContext::new(0, 0, 0, String::new(), String::new(), 0)
250 }
251
252 #[test]
253 fn test_registry_new_is_empty() {
254 let registry = SourceRegistry::new();
255 assert!(registry.is_empty());
256 assert_eq!(registry.len(), 0);
257 }
258
259 #[test]
260 fn test_registry_register_source() {
261 let mut registry = SourceRegistry::new();
262 registry.register(Arc::new(TestSource::new("test1")));
263
264 assert!(!registry.is_empty());
265 assert_eq!(registry.len(), 1);
266 }
267
268 #[test]
269 fn test_registry_get_by_id() {
270 let mut registry = SourceRegistry::new();
271 registry.register(Arc::new(TestSource::new("source_a")));
272 registry.register(Arc::new(TestSource::new("source_b")));
273
274 let source = registry.get("source_a");
275 assert!(source.is_some());
276 assert_eq!(source.unwrap().source_id(), "source_a");
277
278 let missing = registry.get("nonexistent");
279 assert!(missing.is_none());
280 }
281
282 #[test]
283 fn test_registry_priority_ordering() {
284 let mut registry = SourceRegistry::new();
285
286 registry.register(Arc::new(TestSource::new("low").with_priority(200)));
288 registry.register(Arc::new(TestSource::new("high").with_priority(10)));
289 registry.register(Arc::new(TestSource::new("medium").with_priority(100)));
290
291 let sources = registry.sources();
293 assert_eq!(sources.len(), 3);
294 assert_eq!(sources[0].source_id(), "high");
295 assert_eq!(sources[1].source_id(), "medium");
296 assert_eq!(sources[2].source_id(), "low");
297 }
298
299 #[test]
300 fn test_registry_available_sources() {
301 let mut registry = SourceRegistry::new();
302 registry.register(Arc::new(TestSource::new("available1")));
303 registry.register(Arc::new(TestSource::new("unavailable").unavailable()));
304 registry.register(Arc::new(TestSource::new("available2")));
305
306 let ctx = make_context();
307 let available = registry.available_sources(&ctx);
308
309 assert_eq!(available.len(), 2);
310 let ids: Vec<_> = available.iter().map(|s| s.source_id()).collect();
311 assert!(ids.contains(&"available1"));
312 assert!(ids.contains(&"available2"));
313 assert!(!ids.contains(&"unavailable"));
314 }
315
316 #[test]
317 fn test_registry_all_trigger_characters() {
318 let mut registry = SourceRegistry::new();
319 registry.register(Arc::new(TestSource::new("source1").with_trigger_chars(&['.', ':'])));
320 registry.register(Arc::new(TestSource::new("source2"))); registry.register(Arc::new(TestSource::new("source3").with_trigger_chars(&['/', '<'])));
322
323 let triggers = registry.all_trigger_characters();
324
325 assert_eq!(triggers.len(), 4);
326 assert!(triggers.contains(&'.'));
327 assert!(triggers.contains(&':'));
328 assert!(triggers.contains(&'/'));
329 assert!(triggers.contains(&'<'));
330 }
331
332 #[test]
333 fn test_registry_sources_returns_all() {
334 let mut registry = SourceRegistry::new();
335 registry.register(Arc::new(TestSource::new("a")));
336 registry.register(Arc::new(TestSource::new("b")));
337 registry.register(Arc::new(TestSource::new("c")));
338
339 let sources = registry.sources();
340 assert_eq!(sources.len(), 3);
341 }
342
343 #[test]
344 fn test_registry_debug_format() {
345 let mut registry = SourceRegistry::new();
346 registry.register(Arc::new(TestSource::new("test_source")));
347
348 let debug_str = format!("{:?}", registry);
349 assert!(debug_str.contains("SourceRegistry"));
350 assert!(debug_str.contains("test_source"));
351 }
352
353 #[test]
354 fn test_source_default_implementations() {
355 struct MinimalSource;
356
357 impl SourceSupport for MinimalSource {
358 fn source_id(&self) -> &'static str {
359 "minimal"
360 }
361
362 fn complete<'a>(
363 &'a self,
364 _ctx: &'a CompletionContext,
365 _content: &'a str,
366 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>> {
367 Box::pin(async move { vec![] })
368 }
369 }
370
371 let source = MinimalSource;
372 let ctx = make_context();
373
374 assert_eq!(source.priority(), 100);
376 assert!(source.is_available(&ctx));
377 assert!(source.trigger_characters().is_none());
378 }
379
380 #[tokio::test]
381 async fn test_source_resolve_default() {
382 struct SimpleSource;
383
384 impl SourceSupport for SimpleSource {
385 fn source_id(&self) -> &'static str {
386 "simple"
387 }
388
389 fn complete<'a>(
390 &'a self,
391 _ctx: &'a CompletionContext,
392 _content: &'a str,
393 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>> {
394 Box::pin(async move { vec![] })
395 }
396 }
397
398 let source = SimpleSource;
399 let item = CompletionItem::new("test", "simple");
400
401 let resolved = source.resolve(&item).await;
403 assert_eq!(resolved.label, item.label);
404 assert_eq!(resolved.source, item.source);
405 }
406}