on 2158366 help sign in
helix/helix-lsp/src/client.rs
763 lines 26 kB history raw
1use crate::{
2 transport::{Payload, Transport},
3 Call, Error, OffsetEncoding, Result,
4};
6use helix_core::{chars::char_is_line_ending, find_root, ChangeSet, Rope};
7use jsonrpc_core as jsonrpc;
8use lsp_types as lsp;
9use serde_json::Value;
10use std::future::Future;
11use std::process::Stdio;
12use std::sync::atomic::{AtomicU64, Ordering};
13use tokio::{
14 io::{BufReader, BufWriter},
15 process::{Child, Command},
16 sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
17};
19#[derive(Debug)]
20pub struct Client {
21 id: usize,
22 _process: Child,
23 server_tx: UnboundedSender<Payload>,
24 request_counter: AtomicU64,
25 capabilities: Option<lsp::ServerCapabilities>,
26 offset_encoding: OffsetEncoding,
27 config: Option<Value>,
30impl Client {
31 pub fn start(
32 cmd: &str,
33 args: &[String],
34 config: Option<Value>,
35 id: usize,
36 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
37 let process = Command::new(cmd)
38 .args(args)
39 .stdin(Stdio::piped())
40 .stdout(Stdio::piped())
41 .stderr(Stdio::piped())
42 // make sure the process is reaped on drop
43 .kill_on_drop(true)
44 .spawn();
46 let mut process = process?;
48 // TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
49 let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
50 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
51 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
53 let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
55 let client = Self {
56 id,
57 _process: process,
58 server_tx,
59 request_counter: AtomicU64::new(0),
60 capabilities: None,
61 offset_encoding: OffsetEncoding::Utf8,
62 config,
63 };
65 // TODO: async client.initialize()
66 // maybe use an arc<atomic> flag
68 Ok((client, server_rx))
69 }
71 pub fn id(&self) -> usize {
72 self.id
73 }
75 fn next_request_id(&self) -> jsonrpc::Id {
76 let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
77 jsonrpc::Id::Num(id)
78 }
80 fn value_into_params(value: Value) -> jsonrpc::Params {
81 use jsonrpc::Params;
83 match value {
84 Value::Null => Params::None,
85 Value::Bool(_) | Value::Number(_) | Value::String(_) => Params::Array(vec![value]),
86 Value::Array(vec) => Params::Array(vec),
87 Value::Object(map) => Params::Map(map),
88 }
89 }
91 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
92 self.capabilities
93 .as_ref()
94 .expect("language server not yet initialized!")
95 }
97 pub fn offset_encoding(&self) -> OffsetEncoding {
98 self.offset_encoding
99 }
101 /// Execute a RPC request on the language server.
102 async fn request<R: lsp::request::Request>(&self, params: R::Params) -> Result<R::Result>
103 where
104 R::Params: serde::Serialize,
105 R::Result: core::fmt::Debug, // TODO: temporary
106 {
107 // a future that resolves into the response
108 let json = self.call::<R>(params).await?;
109 let response = serde_json::from_value(json)?;
110 Ok(response)
111 }
113 /// Execute a RPC request on the language server.
114 fn call<R: lsp::request::Request>(
115 &self,
116 params: R::Params,
117 ) -> impl Future<Output = Result<Value>>
118 where
119 R::Params: serde::Serialize,
120 {
121 let server_tx = self.server_tx.clone();
122 let id = self.next_request_id();
124 async move {
125 use std::time::Duration;
126 use tokio::time::timeout;
128 let params = serde_json::to_value(params)?;
130 let request = jsonrpc::MethodCall {
131 jsonrpc: Some(jsonrpc::Version::V2),
132 id,
133 method: R::METHOD.to_string(),
134 params: Self::value_into_params(params),
135 };
137 let (tx, mut rx) = channel::<Result<Value>>(1);
139 server_tx
140 .send(Payload::Request {
141 chan: tx,
142 value: request,
143 })
144 .map_err(|e| Error::Other(e.into()))?;
146 timeout(Duration::from_secs(2), rx.recv())
147 .await
148 .map_err(|_| Error::Timeout)? // return Timeout
149 .ok_or(Error::StreamClosed)?
150 }
151 }
153 /// Send a RPC notification to the language server.
154 fn notify<R: lsp::notification::Notification>(
155 &self,
156 params: R::Params,
157 ) -> impl Future<Output = Result<()>>
158 where
159 R::Params: serde::Serialize,
160 {
161 let server_tx = self.server_tx.clone();
163 async move {
164 let params = serde_json::to_value(params)?;
166 let notification = jsonrpc::Notification {
167 jsonrpc: Some(jsonrpc::Version::V2),
168 method: R::METHOD.to_string(),
169 params: Self::value_into_params(params),
170 };
172 server_tx
173 .send(Payload::Notification(notification))
174 .map_err(|e| Error::Other(e.into()))?;
176 Ok(())
177 }
178 }
180 /// Reply to a language server RPC call.
181 pub fn reply(
182 &self,
183 id: jsonrpc::Id,
184 result: core::result::Result<Value, jsonrpc::Error>,
185 ) -> impl Future<Output = Result<()>> {
186 use jsonrpc::{Failure, Output, Success, Version};
188 let server_tx = self.server_tx.clone();
190 async move {
191 let output = match result {
192 Ok(result) => Output::Success(Success {
193 jsonrpc: Some(Version::V2),
194 id,
195 result,
196 }),
197 Err(error) => Output::Failure(Failure {
198 jsonrpc: Some(Version::V2),
199 id,
200 error,
201 }),
202 };
204 server_tx
205 .send(Payload::Response(output))
206 .map_err(|e| Error::Other(e.into()))?;
208 Ok(())
209 }
210 }
212 // -------------------------------------------------------------------------------------------
213 // General messages
214 // -------------------------------------------------------------------------------------------
216 pub(crate) async fn initialize(&mut self) -> Result<()> {
217 // TODO: delay any requests that are triggered prior to initialize
218 let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
220 if self.config.is_some() {
221 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
222 }
224 #[allow(deprecated)]
225 let params = lsp::InitializeParams {
226 process_id: Some(std::process::id()),
227 // root_path is obsolete, use root_uri
228 root_path: None,
229 root_uri: root,
230 initialization_options: self.config.clone(),
231 capabilities: lsp::ClientCapabilities {
232 text_document: Some(lsp::TextDocumentClientCapabilities {
233 completion: Some(lsp::CompletionClientCapabilities {
234 completion_item: Some(lsp::CompletionItemCapability {
235 snippet_support: Some(false),
236 ..Default::default()
237 }),
238 completion_item_kind: Some(lsp::CompletionItemKindCapability {
239 ..Default::default()
240 }),
241 context_support: None, // additional context information Some(true)
242 ..Default::default()
243 }),
244 hover: Some(lsp::HoverClientCapabilities {
245 // if not specified, rust-analyzer returns plaintext marked as markdown but
246 // badly formatted.
247 content_format: Some(vec![lsp::MarkupKind::Markdown]),
248 ..Default::default()
249 }),
250 code_action: Some(lsp::CodeActionClientCapabilities {
251 code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
252 code_action_kind: lsp::CodeActionKindLiteralSupport {
253 value_set: [
254 lsp::CodeActionKind::EMPTY,
255 lsp::CodeActionKind::QUICKFIX,
256 lsp::CodeActionKind::REFACTOR,
257 lsp::CodeActionKind::REFACTOR_EXTRACT,
258 lsp::CodeActionKind::REFACTOR_INLINE,
259 lsp::CodeActionKind::REFACTOR_REWRITE,
260 lsp::CodeActionKind::SOURCE,
261 lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
262 ]
263 .iter()
264 .map(|kind| kind.as_str().to_string())
265 .collect(),
266 },
267 }),
268 ..Default::default()
269 }),
270 ..Default::default()
271 }),
272 window: Some(lsp::WindowClientCapabilities {
273 work_done_progress: Some(true),
274 ..Default::default()
275 }),
276 ..Default::default()
277 },
278 trace: None,
279 workspace_folders: None,
280 client_info: None,
281 locale: None, // TODO
282 };
284 let response = self.request::<lsp::request::Initialize>(params).await?;
285 self.capabilities = Some(response.capabilities);
287 // next up, notify<initialized>
288 self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
289 .await?;
291 Ok(())
292 }
294 pub async fn shutdown(&self) -> Result<()> {
295 self.request::<lsp::request::Shutdown>(()).await
296 }
298 pub fn exit(&self) -> impl Future<Output = Result<()>> {
299 self.notify::<lsp::notification::Exit>(())
300 }
302 /// Tries to shut down the language server but returns
303 /// early if server responds with an error.
304 pub async fn shutdown_and_exit(&self) -> Result<()> {
305 self.shutdown().await?;
306 self.exit().await
307 }
309 /// Forcefully shuts down the language server ignoring any errors.
310 pub async fn force_shutdown(&self) -> Result<()> {
311 if let Err(e) = self.shutdown().await {
312 log::warn!("language server failed to terminate gracefully - {}", e);
313 }
314 self.exit().await
315 }
317 // -------------------------------------------------------------------------------------------
318 // Text document
319 // -------------------------------------------------------------------------------------------
321 pub fn text_document_did_open(
322 &self,
323 uri: lsp::Url,
324 version: i32,
325 doc: &Rope,
326 language_id: String,
327 ) -> impl Future<Output = Result<()>> {
328 self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
329 text_document: lsp::TextDocumentItem {
330 uri,
331 language_id,
332 version,
333 text: String::from(doc),
334 },
335 })
336 }
338 pub fn changeset_to_changes(
339 old_text: &Rope,
340 new_text: &Rope,
341 changeset: &ChangeSet,
342 offset_encoding: OffsetEncoding,
343 ) -> Vec<lsp::TextDocumentContentChangeEvent> {
344 let mut iter = changeset.changes().iter().peekable();
345 let mut old_pos = 0;
346 let mut new_pos = 0;
348 let mut changes = Vec::new();
350 use crate::util::pos_to_lsp_pos;
351 use helix_core::Operation::*;
353 // this is dumb. TextEdit describes changes to the initial doc (concurrent), but
354 // TextDocumentContentChangeEvent describes a series of changes (sequential).
355 // So S -> S1 -> S2, meaning positioning depends on the previous edits.
356 //
357 // Calculation is therefore a bunch trickier.
359 // TODO: stolen from syntax.rs, share
360 use helix_core::RopeSlice;
361 fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
362 let lsp::Position {
363 mut line,
364 mut character,
365 } = pos;
367 let mut chars = text.chars().peekable();
368 while let Some(ch) = chars.next() {
369 if char_is_line_ending(ch) && !(ch == '\r' && chars.peek() == Some(&'\n')) {
370 line += 1;
371 character = 0;
372 } else {
373 character += ch.len_utf16() as u32;
374 }
375 }
376 lsp::Position { line, character }
377 }
379 let old_text = old_text.slice(..);
381 while let Some(change) = iter.next() {
382 let len = match change {
383 Delete(i) | Retain(i) => *i,
384 Insert(_) => 0,
385 };
386 let mut old_end = old_pos + len;
388 match change {
389 Retain(i) => {
390 new_pos += i;
391 }
392 Delete(_) => {
393 let start = pos_to_lsp_pos(new_text, new_pos, offset_encoding);
394 let end = traverse(start, old_text.slice(old_pos..old_end));
396 // deletion
397 changes.push(lsp::TextDocumentContentChangeEvent {
398 range: Some(lsp::Range::new(start, end)),
399 text: "".to_string(),
400 range_length: None,
401 });
402 }
403 Insert(s) => {
404 let start = pos_to_lsp_pos(new_text, new_pos, offset_encoding);
406 new_pos += s.chars().count();
408 // a subsequent delete means a replace, consume it
409 let end = if let Some(Delete(len)) = iter.peek() {
410 old_end = old_pos + len;
411 let end = traverse(start, old_text.slice(old_pos..old_end));
413 iter.next();
415 // replacement
416 end
417 } else {
418 // insert
419 start
420 };
422 changes.push(lsp::TextDocumentContentChangeEvent {
423 range: Some(lsp::Range::new(start, end)),
424 text: s.into(),
425 range_length: None,
426 });
427 }
428 }
429 old_pos = old_end;
430 }
432 changes
433 }
435 pub fn text_document_did_change(
436 &self,
437 text_document: lsp::VersionedTextDocumentIdentifier,
438 old_text: &Rope,
439 new_text: &Rope,
440 changes: &ChangeSet,
441 ) -> Option<impl Future<Output = Result<()>>> {
442 // figure out what kind of sync the server supports
444 let capabilities = self.capabilities.as_ref().unwrap();
446 let sync_capabilities = match capabilities.text_document_sync {
447 Some(lsp::TextDocumentSyncCapability::Kind(kind))
448 | Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
449 change: Some(kind),
450 ..
451 })) => kind,
452 // None | SyncOptions { changes: None }
453 _ => return None,
454 };
456 let changes = match sync_capabilities {
457 lsp::TextDocumentSyncKind::Full => {
458 vec![lsp::TextDocumentContentChangeEvent {
459 // range = None -> whole document
460 range: None, //Some(Range)
461 range_length: None, // u64 apparently deprecated
462 text: "".to_string(),
463 }]
464 }
465 lsp::TextDocumentSyncKind::Incremental => {
466 Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding)
467 }
468 lsp::TextDocumentSyncKind::None => return None,
469 };
471 Some(self.notify::<lsp::notification::DidChangeTextDocument>(
472 lsp::DidChangeTextDocumentParams {
473 text_document,
474 content_changes: changes,
475 },
476 ))
477 }
479 pub fn text_document_did_close(
480 &self,
481 text_document: lsp::TextDocumentIdentifier,
482 ) -> impl Future<Output = Result<()>> {
483 self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
484 text_document,
485 })
486 }
488 // will_save / will_save_wait_until
490 pub async fn text_document_did_save(
491 &self,
492 text_document: lsp::TextDocumentIdentifier,
493 text: &Rope,
494 ) -> Result<()> {
495 let capabilities = self.capabilities.as_ref().unwrap();
497 let include_text = match &capabilities.text_document_sync {
498 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
499 save: Some(options),
500 ..
501 })) => match options {
502 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
503 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
504 include_text,
505 }) => include_text.unwrap_or(false),
506 // Supported(false)
507 _ => return Ok(()),
508 },
509 // unsupported
510 _ => return Ok(()),
511 };
513 self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
514 text_document,
515 text: include_text.then(|| text.into()),
516 })
517 .await
518 }
520 pub fn completion(
521 &self,
522 text_document: lsp::TextDocumentIdentifier,
523 position: lsp::Position,
524 work_done_token: Option<lsp::ProgressToken>,
525 ) -> impl Future<Output = Result<Value>> {
526 // ) -> Result<Vec<lsp::CompletionItem>> {
527 let params = lsp::CompletionParams {
528 text_document_position: lsp::TextDocumentPositionParams {
529 text_document,
530 position,
531 },
532 // TODO: support these tokens by async receiving and updating the choice list
533 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
534 partial_result_params: lsp::PartialResultParams {
535 partial_result_token: None,
536 },
537 context: None,
538 // lsp::CompletionContext { trigger_kind: , trigger_character: Some(), }
539 };
541 self.call::<lsp::request::Completion>(params)
542 }
544 pub fn text_document_signature_help(
545 &self,
546 text_document: lsp::TextDocumentIdentifier,
547 position: lsp::Position,
548 work_done_token: Option<lsp::ProgressToken>,
549 ) -> impl Future<Output = Result<Value>> {
550 let params = lsp::SignatureHelpParams {
551 text_document_position_params: lsp::TextDocumentPositionParams {
552 text_document,
553 position,
554 },
555 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
556 context: None,
557 // lsp::SignatureHelpContext
558 };
560 self.call::<lsp::request::SignatureHelpRequest>(params)
561 }
563 pub fn text_document_hover(
564 &self,
565 text_document: lsp::TextDocumentIdentifier,
566 position: lsp::Position,
567 work_done_token: Option<lsp::ProgressToken>,
568 ) -> impl Future<Output = Result<Value>> {
569 let params = lsp::HoverParams {
570 text_document_position_params: lsp::TextDocumentPositionParams {
571 text_document,
572 position,
573 },
574 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
575 // lsp::SignatureHelpContext
576 };
578 self.call::<lsp::request::HoverRequest>(params)
579 }
581 // formatting
583 pub async fn text_document_formatting(
584 &self,
585 text_document: lsp::TextDocumentIdentifier,
586 options: lsp::FormattingOptions,
587 work_done_token: Option<lsp::ProgressToken>,
588 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
589 let capabilities = self.capabilities.as_ref().unwrap();
591 // check if we're able to format
592 match capabilities.document_formatting_provider {
593 Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
594 // None | Some(false)
595 _ => return Ok(Vec::new()),
596 };
597 // TODO: return err::unavailable so we can fall back to tree sitter formatting
599 let params = lsp::DocumentFormattingParams {
600 text_document,
601 options,
602 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
603 };
605 let response = self.request::<lsp::request::Formatting>(params).await?;
607 Ok(response.unwrap_or_default())
608 }
610 pub async fn text_document_range_formatting(
611 &self,
612 text_document: lsp::TextDocumentIdentifier,
613 range: lsp::Range,
614 options: lsp::FormattingOptions,
615 work_done_token: Option<lsp::ProgressToken>,
616 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
617 let capabilities = self.capabilities.as_ref().unwrap();
619 // check if we're able to format
620 match capabilities.document_range_formatting_provider {
621 Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
622 // None | Some(false)
623 _ => return Ok(Vec::new()),
624 };
625 // TODO: return err::unavailable so we can fall back to tree sitter formatting
627 let params = lsp::DocumentRangeFormattingParams {
628 text_document,
629 range,
630 options,
631 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
632 };
634 let response = self
635 .request::<lsp::request::RangeFormatting>(params)
636 .await?;
638 Ok(response.unwrap_or_default())
639 }
641 fn goto_request<
642 T: lsp::request::Request<
643 Params = lsp::GotoDefinitionParams,
644 Result = Option<lsp::GotoDefinitionResponse>,
645 >,
646 >(
647 &self,
648 text_document: lsp::TextDocumentIdentifier,
649 position: lsp::Position,
650 work_done_token: Option<lsp::ProgressToken>,
651 ) -> impl Future<Output = Result<Value>> {
652 let params = lsp::GotoDefinitionParams {
653 text_document_position_params: lsp::TextDocumentPositionParams {
654 text_document,
655 position,
656 },
657 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
658 partial_result_params: lsp::PartialResultParams {
659 partial_result_token: None,
660 },
661 };
663 self.call::<T>(params)
664 }
666 pub fn goto_definition(
667 &self,
668 text_document: lsp::TextDocumentIdentifier,
669 position: lsp::Position,
670 work_done_token: Option<lsp::ProgressToken>,
671 ) -> impl Future<Output = Result<Value>> {
672 self.goto_request::<lsp::request::GotoDefinition>(text_document, position, work_done_token)
673 }
675 pub fn goto_type_definition(
676 &self,
677 text_document: lsp::TextDocumentIdentifier,
678 position: lsp::Position,
679 work_done_token: Option<lsp::ProgressToken>,
680 ) -> impl Future<Output = Result<Value>> {
681 self.goto_request::<lsp::request::GotoTypeDefinition>(
682 text_document,
683 position,
684 work_done_token,
685 )
686 }
688 pub fn goto_implementation(
689 &self,
690 text_document: lsp::TextDocumentIdentifier,
691 position: lsp::Position,
692 work_done_token: Option<lsp::ProgressToken>,
693 ) -> impl Future<Output = Result<Value>> {
694 self.goto_request::<lsp::request::GotoImplementation>(
695 text_document,
696 position,
697 work_done_token,
698 )
699 }
701 pub fn goto_reference(
702 &self,
703 text_document: lsp::TextDocumentIdentifier,
704 position: lsp::Position,
705 work_done_token: Option<lsp::ProgressToken>,
706 ) -> impl Future<Output = Result<Value>> {
707 let params = lsp::ReferenceParams {
708 text_document_position: lsp::TextDocumentPositionParams {
709 text_document,
710 position,
711 },
712 context: lsp::ReferenceContext {
713 include_declaration: true,
714 },
715 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
716 partial_result_params: lsp::PartialResultParams {
717 partial_result_token: None,
718 },
719 };
721 self.call::<lsp::request::References>(params)
722 }
724 pub fn document_symbols(
725 &self,
726 text_document: lsp::TextDocumentIdentifier,
727 ) -> impl Future<Output = Result<Value>> {
728 let params = lsp::DocumentSymbolParams {
729 text_document,
730 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
731 partial_result_params: lsp::PartialResultParams::default(),
732 };
734 self.call::<lsp::request::DocumentSymbolRequest>(params)
735 }
737 // empty string to get all symbols
738 pub fn workspace_symbols(&self, query: String) -> impl Future<Output = Result<Value>> {
739 let params = lsp::WorkspaceSymbolParams {
740 query,
741 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
742 partial_result_params: lsp::PartialResultParams::default(),
743 };
745 self.call::<lsp::request::WorkspaceSymbol>(params)
746 }
748 pub fn code_actions(
749 &self,
750 text_document: lsp::TextDocumentIdentifier,
751 range: lsp::Range,
752 ) -> impl Future<Output = Result<Value>> {
753 let params = lsp::CodeActionParams {
754 text_document,
755 range,
756 context: lsp::CodeActionContext::default(),
757 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
758 partial_result_params: lsp::PartialResultParams::default(),
759 };
761 self.call::<lsp::request::CodeActionRequest>(params)
762 }