ricecoder_completion/engine.rs
1/// Core completion engine with language-agnostic architecture
2///
3/// This module provides the main completion engine and related traits for generating,
4/// ranking, and managing code completions. The engine is designed to be language-agnostic
5/// with pluggable providers for language-specific behavior and external LSP integration.
6///
7/// # Architecture
8///
9/// The completion engine follows a pipeline architecture with external LSP routing:
10///
11/// 1. **External LSP Routing** (Primary): Route to external LSP server if available and configured
12/// 2. **Context Analysis**: Analyze code context to determine available symbols and expected types
13/// 3. **Completion Generation**: Generate suggestions using language-specific provider or generic generator
14/// 4. **Merging**: Merge external LSP completions with internal completions (external takes priority)
15/// 5. **Ranking**: Rank completions by relevance, frequency, and recency
16///
17/// # External LSP Integration
18///
19/// The completion engine integrates with external LSP servers through the `ExternalLspCompletionProxy`.
20/// When a completion request is made:
21///
22/// 1. If an external LSP server is configured for the language, the request is forwarded to it
23/// 2. The external LSP response is transformed to ricecoder's internal model
24/// 3. External completions are merged with internal completions:
25/// - External completions have higher priority (appear first)
26/// - Internal completions are added if they don't duplicate external ones
27/// - Results are sorted by relevance score
28/// 4. If the external LSP is unavailable or times out, the system falls back to internal providers
29///
30/// # Merge Strategy
31///
32/// The merge strategy for combining external and internal completions:
33///
34/// - **Priority**: External completions are prioritized over internal ones
35/// - **Deduplication**: Completions with the same label are deduplicated (external wins)
36/// - **Sorting**: All completions are sorted by relevance score
37/// - **Fallback**: If external LSP fails, internal completions are used as fallback
38///
39/// This ensures users get the best available completions while maintaining graceful degradation.
40///
41/// # Example
42///
43/// ```ignore
44/// use ricecoder_completion::engine::*;
45/// use ricecoder_completion::types::*;
46/// use std::sync::Arc;
47///
48/// // Create engine components
49/// let context_analyzer = Arc::new(GenericContextAnalyzer);
50/// let generator = Arc::new(BasicCompletionGenerator);
51/// let ranker = Arc::new(BasicCompletionRanker::default_weights());
52/// let registry = ProviderRegistry::new();
53///
54/// // Create engine
55/// let engine = GenericCompletionEngine::new(
56/// context_analyzer,
57/// generator,
58/// ranker,
59/// registry,
60/// );
61///
62/// // Generate completions
63/// let completions = engine.generate_completions(
64/// "fn main() { let x = ",
65/// Position::new(0, 20),
66/// "rust",
67/// ).await?;
68/// ```
69use crate::context::ContextAnalyzer;
70use crate::types::*;
71use async_trait::async_trait;
72use std::sync::Arc;
73
74/// Main completion engine trait
75///
76/// Implementations of this trait orchestrate the completion process by coordinating
77/// context analysis, completion generation, and ranking.
78///
79/// # Async Behavior
80///
81/// All methods are async to support non-blocking I/O and streaming responses.
82/// Implementations should handle cancellation gracefully.
83#[async_trait]
84pub trait CompletionEngine: Send + Sync {
85 /// Generate completion suggestions for the given code at the specified position
86 ///
87 /// # Arguments
88 ///
89 /// * `code` - The source code to analyze
90 /// * `position` - The cursor position where completions are requested
91 /// * `language` - The programming language identifier (e.g., "rust", "typescript", "python")
92 ///
93 /// # Returns
94 ///
95 /// A vector of completion items ranked by relevance, or an error if generation fails.
96 ///
97 /// # Errors
98 ///
99 /// Returns `CompletionError` if:
100 /// - Context analysis fails
101 /// - Completion generation fails
102 /// - Ranking fails
103 /// - The language is not supported
104 async fn generate_completions(
105 &self,
106 code: &str,
107 position: Position,
108 language: &str,
109 ) -> CompletionResult<Vec<CompletionItem>>;
110
111 /// Resolve additional details for a completion item
112 ///
113 /// This method is called when the user selects a completion item to resolve
114 /// additional details like documentation, type information, or additional edits.
115 ///
116 /// # Arguments
117 ///
118 /// * `item` - The completion item to resolve
119 ///
120 /// # Returns
121 ///
122 /// The resolved completion item with additional details, or an error if resolution fails.
123 async fn resolve_completion(&self, item: &CompletionItem) -> CompletionResult<CompletionItem>;
124}
125
126/// Generic completion engine implementation
127///
128/// This is the main implementation of the completion engine. It coordinates
129/// external LSP routing, context analysis, completion generation, and ranking
130/// to produce ranked completion suggestions.
131///
132/// # Completion Flow
133///
134/// The engine follows this flow for each completion request:
135///
136/// 1. **External LSP Check**: Check if an external LSP server is configured for the language
137/// 2. **Context Analysis**: Analyze code context to determine available symbols and expected types
138/// 3. **Completion Generation**: Generate suggestions using:
139/// - External LSP server (if available and configured)
140/// - Language-specific provider (if registered)
141/// - Generic completion generator (fallback)
142/// 4. **Merging**: Merge external and internal completions (external takes priority)
143/// 5. **Ranking**: Rank all completions by relevance, frequency, and recency
144///
145/// # Language Support
146///
147/// The engine supports multiple languages through a pluggable provider system:
148/// - If an external LSP server is configured, it will be used for semantic completions
149/// - If a language-specific provider is registered, it will be used as fallback
150/// - Otherwise, the generic completion generator is used as a fallback
151///
152/// # Graceful Degradation
153///
154/// If the external LSP server is unavailable or times out:
155/// - The system falls back to language-specific providers
156/// - If no provider is available, the generic generator is used
157/// - Users always get some completions, even if not semantic
158///
159/// # Example
160///
161/// ```ignore
162/// use ricecoder_completion::engine::*;
163/// use ricecoder_completion::types::*;
164/// use std::sync::Arc;
165///
166/// let engine = GenericCompletionEngine::new(
167/// Arc::new(GenericContextAnalyzer),
168/// Arc::new(BasicCompletionGenerator),
169/// Arc::new(BasicCompletionRanker::default_weights()),
170/// ProviderRegistry::new(),
171/// );
172/// ```
173pub struct GenericCompletionEngine {
174 context_analyzer: Arc<dyn ContextAnalyzer>,
175 generator: Arc<dyn CompletionGenerator>,
176 ranker: Arc<dyn CompletionRanker>,
177 provider_registry: ProviderRegistry,
178}
179
180impl GenericCompletionEngine {
181 /// Create a new completion engine
182 ///
183 /// # Arguments
184 ///
185 /// * `context_analyzer` - Analyzer for determining code context
186 /// * `generator` - Generic completion generator (used as fallback)
187 /// * `ranker` - Ranker for sorting completions
188 /// * `provider_registry` - Registry of language-specific providers
189 pub fn new(
190 context_analyzer: Arc<dyn ContextAnalyzer>,
191 generator: Arc<dyn CompletionGenerator>,
192 ranker: Arc<dyn CompletionRanker>,
193 provider_registry: ProviderRegistry,
194 ) -> Self {
195 Self {
196 context_analyzer,
197 generator,
198 ranker,
199 provider_registry,
200 }
201 }
202}
203
204#[async_trait]
205impl CompletionEngine for GenericCompletionEngine {
206 async fn generate_completions(
207 &self,
208 code: &str,
209 position: Position,
210 language: &str,
211 ) -> CompletionResult<Vec<CompletionItem>> {
212 // Analyze context
213 let context = self
214 .context_analyzer
215 .analyze_context(code, position, language)
216 .await?;
217
218 // Generate completions using language-specific provider if available
219 let mut completions = if let Some(provider) = self.provider_registry.get_provider(language)
220 {
221 provider
222 .generate_completions(code, position, &context)
223 .await?
224 } else {
225 // Fall back to generic completion
226 self.generator
227 .generate_completions(code, position, &context)
228 .await?
229 };
230
231 // Rank completions
232 completions = self.ranker.rank_completions(completions, &context);
233
234 Ok(completions)
235 }
236
237 async fn resolve_completion(&self, item: &CompletionItem) -> CompletionResult<CompletionItem> {
238 Ok(item.clone())
239 }
240}
241
242/// Completion generator trait
243///
244/// Implementations generate completion suggestions based on code context.
245/// This is typically used as a fallback when no language-specific provider is available.
246///
247/// # Implementations
248///
249/// - `BasicCompletionGenerator`: Generic text-based completion
250/// - Language-specific providers: Rust, TypeScript, Python
251#[async_trait]
252pub trait CompletionGenerator: Send + Sync {
253 /// Generate completion suggestions
254 ///
255 /// # Arguments
256 ///
257 /// * `code` - The source code to analyze
258 /// * `position` - The cursor position where completions are requested
259 /// * `context` - The analyzed code context
260 ///
261 /// # Returns
262 ///
263 /// A vector of completion items (not yet ranked), or an error if generation fails.
264 async fn generate_completions(
265 &self,
266 code: &str,
267 position: Position,
268 context: &CompletionContext,
269 ) -> CompletionResult<Vec<CompletionItem>>;
270}
271
272/// Completion ranker trait
273///
274/// Implementations rank completion suggestions by relevance, frequency, and recency.
275/// The ranker is responsible for sorting completions so the most relevant appear first.
276///
277/// # Scoring
278///
279/// Rankers typically combine multiple scoring factors:
280/// - **Relevance**: How well the completion matches the context
281/// - **Frequency**: How often the completion is used
282/// - **Recency**: How recently the completion was used
283///
284/// # Implementations
285///
286/// - `BasicCompletionRanker`: Prefix matching and fuzzy matching
287/// - `AdvancedCompletionRanker`: Advanced scoring with frequency and recency
288pub trait CompletionRanker: Send + Sync {
289 /// Rank completions by relevance and frequency
290 ///
291 /// # Arguments
292 ///
293 /// * `items` - Unranked completion items
294 /// * `context` - The analyzed code context
295 ///
296 /// # Returns
297 ///
298 /// The same completion items, sorted by relevance (highest first).
299 fn rank_completions(
300 &self,
301 items: Vec<CompletionItem>,
302 context: &CompletionContext,
303 ) -> Vec<CompletionItem>;
304
305 /// Score relevance of a completion item
306 ///
307 /// # Arguments
308 ///
309 /// * `item` - The completion item to score
310 /// * `context` - The analyzed code context
311 ///
312 /// # Returns
313 ///
314 /// A relevance score (typically 0.0 to 1.0, where 1.0 is most relevant).
315 fn score_relevance(&self, item: &CompletionItem, context: &CompletionContext) -> f32;
316
317 /// Score frequency of a completion item
318 ///
319 /// # Arguments
320 ///
321 /// * `item` - The completion item to score
322 ///
323 /// # Returns
324 ///
325 /// A frequency score (typically 0.0 to 1.0, where 1.0 is most frequently used).
326 fn score_frequency(&self, item: &CompletionItem) -> f32;
327}
328
329/// Pluggable completion provider for language-specific behavior
330///
331/// Implementations provide language-specific completion suggestions. Providers are
332/// registered in the `ProviderRegistry` and selected based on the language identifier.
333///
334/// # Language Support
335///
336/// Each provider supports a single language. The engine queries the registry to find
337/// the appropriate provider for the current language.
338///
339/// # Implementations
340///
341/// - `RustCompletionProvider`: Rust-specific completions
342/// - `TypeScriptCompletionProvider`: TypeScript-specific completions
343/// - `PythonCompletionProvider`: Python-specific completions
344/// - `GenericTextProvider`: Generic text-based completions
345///
346/// # Example
347///
348/// ```ignore
349/// use ricecoder_completion::providers::RustCompletionProvider;
350/// use ricecoder_completion::engine::CompletionProvider;
351///
352/// let provider = RustCompletionProvider;
353/// assert_eq!(provider.language(), "rust");
354/// ```
355#[async_trait]
356pub trait CompletionProvider: Send + Sync {
357 /// Get the language this provider supports
358 ///
359 /// # Returns
360 ///
361 /// A language identifier string (e.g., "rust", "typescript", "python").
362 fn language(&self) -> &str;
363
364 /// Generate completions for this language
365 ///
366 /// # Arguments
367 ///
368 /// * `code` - The source code to analyze
369 /// * `position` - The cursor position where completions are requested
370 /// * `context` - The analyzed code context
371 ///
372 /// # Returns
373 ///
374 /// A vector of language-specific completion items (not yet ranked), or an error if generation fails.
375 async fn generate_completions(
376 &self,
377 code: &str,
378 position: Position,
379 context: &CompletionContext,
380 ) -> CompletionResult<Vec<CompletionItem>>;
381}
382
383/// Registry for completion providers
384///
385/// The provider registry manages language-specific completion providers.
386/// It allows registering, retrieving, and listing available providers.
387///
388/// # Example
389///
390/// ```ignore
391/// use ricecoder_completion::engine::ProviderRegistry;
392/// use ricecoder_completion::providers::RustCompletionProvider;
393/// use std::sync::Arc;
394///
395/// let mut registry = ProviderRegistry::new();
396/// registry.register(Arc::new(RustCompletionProvider));
397///
398/// // Get provider for Rust
399/// let provider = registry.get_provider("rust");
400/// assert!(provider.is_some());
401///
402/// // List all supported languages
403/// let languages = registry.list_languages();
404/// assert!(languages.contains(&"rust".to_string()));
405/// ```
406pub struct ProviderRegistry {
407 providers: std::collections::HashMap<String, Arc<dyn CompletionProvider>>,
408}
409
410impl ProviderRegistry {
411 /// Create a new empty provider registry
412 pub fn new() -> Self {
413 Self {
414 providers: std::collections::HashMap::new(),
415 }
416 }
417
418 /// Create a new provider registry with all built-in providers registered
419 ///
420 /// This is a convenience method that automatically registers all language-specific
421 /// providers (Rust, TypeScript, Python, Go, Java, Kotlin, Dart).
422 pub fn with_builtin_providers() -> Self {
423 let mut registry = Self::new();
424 registry.register_builtin_providers();
425 registry
426 }
427
428 /// Register all built-in language providers
429 ///
430 /// This method registers providers for all supported languages:
431 /// - Rust
432 /// - TypeScript
433 /// - Python
434 /// - Go
435 /// - Java
436 /// - Kotlin
437 /// - Dart
438 pub fn register_builtin_providers(&mut self) {
439 use crate::providers::*;
440
441 self.register(Arc::new(RustCompletionProvider));
442 self.register(Arc::new(TypeScriptCompletionProvider));
443 self.register(Arc::new(PythonCompletionProvider));
444 self.register(Arc::new(GoCompletionProvider));
445 self.register(Arc::new(JavaCompletionProvider));
446 self.register(Arc::new(KotlinCompletionProvider));
447 self.register(Arc::new(DartCompletionProvider));
448 }
449
450 /// Register a completion provider
451 ///
452 /// # Arguments
453 ///
454 /// * `provider` - The provider to register
455 ///
456 /// # Behavior
457 ///
458 /// If a provider for the same language already exists, it will be replaced.
459 pub fn register(&mut self, provider: Arc<dyn CompletionProvider>) {
460 self.providers
461 .insert(provider.language().to_string(), provider);
462 }
463
464 /// Get a completion provider for a language
465 ///
466 /// # Arguments
467 ///
468 /// * `language` - The language identifier
469 ///
470 /// # Returns
471 ///
472 /// The provider for the language, or `None` if no provider is registered.
473 pub fn get_provider(&self, language: &str) -> Option<Arc<dyn CompletionProvider>> {
474 self.providers.get(language).cloned()
475 }
476
477 /// Unregister a completion provider
478 ///
479 /// # Arguments
480 ///
481 /// * `language` - The language identifier
482 ///
483 /// # Returns
484 ///
485 /// The unregistered provider, or `None` if no provider was registered.
486 pub fn unregister(&mut self, language: &str) -> Option<Arc<dyn CompletionProvider>> {
487 self.providers.remove(language)
488 }
489
490 /// List all supported languages
491 ///
492 /// # Returns
493 ///
494 /// A vector of language identifiers for all registered providers.
495 pub fn list_languages(&self) -> Vec<String> {
496 self.providers.keys().cloned().collect()
497 }
498}
499
500impl Default for ProviderRegistry {
501 fn default() -> Self {
502 Self::new()
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_provider_registry_register() {
512 let registry = ProviderRegistry::new();
513 assert_eq!(registry.list_languages().len(), 0);
514 }
515
516 #[test]
517 fn test_provider_registry_get_nonexistent() {
518 let registry = ProviderRegistry::new();
519 assert!(registry.get_provider("rust").is_none());
520 }
521}