2 transport::{Payload, Transport},
3 Call, Error, OffsetEncoding, Result,
6use helix_core::{chars::char_is_line_ending, find_root, ChangeSet, Rope};
7use jsonrpc_core as jsonrpc;
10use std::future::Future;
11use std::process::Stdio;
12use std::sync::atomic::{AtomicU64, Ordering};
14 io::{BufReader, BufWriter},
15 process::{Child, Command},
16 sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
23 server_tx: UnboundedSender<Payload>,
24 request_counter: AtomicU64,
25 capabilities: Option<lsp::ServerCapabilities>,
26 offset_encoding: OffsetEncoding,
27 config: Option<Value>,
34 config: Option<Value>,
36 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
37 let process = Command::new(cmd)
39 .stdin(Stdio::piped())
40 .stdout(Stdio::piped())
41 .stderr(Stdio::piped())
42 // make sure the process is reaped on drop
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);
59 request_counter: AtomicU64::new(0),
61 offset_encoding: OffsetEncoding::Utf8,
65 // TODO: async client.initialize()
66 // maybe use an arc<atomic> flag
68 Ok((client, server_rx))
71 pub fn id(&self) -> usize {
75 fn next_request_id(&self) -> jsonrpc::Id {
76 let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
80 fn value_into_params(value: Value) -> jsonrpc::Params {
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),
91 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
94 .expect("language server not yet initialized!")
97 pub fn offset_encoding(&self) -> OffsetEncoding {
101 /// Execute a RPC request on the language server.
102 async fn request<R: lsp::request::Request>(&self, params: R::Params) -> Result<R::Result>
104 R::Params: serde::Serialize,
105 R::Result: core::fmt::Debug, // TODO: temporary
107 // a future that resolves into the response
108 let json = self.call::<R>(params).await?;
109 let response = serde_json::from_value(json)?;
113 /// Execute a RPC request on the language server.
114 fn call<R: lsp::request::Request>(
117 ) -> impl Future<Output = Result<Value>>
119 R::Params: serde::Serialize,
121 let server_tx = self.server_tx.clone();
122 let id = self.next_request_id();
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),
133 method: R::METHOD.to_string(),
134 params: Self::value_into_params(params),
137 let (tx, mut rx) = channel::<Result<Value>>(1);
140 .send(Payload::Request {
144 .map_err(|e| Error::Other(e.into()))?;
146 timeout(Duration::from_secs(2), rx.recv())
148 .map_err(|_| Error::Timeout)? // return Timeout
149 .ok_or(Error::StreamClosed)?
153 /// Send a RPC notification to the language server.
154 fn notify<R: lsp::notification::Notification>(
157 ) -> impl Future<Output = Result<()>>
159 R::Params: serde::Serialize,
161 let server_tx = self.server_tx.clone();
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),
173 .send(Payload::Notification(notification))
174 .map_err(|e| Error::Other(e.into()))?;
180 /// Reply to a language server RPC call.
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();
191 let output = match result {
192 Ok(result) => Output::Success(Success {
193 jsonrpc: Some(Version::V2),
197 Err(error) => Output::Failure(Failure {
198 jsonrpc: Some(Version::V2),
205 .send(Payload::Response(output))
206 .map_err(|e| Error::Other(e.into()))?;
212 // -------------------------------------------------------------------------------------------
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());
225 let params = lsp::InitializeParams {
226 process_id: Some(std::process::id()),
227 // root_path is obsolete, use root_uri
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),
238 completion_item_kind: Some(lsp::CompletionItemKindCapability {
241 context_support: None, // additional context information Some(true)
244 hover: Some(lsp::HoverClientCapabilities {
245 // if not specified, rust-analyzer returns plaintext marked as markdown but
247 content_format: Some(vec![lsp::MarkupKind::Markdown]),
250 code_action: Some(lsp::CodeActionClientCapabilities {
251 code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
252 code_action_kind: lsp::CodeActionKindLiteralSupport {
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,
264 .map(|kind| kind.as_str().to_string())
272 window: Some(lsp::WindowClientCapabilities {
273 work_done_progress: Some(true),
279 workspace_folders: None,
281 locale: None, // TODO
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 {})
294 pub async fn shutdown(&self) -> Result<()> {
295 self.request::<lsp::request::Shutdown>(()).await
298 pub fn exit(&self) -> impl Future<Output = Result<()>> {
299 self.notify::<lsp::notification::Exit>(())
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?;
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);
317 // -------------------------------------------------------------------------------------------
319 // -------------------------------------------------------------------------------------------
321 pub fn text_document_did_open(
327 ) -> impl Future<Output = Result<()>> {
328 self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
329 text_document: lsp::TextDocumentItem {
333 text: String::from(doc),
338 pub fn changeset_to_changes(
341 changeset: &ChangeSet,
342 offset_encoding: OffsetEncoding,
343 ) -> Vec<lsp::TextDocumentContentChangeEvent> {
344 let mut iter = changeset.changes().iter().peekable();
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.
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 {
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')) {
373 character += ch.len_utf16() as u32;
376 lsp::Position { line, character }
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,
386 let mut old_end = old_pos + len;
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));
397 changes.push(lsp::TextDocumentContentChangeEvent {
398 range: Some(lsp::Range::new(start, end)),
399 text: "".to_string(),
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));
422 changes.push(lsp::TextDocumentContentChangeEvent {
423 range: Some(lsp::Range::new(start, end)),
435 pub fn text_document_did_change(
437 text_document: lsp::VersionedTextDocumentIdentifier,
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 {
452 // None | SyncOptions { changes: None }
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(),
465 lsp::TextDocumentSyncKind::Incremental => {
466 Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding)
468 lsp::TextDocumentSyncKind::None => return None,
471 Some(self.notify::<lsp::notification::DidChangeTextDocument>(
472 lsp::DidChangeTextDocumentParams {
474 content_changes: changes,
479 pub fn text_document_did_close(
481 text_document: lsp::TextDocumentIdentifier,
482 ) -> impl Future<Output = Result<()>> {
483 self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
488 // will_save / will_save_wait_until
490 pub async fn text_document_did_save(
492 text_document: lsp::TextDocumentIdentifier,
495 let capabilities = self.capabilities.as_ref().unwrap();
497 let include_text = match &capabilities.text_document_sync {
498 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
501 })) => match options {
502 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
503 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
505 }) => include_text.unwrap_or(false),
513 self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
515 text: include_text.then(|| text.into()),
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 {
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,
538 // lsp::CompletionContext { trigger_kind: , trigger_character: Some(), }
541 self.call::<lsp::request::Completion>(params)
544 pub fn text_document_signature_help(
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 {
555 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
557 // lsp::SignatureHelpContext
560 self.call::<lsp::request::SignatureHelpRequest>(params)
563 pub fn text_document_hover(
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 {
574 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
575 // lsp::SignatureHelpContext
578 self.call::<lsp::request::HoverRequest>(params)
583 pub async fn text_document_formatting(
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()),
597 // TODO: return err::unavailable so we can fall back to tree sitter formatting
599 let params = lsp::DocumentFormattingParams {
602 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
605 let response = self.request::<lsp::request::Formatting>(params).await?;
607 Ok(response.unwrap_or_default())
610 pub async fn text_document_range_formatting(
612 text_document: lsp::TextDocumentIdentifier,
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()),
625 // TODO: return err::unavailable so we can fall back to tree sitter formatting
627 let params = lsp::DocumentRangeFormattingParams {
631 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
635 .request::<lsp::request::RangeFormatting>(params)
638 Ok(response.unwrap_or_default())
642 T: lsp::request::Request<
643 Params = lsp::GotoDefinitionParams,
644 Result = Option<lsp::GotoDefinitionResponse>,
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 {
657 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
658 partial_result_params: lsp::PartialResultParams {
659 partial_result_token: None,
663 self.call::<T>(params)
666 pub fn goto_definition(
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)
675 pub fn goto_type_definition(
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>(
688 pub fn goto_implementation(
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>(
701 pub fn goto_reference(
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 {
712 context: lsp::ReferenceContext {
713 include_declaration: true,
715 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
716 partial_result_params: lsp::PartialResultParams {
717 partial_result_token: None,
721 self.call::<lsp::request::References>(params)
724 pub fn document_symbols(
726 text_document: lsp::TextDocumentIdentifier,
727 ) -> impl Future<Output = Result<Value>> {
728 let params = lsp::DocumentSymbolParams {
730 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
731 partial_result_params: lsp::PartialResultParams::default(),
734 self.call::<lsp::request::DocumentSymbolRequest>(params)
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 {
741 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
742 partial_result_params: lsp::PartialResultParams::default(),
745 self.call::<lsp::request::WorkspaceSymbol>(params)
750 text_document: lsp::TextDocumentIdentifier,
752 ) -> impl Future<Output = Result<Value>> {
753 let params = lsp::CodeActionParams {
756 context: lsp::CodeActionContext::default(),
757 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
758 partial_result_params: lsp::PartialResultParams::default(),
761 self.call::<lsp::request::CodeActionRequest>(params)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list