on 2158366 help sign in
473 lines 16 kB history raw
1mod client;
2mod transport;
4pub use client::Client;
5pub use futures_executor::block_on;
6pub use jsonrpc::Call;
7pub use jsonrpc_core as jsonrpc;
8pub use lsp::{Position, Url};
9pub use lsp_types as lsp;
11use futures_util::stream::select_all::SelectAll;
12use helix_core::syntax::LanguageConfiguration;
14use std::{
15 collections::{hash_map::Entry, HashMap},
16 sync::{
17 atomic::{AtomicUsize, Ordering},
18 Arc,
19 },
20};
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24use tokio_stream::wrappers::UnboundedReceiverStream;
26pub type Result<T> = core::result::Result<T, Error>;
27type LanguageId = String;
29#[derive(Error, Debug)]
30pub 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),
47#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
48pub 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,
57pub mod util {
58 use super::*;
59 use helix_core::{Range, Rope, Transaction};
61 /// Converts [`lsp::Position`] to a position in the document.
62 ///
63 /// Returns `None` if position exceeds document length or an operation overflows.
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 }
106 /// Converts position in the document to [`lsp::Position`].
107 ///
108 /// Panics when `pos` is out of `doc` bounds or operation overflows.
109 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;
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;
127 lsp::Position::new(line as u32, col as u32)
128 }
129 }
130 }
132 /// Converts a range in the document to [`lsp::Range`].
133 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);
141 lsp::Range::new(start, end)
142 }
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)?;
152 Some(Range::new(start, end))
153 }
155 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 };
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 }
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 }
196 impl From<LspFormatting> for Transaction {
197 fn from(fmt: LspFormatting) -> Transaction {
198 generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding)
199 }
200 }
203#[derive(Debug, PartialEq, Clone)]
204pub enum MethodCall {
205 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
208impl 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 }
227#[derive(Debug, PartialEq, Clone)]
228pub enum Notification {
229 PublishDiagnostics(lsp::PublishDiagnosticsParams),
230 ShowMessage(lsp::ShowMessageParams),
231 LogMessage(lsp::LogMessageParams),
232 ProgressMessage(lsp::ProgressParams),
235impl Notification {
236 pub fn parse(method: &str, params: jsonrpc::Params) -> Option<Notification> {
237 use lsp::notification::Notification as _;
239 let notification = match method {
240 lsp::notification::PublishDiagnostics::METHOD => {
241 let params: lsp::PublishDiagnosticsParams = params
242 .parse()
243 .expect("Failed to parse PublishDiagnostics params");
245 // TODO: need to loop over diagnostics and distinguish them by URI
246 Self::PublishDiagnostics(params)
247 }
249 lsp::notification::ShowMessage::METHOD => {
250 let params: lsp::ShowMessageParams = params.parse().ok()?;
252 Self::ShowMessage(params)
253 }
254 lsp::notification::LogMessage::METHOD => {
255 let params: lsp::LogMessageParams = params.parse().ok()?;
257 Self::LogMessage(params)
258 }
259 lsp::notification::Progress::METHOD => {
260 let params: lsp::ProgressParams = params.parse().ok()?;
262 Self::ProgressMessage(params)
263 }
264 _ => {
265 log::error!("unhandled LSP notification: {}", method);
266 return None;
267 }
268 };
270 Some(notification)
271 }
274#[derive(Debug)]
275pub struct Registry {
276 inner: HashMap<LanguageId, (usize, Arc<Client>)>,
278 counter: AtomicUsize,
279 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
282impl Default for Registry {
283 fn default() -> Self {
284 Self::new()
285 }
288impl Registry {
289 pub fn new() -> Self {
290 Self {
291 inner: HashMap::new(),
292 counter: AtomicUsize::new(0),
293 incoming: SelectAll::new(),
294 }
295 }
297 pub fn get_by_id(&mut self, id: usize) -> Option<&Client> {
298 self.inner
299 .values()
300 .find(|(client_id, _)| client_id == &id)
301 .map(|(_, client)| client.as_ref())
302 }
304 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
305 if let Some(config) = &language_config.language_server {
306 // avoid borrow issues
307 let inner = &mut self.inner;
308 let s_incoming = &mut self.incoming;
310 match inner.entry(language_config.scope.clone()) {
311 Entry::Occupied(entry) => Ok(entry.get().1.clone()),
312 Entry::Vacant(entry) => {
313 // initialize a new client
314 let id = self.counter.fetch_add(1, Ordering::Relaxed);
315 let (mut client, incoming) = Client::start(
316 &config.command,
317 &config.args,
318 serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
319 id,
320 )?;
321 // TODO: run this async without blocking
322 futures_executor::block_on(client.initialize())?;
323 s_incoming.push(UnboundedReceiverStream::new(incoming));
324 let client = Arc::new(client);
326 entry.insert((id, client.clone()));
327 Ok(client)
328 }
329 }
330 } else {
331 Err(Error::LspNotDefined)
332 }
333 }
335 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
336 self.inner.values().map(|(_, client)| client)
337 }
340#[derive(Debug)]
341pub enum ProgressStatus {
342 Created,
343 Started(lsp::WorkDoneProgress),
346impl ProgressStatus {
347 pub fn progress(&self) -> Option<&lsp::WorkDoneProgress> {
348 match &self {
349 ProgressStatus::Created => None,
350 ProgressStatus::Started(progress) => Some(progress),
351 }
352 }
355#[derive(Default, Debug)]
356/// Acts as a container for progress reported by language servers. Each server
357/// has a unique id assigned at creation through [`Registry`]. This id is then used
358/// to store the progress in this map.
359pub struct LspProgressMap(HashMap<usize, HashMap<lsp::ProgressToken, ProgressStatus>>);
361impl LspProgressMap {
362 pub fn new() -> Self {
363 Self::default()
364 }
366 /// Returns a map of all tokens coresponding to the lanaguage server with `id`.
367 pub fn progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>> {
368 self.0.get(&id)
369 }
371 pub fn is_progressing(&self, id: usize) -> bool {
372 self.0.get(&id).map(|it| !it.is_empty()).unwrap_or_default()
373 }
375 /// Returns last progress status for a given server with `id` and `token`.
376 pub fn progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus> {
377 self.0.get(&id).and_then(|values| values.get(token))
378 }
380 /// Checks if progress `token` for server with `id` is created.
381 pub fn is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool {
382 self.0
383 .get(&id)
384 .map(|values| values.get(token).is_some())
385 .unwrap_or_default()
386 }
388 pub fn create(&mut self, id: usize, token: lsp::ProgressToken) {
389 self.0
390 .entry(id)
391 .or_default()
392 .insert(token, ProgressStatus::Created);
393 }
395 /// Ends the progress by removing the `token` from server with `id`, if removed returns the value.
396 pub fn end_progress(
397 &mut self,
398 id: usize,
399 token: &lsp::ProgressToken,
400 ) -> Option<ProgressStatus> {
401 self.0.get_mut(&id).and_then(|vals| vals.remove(token))
402 }
404 /// Updates the progess of `token` for server with `id` to `status`, returns the value replaced or `None`.
405 pub fn update(
406 &mut self,
407 id: usize,
408 token: lsp::ProgressToken,
409 status: lsp::WorkDoneProgress,
410 ) -> Option<ProgressStatus> {
411 self.0
412 .entry(id)
413 .or_default()
414 .insert(token, ProgressStatus::Started(status))
415 }
418// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
419// spawn one server per language type, need to spawn one per workspace if server doesn't support
420// workspaces
421//
422// could also be a client per root dir
423//
424// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
425// accessible during edit/save callbacks
426//
427// the event loop needs to process all incoming streams, maybe we can just have that be a separate
428// task that's continually running and store the state on the client, then use read lock to
429// retrieve data during render
430// -> PROBLEM: how do you trigger an update on the editor side when data updates?
431//
432// -> The data updates should pull all events until we run out so we don't frequently re-render
433//
434//
435// v2:
436//
437// there should be a registry of lsp clients, one per language type (or workspace).
438// the clients should lazy init on first access
439// the client.initialize() should be called async and we buffer any requests until that completes
440// there needs to be a way to process incoming lsp messages from all clients.
441// -> notifications need to be dispatched to wherever
442// -> requests need to generate a reply and travel back to the same lsp!
444#[cfg(test)]
445mod tests {
446 use super::{lsp, util::*, OffsetEncoding};
447 use helix_core::Rope;
449 #[test]
450 fn converts_lsp_pos_to_pos() {
451 macro_rules! test_case {
452 ($doc:expr, ($x:expr, $y:expr) => $want:expr) => {
453 let doc = Rope::from($doc);
454 let pos = lsp::Position::new($x, $y);
455 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf16));
456 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf8))
457 };
458 }
460 test_case!("", (0, 0) => Some(0));
461 test_case!("", (0, 1) => None);
462 test_case!("", (1, 0) => None);
463 test_case!("\n\n", (0, 0) => Some(0));
464 test_case!("\n\n", (1, 0) => Some(1));
465 test_case!("\n\n", (1, 1) => Some(2));
466 test_case!("\n\n", (2, 0) => Some(2));
467 test_case!("\n\n", (3, 0) => None);
468 test_case!("test\n\n\n\ncase", (4, 3) => Some(11));
469 test_case!("test\n\n\n\ncase", (4, 4) => Some(12));
470 test_case!("test\n\n\n\ncase", (4, 5) => None);
471 test_case!("", (u32::MAX, u32::MAX) => None);
472 }