ricecoder_lsp/
completion.rs

1/// Code completion support for LSP
2///
3/// This module provides LSP handlers for code completion requests and item resolution.
4///
5/// # Routing Strategy
6///
7/// The completion handler routes requests to external LSP servers when available:
8///
9/// 1. **External LSP First**: If an external LSP server is configured for the language,
10///    the request is forwarded to it for semantic completions
11/// 2. **Merge Results**: External completions are merged with internal completions
12///    (external takes priority)
13/// 3. **Fallback**: If the external LSP is unavailable or times out, the system falls back
14///    to internal completion providers
15///
16/// # Merge Strategy
17///
18/// When merging external and internal completions:
19///
20/// - External completions appear first (higher priority)
21/// - Internal completions are added if they don't duplicate external ones
22/// - All completions are sorted by relevance score
23/// - Deduplication is based on completion label
24///
25/// # Fallback Behavior
26///
27/// When external LSP is unavailable:
28///
29/// - Internal completion providers are used (keyword and pattern-based)
30/// - Users get basic completions instead of semantic ones
31/// - No error is shown to the user (graceful degradation)
32use crate::types::{LspError, LspResult, Position};
33use ricecoder_completion::{
34    CompletionEngine, CompletionItem, CompletionItemKind, Position as CompletionPosition,
35};
36use serde_json::{json, Value};
37use std::sync::Arc;
38use tracing::debug;
39
40/// Completion handler for LSP
41pub struct CompletionHandler {
42    engine: Arc<dyn CompletionEngine>,
43}
44
45impl CompletionHandler {
46    /// Create a new completion handler
47    pub fn new(engine: Arc<dyn CompletionEngine>) -> Self {
48        Self { engine }
49    }
50
51    /// Handle textDocument/completion request
52    pub async fn handle_completion(
53        &self,
54        code: &str,
55        position: Position,
56        language: &str,
57    ) -> LspResult<Vec<Value>> {
58        debug!(
59            "Handling completion request at line={}, character={}",
60            position.line, position.character
61        );
62
63        // Convert LSP position to completion position
64        let completion_position = CompletionPosition::new(position.line, position.character);
65
66        // Generate completions
67        let completions = self
68            .engine
69            .generate_completions(code, completion_position, language)
70            .await
71            .map_err(|e| LspError::InternalError(format!("Completion generation failed: {}", e)))?;
72
73        debug!("Generated {} completions", completions.len());
74
75        // Convert to LSP format
76        let items: Vec<Value> = completions
77            .iter()
78            .enumerate()
79            .map(|(index, item)| self.completion_item_to_json(item, index as u32))
80            .collect();
81
82        Ok(items)
83    }
84
85    /// Handle completionItem/resolve request
86    pub async fn handle_completion_resolve(&self, item: &Value) -> LspResult<Value> {
87        debug!("Handling completion item resolve");
88
89        // Validate that the item has a label
90        let _label = item.get("label").and_then(|v| v.as_str()).ok_or_else(|| {
91            LspError::InvalidParams("Missing label in completion item".to_string())
92        })?;
93
94        // Resolve additional details (documentation, additional edits, etc.)
95        let mut resolved = item.clone();
96
97        // Add resolved flag to indicate this item has been resolved
98        resolved["resolved"] = json!(true);
99
100        Ok(resolved)
101    }
102
103    /// Apply a completion item to code at the cursor position
104    pub fn apply_completion(
105        &self,
106        code: &str,
107        position: Position,
108        insert_text: &str,
109    ) -> LspResult<String> {
110        debug!(
111            "Applying completion at line={}, character={}",
112            position.line, position.character
113        );
114
115        // Split code into lines
116        let lines: Vec<&str> = code.lines().collect();
117
118        // Validate position
119        if position.line as usize >= lines.len() {
120            return Err(LspError::InvalidParams(format!(
121                "Line {} is out of bounds",
122                position.line
123            )));
124        }
125
126        let line = lines[position.line as usize];
127        if position.character as usize > line.len() {
128            return Err(LspError::InvalidParams(format!(
129                "Character {} is out of bounds on line {}",
130                position.character, position.line
131            )));
132        }
133
134        // Build the new code
135        let mut result = String::new();
136
137        // Add lines before the insertion point
138        for (i, l) in lines.iter().enumerate() {
139            if i < position.line as usize {
140                result.push_str(l);
141                result.push('\n');
142            }
143        }
144
145        // Add the modified line
146        let line = lines[position.line as usize];
147        let before = &line[..position.character as usize];
148        let after = &line[position.character as usize..];
149
150        result.push_str(before);
151        result.push_str(insert_text);
152        result.push_str(after);
153
154        // Add lines after the insertion point
155        if (position.line as usize + 1) < lines.len() {
156            result.push('\n');
157            for l in lines.iter().skip(position.line as usize + 1) {
158                result.push_str(l);
159                result.push('\n');
160            }
161            // Remove trailing newline if original didn't have it
162            if !code.ends_with('\n') {
163                result.pop();
164            }
165        }
166
167        Ok(result)
168    }
169
170    /// Validate that code is still valid after completion insertion
171    pub fn validate_code_validity(&self, code: &str) -> LspResult<bool> {
172        debug!("Validating code validity");
173
174        // Basic validation: check for balanced brackets
175        let mut bracket_count = 0;
176        let mut paren_count = 0;
177        let mut brace_count = 0;
178
179        for ch in code.chars() {
180            match ch {
181                '[' => bracket_count += 1,
182                ']' => bracket_count -= 1,
183                '(' => paren_count += 1,
184                ')' => paren_count -= 1,
185                '{' => brace_count += 1,
186                '}' => brace_count -= 1,
187                _ => {}
188            }
189
190            // Check for negative counts (unbalanced)
191            if bracket_count < 0 || paren_count < 0 || brace_count < 0 {
192                return Ok(false);
193            }
194        }
195
196        // Check final counts
197        Ok(bracket_count == 0 && paren_count == 0 && brace_count == 0)
198    }
199
200    /// Convert a completion item to LSP JSON format
201    fn completion_item_to_json(&self, item: &CompletionItem, index: u32) -> Value {
202        let kind = self.completion_kind_to_lsp(item.kind);
203
204        json!({
205            "label": item.label,
206            "kind": kind,
207            "detail": item.detail,
208            "documentation": item.documentation,
209            "sortText": item.sort_text.as_ref().unwrap_or(&format!("{:04}", index)),
210            "filterText": item.filter_text.as_ref().unwrap_or(&item.label),
211            "insertText": item.insert_text,
212            "score": item.score,
213        })
214    }
215
216    /// Convert completion item kind to LSP kind number
217    fn completion_kind_to_lsp(&self, kind: CompletionItemKind) -> u32 {
218        match kind {
219            CompletionItemKind::Text => 1,
220            CompletionItemKind::Method => 2,
221            CompletionItemKind::Function => 3,
222            CompletionItemKind::Constructor => 4,
223            CompletionItemKind::Field => 5,
224            CompletionItemKind::Variable => 6,
225            CompletionItemKind::Class => 7,
226            CompletionItemKind::Interface => 8,
227            CompletionItemKind::Module => 9,
228            CompletionItemKind::Property => 10,
229            CompletionItemKind::Unit => 11,
230            CompletionItemKind::Value => 12,
231            CompletionItemKind::Enum => 13,
232            CompletionItemKind::Keyword => 14,
233            CompletionItemKind::Snippet => 15,
234            CompletionItemKind::Color => 16,
235            CompletionItemKind::File => 17,
236            CompletionItemKind::Reference => 18,
237            CompletionItemKind::Folder => 19,
238            CompletionItemKind::EnumMember => 20,
239            CompletionItemKind::Constant => 21,
240            CompletionItemKind::Struct => 22,
241            CompletionItemKind::EventListener => 23,
242            CompletionItemKind::Operator => 24,
243            CompletionItemKind::TypeParameter => 25,
244            CompletionItemKind::Trait => 26,
245        }
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_completion_kind_to_lsp() {
255        let handler = CompletionHandler::new(Arc::new(MockCompletionEngine));
256        assert_eq!(
257            handler.completion_kind_to_lsp(CompletionItemKind::Function),
258            3
259        );
260        assert_eq!(
261            handler.completion_kind_to_lsp(CompletionItemKind::Variable),
262            6
263        );
264        assert_eq!(
265            handler.completion_kind_to_lsp(CompletionItemKind::Keyword),
266            14
267        );
268    }
269
270    #[test]
271    fn test_apply_completion_simple() {
272        let handler = CompletionHandler::new(Arc::new(MockCompletionEngine));
273        let code = "fn main() {\n    let x = ";
274        let position = Position::new(1, 12);
275        let result = handler.apply_completion(code, position, "42");
276        assert!(result.is_ok());
277        let new_code = result.unwrap();
278        assert!(new_code.contains("let x = 42"));
279    }
280
281    #[test]
282    fn test_apply_completion_invalid_position() {
283        let handler = CompletionHandler::new(Arc::new(MockCompletionEngine));
284        let code = "fn main() {}";
285        let position = Position::new(10, 0); // Line out of bounds
286        let result = handler.apply_completion(code, position, "test");
287        assert!(result.is_err());
288    }
289
290    #[test]
291    fn test_validate_code_validity_balanced() {
292        let handler = CompletionHandler::new(Arc::new(MockCompletionEngine));
293        let code = "fn main() { let x = [1, 2, 3]; }";
294        let result = handler.validate_code_validity(code);
295        assert!(result.is_ok());
296        assert!(result.unwrap());
297    }
298
299    #[test]
300    fn test_validate_code_validity_unbalanced() {
301        let handler = CompletionHandler::new(Arc::new(MockCompletionEngine));
302        let code = "fn main() { let x = [1, 2, 3; }";
303        let result = handler.validate_code_validity(code);
304        assert!(result.is_ok());
305        assert!(!result.unwrap());
306    }
307
308    // Mock completion engine for testing
309    struct MockCompletionEngine;
310
311    #[async_trait::async_trait]
312    impl CompletionEngine for MockCompletionEngine {
313        async fn generate_completions(
314            &self,
315            _code: &str,
316            _position: CompletionPosition,
317            _language: &str,
318        ) -> ricecoder_completion::CompletionResult<Vec<CompletionItem>> {
319            Ok(vec![])
320        }
321
322        async fn resolve_completion(
323            &self,
324            item: &CompletionItem,
325        ) -> ricecoder_completion::CompletionResult<CompletionItem> {
326            Ok(item.clone())
327        }
328    }
329}