Skip to main content

voirs_cli/lsp/
server.rs

1//! LSP server implementation
2
3use super::{LspState, Position, Range};
4use std::sync::Arc;
5use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
6use tokio::sync::RwLock;
7
8/// LSP server
9pub struct LspServer {
10    /// Server state
11    state: Arc<LspState>,
12
13    /// Server running flag
14    running: Arc<RwLock<bool>>,
15}
16
17impl LspServer {
18    /// Create a new LSP server
19    pub fn new() -> Self {
20        Self {
21            state: Arc::new(LspState::new()),
22            running: Arc::new(RwLock::new(false)),
23        }
24    }
25
26    /// Start the LSP server
27    pub async fn start(&self) -> Result<(), LspError> {
28        *self.running.write().await = true;
29
30        let stdin = tokio::io::stdin();
31        let mut stdout = tokio::io::stdout();
32        let mut reader = BufReader::new(stdin);
33
34        eprintln!("VoiRS LSP server started");
35
36        while *self.running.read().await {
37            let mut headers = String::new();
38
39            // Read headers
40            loop {
41                let mut line = String::new();
42                if reader.read_line(&mut line).await? == 0 {
43                    return Ok(()); // EOF
44                }
45
46                if line == "\r\n" || line == "\n" {
47                    break; // End of headers
48                }
49
50                headers.push_str(&line);
51            }
52
53            // Parse Content-Length header
54            let content_length = self.parse_content_length(&headers)?;
55
56            // Read content
57            let mut content = vec![0u8; content_length];
58            tokio::io::AsyncReadExt::read_exact(&mut reader, &mut content).await?;
59
60            let content_str =
61                String::from_utf8(content).map_err(|e| LspError::InvalidUtf8(e.to_string()))?;
62
63            // Process request
64            if let Some(response) = self.handle_request(&content_str).await? {
65                // Send response
66                let response_json = serde_json::to_string(&response)?;
67                let response_msg = format!(
68                    "Content-Length: {}\r\n\r\n{}",
69                    response_json.len(),
70                    response_json
71                );
72
73                stdout.write_all(response_msg.as_bytes()).await?;
74                stdout.flush().await?;
75            }
76        }
77
78        Ok(())
79    }
80
81    /// Stop the LSP server
82    pub async fn stop(&self) {
83        *self.running.write().await = false;
84    }
85
86    /// Parse Content-Length header
87    fn parse_content_length(&self, headers: &str) -> Result<usize, LspError> {
88        for line in headers.lines() {
89            if line.starts_with("Content-Length:") {
90                let length_str = line.trim_start_matches("Content-Length:").trim();
91                return length_str
92                    .parse()
93                    .map_err(|_| LspError::InvalidContentLength(line.to_string()));
94            }
95        }
96
97        Err(LspError::MissingContentLength)
98    }
99
100    /// Handle an LSP request
101    async fn handle_request(&self, content: &str) -> Result<Option<serde_json::Value>, LspError> {
102        let request: serde_json::Value = serde_json::from_str(content)?;
103
104        let method = request["method"].as_str().ok_or(LspError::MissingMethod)?;
105
106        match method {
107            "initialize" => Ok(Some(self.handle_initialize(&request).await?)),
108            "initialized" => Ok(None), // Notification, no response
109            "shutdown" => Ok(Some(self.handle_shutdown().await?)),
110            "exit" => {
111                self.stop().await;
112                Ok(None)
113            }
114            "textDocument/didOpen" => {
115                self.handle_did_open(&request).await?;
116                Ok(None)
117            }
118            "textDocument/didChange" => {
119                self.handle_did_change(&request).await?;
120                Ok(None)
121            }
122            "textDocument/didClose" => {
123                self.handle_did_close(&request).await?;
124                Ok(None)
125            }
126            "textDocument/completion" => Ok(Some(self.handle_completion(&request).await?)),
127            "textDocument/hover" => Ok(Some(self.handle_hover(&request).await?)),
128            "textDocument/codeAction" => Ok(Some(self.handle_code_action(&request).await?)),
129            "textDocument/formatting" => Ok(Some(self.handle_formatting(&request).await?)),
130            "textDocument/rangeFormatting" => {
131                Ok(Some(self.handle_range_formatting(&request).await?))
132            }
133            "textDocument/onTypeFormatting" => {
134                Ok(Some(self.handle_on_type_formatting(&request).await?))
135            }
136            _ => {
137                eprintln!("Unhandled method: {}", method);
138                Ok(None)
139            }
140        }
141    }
142
143    /// Handle initialize request
144    async fn handle_initialize(
145        &self,
146        request: &serde_json::Value,
147    ) -> Result<serde_json::Value, LspError> {
148        let id = request["id"].clone();
149
150        Ok(serde_json::json!({
151            "jsonrpc": "2.0",
152            "id": id,
153            "result": {
154                "capabilities": {
155                    "textDocumentSync": 1, // Full sync
156                    "completionProvider": {
157                        "triggerCharacters": ["<", " ", "=", "\""]
158                    },
159                    "hoverProvider": true,
160                    "diagnosticProvider": true,
161                    "codeActionProvider": {
162                        "codeActionKinds": [
163                            "quickfix",
164                            "refactor",
165                            "refactor.rewrite",
166                            "source"
167                        ]
168                    },
169                    "documentFormattingProvider": true,
170                    "documentRangeFormattingProvider": true,
171                    "documentOnTypeFormattingProvider": {
172                        "firstTriggerCharacter": ">",
173                        "moreTriggerCharacter": ["}","]"]
174                    }
175                },
176                "serverInfo": {
177                    "name": "voirs-lsp",
178                    "version": env!("CARGO_PKG_VERSION")
179                }
180            }
181        }))
182    }
183
184    /// Handle shutdown request
185    async fn handle_shutdown(&self) -> Result<serde_json::Value, LspError> {
186        Ok(serde_json::json!({
187            "jsonrpc": "2.0",
188            "result": null
189        }))
190    }
191
192    /// Handle textDocument/didOpen notification
193    async fn handle_did_open(&self, request: &serde_json::Value) -> Result<(), LspError> {
194        let params = &request["params"];
195        let text_document = &params["textDocument"];
196
197        let uri = text_document["uri"]
198            .as_str()
199            .ok_or_else(|| LspError::MissingField("uri".to_string()))?
200            .to_string();
201
202        let text = text_document["text"]
203            .as_str()
204            .ok_or_else(|| LspError::MissingField("text".to_string()))?
205            .to_string();
206
207        let language_id = text_document["languageId"]
208            .as_str()
209            .ok_or_else(|| LspError::MissingField("languageId".to_string()))?
210            .to_string();
211
212        self.state.open_document(uri, text, language_id).await;
213
214        Ok(())
215    }
216
217    /// Handle textDocument/didChange notification
218    async fn handle_did_change(&self, request: &serde_json::Value) -> Result<(), LspError> {
219        let params = &request["params"];
220        let text_document = &params["textDocument"];
221        let content_changes = &params["contentChanges"];
222
223        let uri = text_document["uri"]
224            .as_str()
225            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
226
227        let version = text_document["version"]
228            .as_i64()
229            .ok_or_else(|| LspError::MissingField("version".to_string()))?
230            as i32;
231
232        if let Some(change) = content_changes.get(0) {
233            if let Some(text) = change["text"].as_str() {
234                self.state
235                    .update_document(uri, text.to_string(), version)
236                    .await;
237            }
238        }
239
240        Ok(())
241    }
242
243    /// Handle textDocument/didClose notification
244    async fn handle_did_close(&self, request: &serde_json::Value) -> Result<(), LspError> {
245        let params = &request["params"];
246        let text_document = &params["textDocument"];
247
248        let uri = text_document["uri"]
249            .as_str()
250            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
251
252        self.state.close_document(uri).await;
253
254        Ok(())
255    }
256
257    /// Handle textDocument/completion request
258    async fn handle_completion(
259        &self,
260        request: &serde_json::Value,
261    ) -> Result<serde_json::Value, LspError> {
262        let id = request["id"].clone();
263
264        // Get position
265        let params = &request["params"];
266        let position = &params["position"];
267
268        let line = position["line"]
269            .as_u64()
270            .ok_or_else(|| LspError::MissingField("line".to_string()))? as u32;
271
272        let character = position["character"]
273            .as_u64()
274            .ok_or_else(|| LspError::MissingField("character".to_string()))?
275            as u32;
276
277        let pos = Position::new(line, character);
278
279        // Get document URI
280        let uri = params["textDocument"]["uri"]
281            .as_str()
282            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
283
284        // Get completion items
285        let items = self.get_completion_items(uri, pos).await;
286
287        Ok(serde_json::json!({
288            "jsonrpc": "2.0",
289            "id": id,
290            "result": items
291        }))
292    }
293
294    /// Handle textDocument/hover request
295    async fn handle_hover(
296        &self,
297        request: &serde_json::Value,
298    ) -> Result<serde_json::Value, LspError> {
299        let id = request["id"].clone();
300
301        Ok(serde_json::json!({
302            "jsonrpc": "2.0",
303            "id": id,
304            "result": {
305                "contents": {
306                    "kind": "markdown",
307                    "value": "VoiRS SSML Element\n\nHover information will be provided here."
308                }
309            }
310        }))
311    }
312
313    /// Get completion items for a position
314    async fn get_completion_items(&self, _uri: &str, _pos: Position) -> Vec<serde_json::Value> {
315        // Basic SSML tag completions
316        vec![
317            serde_json::json!({
318                "label": "speak",
319                "kind": 14, // Snippet
320                "detail": "SSML root element",
321                "insertText": "<speak>$1</speak>",
322                "insertTextFormat": 2 // Snippet
323            }),
324            serde_json::json!({
325                "label": "voice",
326                "kind": 14,
327                "detail": "Voice selection",
328                "insertText": "<voice name=\"$1\">$2</voice>",
329                "insertTextFormat": 2
330            }),
331            serde_json::json!({
332                "label": "prosody",
333                "kind": 14,
334                "detail": "Prosody control",
335                "insertText": "<prosody rate=\"$1\" pitch=\"$2\">$3</prosody>",
336                "insertTextFormat": 2
337            }),
338            serde_json::json!({
339                "label": "break",
340                "kind": 14,
341                "detail": "Insert pause",
342                "insertText": "<break time=\"${1:500ms}\"/>",
343                "insertTextFormat": 2
344            }),
345        ]
346    }
347
348    /// Handle textDocument/codeAction request
349    async fn handle_code_action(
350        &self,
351        request: &serde_json::Value,
352    ) -> Result<serde_json::Value, LspError> {
353        let id = request["id"].clone();
354        let params = &request["params"];
355
356        // Get document URI and range
357        let uri = params["textDocument"]["uri"]
358            .as_str()
359            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
360
361        let range_json = &params["range"];
362        let range = Range::new(
363            Position::new(
364                range_json["start"]["line"].as_u64().unwrap_or(0) as u32,
365                range_json["start"]["character"].as_u64().unwrap_or(0) as u32,
366            ),
367            Position::new(
368                range_json["end"]["line"].as_u64().unwrap_or(0) as u32,
369                range_json["end"]["character"].as_u64().unwrap_or(0) as u32,
370            ),
371        );
372
373        // Get document
374        let doc = self.state.get_document(uri).await;
375        let actions = if let Some(document) = doc {
376            super::code_actions::get_code_actions(&document.text, range)
377        } else {
378            Vec::new()
379        };
380
381        Ok(serde_json::json!({
382            "jsonrpc": "2.0",
383            "id": id,
384            "result": actions
385        }))
386    }
387
388    /// Handle textDocument/formatting request
389    async fn handle_formatting(
390        &self,
391        request: &serde_json::Value,
392    ) -> Result<serde_json::Value, LspError> {
393        let id = request["id"].clone();
394        let params = &request["params"];
395
396        let uri = params["textDocument"]["uri"]
397            .as_str()
398            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
399
400        let doc = self.state.get_document(uri).await;
401        let edits = if let Some(document) = doc {
402            super::formatting::format_document(&document.text, &document.language_id)
403                .map(|edits| edits.iter().map(|e| e.to_json()).collect::<Vec<_>>())
404                .unwrap_or_default()
405        } else {
406            Vec::new()
407        };
408
409        Ok(serde_json::json!({
410            "jsonrpc": "2.0",
411            "id": id,
412            "result": edits
413        }))
414    }
415
416    /// Handle textDocument/rangeFormatting request
417    async fn handle_range_formatting(
418        &self,
419        request: &serde_json::Value,
420    ) -> Result<serde_json::Value, LspError> {
421        let id = request["id"].clone();
422        let params = &request["params"];
423
424        let uri = params["textDocument"]["uri"]
425            .as_str()
426            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
427
428        let range_json = &params["range"];
429        let range = Range::new(
430            Position::new(
431                range_json["start"]["line"].as_u64().unwrap_or(0) as u32,
432                range_json["start"]["character"].as_u64().unwrap_or(0) as u32,
433            ),
434            Position::new(
435                range_json["end"]["line"].as_u64().unwrap_or(0) as u32,
436                range_json["end"]["character"].as_u64().unwrap_or(0) as u32,
437            ),
438        );
439
440        let doc = self.state.get_document(uri).await;
441        let edits = if let Some(document) = doc {
442            super::formatting::format_range(&document.text, range, &document.language_id)
443                .map(|edits| edits.iter().map(|e| e.to_json()).collect::<Vec<_>>())
444                .unwrap_or_default()
445        } else {
446            Vec::new()
447        };
448
449        Ok(serde_json::json!({
450            "jsonrpc": "2.0",
451            "id": id,
452            "result": edits
453        }))
454    }
455
456    /// Handle textDocument/onTypeFormatting request
457    async fn handle_on_type_formatting(
458        &self,
459        request: &serde_json::Value,
460    ) -> Result<serde_json::Value, LspError> {
461        let id = request["id"].clone();
462        let params = &request["params"];
463
464        let uri = params["textDocument"]["uri"]
465            .as_str()
466            .ok_or_else(|| LspError::MissingField("uri".to_string()))?;
467
468        let position_json = &params["position"];
469        let position = Position::new(
470            position_json["line"].as_u64().unwrap_or(0) as u32,
471            position_json["character"].as_u64().unwrap_or(0) as u32,
472        );
473
474        let ch_str = params["ch"]
475            .as_str()
476            .ok_or_else(|| LspError::MissingField("ch".to_string()))?;
477        let ch = ch_str.chars().next().unwrap_or('>');
478
479        let doc = self.state.get_document(uri).await;
480        let edits = if let Some(document) = doc {
481            super::formatting::format_on_type(&document.text, position, ch, &document.language_id)
482                .map(|edits| edits.iter().map(|e| e.to_json()).collect::<Vec<_>>())
483                .unwrap_or_default()
484        } else {
485            Vec::new()
486        };
487
488        Ok(serde_json::json!({
489            "jsonrpc": "2.0",
490            "id": id,
491            "result": edits
492        }))
493    }
494
495    /// Get server state (for testing)
496    #[cfg(test)]
497    pub fn state(&self) -> Arc<LspState> {
498        Arc::clone(&self.state)
499    }
500}
501
502impl Default for LspServer {
503    fn default() -> Self {
504        Self::new()
505    }
506}
507
508/// LSP error types
509#[derive(Debug, thiserror::Error)]
510pub enum LspError {
511    #[error("I/O error: {0}")]
512    Io(#[from] std::io::Error),
513
514    #[error("JSON error: {0}")]
515    Json(#[from] serde_json::Error),
516
517    #[error("Invalid UTF-8: {0}")]
518    InvalidUtf8(String),
519
520    #[error("Missing Content-Length header")]
521    MissingContentLength,
522
523    #[error("Invalid Content-Length: {0}")]
524    InvalidContentLength(String),
525
526    #[error("Missing method field")]
527    MissingMethod,
528
529    #[error("Missing field: {0}")]
530    MissingField(String),
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    #[test]
538    fn test_lsp_server_creation() {
539        let server = LspServer::new();
540        assert!(server.state.documents.try_read().is_ok());
541    }
542
543    #[tokio::test]
544    async fn test_parse_content_length() {
545        let server = LspServer::new();
546        let headers = "Content-Length: 123\r\nContent-Type: application/json\r\n";
547
548        let length = server.parse_content_length(headers).unwrap();
549        assert_eq!(length, 123);
550    }
551
552    #[tokio::test]
553    async fn test_parse_content_length_error() {
554        let server = LspServer::new();
555        let headers = "Content-Type: application/json\r\n";
556
557        let result = server.parse_content_length(headers);
558        assert!(result.is_err());
559    }
560
561    #[tokio::test]
562    async fn test_get_completion_items() {
563        let server = LspServer::new();
564        let items = server
565            .get_completion_items("file:///test.ssml", Position::new(0, 0))
566            .await;
567
568        assert!(!items.is_empty());
569        assert!(items.iter().any(|item| item["label"] == "speak"));
570        assert!(items.iter().any(|item| item["label"] == "voice"));
571    }
572}