Skip to main content

source_map_tauri/
lsp.rs

1use std::{
2    io::{BufRead, BufReader, Read, Write},
3    path::Path,
4    process::{Child, ChildStdin, ChildStdout, Command, Stdio},
5    str::FromStr,
6};
7
8use anyhow::{anyhow, Context, Result};
9use lsp_types::{
10    DidOpenTextDocumentParams, DocumentSymbol, InitializedParams, Range, TextDocumentIdentifier,
11    TextDocumentItem, Uri,
12};
13use serde_json::{json, Value};
14
15#[derive(Debug, Clone)]
16pub struct SymbolLocation {
17    pub name: String,
18    pub kind: lsp_types::SymbolKind,
19    pub range: Range,
20}
21
22pub struct LspClient {
23    child: Child,
24    stdin: ChildStdin,
25    stdout: BufReader<ChildStdout>,
26    next_id: u64,
27}
28
29impl LspClient {
30    pub fn new(command: &str, root: &Path) -> Result<Self> {
31        let mut child = Command::new(command)
32            .stdin(Stdio::piped())
33            .stdout(Stdio::piped())
34            .stderr(Stdio::null())
35            .spawn()
36            .with_context(|| format!("spawn LSP server {}", command))?;
37        let stdin = child
38            .stdin
39            .take()
40            .ok_or_else(|| anyhow!("missing LSP stdin"))?;
41        let stdout = child
42            .stdout
43            .take()
44            .ok_or_else(|| anyhow!("missing LSP stdout"))?;
45        let mut client = Self {
46            child,
47            stdin,
48            stdout: BufReader::new(stdout),
49            next_id: 1,
50        };
51        client.initialize(root)?;
52        Ok(client)
53    }
54
55    pub fn document_symbols(
56        &mut self,
57        path: &Path,
58        text: &str,
59        language_id: &str,
60    ) -> Result<Vec<SymbolLocation>> {
61        let uri = url::Url::from_file_path(path)
62            .map_err(|_| anyhow!("failed to build file URI for {}", path.display()))?
63            .to_string();
64        let uri = Uri::from_str(&uri)
65            .map_err(|_| anyhow!("failed to parse file URI for {}", path.display()))?;
66        let open = DidOpenTextDocumentParams {
67            text_document: TextDocumentItem {
68                uri: uri.clone(),
69                language_id: language_id.to_owned(),
70                version: 1,
71                text: text.to_owned(),
72            },
73        };
74        self.notify("textDocument/didOpen", serde_json::to_value(open)?)?;
75        let response = self.request(
76            "textDocument/documentSymbol",
77            json!({
78                "textDocument": TextDocumentIdentifier { uri }
79            }),
80        )?;
81        let symbols = parse_document_symbol_response(&response)?;
82        Ok(symbols)
83    }
84
85    fn initialize(&mut self, root: &Path) -> Result<()> {
86        let root_uri = url::Url::from_directory_path(root)
87            .map_err(|_| anyhow!("failed to build root URI for {}", root.display()))?
88            .to_string();
89        let root_uri = Uri::from_str(&root_uri)
90            .map_err(|_| anyhow!("failed to parse root URI for {}", root.display()))?;
91        let params = json!({
92            "processId": null,
93            "capabilities": {},
94            "workspaceFolders": [
95                {
96                    "uri": root_uri,
97                    "name": root
98                        .file_name()
99                        .and_then(|item| item.to_str())
100                        .unwrap_or("workspace")
101                }
102            ]
103        });
104        let _ = self.request("initialize", params)?;
105        self.notify("initialized", serde_json::to_value(InitializedParams {})?)?;
106        Ok(())
107    }
108
109    fn request(&mut self, method: &str, params: Value) -> Result<Value> {
110        let id = self.next_id;
111        self.next_id += 1;
112        let payload = json!({
113            "jsonrpc": "2.0",
114            "id": id,
115            "method": method,
116            "params": params,
117        });
118        self.write_message(&payload)?;
119        loop {
120            let message = self.read_message()?;
121            if message.get("id").and_then(Value::as_u64) == Some(id) {
122                if let Some(error) = message.get("error") {
123                    return Err(anyhow!("LSP request {} failed: {}", method, error));
124                }
125                return Ok(message.get("result").cloned().unwrap_or(Value::Null));
126            }
127        }
128    }
129
130    fn notify(&mut self, method: &str, params: Value) -> Result<()> {
131        let payload = json!({
132            "jsonrpc": "2.0",
133            "method": method,
134            "params": params,
135        });
136        self.write_message(&payload)
137    }
138
139    fn write_message(&mut self, payload: &Value) -> Result<()> {
140        let body = serde_json::to_vec(payload)?;
141        write!(self.stdin, "Content-Length: {}\r\n\r\n", body.len())?;
142        self.stdin.write_all(&body)?;
143        self.stdin.flush()?;
144        Ok(())
145    }
146
147    fn read_message(&mut self) -> Result<Value> {
148        let mut content_length = None;
149        loop {
150            let mut line = String::new();
151            self.stdout.read_line(&mut line)?;
152            if line.is_empty() {
153                return Err(anyhow!("LSP server closed stdout"));
154            }
155            let trimmed = line.trim_end();
156            if trimmed.is_empty() {
157                break;
158            }
159            if let Some(value) = trimmed.strip_prefix("Content-Length:") {
160                content_length = Some(value.trim().parse::<usize>()?);
161            }
162        }
163        let length = content_length.ok_or_else(|| anyhow!("missing Content-Length header"))?;
164        let mut body = vec![0_u8; length];
165        self.stdout.read_exact(&mut body)?;
166        Ok(serde_json::from_slice(&body)?)
167    }
168}
169
170impl Drop for LspClient {
171    fn drop(&mut self) {
172        let _ = self.request("shutdown", Value::Null);
173        let _ = self.notify("exit", Value::Null);
174        let _ = self.child.kill();
175    }
176}
177
178fn parse_document_symbol_response(value: &Value) -> Result<Vec<SymbolLocation>> {
179    if value.is_null() {
180        return Ok(Vec::new());
181    }
182    let symbols: Vec<DocumentSymbol> = serde_json::from_value(value.clone())?;
183    let mut flattened = Vec::new();
184    for symbol in symbols {
185        flatten_document_symbol(&symbol, &mut flattened);
186    }
187    Ok(flattened)
188}
189
190fn flatten_document_symbol(symbol: &DocumentSymbol, out: &mut Vec<SymbolLocation>) {
191    out.push(SymbolLocation {
192        name: symbol.name.clone(),
193        kind: symbol.kind,
194        range: symbol.range,
195    });
196    if let Some(children) = &symbol.children {
197        for child in children {
198            flatten_document_symbol(child, out);
199        }
200    }
201}
202
203pub fn line_contains(range: &Range, one_based_line: u32) -> bool {
204    let zero = one_based_line.saturating_sub(1);
205    range.start.line <= zero && zero <= range.end.line
206}
207
208pub fn range_start_line(range: &Range) -> u32 {
209    range.start.line + 1
210}
211
212pub fn range_end_line(range: &Range) -> u32 {
213    range.end.line + 1
214}