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}