Skip to main content

iced_code_editor/canvas_editor/lsp_process/
mod.rs

1//! LSP (Language Server Protocol) Process Client implementation.
2//!
3//! This module provides a client for communicating with LSP servers via stdio.
4//! It handles document synchronization, hover requests, and completion requests.
5//!
6//! Enable with the `lsp-process` Cargo feature. Not available on WASM targets.
7
8pub mod config;
9pub mod overlay;
10
11/// JSON-RPC method name for server-push progress notifications.
12const METHOD_PROGRESS: &str = "$/progress";
13/// JSON-RPC method name sent by the server when it creates a work-done token.
14const METHOD_WORK_DONE_PROGRESS_CREATE: &str = "window/workDoneProgress/create";
15/// Progress `kind` value that signals the end of a work-done sequence.
16const PROGRESS_KIND_END: &str = "end";
17
18use self::config::{
19    LspCommand, ensure_rust_analyzer_config, lsp_server_config,
20    resolve_lsp_command,
21};
22use crate::canvas_editor::lsp::{
23    LspClient, LspDocument, LspPosition, LspTextChange,
24};
25use serde_json::json;
26use std::collections::HashMap;
27use std::io::{BufRead, BufReader, Read, Write};
28use std::process::{Child, Command, Stdio};
29use std::sync::atomic::{AtomicU64, Ordering};
30use std::sync::{Arc, Mutex, mpsc};
31use std::thread;
32
33// =============================================================================
34// Text Model - Internal document representation for tracking text changes
35// =============================================================================
36
37/// Internal representation of a text document as a vector of lines.
38///
39/// Used to track document state and convert between character and byte indices.
40struct TextModel {
41    /// The document content stored as a vector of lines (without newline characters)
42    lines: Vec<String>,
43}
44
45impl TextModel {
46    /// Creates a new `TextModel` from a string.
47    ///
48    /// Splits the text into lines for easier manipulation.
49    /// An empty string creates a single empty line.
50    fn from_text(text: &str) -> Self {
51        let lines = if text.is_empty() {
52            vec![String::new()]
53        } else {
54            text.lines().map(String::from).collect()
55        };
56        Self { lines }
57    }
58
59    /// Applies a text change (edit) to the document.
60    ///
61    /// Handles multi-line insertions and deletions by splicing the lines vector.
62    fn apply_change(&mut self, change: &LspTextChange) {
63        let start_line = change.range.start.line as usize;
64        let end_line = change.range.end.line as usize;
65
66        if start_line >= self.lines.len() || end_line >= self.lines.len() {
67            return;
68        }
69
70        let start_col = change.range.start.character as usize;
71        let end_col = change.range.end.character as usize;
72
73        let start_byte = char_to_byte_index(&self.lines[start_line], start_col);
74        let end_byte = char_to_byte_index(&self.lines[end_line], end_col);
75
76        let prefix = self.lines[start_line][..start_byte].to_string();
77        let suffix = self.lines[end_line][end_byte..].to_string();
78
79        let inserted: Vec<&str> = change.text.split('\n').collect();
80        let mut replacement: Vec<String> = Vec::new();
81
82        if inserted.len() == 1 {
83            replacement.push(format!("{}{}{}", prefix, inserted[0], suffix));
84        } else {
85            replacement.push(format!("{}{}", prefix, inserted[0]));
86            for mid in inserted.iter().take(inserted.len() - 1).skip(1) {
87                replacement.push((*mid).to_string());
88            }
89            replacement.push(format!(
90                "{}{}",
91                inserted[inserted.len() - 1],
92                suffix
93            ));
94        }
95
96        self.lines.splice(start_line..=end_line, replacement);
97    }
98
99    /// Converts a UTF-8 character position to a UTF-16 position.
100    ///
101    /// This is necessary because LSP uses UTF-16 for character positions.
102    fn to_utf16_position(&self, position: LspPosition) -> LspPosition {
103        let line_index = position.line as usize;
104        let char_index = position.character as usize;
105        let line = self.lines.get(line_index).map_or("", |l| l.as_str());
106
107        let utf16_col =
108            line.chars().take(char_index).map(|c| c.len_utf16() as u32).sum();
109        LspPosition { line: position.line, character: utf16_col }
110    }
111}
112
113/// Converts a character index to a byte index in a string.
114///
115/// Returns the length of the string if the index is out of bounds.
116fn char_to_byte_index(s: &str, char_index: usize) -> usize {
117    s.char_indices().nth(char_index).map_or(s.len(), |(idx, _)| idx)
118}
119
120// =============================================================================
121// Document State - Tracks the state of an open document
122// =============================================================================
123
124/// Represents the state of a single open document.
125struct DocumentState {
126    /// The text content of the document
127    text: TextModel,
128}
129
130// =============================================================================
131// LSP Request Types
132// =============================================================================
133
134/// Enumeration of LSP request types that we track for response handling.
135enum LspRequestKind {
136    /// Hover request — shows type information and documentation
137    Hover,
138    /// Completion request — provides auto-complete suggestions
139    Completion,
140    /// Definition request — go to definition
141    Definition,
142}
143
144// =============================================================================
145// LSP Events - Events sent back to the main application
146// =============================================================================
147
148/// Events that can be sent from the LSP client to the application.
149///
150/// Receive these by polling the `mpsc::Receiver` you pass to
151/// [`LspProcessClient::new_with_server`].
152pub enum LspEvent {
153    /// Hover information received from the LSP server.
154    Hover {
155        /// Markdown or plain-text hover content.
156        text: String,
157    },
158    /// Completion items received from the LSP server.
159    Completion {
160        /// List of completion label strings.
161        items: Vec<String>,
162    },
163    /// Definition location received from the LSP server.
164    Definition {
165        /// Target document URI.
166        uri: String,
167        /// Target range within that document.
168        range: crate::canvas_editor::lsp::LspRange,
169    },
170    /// Progress notification from the LSP server.
171    Progress {
172        /// Progress token identifier.
173        token: String,
174        /// Key of the server that sent this notification.
175        server_key: String,
176        /// Human-readable title for the progress operation.
177        title: String,
178        /// Optional status message.
179        message: Option<String>,
180        /// Optional percentage complete (0–100).
181        percentage: Option<u32>,
182        /// `true` when this is the final progress notification.
183        done: bool,
184    },
185    /// Log message from the LSP server's stderr.
186    Log {
187        /// Key of the server that sent this message.
188        server_key: String,
189        /// The log line.
190        message: String,
191    },
192}
193
194// =============================================================================
195// LSP Process Client - Main client implementation
196// =============================================================================
197
198/// Client for communicating with an LSP server process.
199///
200/// Manages the lifecycle of the server process and handles all communication.
201/// Implements [`LspClient`] so it can be plugged directly into a [`CodeEditor`].
202///
203/// # Examples
204///
205/// ```no_run
206/// use std::sync::mpsc;
207/// use iced_code_editor::{LspProcessClient, LspEvent};
208///
209/// let (tx, rx) = mpsc::channel::<LspEvent>();
210/// let client = LspProcessClient::new_with_server(
211///     "file:///home/user/project",
212///     tx,
213///     "lua-language-server",
214/// );
215/// ```
216///
217/// [`CodeEditor`]: crate::CodeEditor
218pub struct LspProcessClient {
219    /// The child process running the LSP server
220    child: Child,
221    /// Channel for sending messages to the writer thread
222    writer: mpsc::Sender<Vec<u8>>,
223    /// Map of URI to document state for all open documents
224    documents: Arc<Mutex<HashMap<String, DocumentState>>>,
225    /// Counter for generating unique request IDs
226    request_id: AtomicU64,
227    /// Map of pending request IDs to their types (for response routing)
228    pending_requests: Arc<Mutex<HashMap<u64, LspRequestKind>>>,
229    /// Handle to the writer thread (kept alive for the client's lifetime)
230    _writer_thread: thread::JoinHandle<()>,
231    /// Handle to the reader thread (kept alive for the client's lifetime)
232    _reader_thread: thread::JoinHandle<()>,
233    /// Handle to the stderr thread (kept alive for the client's lifetime)
234    _stderr_thread: thread::JoinHandle<()>,
235}
236
237impl LspProcessClient {
238    /// Creates a new LSP client connected to the specified server.
239    ///
240    /// # Arguments
241    ///
242    /// * `root_uri` — the root URI of the workspace (e.g. `"file:///home/user/project"`)
243    /// * `events` — channel to send [`LspEvent`]s back to the application
244    /// * `server_key` — key identifying the LSP server (e.g. `"lua-language-server"`)
245    ///
246    /// # Errors
247    ///
248    /// Returns an error string when the server key is not recognised, when the
249    /// server binary cannot be found, or when the process cannot be spawned.
250    ///
251    /// # Examples
252    ///
253    /// ```no_run
254    /// use std::sync::mpsc;
255    /// use iced_code_editor::{LspProcessClient, LspEvent};
256    ///
257    /// let (tx, _rx) = mpsc::channel::<LspEvent>();
258    /// let client = LspProcessClient::new_with_server(
259    ///     "file:///tmp/project",
260    ///     tx,
261    ///     "lua-language-server",
262    /// );
263    /// assert!(client.is_ok());
264    /// ```
265    pub fn new_with_server(
266        root_uri: &str,
267        events: mpsc::Sender<LspEvent>,
268        server_key: &str,
269    ) -> Result<Self, String> {
270        let config = lsp_server_config(server_key)
271            .ok_or_else(|| format!("Unsupported LSP server: {}", server_key))?;
272
273        if server_key == "rust-analyzer" {
274            ensure_rust_analyzer_config();
275        }
276
277        let command = resolve_lsp_command(config)?;
278        Self::new_with_command(root_uri, events, &command, server_key)
279    }
280
281    /// Creates a new LSP client with a specific command.
282    ///
283    /// This is the internal implementation that spawns the process.
284    ///
285    /// # Errors
286    ///
287    /// Returns an error string if the process cannot be spawned or if stdio
288    /// handles cannot be acquired.
289    fn new_with_command(
290        root_uri: &str,
291        events: mpsc::Sender<LspEvent>,
292        command: &LspCommand,
293        server_key: &str,
294    ) -> Result<Self, String> {
295        let mut child = Command::new(&command.program)
296            .args(&command.args)
297            .stdin(Stdio::piped())
298            .stdout(Stdio::piped())
299            .stderr(Stdio::piped())
300            .spawn()
301            .map_err(|e| {
302                if e.kind() == std::io::ErrorKind::NotFound {
303                    if command.program == "rust-analyzer" {
304                        "LSP server program rust-analyzer not found. Please install rust-analyzer or set RUST_ANALYZER/RUST_ANALYZER_PATH environment variable".to_string()
305                    } else {
306                        format!("LSP server program {} not found", command.program)
307                    }
308                } else {
309                    e.to_string()
310                }
311            })?;
312
313        let stdin = child.stdin.take().ok_or("stdin unavailable")?;
314        let stdout = child.stdout.take().ok_or("stdout unavailable")?;
315        let stderr = child.stderr.take().ok_or("stderr unavailable")?;
316
317        let (tx, rx) = mpsc::channel::<Vec<u8>>();
318        let pending_requests = Arc::new(Mutex::new(HashMap::new()));
319        let pending_reader = pending_requests.clone();
320        let events_reader = events.clone();
321        let events_log = events;
322        let server_key = server_key.to_string();
323        let server_key_reader = server_key.clone();
324        let server_key_log = server_key;
325        let tx_reader = tx.clone();
326
327        let writer_thread = thread::spawn(move || {
328            let mut input = stdin;
329            for bytes in rx {
330                if input.write_all(&bytes).is_err() {
331                    break;
332                }
333                let _ = input.flush();
334            }
335        });
336
337        let reader_thread = thread::spawn(move || {
338            let mut reader = BufReader::new(stdout);
339            loop {
340                let mut content_length: Option<usize> = None;
341                let mut line = String::new();
342
343                loop {
344                    line.clear();
345                    if reader
346                        .read_line(&mut line)
347                        .ok()
348                        .filter(|n| *n > 0)
349                        .is_none()
350                    {
351                        return;
352                    }
353                    let trimmed = line.trim();
354                    if trimmed.is_empty() {
355                        break;
356                    }
357                    if let Some(value) = trimmed.strip_prefix("Content-Length:")
358                        && let Ok(len) = value.trim().parse::<usize>()
359                    {
360                        content_length = Some(len);
361                    }
362                }
363
364                let Some(len) = content_length else { continue };
365                let mut buf = vec![0u8; len];
366                if reader.read_exact(&mut buf).is_err() {
367                    return;
368                }
369
370                if let Ok(value) =
371                    serde_json::from_slice::<serde_json::Value>(&buf)
372                {
373                    if let Some(id) = value.get("id").and_then(|v| v.as_u64()) {
374                        if let Some(method) =
375                            value.get("method").and_then(|m| m.as_str())
376                        {
377                            handle_server_request(id, method, &tx_reader);
378                        } else {
379                            handle_client_response(
380                                id,
381                                &value,
382                                &pending_reader,
383                                &events_reader,
384                            );
385                        }
386                    } else if let Some(method) =
387                        value.get("method").and_then(|m| m.as_str())
388                        && let Some(params) = value.get("params")
389                    {
390                        handle_server_notification(
391                            method,
392                            params,
393                            &events_reader,
394                            &server_key_reader,
395                        );
396                    }
397                }
398            }
399        });
400
401        let stderr_thread = thread::spawn(move || {
402            let reader = BufReader::new(stderr);
403            for line in reader.lines() {
404                let Ok(line) = line else { break };
405                let line = line.trim();
406                if line.is_empty() {
407                    continue;
408                }
409                let _ = events_log.send(LspEvent::Log {
410                    server_key: server_key_log.clone(),
411                    message: line.to_string(),
412                });
413            }
414        });
415
416        let client = Self {
417            child,
418            writer: tx,
419            documents: Arc::new(Mutex::new(HashMap::new())),
420            request_id: AtomicU64::new(1),
421            pending_requests,
422            _writer_thread: writer_thread,
423            _reader_thread: reader_thread,
424            _stderr_thread: stderr_thread,
425        };
426
427        let initialize = json!({
428            "jsonrpc": "2.0",
429            "id": client.next_id(),
430            "method": "initialize",
431            "params": {
432                "processId": std::process::id(),
433                "rootUri": root_uri,
434                "capabilities": {
435                    "textDocument": {
436                        "synchronization": {
437                            "dynamicRegistration": false,
438                            "willSave": false,
439                            "didSave": true
440                        }
441                    },
442                    "window": {
443                        "workDoneProgress": true
444                    }
445                },
446                "workspaceFolders": null
447            }
448        });
449        client.send_message(&initialize);
450
451        let initialized = json!({
452            "jsonrpc": "2.0",
453            "method": "initialized",
454            "params": {}
455        });
456        client.send_message(&initialized);
457
458        Ok(client)
459    }
460
461    /// Generates the next unique request ID using atomic operations.
462    fn next_id(&self) -> u64 {
463        self.request_id.fetch_add(1, Ordering::Relaxed)
464    }
465
466    /// Sends a JSON-RPC message to the LSP server.
467    ///
468    /// Formats the message with the required `Content-Length` header.
469    fn send_message(&self, value: &serde_json::Value) {
470        if let Ok(data) = serde_json::to_vec(&value) {
471            let mut header =
472                format!("Content-Length: {}\r\n\r\n", data.len()).into_bytes();
473            header.extend_from_slice(&data);
474            let _ = self.writer.send(header);
475        }
476    }
477
478    /// Applies text changes to a document and converts them to JSON format.
479    ///
480    /// Also converts positions to UTF-16 as required by LSP.
481    fn apply_change_and_convert(
482        &self,
483        uri: &str,
484        changes: &[LspTextChange],
485    ) -> Vec<serde_json::Value> {
486        let mut out = Vec::new();
487        let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
488        let Some(state) = docs.get_mut(uri) else { return out };
489
490        for change in changes {
491            let start = state.text.to_utf16_position(change.range.start);
492            let end = state.text.to_utf16_position(change.range.end);
493
494            out.push(json!({
495                "range": {
496                    "start": { "line": start.line, "character": start.character },
497                    "end": { "line": end.line, "character": end.character }
498                },
499                "text": change.text
500            }));
501
502            state.text.apply_change(change);
503        }
504        out
505    }
506}
507
508// =============================================================================
509// Reader thread helper functions
510// =============================================================================
511
512/// Handles an LSP server request that requires a JSON-RPC response.
513///
514/// Currently handles `window/workDoneProgress/create` by replying with a null
515/// result. Unknown methods are silently ignored.
516fn handle_server_request(id: u64, method: &str, tx: &mpsc::Sender<Vec<u8>>) {
517    if method == METHOD_WORK_DONE_PROGRESS_CREATE {
518        let response = json!({
519            "jsonrpc": "2.0",
520            "id": id,
521            "result": null
522        });
523        if let Ok(data) = serde_json::to_vec(&response) {
524            let mut header =
525                format!("Content-Length: {}\r\n\r\n", data.len()).into_bytes();
526            header.extend_from_slice(&data);
527            let _ = tx.send(header);
528        }
529    }
530}
531
532/// Dispatches a server response to the appropriate pending request handler.
533///
534/// Looks up the request kind by `id`, parses the result, and emits a
535/// [`LspEvent::Hover`], [`LspEvent::Completion`], or [`LspEvent::Definition`].
536fn handle_client_response(
537    id: u64,
538    value: &serde_json::Value,
539    pending: &Arc<Mutex<HashMap<u64, LspRequestKind>>>,
540    events: &mpsc::Sender<LspEvent>,
541) {
542    let kind = {
543        let mut map = pending.lock().unwrap_or_else(|e| e.into_inner());
544        map.remove(&id)
545    };
546
547    let Some(kind) = kind else { return };
548    let result = value.get("result").unwrap_or(&serde_json::Value::Null);
549
550    match kind {
551        LspRequestKind::Hover => {
552            let text = parse_hover_text(result).unwrap_or_default();
553            let _ = events.send(LspEvent::Hover { text });
554        }
555        LspRequestKind::Completion => {
556            let items = parse_completion_items(result);
557            if !items.is_empty() {
558                let _ = events.send(LspEvent::Completion { items });
559            }
560        }
561        LspRequestKind::Definition => {
562            if let Some((uri, range)) = parse_definition_location(result) {
563                let _ = events.send(LspEvent::Definition { uri, range });
564            }
565        }
566    }
567}
568
569/// Handles a server-initiated notification (e.g. `$/progress`).
570///
571/// Parses the progress payload and emits a [`LspEvent::Progress`].
572/// Notifications for unknown methods are silently ignored.
573fn handle_server_notification(
574    method: &str,
575    params: &serde_json::Value,
576    events: &mpsc::Sender<LspEvent>,
577    server_key: &str,
578) {
579    if method != METHOD_PROGRESS {
580        return;
581    }
582
583    let Some(token) = params.get("token").and_then(|t| {
584        t.as_str()
585            .map(String::from)
586            .or_else(|| t.as_i64().map(|i| i.to_string()))
587    }) else {
588        return;
589    };
590
591    let Some(val) = params.get("value") else { return };
592
593    let kind = val.get("kind").and_then(|k| k.as_str()).unwrap_or("");
594    let title = val
595        .get("title")
596        .and_then(|t| t.as_str())
597        .map(String::from)
598        .unwrap_or_default();
599    let message = val.get("message").and_then(|m| m.as_str()).map(String::from);
600    let percentage =
601        val.get("percentage").and_then(|p| p.as_u64()).map(|p| p as u32);
602    let done = kind == PROGRESS_KIND_END;
603
604    let _ = events.send(LspEvent::Progress {
605        token,
606        server_key: server_key.to_string(),
607        title,
608        message,
609        percentage,
610        done,
611    });
612}
613
614/// Sends shutdown/exit notifications and kills the process on drop.
615impl Drop for LspProcessClient {
616    fn drop(&mut self) {
617        let shutdown = json!({
618            "jsonrpc": "2.0",
619            "id": self.next_id(),
620            "method": "shutdown",
621            "params": null
622        });
623        self.send_message(&shutdown);
624
625        let exit = json!({
626            "jsonrpc": "2.0",
627            "method": "exit",
628            "params": {}
629        });
630        self.send_message(&exit);
631
632        if self.child.try_wait().ok().flatten().is_none() {
633            let _ = self.child.kill();
634        }
635    }
636}
637
638// =============================================================================
639// LSP Response Parsing Functions
640// =============================================================================
641
642/// Parses hover text from an LSP hover response.
643fn parse_hover_text(result: &serde_json::Value) -> Option<String> {
644    let contents = result.get("contents")?;
645    hover_text_from_contents(contents)
646}
647
648/// Recursively extracts hover text from various content formats.
649///
650/// Handles strings, arrays, and objects with a `"value"` field.
651fn hover_text_from_contents(value: &serde_json::Value) -> Option<String> {
652    match value {
653        serde_json::Value::String(text) => Some(text.clone()),
654        serde_json::Value::Array(items) => {
655            let parts: Vec<String> =
656                items.iter().filter_map(hover_text_from_contents).collect();
657            if parts.is_empty() { None } else { Some(parts.join("\n")) }
658        }
659        serde_json::Value::Object(map) => {
660            map.get("value").and_then(|v| v.as_str()).map(String::from)
661        }
662        _ => None,
663    }
664}
665
666/// Parses completion items from an LSP completion response.
667///
668/// Handles both array responses and object responses with an `"items"` field.
669fn parse_completion_items(result: &serde_json::Value) -> Vec<String> {
670    let mut items = Vec::new();
671
672    if let Some(array) = result.as_array() {
673        items.extend(array.iter());
674    } else if let Some(array) = result.get("items").and_then(|v| v.as_array()) {
675        items.extend(array.iter());
676    }
677
678    items
679        .iter()
680        .filter_map(|item| item.get("label").and_then(|v| v.as_str()))
681        .map(String::from)
682        .collect()
683}
684
685/// Parses definition location from an LSP definition response.
686///
687/// Handles `Location`, `Location[]`, and `LocationLink[]` responses.
688fn parse_definition_location(
689    result: &serde_json::Value,
690) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
691    fn extract_location(
692        loc: &serde_json::Value,
693    ) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
694        let uri = loc.get("uri")?.as_str()?.to_string();
695        let range_val = loc.get("range")?;
696
697        let start = range_val.get("start")?;
698        let end = range_val.get("end")?;
699
700        let start_line = start.get("line")?.as_u64()? as u32;
701        let start_char = start.get("character")?.as_u64()? as u32;
702        let end_line = end.get("line")?.as_u64()? as u32;
703        let end_char = end.get("character")?.as_u64()? as u32;
704
705        Some((
706            uri,
707            crate::canvas_editor::lsp::LspRange {
708                start: crate::canvas_editor::lsp::LspPosition {
709                    line: start_line,
710                    character: start_char,
711                },
712                end: crate::canvas_editor::lsp::LspPosition {
713                    line: end_line,
714                    character: end_char,
715                },
716            },
717        ))
718    }
719
720    fn extract_link(
721        link: &serde_json::Value,
722    ) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
723        let uri = link.get("targetUri")?.as_str()?.to_string();
724        let range_val =
725            link.get("targetSelectionRange").or(link.get("targetRange"))?;
726
727        let start = range_val.get("start")?;
728        let end = range_val.get("end")?;
729
730        let start_line = start.get("line")?.as_u64()? as u32;
731        let start_char = start.get("character")?.as_u64()? as u32;
732        let end_line = end.get("line")?.as_u64()? as u32;
733        let end_char = end.get("character")?.as_u64()? as u32;
734
735        Some((
736            uri,
737            crate::canvas_editor::lsp::LspRange {
738                start: crate::canvas_editor::lsp::LspPosition {
739                    line: start_line,
740                    character: start_char,
741                },
742                end: crate::canvas_editor::lsp::LspPosition {
743                    line: end_line,
744                    character: end_char,
745                },
746            },
747        ))
748    }
749
750    if let Some(array) = result.as_array() {
751        if let Some(first) = array.first() {
752            if first.get("targetUri").is_some() {
753                extract_link(first)
754            } else {
755                extract_location(first)
756            }
757        } else {
758            None
759        }
760    } else if result.is_object() {
761        extract_location(result)
762    } else {
763        None
764    }
765}
766
767// =============================================================================
768// LspClient Trait Implementation
769// =============================================================================
770
771impl LspClient for LspProcessClient {
772    fn did_open(&mut self, document: &LspDocument, text: &str) {
773        let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
774        docs.insert(
775            document.uri.clone(),
776            DocumentState { text: TextModel::from_text(text) },
777        );
778
779        let msg = json!({
780            "jsonrpc": "2.0",
781            "method": "textDocument/didOpen",
782            "params": {
783                "textDocument": {
784                    "uri": document.uri,
785                    "languageId": document.language_id,
786                    "version": document.version,
787                    "text": text
788                }
789            }
790        });
791        self.send_message(&msg);
792    }
793
794    fn did_change(
795        &mut self,
796        document: &LspDocument,
797        changes: &[LspTextChange],
798    ) {
799        let content_changes =
800            self.apply_change_and_convert(&document.uri, changes);
801        if content_changes.is_empty() {
802            return;
803        }
804
805        let msg = json!({
806            "jsonrpc": "2.0",
807            "method": "textDocument/didChange",
808            "params": {
809                "textDocument": {
810                    "uri": document.uri,
811                    "version": document.version
812                },
813                "contentChanges": content_changes
814            }
815        });
816        self.send_message(&msg);
817    }
818
819    fn did_save(&mut self, document: &LspDocument, text: &str) {
820        let msg = json!({
821            "jsonrpc": "2.0",
822            "method": "textDocument/didSave",
823            "params": {
824                "textDocument": { "uri": document.uri },
825                "text": text
826            }
827        });
828        self.send_message(&msg);
829    }
830
831    fn did_close(&mut self, document: &LspDocument) {
832        let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
833        docs.remove(&document.uri);
834
835        let msg = json!({
836            "jsonrpc": "2.0",
837            "method": "textDocument/didClose",
838            "params": {
839                "textDocument": { "uri": document.uri }
840            }
841        });
842        self.send_message(&msg);
843    }
844
845    fn request_hover(&mut self, document: &LspDocument, position: LspPosition) {
846        let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
847        let Some(state) = docs.get(&document.uri) else { return };
848        let pos = state.text.to_utf16_position(position);
849
850        let id = self.next_id();
851        {
852            let mut pending =
853                self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
854            pending.insert(id, LspRequestKind::Hover);
855        }
856
857        let msg = json!({
858            "jsonrpc": "2.0",
859            "id": id,
860            "method": "textDocument/hover",
861            "params": {
862                "textDocument": { "uri": document.uri },
863                "position": { "line": pos.line, "character": pos.character }
864            }
865        });
866        self.send_message(&msg);
867    }
868
869    fn request_completion(
870        &mut self,
871        document: &LspDocument,
872        position: LspPosition,
873    ) {
874        let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
875        let Some(state) = docs.get(&document.uri) else { return };
876        let pos = state.text.to_utf16_position(position);
877
878        let id = self.next_id();
879        {
880            let mut pending =
881                self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
882            pending.insert(id, LspRequestKind::Completion);
883        }
884
885        let msg = json!({
886            "jsonrpc": "2.0",
887            "id": id,
888            "method": "textDocument/completion",
889            "params": {
890                "textDocument": { "uri": document.uri },
891                "position": { "line": pos.line, "character": pos.character },
892                "context": { "triggerKind": 1 }
893            }
894        });
895        self.send_message(&msg);
896    }
897
898    fn request_definition(
899        &mut self,
900        document: &LspDocument,
901        position: LspPosition,
902    ) {
903        let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
904        let Some(state) = docs.get(&document.uri) else { return };
905        let pos = state.text.to_utf16_position(position);
906
907        let id = self.next_id();
908        {
909            let mut pending =
910                self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
911            pending.insert(id, LspRequestKind::Definition);
912        }
913
914        let msg = json!({
915            "jsonrpc": "2.0",
916            "id": id,
917            "method": "textDocument/definition",
918            "params": {
919                "textDocument": { "uri": document.uri },
920                "position": { "line": pos.line, "character": pos.character }
921            }
922        });
923        self.send_message(&msg);
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use super::*;
930
931    /// Returns a `Content-Length`-framed JSON string from raw bytes sent on the channel.
932    fn decode_sent(data: Vec<u8>) -> serde_json::Value {
933        let header_end = data
934            .windows(4)
935            .position(|w| w == b"\r\n\r\n")
936            .expect("missing header separator");
937        let body = &data[header_end + 4..];
938        serde_json::from_slice(body).expect("invalid JSON body")
939    }
940
941    // -------------------------------------------------------------------------
942    // handle_server_request
943    // -------------------------------------------------------------------------
944
945    #[test]
946    fn test_handle_server_request_work_done_progress_create() {
947        let (tx, rx) = mpsc::channel::<Vec<u8>>();
948        handle_server_request(42, METHOD_WORK_DONE_PROGRESS_CREATE, &tx);
949
950        let bytes = rx.try_recv().expect("expected a response on the channel");
951        let value = decode_sent(bytes);
952        assert_eq!(value["id"], 42);
953        assert_eq!(value["jsonrpc"], "2.0");
954        assert!(value["result"].is_null());
955    }
956
957    #[test]
958    fn test_handle_server_request_unknown_method_ignored() {
959        let (tx, rx) = mpsc::channel::<Vec<u8>>();
960        handle_server_request(1, "unknown/method", &tx);
961        assert!(
962            rx.try_recv().is_err(),
963            "unknown methods must not send a reply"
964        );
965    }
966
967    // -------------------------------------------------------------------------
968    // handle_client_response
969    // -------------------------------------------------------------------------
970
971    #[test]
972    fn test_handle_client_response_hover() {
973        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
974        let pending = Arc::new(Mutex::new(HashMap::new()));
975        pending.lock().unwrap().insert(1u64, LspRequestKind::Hover);
976
977        let value = serde_json::json!({
978            "id": 1,
979            "result": { "contents": { "value": "hover info" } }
980        });
981        handle_client_response(1, &value, &pending, &events_tx);
982
983        match events_rx.try_recv().expect("expected a Hover event") {
984            LspEvent::Hover { text } => assert_eq!(text, "hover info"),
985            _ => panic!("expected LspEvent::Hover"),
986        }
987        assert!(pending.lock().unwrap().is_empty());
988    }
989
990    #[test]
991    fn test_handle_client_response_completion() {
992        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
993        let pending = Arc::new(Mutex::new(HashMap::new()));
994        pending.lock().unwrap().insert(2u64, LspRequestKind::Completion);
995
996        let value = serde_json::json!({
997            "id": 2,
998            "result": { "items": [{ "label": "foo" }, { "label": "bar" }] }
999        });
1000        handle_client_response(2, &value, &pending, &events_tx);
1001
1002        match events_rx.try_recv().expect("expected a Completion event") {
1003            LspEvent::Completion { items } => {
1004                assert_eq!(items, vec!["foo", "bar"]);
1005            }
1006            _ => panic!("expected LspEvent::Completion"),
1007        }
1008    }
1009
1010    #[test]
1011    fn test_handle_client_response_definition() {
1012        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1013        let pending = Arc::new(Mutex::new(HashMap::new()));
1014        pending.lock().unwrap().insert(3u64, LspRequestKind::Definition);
1015
1016        let value = serde_json::json!({
1017            "id": 3,
1018            "result": {
1019                "uri": "file:///foo/bar.rs",
1020                "range": {
1021                    "start": { "line": 0, "character": 0 },
1022                    "end": { "line": 0, "character": 5 }
1023                }
1024            }
1025        });
1026        handle_client_response(3, &value, &pending, &events_tx);
1027
1028        match events_rx.try_recv().expect("expected a Definition event") {
1029            LspEvent::Definition { uri, .. } => {
1030                assert_eq!(uri, "file:///foo/bar.rs");
1031            }
1032            _ => panic!("expected LspEvent::Definition"),
1033        }
1034    }
1035
1036    #[test]
1037    fn test_handle_client_response_unknown_id_ignored() {
1038        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1039        let pending = Arc::new(Mutex::new(HashMap::new()));
1040
1041        let value = serde_json::json!({ "id": 99, "result": null });
1042        handle_client_response(99, &value, &pending, &events_tx);
1043        assert!(
1044            events_rx.try_recv().is_err(),
1045            "unknown IDs must not emit events"
1046        );
1047    }
1048
1049    // -------------------------------------------------------------------------
1050    // handle_server_notification
1051    // -------------------------------------------------------------------------
1052
1053    #[test]
1054    fn test_handle_server_notification_progress_done() {
1055        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1056        let params = serde_json::json!({
1057            "token": "my-token",
1058            "value": {
1059                "kind": "end",
1060                "title": "Indexing",
1061                "message": "done"
1062            }
1063        });
1064
1065        handle_server_notification(
1066            METHOD_PROGRESS,
1067            &params,
1068            &events_tx,
1069            "lua-ls",
1070        );
1071
1072        match events_rx.try_recv().expect("expected a Progress event") {
1073            LspEvent::Progress { token, done, server_key, .. } => {
1074                assert_eq!(token, "my-token");
1075                assert!(done);
1076                assert_eq!(server_key, "lua-ls");
1077            }
1078            _ => panic!("expected LspEvent::Progress"),
1079        }
1080    }
1081
1082    #[test]
1083    fn test_handle_server_notification_progress_not_done() {
1084        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1085        let params = serde_json::json!({
1086            "token": "tok",
1087            "value": { "kind": "report", "title": "Building" }
1088        });
1089
1090        handle_server_notification(
1091            METHOD_PROGRESS,
1092            &params,
1093            &events_tx,
1094            "rust-analyzer",
1095        );
1096
1097        match events_rx.try_recv().expect("expected a Progress event") {
1098            LspEvent::Progress { done, .. } => assert!(!done),
1099            _ => panic!("expected LspEvent::Progress"),
1100        }
1101    }
1102
1103    #[test]
1104    fn test_handle_server_notification_unknown_method_ignored() {
1105        let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1106        let params = serde_json::json!({});
1107        handle_server_notification(
1108            "$/somethingElse",
1109            &params,
1110            &events_tx,
1111            "server",
1112        );
1113        assert!(events_rx.try_recv().is_err());
1114    }
1115}