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:
parent
c7961b9054
commit
4944dc9d78
3 changed files with 99 additions and 22 deletions
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue