1use crate::code_actions::{CodeActionsEngine, DefaultCodeActionsEngine};
7use crate::completion::CompletionHandler;
8use crate::config::CompletionConfig;
9use crate::diagnostics::{DefaultDiagnosticsEngine, DiagnosticsEngine};
10use crate::hover::HoverProvider;
11use crate::refactoring::RefactoringHandler;
12use crate::transport::{AsyncStdioTransport, JsonRpcError, JsonRpcResponse, LspMessage};
13use crate::types::{Language, LspError, LspResult, Position, ServerState};
14use ricecoder_completion::CompletionEngine;
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::sync::Arc;
18use tracing::{debug, error, info, warn};
19
20#[derive(Debug, Clone)]
22pub struct ServerCapabilities {
23 pub text_document_sync: u32,
25 pub hover_provider: bool,
27 pub code_action_provider: bool,
29 pub diagnostic_provider: bool,
31 pub completion_provider: bool,
33}
34
35impl Default for ServerCapabilities {
36 fn default() -> Self {
37 Self {
38 text_document_sync: 1, hover_provider: true,
40 code_action_provider: true,
41 diagnostic_provider: true,
42 completion_provider: true,
43 }
44 }
45}
46
47impl ServerCapabilities {
48 pub fn to_json(&self) -> Value {
50 json!({
51 "textDocumentSync": self.text_document_sync,
52 "hoverProvider": self.hover_provider,
53 "codeActionProvider": self.code_action_provider,
54 "diagnosticProvider": self.diagnostic_provider,
55 "completionProvider": {
56 "resolveProvider": true,
57 "triggerCharacters": [".", ":", "::", "(", "[", "{", " "]
58 },
59 })
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct ClientCapabilities {
66 pub raw: Value,
68}
69
70impl ClientCapabilities {
71 pub fn from_json(value: Value) -> Self {
73 Self { raw: value }
74 }
75}
76
77pub struct LspServer {
79 state: ServerState,
81 capabilities: ServerCapabilities,
83 client_capabilities: Option<ClientCapabilities>,
85 documents: HashMap<String, String>,
87 transport: AsyncStdioTransport,
89 hover_provider: HoverProvider,
91 diagnostics_engine: Box<dyn DiagnosticsEngine>,
93 code_actions_engine: Box<dyn CodeActionsEngine>,
95 completion_handler: Option<CompletionHandler>,
97 completion_config: CompletionConfig,
99 refactoring_handler: RefactoringHandler,
101}
102
103impl LspServer {
104 pub fn new() -> Self {
106 Self {
107 state: ServerState::Initializing,
108 capabilities: ServerCapabilities::default(),
109 client_capabilities: None,
110 documents: HashMap::new(),
111 transport: AsyncStdioTransport::new(),
112 hover_provider: HoverProvider::new(),
113 diagnostics_engine: Box::new(DefaultDiagnosticsEngine::new()),
114 code_actions_engine: Box::new(DefaultCodeActionsEngine::new()),
115 completion_handler: None,
116 completion_config: CompletionConfig::default(),
117 refactoring_handler: RefactoringHandler::new(),
118 }
119 }
120
121 pub fn with_completion_config(completion_config: CompletionConfig) -> Self {
123 Self {
124 state: ServerState::Initializing,
125 capabilities: ServerCapabilities::default(),
126 client_capabilities: None,
127 documents: HashMap::new(),
128 transport: AsyncStdioTransport::new(),
129 hover_provider: HoverProvider::new(),
130 diagnostics_engine: Box::new(DefaultDiagnosticsEngine::new()),
131 code_actions_engine: Box::new(DefaultCodeActionsEngine::new()),
132 completion_handler: None,
133 completion_config,
134 refactoring_handler: RefactoringHandler::new(),
135 }
136 }
137
138 pub fn register_completion_engine(&mut self, engine: Arc<dyn CompletionEngine>) {
140 self.completion_handler = Some(CompletionHandler::new(engine));
141 info!("Completion engine registered");
142 }
143
144 pub fn completion_config(&self) -> &CompletionConfig {
146 &self.completion_config
147 }
148
149 pub fn set_completion_config(&mut self, config: CompletionConfig) {
151 self.completion_config = config;
152 debug!("Completion configuration updated");
153 }
154
155 pub fn is_completion_enabled(&self) -> bool {
157 self.completion_config.enabled && self.completion_handler.is_some()
158 }
159
160 pub fn refactoring_handler(&self) -> &RefactoringHandler {
162 &self.refactoring_handler
163 }
164
165 pub fn refactoring_handler_mut(&mut self) -> &mut RefactoringHandler {
167 &mut self.refactoring_handler
168 }
169
170 pub fn set_refactoring_enabled(&mut self, enabled: bool) {
172 self.refactoring_handler.set_enabled(enabled);
173 }
174
175 pub fn is_refactoring_enabled(&self) -> bool {
177 self.refactoring_handler.is_enabled()
178 }
179
180 pub fn state(&self) -> ServerState {
182 self.state
183 }
184
185 pub fn set_state(&mut self, state: ServerState) {
187 self.state = state;
188 }
189
190 pub fn capabilities(&self) -> &ServerCapabilities {
192 &self.capabilities
193 }
194
195 pub fn client_capabilities(&self) -> Option<&ClientCapabilities> {
197 self.client_capabilities.as_ref()
198 }
199
200 pub fn get_document(&self, uri: &str) -> Option<&str> {
202 self.documents.get(uri).map(|s| s.as_str())
203 }
204
205 pub fn set_document(&mut self, uri: String, content: String) {
207 self.documents.insert(uri, content);
208 }
209
210 pub fn remove_document(&mut self, uri: &str) {
212 self.documents.remove(uri);
213 }
214
215 pub async fn handle_initialize(&mut self, params: Value) -> LspResult<Value> {
217 if self.state != ServerState::Initializing {
218 return Err(LspError::InvalidRequest(
219 "Server is not in initializing state".to_string(),
220 ));
221 }
222
223 info!("Initializing LSP server");
224
225 if let Some(capabilities) = params.get("capabilities") {
227 self.client_capabilities = Some(ClientCapabilities::from_json(capabilities.clone()));
228 debug!("Client capabilities received");
229 }
230
231 info!("LSP server initialization complete");
233 Ok(json!({
234 "capabilities": self.capabilities.to_json(),
235 "serverInfo": {
236 "name": "ricecoder-lsp",
237 "version": "0.1.0"
238 }
239 }))
240 }
241
242 pub async fn handle_initialized(&mut self) -> LspResult<()> {
244 if self.state != ServerState::Initializing {
245 return Err(LspError::InvalidRequest(
246 "Server is not in initializing state".to_string(),
247 ));
248 }
249
250 self.state = ServerState::Initialized;
251 Ok(())
252 }
253
254 pub async fn handle_shutdown(&mut self) -> LspResult<Value> {
256 if self.state != ServerState::Initialized {
257 return Err(LspError::InvalidRequest(
258 "Server is not initialized".to_string(),
259 ));
260 }
261
262 info!("Shutdown request received");
263 self.state = ServerState::ShuttingDown;
264 info!("Server state changed to ShuttingDown");
265 Ok(json!(null))
266 }
267
268 pub async fn handle_exit(&mut self) -> LspResult<()> {
270 if self.state != ServerState::ShuttingDown {
271 return Err(LspError::InvalidRequest(
272 "Server is not shutting down".to_string(),
273 ));
274 }
275
276 info!("Exit notification received");
277 self.state = ServerState::ShutDown;
278 info!("Server state changed to ShutDown");
279 Ok(())
280 }
281
282 pub async fn handle_did_open(&mut self, params: Value) -> LspResult<()> {
284 if self.state != ServerState::Initialized {
285 return Err(LspError::InvalidRequest(
286 "Server is not initialized".to_string(),
287 ));
288 }
289
290 let text_document = params
291 .get("textDocument")
292 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
293
294 let uri = text_document
295 .get("uri")
296 .and_then(|v| v.as_str())
297 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
298
299 let text = text_document
300 .get("text")
301 .and_then(|v| v.as_str())
302 .ok_or_else(|| LspError::InvalidParams("Missing text".to_string()))?;
303
304 debug!("Document opened: uri={}, size={} bytes", uri, text.len());
305 self.set_document(uri.to_string(), text.to_string());
306 Ok(())
307 }
308
309 pub async fn handle_did_change(&mut self, params: Value) -> LspResult<()> {
311 if self.state != ServerState::Initialized {
312 return Err(LspError::InvalidRequest(
313 "Server is not initialized".to_string(),
314 ));
315 }
316
317 let text_document = params
318 .get("textDocument")
319 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
320
321 let uri = text_document
322 .get("uri")
323 .and_then(|v| v.as_str())
324 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
325
326 let content_changes = params
327 .get("contentChanges")
328 .and_then(|v| v.as_array())
329 .ok_or_else(|| LspError::InvalidParams("Missing contentChanges".to_string()))?;
330
331 if let Some(last_change) = content_changes.last() {
333 if let Some(text) = last_change.get("text").and_then(|v| v.as_str()) {
334 self.set_document(uri.to_string(), text.to_string());
335 }
336 }
337
338 Ok(())
339 }
340
341 pub async fn handle_did_close(&mut self, params: Value) -> LspResult<()> {
343 if self.state != ServerState::Initialized {
344 return Err(LspError::InvalidRequest(
345 "Server is not initialized".to_string(),
346 ));
347 }
348
349 let text_document = params
350 .get("textDocument")
351 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
352
353 let uri = text_document
354 .get("uri")
355 .and_then(|v| v.as_str())
356 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
357
358 debug!("Document closed: uri={}", uri);
359 self.remove_document(uri);
360 Ok(())
361 }
362
363 pub async fn handle_hover(&self, params: Value) -> LspResult<Value> {
365 if self.state != ServerState::Initialized {
366 return Err(LspError::InvalidRequest(
367 "Server is not initialized".to_string(),
368 ));
369 }
370
371 let text_document = params
372 .get("textDocument")
373 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
374
375 let uri = text_document
376 .get("uri")
377 .and_then(|v| v.as_str())
378 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
379
380 let position = params
381 .get("position")
382 .ok_or_else(|| LspError::InvalidParams("Missing position".to_string()))?;
383
384 let line = position
385 .get("line")
386 .and_then(|v| v.as_u64())
387 .ok_or_else(|| LspError::InvalidParams("Missing line".to_string()))?
388 as u32;
389
390 let character = position
391 .get("character")
392 .and_then(|v| v.as_u64())
393 .ok_or_else(|| LspError::InvalidParams("Missing character".to_string()))?
394 as u32;
395
396 let code = self
398 .get_document(uri)
399 .ok_or_else(|| LspError::InvalidParams(format!("Document not found: {}", uri)))?;
400
401 let hover_info = self
403 .hover_provider
404 .get_hover_info(code, Position::new(line, character));
405
406 match hover_info {
408 Some(info) => Ok(json!({
409 "contents": {
410 "kind": match info.contents.kind {
411 crate::types::MarkupKind::PlainText => "plaintext",
412 crate::types::MarkupKind::Markdown => "markdown",
413 },
414 "value": info.contents.value,
415 },
416 "range": info.range.map(|r| {
417 json!({
418 "start": {
419 "line": r.start.line,
420 "character": r.start.character,
421 },
422 "end": {
423 "line": r.end.line,
424 "character": r.end.character,
425 },
426 })
427 }),
428 })),
429 None => Ok(json!(null)),
430 }
431 }
432
433 pub async fn handle_diagnostics(&self, params: Value) -> LspResult<Value> {
435 if self.state != ServerState::Initialized {
436 return Err(LspError::InvalidRequest(
437 "Server is not initialized".to_string(),
438 ));
439 }
440
441 let text_document = params
442 .get("textDocument")
443 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
444
445 let uri = text_document
446 .get("uri")
447 .and_then(|v| v.as_str())
448 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
449
450 let code = self
452 .get_document(uri)
453 .ok_or_else(|| LspError::InvalidParams(format!("Document not found: {}", uri)))?;
454
455 let language = self.detect_language(uri);
457
458 let diagnostics = self
460 .diagnostics_engine
461 .generate_diagnostics(code, language)
462 .map_err(|e| LspError::InternalError(e.to_string()))?;
463
464 let diagnostics_json: Vec<Value> = diagnostics
466 .iter()
467 .map(|diag| {
468 json!({
469 "range": {
470 "start": {
471 "line": diag.range.start.line,
472 "character": diag.range.start.character,
473 },
474 "end": {
475 "line": diag.range.end.line,
476 "character": diag.range.end.character,
477 },
478 },
479 "severity": match diag.severity {
480 crate::types::DiagnosticSeverity::Error => 1,
481 crate::types::DiagnosticSeverity::Warning => 2,
482 crate::types::DiagnosticSeverity::Information => 3,
483 crate::types::DiagnosticSeverity::Hint => 4,
484 },
485 "message": diag.message,
486 "code": diag.code,
487 "source": diag.source,
488 })
489 })
490 .collect();
491
492 Ok(json!(diagnostics_json))
493 }
494
495 pub async fn handle_completion(&self, params: Value) -> LspResult<Value> {
497 if self.state != ServerState::Initialized {
498 return Err(LspError::InvalidRequest(
499 "Server is not initialized".to_string(),
500 ));
501 }
502
503 if !self.is_completion_enabled() {
504 return Ok(json!([]));
505 }
506
507 let text_document = params
508 .get("textDocument")
509 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
510
511 let uri = text_document
512 .get("uri")
513 .and_then(|v| v.as_str())
514 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
515
516 let position = params
517 .get("position")
518 .ok_or_else(|| LspError::InvalidParams("Missing position".to_string()))?;
519
520 let line = position
521 .get("line")
522 .and_then(|v| v.as_u64())
523 .ok_or_else(|| LspError::InvalidParams("Missing line".to_string()))?
524 as u32;
525
526 let character = position
527 .get("character")
528 .and_then(|v| v.as_u64())
529 .ok_or_else(|| LspError::InvalidParams("Missing character".to_string()))?
530 as u32;
531
532 let code = self
534 .get_document(uri)
535 .ok_or_else(|| LspError::InvalidParams(format!("Document not found: {}", uri)))?;
536
537 let language = self.detect_language(uri);
539
540 let handler = self.completion_handler.as_ref().ok_or_else(|| {
542 LspError::InternalError("Completion engine not initialized".to_string())
543 })?;
544
545 let completions = handler
547 .handle_completion(code, Position::new(line, character), language.as_str())
548 .await?;
549
550 Ok(json!(completions))
551 }
552
553 pub async fn handle_completion_resolve(&self, params: Value) -> LspResult<Value> {
555 if self.state != ServerState::Initialized {
556 return Err(LspError::InvalidRequest(
557 "Server is not initialized".to_string(),
558 ));
559 }
560
561 if !self.is_completion_enabled() {
562 return Ok(params);
563 }
564
565 let handler = self.completion_handler.as_ref().ok_or_else(|| {
566 LspError::InternalError("Completion engine not initialized".to_string())
567 })?;
568
569 handler.handle_completion_resolve(¶ms).await
570 }
571
572 pub async fn handle_code_action(&self, params: Value) -> LspResult<Value> {
574 if self.state != ServerState::Initialized {
575 return Err(LspError::InvalidRequest(
576 "Server is not initialized".to_string(),
577 ));
578 }
579
580 let text_document = params
581 .get("textDocument")
582 .ok_or_else(|| LspError::InvalidParams("Missing textDocument".to_string()))?;
583
584 let uri = text_document
585 .get("uri")
586 .and_then(|v| v.as_str())
587 .ok_or_else(|| LspError::InvalidParams("Missing uri".to_string()))?;
588
589 let context = params
590 .get("context")
591 .ok_or_else(|| LspError::InvalidParams("Missing context".to_string()))?;
592
593 let diagnostics_array = context
594 .get("diagnostics")
595 .and_then(|v| v.as_array())
596 .ok_or_else(|| LspError::InvalidParams("Missing diagnostics in context".to_string()))?;
597
598 let code = self
600 .get_document(uri)
601 .ok_or_else(|| LspError::InvalidParams(format!("Document not found: {}", uri)))?;
602
603 let mut actions = Vec::new();
604
605 for diag_json in diagnostics_array {
607 if let Ok(diagnostic) =
609 serde_json::from_value::<crate::types::Diagnostic>(diag_json.clone())
610 {
611 match self
612 .code_actions_engine
613 .suggest_code_actions(&diagnostic, code)
614 {
615 Ok(suggested_actions) => {
616 for action in suggested_actions {
617 actions.push(json!({
618 "title": action.title,
619 "kind": match action.kind {
620 crate::types::CodeActionKind::QuickFix => "quickfix",
621 crate::types::CodeActionKind::Refactor => "refactor",
622 crate::types::CodeActionKind::RefactorExtract => "refactor.extract",
623 crate::types::CodeActionKind::RefactorInline => "refactor.inline",
624 crate::types::CodeActionKind::RefactorRewrite => "refactor.rewrite",
625 crate::types::CodeActionKind::Source => "source",
626 crate::types::CodeActionKind::SourceOrganizeImports => "source.organizeImports",
627 crate::types::CodeActionKind::SourceFixAll => "source.fixAll",
628 },
629 "edit": {
630 "changes": action.edit.changes.iter().map(|(uri, edits)| {
631 (uri.clone(), edits.iter().map(|edit| {
632 json!({
633 "range": {
634 "start": {
635 "line": edit.range.start.line,
636 "character": edit.range.start.character,
637 },
638 "end": {
639 "line": edit.range.end.line,
640 "character": edit.range.end.character,
641 },
642 },
643 "newText": edit.new_text,
644 })
645 }).collect::<Vec<_>>())
646 }).collect::<std::collections::HashMap<_, _>>(),
647 },
648 }));
649 }
650 }
651 Err(e) => {
652 eprintln!("Error generating code actions: {}", e);
654 }
655 }
656 }
657 }
658
659 Ok(json!(actions))
660 }
661
662 fn detect_language(&self, uri: &str) -> Language {
664 if let Some(last_dot) = uri.rfind('.') {
666 let ext = &uri[last_dot + 1..];
667 Language::from_extension(ext)
668 } else {
669 Language::Unknown
670 }
671 }
672
673 async fn process_message(&mut self, message: LspMessage) -> LspResult<Option<Value>> {
675 match message {
676 LspMessage::Request(req) => match req.method.as_str() {
677 "initialize" => {
678 let params = req.params.unwrap_or(json!({}));
679 self.handle_initialize(params).await
680 }
681 "shutdown" => self.handle_shutdown().await,
682 "textDocument/hover" => {
683 let params = req.params.unwrap_or(json!({}));
684 self.handle_hover(params).await
685 }
686 "textDocument/diagnostics" => {
687 let params = req.params.unwrap_or(json!({}));
688 self.handle_diagnostics(params).await
689 }
690 "textDocument/codeAction" => {
691 let params = req.params.unwrap_or(json!({}));
692 self.handle_code_action(params).await
693 }
694 "textDocument/completion" => {
695 let params = req.params.unwrap_or(json!({}));
696 self.handle_completion(params).await
697 }
698 "completionItem/resolve" => {
699 let params = req.params.unwrap_or(json!({}));
700 self.handle_completion_resolve(params).await
701 }
702 _ => Err(LspError::MethodNotFound(req.method)),
703 }
704 .map(Some),
705 LspMessage::Notification(notif) => {
706 match notif.method.as_str() {
707 "initialized" => self.handle_initialized().await,
708 "textDocument/didOpen" => {
709 let params = notif.params.unwrap_or(json!({}));
710 self.handle_did_open(params).await
711 }
712 "textDocument/didChange" => {
713 let params = notif.params.unwrap_or(json!({}));
714 self.handle_did_change(params).await
715 }
716 "textDocument/didClose" => {
717 let params = notif.params.unwrap_or(json!({}));
718 self.handle_did_close(params).await
719 }
720 "exit" => self.handle_exit().await,
721 _ => {
722 Ok(())
724 }
725 }
726 .map(|_| None)
727 }
728 LspMessage::Response(_) => {
729 Ok(None)
731 }
732 }
733 }
734
735 pub async fn run(&mut self) -> LspResult<()> {
737 info!("LSP server started");
738
739 loop {
740 match self.transport.read_message().await {
742 Ok(message) => {
743 match &message {
745 LspMessage::Request(req) => {
746 debug!("Received request: method={}, id={:?}", req.method, req.id);
747 }
748 LspMessage::Notification(notif) => {
749 debug!("Received notification: method={}", notif.method);
750 }
751 LspMessage::Response(_) => {
752 debug!("Received response");
753 }
754 }
755
756 match self.process_message(message.clone()).await {
758 Ok(Some(result)) => {
759 if let LspMessage::Request(req) = message {
761 debug!("Sending response for request: id={:?}", req.id);
762 let response = JsonRpcResponse::success(req.id, result);
763 let response_msg = LspMessage::Response(response);
764 if let Err(e) = self.transport.write_message(&response_msg).await {
765 error!("Failed to send response: {}", e);
766 }
767 }
768 }
769 Ok(None) => {
770 if let LspMessage::Notification(notif) = message {
772 debug!("Notification processed: method={}", notif.method);
773 }
774 }
775 Err(err) => {
776 if let LspMessage::Request(req) = message {
778 warn!("Error processing request: {}", err);
779 let error = match err {
780 LspError::MethodNotFound(method) => {
781 error!("Method not found: {}", method);
782 JsonRpcError::method_not_found(method)
783 }
784 LspError::InvalidParams(msg) => {
785 warn!("Invalid parameters: {}", msg);
786 JsonRpcError::invalid_params(msg)
787 }
788 LspError::ParseError(msg) => {
789 error!("Parse error: {}", msg);
790 JsonRpcError::parse_error(msg)
791 }
792 LspError::TimeoutError(msg) => {
793 warn!("Timeout error: {}", msg);
794 JsonRpcError::internal_error(msg)
795 }
796 _ => {
797 error!("Internal error: {}", err);
798 JsonRpcError::internal_error(err.to_string())
799 }
800 };
801 let response = JsonRpcResponse::error(req.id, error);
802 let response_msg = LspMessage::Response(response);
803 if let Err(e) = self.transport.write_message(&response_msg).await {
804 error!("Failed to send error response: {}", e);
805 }
806 }
807 }
808 }
809 }
810 Err(e) => {
811 error!("Failed to read message: {}", e);
812 }
814 }
815
816 if self.state == ServerState::ShutDown {
818 info!("LSP server shutting down");
819 break;
820 }
821 }
822
823 info!("LSP server stopped");
824 Ok(())
825 }
826}
827
828impl Default for LspServer {
829 fn default() -> Self {
830 Self::new()
831 }
832}
833
834#[cfg(test)]
835mod tests {
836 use super::*;
837
838 #[test]
839 fn test_server_creation() {
840 let server = LspServer::new();
841 assert_eq!(server.state(), ServerState::Initializing);
842 }
843
844 #[test]
845 fn test_server_capabilities() {
846 let server = LspServer::new();
847 let caps = server.capabilities();
848 assert!(caps.hover_provider);
849 assert!(caps.code_action_provider);
850 }
851
852 #[test]
853 fn test_document_management() {
854 let mut server = LspServer::new();
855 server.set_document("file://test.rs".to_string(), "fn main() {}".to_string());
856 assert_eq!(server.get_document("file://test.rs"), Some("fn main() {}"));
857
858 server.remove_document("file://test.rs");
859 assert_eq!(server.get_document("file://test.rs"), None);
860 }
861
862 #[test]
863 fn test_server_capabilities_json() {
864 let caps = ServerCapabilities::default();
865 let json = caps.to_json();
866 assert!(json.get("textDocumentSync").is_some());
867 assert!(json.get("hoverProvider").is_some());
868 }
869
870 #[test]
871 fn test_error_handling_invalid_request() {
872 let server = LspServer::new();
873 let result = tokio::runtime::Runtime::new()
875 .unwrap()
876 .block_on(server.handle_hover(json!({})));
877 assert!(result.is_err());
878 }
879
880 #[test]
881 fn test_error_handling_missing_params() {
882 let mut server = LspServer::new();
883 server.state = ServerState::Initialized;
884
885 let result = tokio::runtime::Runtime::new()
887 .unwrap()
888 .block_on(server.handle_hover(json!({})));
889 assert!(result.is_err());
890 }
891
892 #[test]
893 fn test_language_detection_rust() {
894 let server = LspServer::new();
895 let lang = server.detect_language("file://test.rs");
896 assert_eq!(lang, Language::Rust);
897 }
898
899 #[test]
900 fn test_language_detection_typescript() {
901 let server = LspServer::new();
902 let lang = server.detect_language("file://test.ts");
903 assert_eq!(lang, Language::TypeScript);
904 }
905
906 #[test]
907 fn test_language_detection_python() {
908 let server = LspServer::new();
909 let lang = server.detect_language("file://test.py");
910 assert_eq!(lang, Language::Python);
911 }
912
913 #[test]
914 fn test_language_detection_unknown() {
915 let server = LspServer::new();
916 let lang = server.detect_language("file://test.unknown");
917 assert_eq!(lang, Language::Unknown);
918 }
919
920 #[test]
921 fn test_error_handling_document_not_found() {
922 let mut server = LspServer::new();
923 server.state = ServerState::Initialized;
924
925 let result = tokio::runtime::Runtime::new()
927 .unwrap()
928 .block_on(server.handle_hover(json!({
929 "textDocument": { "uri": "file://nonexistent.rs" },
930 "position": { "line": 0, "character": 0 }
931 })));
932 assert!(result.is_err());
933 }
934
935 #[test]
936 fn test_diagnostics_handler_integration() {
937 let mut server = LspServer::new();
938 server.state = ServerState::Initialized;
939
940 server.set_document("file://test.rs".to_string(), "fn main() {}".to_string());
942
943 let result = tokio::runtime::Runtime::new()
945 .unwrap()
946 .block_on(server.handle_diagnostics(json!({
947 "textDocument": { "uri": "file://test.rs" }
948 })));
949
950 assert!(result.is_ok());
952 let response = result.unwrap();
953 assert!(response.is_array());
954 }
955
956 #[test]
957 fn test_code_action_handler_integration() {
958 let mut server = LspServer::new();
959 server.state = ServerState::Initialized;
960
961 server.set_document("file://test.rs".to_string(), "fn main() {}".to_string());
963
964 let result = tokio::runtime::Runtime::new()
966 .unwrap()
967 .block_on(server.handle_code_action(json!({
968 "textDocument": { "uri": "file://test.rs" },
969 "context": { "diagnostics": [] }
970 })));
971
972 assert!(result.is_ok());
974 let response = result.unwrap();
975 assert!(response.is_array());
976 }
977
978 #[test]
979 fn test_hover_handler_integration() {
980 let mut server = LspServer::new();
981 server.state = ServerState::Initialized;
982
983 server.set_document("file://test.rs".to_string(), "fn main() {}".to_string());
985
986 let result = tokio::runtime::Runtime::new()
988 .unwrap()
989 .block_on(server.handle_hover(json!({
990 "textDocument": { "uri": "file://test.rs" },
991 "position": { "line": 0, "character": 0 }
992 })));
993
994 assert!(result.is_ok());
996 }
997
998 #[test]
999 fn test_diagnostics_with_language_detection() {
1000 let mut server = LspServer::new();
1001 server.state = ServerState::Initialized;
1002
1003 server.set_document("file://test.rs".to_string(), "fn main() {}".to_string());
1005
1006 let result = tokio::runtime::Runtime::new()
1008 .unwrap()
1009 .block_on(server.handle_diagnostics(json!({
1010 "textDocument": { "uri": "file://test.rs" }
1011 })));
1012
1013 assert!(result.is_ok());
1014
1015 server.set_document("file://test.py".to_string(), "def main(): pass".to_string());
1017
1018 let result = tokio::runtime::Runtime::new()
1020 .unwrap()
1021 .block_on(server.handle_diagnostics(json!({
1022 "textDocument": { "uri": "file://test.py" }
1023 })));
1024
1025 assert!(result.is_ok());
1026 }
1027}