Show status of LSP actions (#9818)

Fixes #4380

Parts im still unsure about:
- [x] where exactly I should call `on_lsp_start`/`on_lsp_end`
- [x] how to handle things better than `let is_references =
TypeId::of::<R>() == TypeId::of::<GetReferences>();`, which feels very
janky
- [x] I want to have the message be something like `"Finding references
to [...]"` instead of just `textDocument/references`, but I'm not sure
how to retrieve the name of the symbol that's being queried
- [ ] I think the bulk of the runtime is occupied by `let result =
language_server.request::<R::LspRequest>(lsp_params).await;`, but since
`ModelContext` isn't passed into it, I'm not sure how to update progress
from within that function
- [x] A good way to disambiguate between multiple calls to the same lsp
function; im currently using the function name itself as the unique
identifier for that request, which could create issues if multiple
`textDocument/references` requests are sent in parallel

Any help with these would be deeply appreciated!

Release Notes:

- Adds a status indicator for LSP actions

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Daniel Zhu 2024-04-06 22:48:11 -04:00 committed by GitHub
parent c7961b9054
commit 4944dc9d78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 22 deletions

View file

@ -4,7 +4,7 @@ pub use lsp_types::*;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::HashMap; 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 gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::{barrier, prelude::Stream}; use postage::{barrier, prelude::Stream};
@ -22,14 +22,15 @@ use smol::process::windows::CommandExt;
use std::{ use std::{
ffi::OsString, ffi::OsString,
fmt, fmt,
future::Future,
io::Write, io::Write,
path::PathBuf, path::PathBuf,
pin::Pin,
str::{self, FromStr as _}, str::{self, FromStr as _},
sync::{ sync::{
atomic::{AtomicI32, Ordering::SeqCst}, atomic::{AtomicI32, Ordering::SeqCst},
Arc, Weak, Arc, Weak,
}, },
task::Poll,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use std::{path::Path, process::Stdio}; use std::{path::Path, process::Stdio};
@ -168,6 +169,37 @@ struct Error {
message: String, message: String,
} }
pub trait LspRequestFuture<O>: Future<Output = O> {
fn id(&self) -> i32;
}
struct LspRequest<F> {
id: i32,
request: F,
}
impl<F> LspRequest<F> {
pub fn new(id: i32, request: F) -> Self {
Self { id, request }
}
}
impl<F: Future> Future for LspRequest<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
// 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<F: Future> LspRequestFuture<F::Output> for LspRequest<F> {
fn id(&self) -> i32 {
self.id
}
}
/// Experimental: Informs the end user about the state of the server /// 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) /// [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<T: request::Request>( pub fn request<T: request::Request>(
&self, &self,
params: T::Params, params: T::Params,
) -> impl Future<Output = Result<T::Result>> ) -> impl LspRequestFuture<Result<T::Result>>
where where
T::Result: 'static + Send, T::Result: 'static + Send,
{ {
@ -935,7 +967,7 @@ impl LanguageServer {
outbound_tx: &channel::Sender<String>, outbound_tx: &channel::Sender<String>,
executor: &BackgroundExecutor, executor: &BackgroundExecutor,
params: T::Params, params: T::Params,
) -> impl 'static + Future<Output = anyhow::Result<T::Result>> ) -> impl LspRequestFuture<Result<T::Result>>
where where
T::Result: 'static + Send, T::Result: 'static + Send,
{ {
@ -984,7 +1016,7 @@ impl LanguageServer {
let outbound_tx = outbound_tx.downgrade(); let outbound_tx = outbound_tx.downgrade();
let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse(); let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse();
let started = Instant::now(); let started = Instant::now();
async move { LspRequest::new(id, async move {
handle_response?; handle_response?;
send?; send?;
@ -1014,7 +1046,7 @@ impl LanguageServer {
anyhow::bail!("LSP request timeout"); anyhow::bail!("LSP request timeout");
} }
} }
} })
} }
/// Sends a RPC notification to the language server. /// Sends a RPC notification to the language server.

View file

@ -41,6 +41,10 @@ pub trait LspCommand: 'static + Sized + Send {
true true
} }
fn status(&self) -> Option<String> {
None
}
fn to_lsp( fn to_lsp(
&self, &self,
path: &Path, path: &Path,
@ -895,6 +899,10 @@ impl LspCommand for GetReferences {
type LspRequest = lsp::request::References; type LspRequest = lsp::request::References;
type ProtoRequest = proto::GetReferences; type ProtoRequest = proto::GetReferences;
fn status(&self) -> Option<String> {
return Some("Finding references...".to_owned());
}
fn to_lsp( fn to_lsp(
&self, &self,
path: &Path, path: &Path,

View file

@ -55,7 +55,8 @@ use log::error;
use lsp::{ use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId,
MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus, ServerStatus, LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus,
ServerStatus,
}; };
use lsp_command::*; use lsp_command::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -5697,7 +5698,7 @@ impl Project {
} }
fn code_actions_impl( fn code_actions_impl(
&self, &mut self,
buffer_handle: &Model<Buffer>, buffer_handle: &Model<Buffer>,
range: Range<Anchor>, range: Range<Anchor>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -5777,7 +5778,7 @@ impl Project {
} }
pub fn code_actions<T: Clone + ToOffset>( pub fn code_actions<T: Clone + ToOffset>(
&self, &mut self,
buffer_handle: &Model<Buffer>, buffer_handle: &Model<Buffer>,
range: Range<T>, range: Range<T>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -6131,7 +6132,7 @@ impl Project {
} }
fn prepare_rename_impl( fn prepare_rename_impl(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: PointUtf16, position: PointUtf16,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -6144,7 +6145,7 @@ impl Project {
) )
} }
pub fn prepare_rename<T: ToPointUtf16>( pub fn prepare_rename<T: ToPointUtf16>(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: T, position: T,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -6154,7 +6155,7 @@ impl Project {
} }
fn perform_rename_impl( fn perform_rename_impl(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: PointUtf16, position: PointUtf16,
new_name: String, new_name: String,
@ -6174,7 +6175,7 @@ impl Project {
) )
} }
pub fn perform_rename<T: ToPointUtf16>( pub fn perform_rename<T: ToPointUtf16>(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: T, position: T,
new_name: String, new_name: String,
@ -6186,7 +6187,7 @@ impl Project {
} }
pub fn on_type_format_impl( pub fn on_type_format_impl(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: PointUtf16, position: PointUtf16,
trigger: String, trigger: String,
@ -6210,7 +6211,7 @@ impl Project {
} }
pub fn on_type_format<T: ToPointUtf16>( pub fn on_type_format<T: ToPointUtf16>(
&self, &mut self,
buffer: Model<Buffer>, buffer: Model<Buffer>,
position: T, position: T,
trigger: String, trigger: String,
@ -6222,7 +6223,7 @@ impl Project {
} }
pub fn inlay_hints<T: ToOffset>( pub fn inlay_hints<T: ToOffset>(
&self, &mut self,
buffer_handle: Model<Buffer>, buffer_handle: Model<Buffer>,
range: Range<T>, range: Range<T>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -6232,7 +6233,7 @@ impl Project {
self.inlay_hints_impl(buffer_handle, range, cx) self.inlay_hints_impl(buffer_handle, range, cx)
} }
fn inlay_hints_impl( fn inlay_hints_impl(
&self, &mut self,
buffer_handle: Model<Buffer>, buffer_handle: Model<Buffer>,
range: Range<Anchor>, range: Range<Anchor>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -6711,12 +6712,34 @@ impl Project {
let file = File::from_dyn(buffer.file()).and_then(File::as_local); let file = File::from_dyn(buffer.file()).and_then(File::as_local);
if let (Some(file), Some(language_server)) = (file, language_server) { 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 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 { return cx.spawn(move |this, cx| async move {
if !request.check_capabilities(language_server.capabilities()) { if !request.check_capabilities(language_server.capabilities()) {
return Ok(Default::default()); return Ok(Default::default());
} }
let result = language_server.request::<R::LspRequest>(lsp_params).await; let lsp_request = language_server.request::<R::LspRequest>(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 { let response = match result {
Ok(response) => response, Ok(response) => response,
@ -6729,16 +6752,30 @@ impl Project {
return Err(err); return Err(err);
} }
}; };
let result = request
request
.response_from_lsp( .response_from_lsp(
response, response,
this.upgrade().ok_or_else(|| anyhow!("no app context"))?, this.upgrade().ok_or_else(|| anyhow!("no app context"))?,
buffer_handle, buffer_handle,
language_server.server_id(), 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() { } else if let Some(project_id) = self.remote_id() {