ricecoder_ide/
response_formatter.rs

1//! IDE response formatting
2//!
3//! This module provides formatting utilities for IDE responses to ensure they are
4//! properly formatted for IDE consumption. It handles formatting for different IDE types
5//! and ensures responses are in the correct format for each IDE.
6
7use crate::types::*;
8use serde_json::{json, Value};
9use tracing::debug;
10
11/// Response formatter for IDE responses
12pub struct ResponseFormatter;
13
14impl ResponseFormatter {
15    /// Format completion items for IDE consumption
16    pub fn format_completions(items: &[CompletionItem]) -> Value {
17        debug!("Formatting {} completion items for IDE", items.len());
18
19        let formatted_items: Vec<Value> = items
20            .iter()
21            .map(|item| {
22                json!({
23                    "label": item.label,
24                    "kind": Self::format_completion_kind(item.kind),
25                    "detail": item.detail,
26                    "documentation": item.documentation,
27                    "insertText": item.insert_text,
28                })
29            })
30            .collect();
31
32        json!({
33            "items": formatted_items,
34            "isIncomplete": false,
35        })
36    }
37
38    /// Format diagnostics for IDE display
39    pub fn format_diagnostics(diagnostics: &[Diagnostic]) -> Value {
40        debug!("Formatting {} diagnostics for IDE", diagnostics.len());
41
42        let formatted_diagnostics: Vec<Value> = diagnostics
43            .iter()
44            .map(|diag| {
45                json!({
46                    "range": {
47                        "start": {
48                            "line": diag.range.start.line,
49                            "character": diag.range.start.character,
50                        },
51                        "end": {
52                            "line": diag.range.end.line,
53                            "character": diag.range.end.character,
54                        },
55                    },
56                    "severity": Self::format_diagnostic_severity(diag.severity),
57                    "message": diag.message,
58                    "source": diag.source,
59                })
60            })
61            .collect();
62
63        json!({
64            "diagnostics": formatted_diagnostics,
65        })
66    }
67
68    /// Format hover information for IDE display
69    pub fn format_hover(hover: &Option<Hover>) -> Value {
70        debug!("Formatting hover information for IDE");
71
72        match hover {
73            Some(h) => {
74                let range = h.range.map(|r| {
75                    json!({
76                        "start": {
77                            "line": r.start.line,
78                            "character": r.start.character,
79                        },
80                        "end": {
81                            "line": r.end.line,
82                            "character": r.end.character,
83                        },
84                    })
85                });
86
87                json!({
88                    "contents": h.contents,
89                    "range": range,
90                })
91            }
92            None => json!(null),
93        }
94    }
95
96    /// Format definition location for IDE navigation
97    pub fn format_definition(location: &Option<Location>) -> Value {
98        debug!("Formatting definition location for IDE");
99
100        match location {
101            Some(loc) => {
102                json!({
103                    "uri": loc.file_path,
104                    "range": {
105                        "start": {
106                            "line": loc.range.start.line,
107                            "character": loc.range.start.character,
108                        },
109                        "end": {
110                            "line": loc.range.end.line,
111                            "character": loc.range.end.character,
112                        },
113                    },
114                })
115            }
116            None => json!(null),
117        }
118    }
119
120    /// Format completion item kind for IDE
121    fn format_completion_kind(kind: CompletionItemKind) -> u32 {
122        match kind {
123            CompletionItemKind::Text => 1,
124            CompletionItemKind::Method => 2,
125            CompletionItemKind::Function => 3,
126            CompletionItemKind::Constructor => 4,
127            CompletionItemKind::Field => 5,
128            CompletionItemKind::Variable => 6,
129            CompletionItemKind::Class => 7,
130            CompletionItemKind::Interface => 8,
131            CompletionItemKind::Module => 9,
132            CompletionItemKind::Property => 10,
133            CompletionItemKind::Unit => 11,
134            CompletionItemKind::Value => 12,
135            CompletionItemKind::Enum => 13,
136            CompletionItemKind::Keyword => 14,
137            CompletionItemKind::Snippet => 15,
138            CompletionItemKind::Color => 16,
139            CompletionItemKind::File => 17,
140            CompletionItemKind::Reference => 18,
141            CompletionItemKind::Folder => 19,
142            CompletionItemKind::EnumMember => 20,
143            CompletionItemKind::Constant => 21,
144            CompletionItemKind::Struct => 22,
145            CompletionItemKind::Event => 23,
146            CompletionItemKind::Operator => 24,
147            CompletionItemKind::TypeParameter => 25,
148        }
149    }
150
151    /// Format diagnostic severity for IDE
152    fn format_diagnostic_severity(severity: DiagnosticSeverity) -> u32 {
153        match severity {
154            DiagnosticSeverity::Error => 1,
155            DiagnosticSeverity::Warning => 2,
156            DiagnosticSeverity::Information => 3,
157            DiagnosticSeverity::Hint => 4,
158        }
159    }
160
161    /// Format response for VS Code
162    pub fn format_for_vscode(response_type: &str, data: Value) -> Value {
163        debug!("Formatting response for VS Code: {}", response_type);
164
165        json!({
166            "jsonrpc": "2.0",
167            "result": data,
168        })
169    }
170
171    /// Format response for terminal editors
172    pub fn format_for_terminal(response_type: &str, data: Value) -> Value {
173        debug!("Formatting response for terminal editor: {}", response_type);
174
175        json!({
176            "type": response_type,
177            "data": data,
178        })
179    }
180
181    /// Format error response
182    pub fn format_error(code: i32, message: &str) -> Value {
183        debug!("Formatting error response: code={}, message={}", code, message);
184
185        json!({
186            "jsonrpc": "2.0",
187            "error": {
188                "code": code,
189                "message": message,
190            },
191        })
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_format_completions() {
201        let items = vec![
202            CompletionItem {
203                label: "test".to_string(),
204                kind: CompletionItemKind::Function,
205                detail: Some("test function".to_string()),
206                documentation: None,
207                insert_text: "test()".to_string(),
208            },
209            CompletionItem {
210                label: "hello".to_string(),
211                kind: CompletionItemKind::Variable,
212                detail: None,
213                documentation: Some("hello variable".to_string()),
214                insert_text: "hello".to_string(),
215            },
216        ];
217
218        let result = ResponseFormatter::format_completions(&items);
219        assert!(result.get("items").is_some());
220        assert_eq!(result["items"].as_array().unwrap().len(), 2);
221        assert_eq!(result["items"][0]["label"], "test");
222        assert_eq!(result["items"][1]["label"], "hello");
223    }
224
225    #[test]
226    fn test_format_diagnostics() {
227        let diagnostics = vec![
228            Diagnostic {
229                range: Range {
230                    start: Position {
231                        line: 1,
232                        character: 0,
233                    },
234                    end: Position {
235                        line: 1,
236                        character: 10,
237                    },
238                },
239                severity: DiagnosticSeverity::Error,
240                message: "Test error".to_string(),
241                source: "test".to_string(),
242            },
243        ];
244
245        let result = ResponseFormatter::format_diagnostics(&diagnostics);
246        assert!(result.get("diagnostics").is_some());
247        assert_eq!(result["diagnostics"].as_array().unwrap().len(), 1);
248        assert_eq!(result["diagnostics"][0]["message"], "Test error");
249        assert_eq!(result["diagnostics"][0]["severity"], 1); // Error
250    }
251
252    #[test]
253    fn test_format_hover_with_content() {
254        let hover = Some(Hover {
255            contents: "test hover".to_string(),
256            range: Some(Range {
257                start: Position {
258                    line: 1,
259                    character: 0,
260                },
261                end: Position {
262                    line: 1,
263                    character: 4,
264                },
265            }),
266        });
267
268        let result = ResponseFormatter::format_hover(&hover);
269        assert_eq!(result["contents"], "test hover");
270        assert!(result.get("range").is_some());
271    }
272
273    #[test]
274    fn test_format_hover_empty() {
275        let hover: Option<Hover> = None;
276
277        let result = ResponseFormatter::format_hover(&hover);
278        assert!(result.is_null());
279    }
280
281    #[test]
282    fn test_format_definition_with_location() {
283        let location = Some(Location {
284            file_path: "src/main.rs".to_string(),
285            range: Range {
286                start: Position {
287                    line: 10,
288                    character: 0,
289                },
290                end: Position {
291                    line: 10,
292                    character: 5,
293                },
294            },
295        });
296
297        let result = ResponseFormatter::format_definition(&location);
298        assert_eq!(result["uri"], "src/main.rs");
299        assert_eq!(result["range"]["start"]["line"], 10);
300    }
301
302    #[test]
303    fn test_format_definition_empty() {
304        let location: Option<Location> = None;
305
306        let result = ResponseFormatter::format_definition(&location);
307        assert!(result.is_null());
308    }
309
310    #[test]
311    fn test_format_completion_kind() {
312        assert_eq!(ResponseFormatter::format_completion_kind(CompletionItemKind::Text), 1);
313        assert_eq!(ResponseFormatter::format_completion_kind(CompletionItemKind::Function), 3);
314        assert_eq!(ResponseFormatter::format_completion_kind(CompletionItemKind::Variable), 6);
315    }
316
317    #[test]
318    fn test_format_diagnostic_severity() {
319        assert_eq!(ResponseFormatter::format_diagnostic_severity(DiagnosticSeverity::Error), 1);
320        assert_eq!(ResponseFormatter::format_diagnostic_severity(DiagnosticSeverity::Warning), 2);
321        assert_eq!(ResponseFormatter::format_diagnostic_severity(DiagnosticSeverity::Information), 3);
322        assert_eq!(ResponseFormatter::format_diagnostic_severity(DiagnosticSeverity::Hint), 4);
323    }
324
325    #[test]
326    fn test_format_for_vscode() {
327        let data = json!({"test": "data"});
328        let result = ResponseFormatter::format_for_vscode("completion", data);
329        assert_eq!(result["jsonrpc"], "2.0");
330        assert!(result.get("result").is_some());
331    }
332
333    #[test]
334    fn test_format_for_terminal() {
335        let data = json!({"test": "data"});
336        let result = ResponseFormatter::format_for_terminal("completion", data);
337        assert_eq!(result["type"], "completion");
338        assert!(result.get("data").is_some());
339    }
340
341    #[test]
342    fn test_format_error() {
343        let result = ResponseFormatter::format_error(-32600, "Invalid Request");
344        assert_eq!(result["jsonrpc"], "2.0");
345        assert_eq!(result["error"]["code"], -32600);
346        assert_eq!(result["error"]["message"], "Invalid Request");
347    }
348}