1 mod client;
2 mod transport;
3 
4 pub use client::Client;
5 pub use futures_executor::block_on;
6 pub use jsonrpc::Call;
7 pub use jsonrpc_core as jsonrpc;
8 pub use lsp::{Position, Url};
9 pub use lsp_types as lsp;
10 
11 use futures_util::stream::select_all::SelectAll;
12 use helix_core::syntax::LanguageConfiguration;
13 
14 use std::{
15     collections::{hash_map::Entry, HashMap},
16     sync::{
17         atomic::{AtomicUsize, Ordering},
18         Arc,
19     },
20 };
21 
22 use serde::{Deserialize, Serialize};
23 use thiserror::Error;
24 use tokio_stream::wrappers::UnboundedReceiverStream;
25 
26 pub type Result<T> = core::result::Result<T, Error>;
27 type LanguageId = String;
28 
29 #[derive(Error, Debug)]
30 pub enum Error {
31     #[error("protocol error: {0}")]
32     Rpc(#[from] jsonrpc::Error),
33     #[error("failed to parse: {0}")]
34     Parse(#[from] serde_json::Error),
35     #[error("IO Error: {0}")]
36     IO(#[from] std::io::Error),
37     #[error("request timed out")]
38     Timeout,
39     #[error("server closed the stream")]
40     StreamClosed,
41     #[error("LSP not defined")]
42     LspNotDefined,
43     #[error(transparent)]
44     Other(#[from] anyhow::Error),
45 }
46 
47 #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
48 pub enum OffsetEncoding {
49     /// UTF-8 code units aka bytes
50     #[serde(rename = "utf-8")]
51     Utf8,
52     /// UTF-16 code units
53     #[serde(rename = "utf-16")]
54     Utf16,
55 }
56 
57 pub mod util {
58     use super::*;
59     use helix_core::{Range, Rope, Transaction};
60 
61     /// Converts [`lsp::Position`] to a position in the document.
62     ///
63     /// Returns `None` if position exceeds document length or an operation overflows.
lsp_pos_to_pos( doc: &Rope, pos: lsp::Position, offset_encoding: OffsetEncoding, ) -> Option<usize>64     pub fn lsp_pos_to_pos(
65         doc: &Rope,
66         pos: lsp::Position,
67         offset_encoding: OffsetEncoding,
68     ) -> Option<usize> {
69         let max_line = doc.lines().count().saturating_sub(1);
70         let pos_line = pos.line as usize;
71         let pos_line = if pos_line > max_line {
72             return None;
73         } else {
74             pos_line
75         };
76         match offset_encoding {
77             OffsetEncoding::Utf8 => {
78                 let max_char = doc
79                     .line_to_char(max_line)
80                     .checked_add(doc.line(max_line).len_chars())?;
81                 let line = doc.line_to_char(pos_line);
82                 let pos = line.checked_add(pos.character as usize)?;
83                 if pos <= max_char {
84                     Some(pos)
85                 } else {
86                     None
87                 }
88             }
89             OffsetEncoding::Utf16 => {
90                 let max_char = doc
91                     .line_to_char(max_line)
92                     .checked_add(doc.line(max_line).len_chars())?;
93                 let max_cu = doc.char_to_utf16_cu(max_char);
94                 let line = doc.line_to_char(pos_line);
95                 let line_start = doc.char_to_utf16_cu(line);
96                 let pos = line_start.checked_add(pos.character as usize)?;
97                 if pos <= max_cu {
98                     Some(doc.utf16_cu_to_char(pos))
99                 } else {
100                     None
101                 }
102             }
103         }
104     }
105 
106     /// Converts position in the document to [`lsp::Position`].
107     ///
108     /// Panics when `pos` is out of `doc` bounds or operation overflows.
pos_to_lsp_pos( doc: &Rope, pos: usize, offset_encoding: OffsetEncoding, ) -> lsp::Position109     pub fn pos_to_lsp_pos(
110         doc: &Rope,
111         pos: usize,
112         offset_encoding: OffsetEncoding,
113     ) -> lsp::Position {
114         match offset_encoding {
115             OffsetEncoding::Utf8 => {
116                 let line = doc.char_to_line(pos);
117                 let line_start = doc.line_to_char(line);
118                 let col = pos - line_start;
119 
120                 lsp::Position::new(line as u32, col as u32)
121             }
122             OffsetEncoding::Utf16 => {
123                 let line = doc.char_to_line(pos);
124                 let line_start = doc.char_to_utf16_cu(doc.line_to_char(line));
125                 let col = doc.char_to_utf16_cu(pos) - line_start;
126 
127                 lsp::Position::new(line as u32, col as u32)
128             }
129         }
130     }
131 
132     /// Converts a range in the document to [`lsp::Range`].
range_to_lsp_range( doc: &Rope, range: Range, offset_encoding: OffsetEncoding, ) -> lsp::Range133     pub fn range_to_lsp_range(
134         doc: &Rope,
135         range: Range,
136         offset_encoding: OffsetEncoding,
137     ) -> lsp::Range {
138         let start = pos_to_lsp_pos(doc, range.from(), offset_encoding);
139         let end = pos_to_lsp_pos(doc, range.to(), offset_encoding);
140 
141         lsp::Range::new(start, end)
142     }
143 
lsp_range_to_range( doc: &Rope, range: lsp::Range, offset_encoding: OffsetEncoding, ) -> Option<Range>144     pub fn lsp_range_to_range(
145         doc: &Rope,
146         range: lsp::Range,
147         offset_encoding: OffsetEncoding,
148     ) -> Option<Range> {
149         let start = lsp_pos_to_pos(doc, range.start, offset_encoding)?;
150         let end = lsp_pos_to_pos(doc, range.end, offset_encoding)?;
151 
152         Some(Range::new(start, end))
153     }
154 
generate_transaction_from_edits( doc: &Rope, edits: Vec<lsp::TextEdit>, offset_encoding: OffsetEncoding, ) -> Transaction155     pub fn generate_transaction_from_edits(
156         doc: &Rope,
157         edits: Vec<lsp::TextEdit>,
158         offset_encoding: OffsetEncoding,
159     ) -> Transaction {
160         Transaction::change(
161             doc,
162             edits.into_iter().map(|edit| {
163                 // simplify "" into None for cleaner changesets
164                 let replacement = if !edit.new_text.is_empty() {
165                     Some(edit.new_text.into())
166                 } else {
167                     None
168                 };
169 
170                 let start =
171                     if let Some(start) = lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
172                         start
173                     } else {
174                         return (0, 0, None);
175                     };
176                 let end = if let Some(end) = lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
177                     end
178                 } else {
179                     return (0, 0, None);
180                 };
181                 (start, end, replacement)
182             }),
183         )
184     }
185 
186     /// The result of asking the language server to format the document. This can be turned into a
187     /// `Transaction`, but the advantage of not doing that straight away is that this one is
188     /// `Send` and `Sync`.
189     #[derive(Clone, Debug)]
190     pub struct LspFormatting {
191         pub doc: Rope,
192         pub edits: Vec<lsp::TextEdit>,
193         pub offset_encoding: OffsetEncoding,
194     }
195 
196     impl From<LspFormatting> for Transaction {
from(fmt: LspFormatting) -> Transaction197         fn from(fmt: LspFormatting) -> Transaction {
198             generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding)
199         }
200     }
201 }
202 
203 #[derive(Debug, PartialEq, Clone)]
204 pub enum MethodCall {
205     WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
206 }
207 
208 impl MethodCall {
parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall>209     pub fn parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall> {
210         use lsp::request::Request;
211         let request = match method {
212             lsp::request::WorkDoneProgressCreate::METHOD => {
213                 let params: lsp::WorkDoneProgressCreateParams = params
214                     .parse()
215                     .expect("Failed to parse WorkDoneCreate params");
216                 Self::WorkDoneProgressCreate(params)
217             }
218             _ => {
219                 log::warn!("unhandled lsp request: {}", method);
220                 return None;
221             }
222         };
223         Some(request)
224     }
225 }
226 
227 #[derive(Debug, PartialEq, Clone)]
228 pub enum Notification {
229     // we inject this notification to signal the LSP is ready
230     Initialized,
231     PublishDiagnostics(lsp::PublishDiagnosticsParams),
232     ShowMessage(lsp::ShowMessageParams),
233     LogMessage(lsp::LogMessageParams),
234     ProgressMessage(lsp::ProgressParams),
235 }
236 
237 impl Notification {
parse(method: &str, params: jsonrpc::Params) -> Option<Notification>238     pub fn parse(method: &str, params: jsonrpc::Params) -> Option<Notification> {
239         use lsp::notification::Notification as _;
240 
241         let notification = match method {
242             lsp::notification::Initialized::METHOD => Self::Initialized,
243             lsp::notification::PublishDiagnostics::METHOD => {
244                 let params: lsp::PublishDiagnosticsParams = params
245                     .parse()
246                     .expect("Failed to parse PublishDiagnostics params");
247 
248                 // TODO: need to loop over diagnostics and distinguish them by URI
249                 Self::PublishDiagnostics(params)
250             }
251 
252             lsp::notification::ShowMessage::METHOD => {
253                 let params: lsp::ShowMessageParams = params.parse().ok()?;
254 
255                 Self::ShowMessage(params)
256             }
257             lsp::notification::LogMessage::METHOD => {
258                 let params: lsp::LogMessageParams = params.parse().ok()?;
259 
260                 Self::LogMessage(params)
261             }
262             lsp::notification::Progress::METHOD => {
263                 let params: lsp::ProgressParams = params.parse().ok()?;
264 
265                 Self::ProgressMessage(params)
266             }
267             _ => {
268                 log::error!("unhandled LSP notification: {}", method);
269                 return None;
270             }
271         };
272 
273         Some(notification)
274     }
275 }
276 
277 #[derive(Debug)]
278 pub struct Registry {
279     inner: HashMap<LanguageId, (usize, Arc<Client>)>,
280 
281     counter: AtomicUsize,
282     pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
283 }
284 
285 impl Default for Registry {
default() -> Self286     fn default() -> Self {
287         Self::new()
288     }
289 }
290 
291 impl Registry {
new() -> Self292     pub fn new() -> Self {
293         Self {
294             inner: HashMap::new(),
295             counter: AtomicUsize::new(0),
296             incoming: SelectAll::new(),
297         }
298     }
299 
get_by_id(&self, id: usize) -> Option<&Client>300     pub fn get_by_id(&self, id: usize) -> Option<&Client> {
301         self.inner
302             .values()
303             .find(|(client_id, _)| client_id == &id)
304             .map(|(_, client)| client.as_ref())
305     }
306 
get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>>307     pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
308         let config = match &language_config.language_server {
309             Some(config) => config,
310             None => return Err(Error::LspNotDefined),
311         };
312 
313         match self.inner.entry(language_config.scope.clone()) {
314             Entry::Occupied(entry) => Ok(entry.get().1.clone()),
315             Entry::Vacant(entry) => {
316                 // initialize a new client
317                 let id = self.counter.fetch_add(1, Ordering::Relaxed);
318                 let (client, incoming, initialize_notify) = Client::start(
319                     &config.command,
320                     &config.args,
321                     language_config.config.clone(),
322                     id,
323                 )?;
324                 self.incoming.push(UnboundedReceiverStream::new(incoming));
325                 let client = Arc::new(client);
326 
327                 // Initialize the client asynchronously
328                 let _client = client.clone();
329                 tokio::spawn(async move {
330                     use futures_util::TryFutureExt;
331                     let value = _client
332                         .capabilities
333                         .get_or_try_init(|| {
334                             _client
335                                 .initialize()
336                                 .map_ok(|response| response.capabilities)
337                         })
338                         .await;
339 
340                     value.expect("failed to initialize capabilities");
341 
342                     // next up, notify<initialized>
343                     _client
344                         .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
345                         .await
346                         .unwrap();
347 
348                     initialize_notify.notify_one();
349                 });
350 
351                 entry.insert((id, client.clone()));
352                 Ok(client)
353             }
354         }
355     }
356 
iter_clients(&self) -> impl Iterator<Item = &Arc<Client>>357     pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
358         self.inner.values().map(|(_, client)| client)
359     }
360 }
361 
362 #[derive(Debug)]
363 pub enum ProgressStatus {
364     Created,
365     Started(lsp::WorkDoneProgress),
366 }
367 
368 impl ProgressStatus {
progress(&self) -> Option<&lsp::WorkDoneProgress>369     pub fn progress(&self) -> Option<&lsp::WorkDoneProgress> {
370         match &self {
371             ProgressStatus::Created => None,
372             ProgressStatus::Started(progress) => Some(progress),
373         }
374     }
375 }
376 
377 #[derive(Default, Debug)]
378 /// Acts as a container for progress reported by language servers. Each server
379 /// has a unique id assigned at creation through [`Registry`]. This id is then used
380 /// to store the progress in this map.
381 pub struct LspProgressMap(HashMap<usize, HashMap<lsp::ProgressToken, ProgressStatus>>);
382 
383 impl LspProgressMap {
new() -> Self384     pub fn new() -> Self {
385         Self::default()
386     }
387 
388     /// Returns a map of all tokens coresponding to the lanaguage server with `id`.
progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>>389     pub fn progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>> {
390         self.0.get(&id)
391     }
392 
is_progressing(&self, id: usize) -> bool393     pub fn is_progressing(&self, id: usize) -> bool {
394         self.0.get(&id).map(|it| !it.is_empty()).unwrap_or_default()
395     }
396 
397     /// Returns last progress status for a given server with `id` and `token`.
progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus>398     pub fn progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus> {
399         self.0.get(&id).and_then(|values| values.get(token))
400     }
401 
402     /// Checks if progress `token` for server with `id` is created.
is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool403     pub fn is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool {
404         self.0
405             .get(&id)
406             .map(|values| values.get(token).is_some())
407             .unwrap_or_default()
408     }
409 
create(&mut self, id: usize, token: lsp::ProgressToken)410     pub fn create(&mut self, id: usize, token: lsp::ProgressToken) {
411         self.0
412             .entry(id)
413             .or_default()
414             .insert(token, ProgressStatus::Created);
415     }
416 
417     /// Ends the progress by removing the `token` from server with `id`, if removed returns the value.
end_progress( &mut self, id: usize, token: &lsp::ProgressToken, ) -> Option<ProgressStatus>418     pub fn end_progress(
419         &mut self,
420         id: usize,
421         token: &lsp::ProgressToken,
422     ) -> Option<ProgressStatus> {
423         self.0.get_mut(&id).and_then(|vals| vals.remove(token))
424     }
425 
426     /// Updates the progess of `token` for server with `id` to `status`, returns the value replaced or `None`.
update( &mut self, id: usize, token: lsp::ProgressToken, status: lsp::WorkDoneProgress, ) -> Option<ProgressStatus>427     pub fn update(
428         &mut self,
429         id: usize,
430         token: lsp::ProgressToken,
431         status: lsp::WorkDoneProgress,
432     ) -> Option<ProgressStatus> {
433         self.0
434             .entry(id)
435             .or_default()
436             .insert(token, ProgressStatus::Started(status))
437     }
438 }
439 
440 #[cfg(test)]
441 mod tests {
442     use super::{lsp, util::*, OffsetEncoding};
443     use helix_core::Rope;
444 
445     #[test]
converts_lsp_pos_to_pos()446     fn converts_lsp_pos_to_pos() {
447         macro_rules! test_case {
448             ($doc:expr, ($x:expr, $y:expr) => $want:expr) => {
449                 let doc = Rope::from($doc);
450                 let pos = lsp::Position::new($x, $y);
451                 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf16));
452                 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf8))
453             };
454         }
455 
456         test_case!("", (0, 0) => Some(0));
457         test_case!("", (0, 1) => None);
458         test_case!("", (1, 0) => None);
459         test_case!("\n\n", (0, 0) => Some(0));
460         test_case!("\n\n", (1, 0) => Some(1));
461         test_case!("\n\n", (1, 1) => Some(2));
462         test_case!("\n\n", (2, 0) => Some(2));
463         test_case!("\n\n", (3, 0) => None);
464         test_case!("test\n\n\n\ncase", (4, 3) => Some(11));
465         test_case!("test\n\n\n\ncase", (4, 4) => Some(12));
466         test_case!("test\n\n\n\ncase", (4, 5) => None);
467         test_case!("", (u32::MAX, u32::MAX) => None);
468     }
469 }
470