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