| 1 | //! LSP message handling and callback management |
| 2 | //! |
| 3 | //! Handles routing of LSP responses to appropriate callbacks. |
| 4 | //! |
| 5 | //! Note: ParsedResponse helpers are for planned features. |
| 6 | #![allow(dead_code)] |
| 7 | |
| 8 | use serde_json::Value; |
| 9 | use std::collections::HashMap; |
| 10 | |
| 11 | use super::protocol::{LspMessage, ResponseError}; |
| 12 | use super::types::{ |
| 13 | CompletionItem, Diagnostic, DocumentSymbol, HoverInfo, Location, TextEdit, WorkspaceEdit, |
| 14 | }; |
| 15 | |
| 16 | /// Result type for LSP responses |
| 17 | pub type LspResult<T> = Result<T, ResponseError>; |
| 18 | |
| 19 | /// Callback for LSP responses |
| 20 | pub type ResponseCallback = Box<dyn FnOnce(i64, LspResult<Value>) + Send>; |
| 21 | |
| 22 | /// Callback for diagnostics notifications |
| 23 | pub type DiagnosticsCallback = Box<dyn Fn(String, Vec<Diagnostic>) + Send>; |
| 24 | |
| 25 | /// Tracks pending requests and their callbacks |
| 26 | pub struct MessageHandler { |
| 27 | /// Pending request callbacks indexed by request ID |
| 28 | pending: HashMap<i64, ResponseCallback>, |
| 29 | /// Callback for diagnostics notifications |
| 30 | diagnostics_callback: Option<DiagnosticsCallback>, |
| 31 | } |
| 32 | |
| 33 | impl MessageHandler { |
| 34 | pub fn new() -> Self { |
| 35 | Self { |
| 36 | pending: HashMap::new(), |
| 37 | diagnostics_callback: None, |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | /// Register a callback for a request |
| 42 | pub fn register_callback(&mut self, id: i64, callback: ResponseCallback) { |
| 43 | self.pending.insert(id, callback); |
| 44 | } |
| 45 | |
| 46 | /// Set the diagnostics callback |
| 47 | pub fn set_diagnostics_callback(&mut self, callback: DiagnosticsCallback) { |
| 48 | self.diagnostics_callback = Some(callback); |
| 49 | } |
| 50 | |
| 51 | /// Handle an incoming message |
| 52 | pub fn handle_message(&mut self, message: LspMessage) -> Option<LspMessage> { |
| 53 | match message { |
| 54 | LspMessage::Response { id, result, error } => { |
| 55 | self.handle_response(id, result, error); |
| 56 | None |
| 57 | } |
| 58 | LspMessage::Notification { method, params } => { |
| 59 | self.handle_notification(&method, params); |
| 60 | None |
| 61 | } |
| 62 | LspMessage::Request { id, method, params } => { |
| 63 | // Handle server-to-client requests |
| 64 | self.handle_server_request(id, &method, params) |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /// Handle a response message |
| 70 | fn handle_response(&mut self, id: i64, result: Option<Value>, error: Option<ResponseError>) { |
| 71 | if let Some(callback) = self.pending.remove(&id) { |
| 72 | let response = if let Some(err) = error { |
| 73 | Err(err) |
| 74 | } else { |
| 75 | Ok(result.unwrap_or(Value::Null)) |
| 76 | }; |
| 77 | callback(id, response); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /// Handle a notification message |
| 82 | fn handle_notification(&mut self, method: &str, params: Option<Value>) { |
| 83 | match method { |
| 84 | "textDocument/publishDiagnostics" => { |
| 85 | if let (Some(params), Some(callback)) = (params, &self.diagnostics_callback) { |
| 86 | let (uri, diagnostics) = super::protocol::parse_diagnostics(¶ms); |
| 87 | callback(uri, diagnostics); |
| 88 | } |
| 89 | } |
| 90 | "window/logMessage" | "window/showMessage" => { |
| 91 | // Silently ignore server log messages |
| 92 | // These could be surfaced to the status bar via a callback if needed |
| 93 | let _ = params; |
| 94 | } |
| 95 | _ => { |
| 96 | // Ignore other notifications |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | /// Handle a server-to-client request (return a response if needed) |
| 102 | fn handle_server_request( |
| 103 | &mut self, |
| 104 | id: i64, |
| 105 | method: &str, |
| 106 | _params: Option<Value>, |
| 107 | ) -> Option<LspMessage> { |
| 108 | match method { |
| 109 | "workspace/configuration" => { |
| 110 | // Return empty configuration |
| 111 | Some(LspMessage::Response { |
| 112 | id, |
| 113 | result: Some(Value::Array(vec![])), |
| 114 | error: None, |
| 115 | }) |
| 116 | } |
| 117 | "client/registerCapability" | "client/unregisterCapability" => { |
| 118 | // Acknowledge capability registration |
| 119 | Some(LspMessage::Response { |
| 120 | id, |
| 121 | result: Some(Value::Null), |
| 122 | error: None, |
| 123 | }) |
| 124 | } |
| 125 | "window/workDoneProgress/create" => { |
| 126 | // Acknowledge progress creation |
| 127 | Some(LspMessage::Response { |
| 128 | id, |
| 129 | result: Some(Value::Null), |
| 130 | error: None, |
| 131 | }) |
| 132 | } |
| 133 | _ => { |
| 134 | // Unknown request - return method not found error |
| 135 | Some(LspMessage::Response { |
| 136 | id, |
| 137 | result: None, |
| 138 | error: Some(ResponseError { |
| 139 | code: -32601, // Method not found |
| 140 | message: format!("Method not found: {}", method), |
| 141 | data: None, |
| 142 | }), |
| 143 | }) |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /// Check if there are pending requests |
| 149 | pub fn has_pending(&self) -> bool { |
| 150 | !self.pending.is_empty() |
| 151 | } |
| 152 | |
| 153 | /// Get number of pending requests |
| 154 | pub fn pending_count(&self) -> usize { |
| 155 | self.pending.len() |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | impl Default for MessageHandler { |
| 160 | fn default() -> Self { |
| 161 | Self::new() |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | /// Parsed LSP response types for convenience |
| 166 | pub enum ParsedResponse { |
| 167 | Completions(Vec<CompletionItem>), |
| 168 | Hover(Option<HoverInfo>), |
| 169 | Locations(Vec<Location>), |
| 170 | Symbols(Vec<DocumentSymbol>), |
| 171 | TextEdits(Vec<TextEdit>), |
| 172 | WorkspaceEdit(WorkspaceEdit), |
| 173 | Empty, |
| 174 | } |
| 175 | |
| 176 | impl ParsedResponse { |
| 177 | /// Parse a completion response |
| 178 | pub fn parse_completions(result: &Value) -> Self { |
| 179 | ParsedResponse::Completions(super::protocol::parse_completion_items(result)) |
| 180 | } |
| 181 | |
| 182 | /// Parse a hover response |
| 183 | pub fn parse_hover(result: &Value) -> Self { |
| 184 | ParsedResponse::Hover(super::protocol::parse_hover(result)) |
| 185 | } |
| 186 | |
| 187 | /// Parse a definition/references response |
| 188 | pub fn parse_locations(result: &Value) -> Self { |
| 189 | ParsedResponse::Locations(super::protocol::parse_locations(result)) |
| 190 | } |
| 191 | |
| 192 | /// Parse a document symbols response |
| 193 | pub fn parse_symbols(result: &Value) -> Self { |
| 194 | ParsedResponse::Symbols(super::protocol::parse_document_symbols(result)) |
| 195 | } |
| 196 | |
| 197 | /// Parse a formatting response |
| 198 | pub fn parse_text_edits(result: &Value) -> Self { |
| 199 | ParsedResponse::TextEdits(super::protocol::parse_text_edits(result)) |
| 200 | } |
| 201 | |
| 202 | /// Parse a rename response |
| 203 | pub fn parse_workspace_edit(result: &Value) -> Self { |
| 204 | ParsedResponse::WorkspaceEdit(super::protocol::parse_workspace_edit(result)) |
| 205 | } |
| 206 | } |
| 207 |