diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 2c433bb195e192d481cb4a0f25e84367d948b30d..ea7a6694fa2dce459766c096839b1868ffae1259 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -4,7 +4,7 @@ pub use lsp_types::*; use anyhow::{anyhow, Context, Result}; use collections::HashMap; -use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, FutureExt}; +use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt}; use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; use parking_lot::Mutex; use postage::{barrier, prelude::Stream}; @@ -22,14 +22,15 @@ use smol::process::windows::CommandExt; use std::{ ffi::OsString, fmt, - future::Future, io::Write, path::PathBuf, + pin::Pin, str::{self, FromStr as _}, sync::{ atomic::{AtomicI32, Ordering::SeqCst}, Arc, Weak, }, + task::Poll, time::{Duration, Instant}, }; use std::{path::Path, process::Stdio}; @@ -168,6 +169,37 @@ struct Error { message: String, } +pub trait LspRequestFuture: Future { + fn id(&self) -> i32; +} + +struct LspRequest { + id: i32, + request: F, +} + +impl LspRequest { + pub fn new(id: i32, request: F) -> Self { + Self { id, request } + } +} + +impl Future for LspRequest { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + // SAFETY: This is standard pin projection, we're pinned so our fields must be pinned. + let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().request) }; + inner.poll(cx) + } +} + +impl LspRequestFuture for LspRequest { + fn id(&self) -> i32 { + self.id + } +} + /// Experimental: Informs the end user about the state of the server /// /// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status) @@ -916,7 +948,7 @@ impl LanguageServer { pub fn request( &self, params: T::Params, - ) -> impl Future> + ) -> impl LspRequestFuture> where T::Result: 'static + Send, { @@ -935,7 +967,7 @@ impl LanguageServer { outbound_tx: &channel::Sender, executor: &BackgroundExecutor, params: T::Params, - ) -> impl 'static + Future> + ) -> impl LspRequestFuture> where T::Result: 'static + Send, { @@ -984,7 +1016,7 @@ impl LanguageServer { let outbound_tx = outbound_tx.downgrade(); let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse(); let started = Instant::now(); - async move { + LspRequest::new(id, async move { handle_response?; send?; @@ -1014,7 +1046,7 @@ impl LanguageServer { anyhow::bail!("LSP request timeout"); } } - } + }) } /// Sends a RPC notification to the language server. diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 969a2d5759be7130d8a839e0f94cc3f04c989a03..043503bf0d9ec4aca7ace0a67cba3a06b5b0cc2d 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -41,6 +41,10 @@ pub trait LspCommand: 'static + Sized + Send { true } + fn status(&self) -> Option { + None + } + fn to_lsp( &self, path: &Path, @@ -895,6 +899,10 @@ impl LspCommand for GetReferences { type LspRequest = lsp::request::References; type ProtoRequest = proto::GetReferences; + fn status(&self) -> Option { + return Some("Finding references...".to_owned()); + } + fn to_lsp( &self, path: &Path, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3cd32cb0d0dc954e03690062e80e6bcb4e46d62b..75f329e824f126cd94d92ca680d6205b70ba86e8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -55,7 +55,8 @@ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, - MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus, ServerStatus, + LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus, + ServerStatus, }; use lsp_command::*; use node_runtime::NodeRuntime; @@ -5697,7 +5698,7 @@ impl Project { } fn code_actions_impl( - &self, + &mut self, buffer_handle: &Model, range: Range, cx: &mut ModelContext, @@ -5777,7 +5778,7 @@ impl Project { } pub fn code_actions( - &self, + &mut self, buffer_handle: &Model, range: Range, cx: &mut ModelContext, @@ -6131,7 +6132,7 @@ impl Project { } fn prepare_rename_impl( - &self, + &mut self, buffer: Model, position: PointUtf16, cx: &mut ModelContext, @@ -6144,7 +6145,7 @@ impl Project { ) } pub fn prepare_rename( - &self, + &mut self, buffer: Model, position: T, cx: &mut ModelContext, @@ -6154,7 +6155,7 @@ impl Project { } fn perform_rename_impl( - &self, + &mut self, buffer: Model, position: PointUtf16, new_name: String, @@ -6174,7 +6175,7 @@ impl Project { ) } pub fn perform_rename( - &self, + &mut self, buffer: Model, position: T, new_name: String, @@ -6186,7 +6187,7 @@ impl Project { } pub fn on_type_format_impl( - &self, + &mut self, buffer: Model, position: PointUtf16, trigger: String, @@ -6210,7 +6211,7 @@ impl Project { } pub fn on_type_format( - &self, + &mut self, buffer: Model, position: T, trigger: String, @@ -6222,7 +6223,7 @@ impl Project { } pub fn inlay_hints( - &self, + &mut self, buffer_handle: Model, range: Range, cx: &mut ModelContext, @@ -6232,7 +6233,7 @@ impl Project { self.inlay_hints_impl(buffer_handle, range, cx) } fn inlay_hints_impl( - &self, + &mut self, buffer_handle: Model, range: Range, cx: &mut ModelContext, @@ -6711,12 +6712,34 @@ impl Project { let file = File::from_dyn(buffer.file()).and_then(File::as_local); if let (Some(file), Some(language_server)) = (file, language_server) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); + let status = request.status(); return cx.spawn(move |this, cx| async move { if !request.check_capabilities(language_server.capabilities()) { return Ok(Default::default()); } - let result = language_server.request::(lsp_params).await; + let lsp_request = language_server.request::(lsp_params); + + let id = lsp_request.id(); + if status.is_some() { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.on_lsp_work_start( + language_server.server_id(), + id.to_string(), + LanguageServerProgress { + message: status.clone(), + percentage: None, + last_update_at: Instant::now(), + }, + cx, + ); + }) + }) + .log_err(); + } + + let result = lsp_request.await; let response = match result { Ok(response) => response, @@ -6729,16 +6752,30 @@ impl Project { return Err(err); } }; - - request + let result = request .response_from_lsp( response, this.upgrade().ok_or_else(|| anyhow!("no app context"))?, buffer_handle, language_server.server_id(), - cx, + cx.clone(), ) - .await + .await; + + if status.is_some() { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.on_lsp_work_end( + language_server.server_id(), + id.to_string(), + cx, + ); + }) + }) + .log_err(); + } + + result }); } } else if let Some(project_id) = self.remote_id() {