Allow canceling in-progress language server work (e.g. cargo check
) (#13173)
Release Notes: - Added a more detailed message in place of the generic `checking...` messages when Rust-analyzer is running. - Added a rate limit for language server status messages, to reduce noisiness of those updates. - Added a `cancel language server work` action which will cancel long-running language server tasks. --------- Co-authored-by: Richard <richard@zed.dev>
This commit is contained in:
parent
f489c8b79f
commit
7003b0f211
15 changed files with 308 additions and 164 deletions
|
@ -3,22 +3,19 @@ use editor::Editor;
|
||||||
use extension::ExtensionStore;
|
use extension::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
|
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||||
ViewContext, VisualContext as _,
|
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||||
use project::{LanguageServerProgress, Project};
|
use project::{LanguageServerProgress, Project};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
|
||||||
const WARNING_ICON: &str = "icons/warning.svg";
|
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ShowError { lsp_name: Arc<str>, error: String },
|
ShowError { lsp_name: Arc<str>, error: String },
|
||||||
}
|
}
|
||||||
|
@ -35,14 +32,13 @@ struct LspStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingWork<'a> {
|
struct PendingWork<'a> {
|
||||||
language_server_name: &'a str,
|
|
||||||
progress_token: &'a str,
|
progress_token: &'a str,
|
||||||
progress: &'a LanguageServerProgress,
|
progress: &'a LanguageServerProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Content {
|
struct Content {
|
||||||
icon: Option<&'static str>,
|
icon: Option<gpui::AnyElement>,
|
||||||
message: String,
|
message: String,
|
||||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||||
}
|
}
|
||||||
|
@ -159,7 +155,6 @@ impl ActivityIndicator {
|
||||||
.pending_work
|
.pending_work
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(token, progress)| PendingWork {
|
.map(|(token, progress)| PendingWork {
|
||||||
language_server_name: status.name.as_str(),
|
|
||||||
progress_token: token.as_str(),
|
progress_token: token.as_str(),
|
||||||
progress,
|
progress,
|
||||||
})
|
})
|
||||||
|
@ -175,31 +170,41 @@ impl ActivityIndicator {
|
||||||
// Show any language server has pending activity.
|
// Show any language server has pending activity.
|
||||||
let mut pending_work = self.pending_language_server_work(cx);
|
let mut pending_work = self.pending_language_server_work(cx);
|
||||||
if let Some(PendingWork {
|
if let Some(PendingWork {
|
||||||
language_server_name,
|
|
||||||
progress_token,
|
progress_token,
|
||||||
progress,
|
progress,
|
||||||
}) = pending_work.next()
|
}) = pending_work.next()
|
||||||
{
|
{
|
||||||
let mut message = language_server_name.to_string();
|
let mut message = progress
|
||||||
|
.title
|
||||||
message.push_str(": ");
|
.as_deref()
|
||||||
if let Some(progress_message) = progress.message.as_ref() {
|
.unwrap_or(progress_token)
|
||||||
message.push_str(progress_message);
|
.to_string();
|
||||||
} else {
|
|
||||||
message.push_str(progress_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(percentage) = progress.percentage {
|
if let Some(percentage) = progress.percentage {
|
||||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(progress_message) = progress.message.as_ref() {
|
||||||
|
message.push_str(": ");
|
||||||
|
message.push_str(progress_message);
|
||||||
|
}
|
||||||
|
|
||||||
let additional_work_count = pending_work.count();
|
let additional_work_count = pending_work.count();
|
||||||
if additional_work_count > 0 {
|
if additional_work_count > 0 {
|
||||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Content {
|
return Content {
|
||||||
icon: None,
|
icon: Some(
|
||||||
|
Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message,
|
message,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
|
@ -222,7 +227,11 @@ impl ActivityIndicator {
|
||||||
|
|
||||||
if !downloading.is_empty() {
|
if !downloading.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Downloading {}...", downloading.join(", "),),
|
message: format!("Downloading {}...", downloading.join(", "),),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
|
@ -230,7 +239,11 @@ impl ActivityIndicator {
|
||||||
|
|
||||||
if !checking_for_update.is_empty() {
|
if !checking_for_update.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Checking for updates to {}...",
|
"Checking for updates to {}...",
|
||||||
checking_for_update.join(", "),
|
checking_for_update.join(", "),
|
||||||
|
@ -241,7 +254,11 @@ impl ActivityIndicator {
|
||||||
|
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Failed to download {}. Click to show error.",
|
"Failed to download {}. Click to show error.",
|
||||||
failed.join(", "),
|
failed.join(", "),
|
||||||
|
@ -255,7 +272,11 @@ impl ActivityIndicator {
|
||||||
// Show any formatting failure
|
// Show any formatting failure
|
||||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||||
on_click: Some(Arc::new(|_, cx| {
|
on_click: Some(Arc::new(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||||
|
@ -267,17 +288,29 @@ impl ActivityIndicator {
|
||||||
if let Some(updater) = &self.auto_updater {
|
if let Some(updater) = &self.auto_updater {
|
||||||
return match &updater.read(cx).status() {
|
return match &updater.read(cx).status() {
|
||||||
AutoUpdateStatus::Checking => Content {
|
AutoUpdateStatus::Checking => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Checking for Zed updates…".to_string(),
|
message: "Checking for Zed updates…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Downloading => Content {
|
AutoUpdateStatus::Downloading => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Downloading Zed update…".to_string(),
|
message: "Downloading Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Installing => Content {
|
AutoUpdateStatus::Installing => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Installing Zed update…".to_string(),
|
message: "Installing Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
|
@ -292,7 +325,11 @@ impl ActivityIndicator {
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Errored => Content {
|
AutoUpdateStatus::Errored => Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Auto update failed".to_string(),
|
message: "Auto update failed".to_string(),
|
||||||
on_click: Some(Arc::new(|this, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&Default::default(), cx)
|
this.dismiss_error_message(&Default::default(), cx)
|
||||||
|
@ -307,7 +344,11 @@ impl ActivityIndicator {
|
||||||
{
|
{
|
||||||
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Updating {extension_id} extension…"),
|
message: format!("Updating {extension_id} extension…"),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
|
@ -338,7 +379,8 @@ impl Render for ActivityIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
.children(content.icon.map(|icon| svg().path(icon)))
|
.gap_2()
|
||||||
|
.children(content.icon)
|
||||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ use language::{
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::{
|
use project::{
|
||||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||||
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||||
};
|
};
|
||||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||||
use rpc::RECEIVE_TIMEOUT;
|
use rpc::RECEIVE_TIMEOUT;
|
||||||
|
@ -1006,6 +1006,8 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.start_progress("the-token").await;
|
fake_language_server.start_progress("the-token").await;
|
||||||
|
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
|
@ -1015,7 +1017,6 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
|
@ -1040,6 +1041,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
|
@ -1049,7 +1051,6 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
percentage, rems, Animation, AnimationExt, EventEmitter, IntoElement, ParentElement, Render,
|
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
|
||||||
Styled, Subscription, Transformation, View, ViewContext, WeakView,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use language::Diagnostic;
|
use language::Diagnostic;
|
||||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||||
|
@ -61,42 +59,7 @@ impl Render for DiagnosticIndicator {
|
||||||
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let has_in_progress_checks = self
|
let status = if let Some(diagnostic) = &self.current_diagnostic {
|
||||||
.workspace
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|workspace| {
|
|
||||||
workspace
|
|
||||||
.read(cx)
|
|
||||||
.project()
|
|
||||||
.read(cx)
|
|
||||||
.language_servers_running_disk_based_diagnostics()
|
|
||||||
.next()
|
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
let status = if has_in_progress_checks {
|
|
||||||
Some(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ArrowCircle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|icon, delta| {
|
|
||||||
icon.transform(Transformation::rotate(percentage(delta)))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("Checking…")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
|
||||||
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
||||||
Some(
|
Some(
|
||||||
Button::new("diagnostic_message", message)
|
Button::new("diagnostic_message", message)
|
||||||
|
|
|
@ -169,6 +169,7 @@ gpui::actions!(
|
||||||
AddSelectionBelow,
|
AddSelectionBelow,
|
||||||
Backspace,
|
Backspace,
|
||||||
Cancel,
|
Cancel,
|
||||||
|
CancelLanguageServerWork,
|
||||||
ConfirmRename,
|
ConfirmRename,
|
||||||
ContextMenuFirst,
|
ContextMenuFirst,
|
||||||
ContextMenuLast,
|
ContextMenuLast,
|
||||||
|
|
|
@ -9494,6 +9494,20 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cancel_language_server_work(
|
||||||
|
&mut self,
|
||||||
|
_: &CancelLanguageServerWork,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(project) = self.project.clone() {
|
||||||
|
self.buffer.update(cx, |multi_buffer, cx| {
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||||
cx.show_character_palette();
|
cx.show_character_palette();
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,6 +341,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
register_action(view, cx, Editor::restart_language_server);
|
register_action(view, cx, Editor::restart_language_server);
|
||||||
|
register_action(view, cx, Editor::cancel_language_server_work);
|
||||||
register_action(view, cx, Editor::show_character_palette);
|
register_action(view, cx, Editor::show_character_palette);
|
||||||
register_action(view, cx, |editor, action, cx| {
|
register_action(view, cx, |editor, action, cx| {
|
||||||
if let Some(task) = editor.confirm_completion(action, cx) {
|
if let Some(task) = editor.confirm_completion(action, cx) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use waker_fn::waker_fn;
|
use waker_fn::waker_fn;
|
||||||
|
@ -316,6 +316,14 @@ impl BackgroundExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current time.
|
||||||
|
///
|
||||||
|
/// Calling this instead of `std::time::Instant::now` allows the use
|
||||||
|
/// of fake timers in tests.
|
||||||
|
pub fn now(&self) -> Instant {
|
||||||
|
self.dispatcher.now()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a task that will complete after the given duration.
|
/// Returns a task that will complete after the given duration.
|
||||||
/// Depending on other concurrent tasks the elapsed duration may be longer
|
/// Depending on other concurrent tasks the elapsed duration may be longer
|
||||||
/// than requested.
|
/// than requested.
|
||||||
|
|
|
@ -38,7 +38,7 @@ use seahash::SeaHasher;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
@ -275,6 +275,9 @@ pub trait PlatformDispatcher: Send + Sync {
|
||||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
||||||
fn park(&self, timeout: Option<Duration>) -> bool;
|
fn park(&self, timeout: Option<Duration>) -> bool;
|
||||||
fn unparker(&self) -> Unparker;
|
fn unparker(&self) -> Unparker;
|
||||||
|
fn now(&self) -> Instant {
|
||||||
|
Instant::now()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fn as_test(&self) -> Option<&TestDispatcher> {
|
fn as_test(&self) -> Option<&TestDispatcher> {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ struct TestDispatcherState {
|
||||||
background: Vec<Runnable>,
|
background: Vec<Runnable>,
|
||||||
deprioritized_background: Vec<Runnable>,
|
deprioritized_background: Vec<Runnable>,
|
||||||
delayed: Vec<(Duration, Runnable)>,
|
delayed: Vec<(Duration, Runnable)>,
|
||||||
|
start_time: Instant,
|
||||||
time: Duration,
|
time: Duration,
|
||||||
is_main_thread: bool,
|
is_main_thread: bool,
|
||||||
next_id: TestDispatcherId,
|
next_id: TestDispatcherId,
|
||||||
|
@ -52,6 +53,7 @@ impl TestDispatcher {
|
||||||
deprioritized_background: Vec::new(),
|
deprioritized_background: Vec::new(),
|
||||||
delayed: Vec::new(),
|
delayed: Vec::new(),
|
||||||
time: Duration::ZERO,
|
time: Duration::ZERO,
|
||||||
|
start_time: Instant::now(),
|
||||||
is_main_thread: true,
|
is_main_thread: true,
|
||||||
next_id: TestDispatcherId(1),
|
next_id: TestDispatcherId(1),
|
||||||
allow_parking: false,
|
allow_parking: false,
|
||||||
|
@ -251,6 +253,11 @@ impl PlatformDispatcher for TestDispatcher {
|
||||||
self.state.lock().is_main_thread
|
self.state.lock().is_main_thread
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn now(&self) -> Instant {
|
||||||
|
let state = self.state.lock();
|
||||||
|
state.start_time + state.time
|
||||||
|
}
|
||||||
|
|
||||||
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
|
|
@ -1351,6 +1351,14 @@ impl FakeLanguageServer {
|
||||||
|
|
||||||
/// Simulate that the server has started work and notifies about its progress with the specified token.
|
/// Simulate that the server has started work and notifies about its progress with the specified token.
|
||||||
pub async fn start_progress(&self, token: impl Into<String>) {
|
pub async fn start_progress(&self, token: impl Into<String>) {
|
||||||
|
self.start_progress_with(token, Default::default()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_progress_with(
|
||||||
|
&self,
|
||||||
|
token: impl Into<String>,
|
||||||
|
progress: WorkDoneProgressBegin,
|
||||||
|
) {
|
||||||
let token = token.into();
|
let token = token.into();
|
||||||
self.request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
|
self.request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
|
||||||
token: NumberOrString::String(token.clone()),
|
token: NumberOrString::String(token.clone()),
|
||||||
|
@ -1359,7 +1367,7 @@ impl FakeLanguageServer {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.notify::<notification::Progress>(ProgressParams {
|
self.notify::<notification::Progress>(ProgressParams {
|
||||||
token: NumberOrString::String(token),
|
token: NumberOrString::String(token),
|
||||||
value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(Default::default())),
|
value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(progress)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ use client::{
|
||||||
TypedEnvelope, UserStore,
|
TypedEnvelope, UserStore,
|
||||||
};
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
|
use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet, VecDeque};
|
||||||
use debounced_delay::DebouncedDelay;
|
use debounced_delay::DebouncedDelay;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{
|
channel::{
|
||||||
|
@ -62,6 +62,7 @@ use lsp::{
|
||||||
DocumentHighlightKind, Edit, FileSystemWatcher, InsertTextFormat, LanguageServer,
|
DocumentHighlightKind, Edit, FileSystemWatcher, InsertTextFormat, LanguageServer,
|
||||||
LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, OneOf,
|
LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, OneOf,
|
||||||
ServerCapabilities, ServerHealthStatus, ServerStatus, TextEdit, Uri,
|
ServerCapabilities, ServerHealthStatus, ServerStatus, TextEdit, Uri,
|
||||||
|
WorkDoneProgressCancelParams,
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
|
@ -131,7 +132,7 @@ pub use worktree::{
|
||||||
const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
|
const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
|
||||||
const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||||
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
pub const SERVER_PROGRESS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
|
pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
||||||
|
|
||||||
|
@ -164,9 +165,6 @@ pub struct Project {
|
||||||
worktrees_reordered: bool,
|
worktrees_reordered: bool,
|
||||||
active_entry: Option<ProjectEntryId>,
|
active_entry: Option<ProjectEntryId>,
|
||||||
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
|
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
|
||||||
pending_language_server_update: Option<BufferOrderedMessage>,
|
|
||||||
flush_language_server_update: Option<Task<()>>,
|
|
||||||
|
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
supplementary_language_servers:
|
supplementary_language_servers:
|
||||||
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
|
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
|
||||||
|
@ -381,6 +379,9 @@ pub struct LanguageServerStatus {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct LanguageServerProgress {
|
pub struct LanguageServerProgress {
|
||||||
|
pub is_disk_based_diagnostics_progress: bool,
|
||||||
|
pub is_cancellable: bool,
|
||||||
|
pub title: Option<String>,
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
pub percentage: Option<usize>,
|
pub percentage: Option<usize>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
|
@ -723,8 +724,6 @@ impl Project {
|
||||||
worktrees: Vec::new(),
|
worktrees: Vec::new(),
|
||||||
worktrees_reordered: false,
|
worktrees_reordered: false,
|
||||||
buffer_ordered_messages_tx: tx,
|
buffer_ordered_messages_tx: tx,
|
||||||
flush_language_server_update: None,
|
|
||||||
pending_language_server_update: None,
|
|
||||||
collaborators: Default::default(),
|
collaborators: Default::default(),
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
shared_buffers: Default::default(),
|
shared_buffers: Default::default(),
|
||||||
|
@ -864,8 +863,6 @@ impl Project {
|
||||||
worktrees: Vec::new(),
|
worktrees: Vec::new(),
|
||||||
worktrees_reordered: false,
|
worktrees_reordered: false,
|
||||||
buffer_ordered_messages_tx: tx,
|
buffer_ordered_messages_tx: tx,
|
||||||
pending_language_server_update: None,
|
|
||||||
flush_language_server_update: None,
|
|
||||||
loading_buffers_by_path: Default::default(),
|
loading_buffers_by_path: Default::default(),
|
||||||
loading_buffers: Default::default(),
|
loading_buffers: Default::default(),
|
||||||
shared_buffers: Default::default(),
|
shared_buffers: Default::default(),
|
||||||
|
@ -4142,6 +4139,40 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel_language_server_work_for_buffers(
|
||||||
|
&mut self,
|
||||||
|
buffers: impl IntoIterator<Item = Model<Buffer>>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let servers = buffers
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|buffer| {
|
||||||
|
self.language_server_ids_for_buffer(buffer.read(cx), cx)
|
||||||
|
.into_iter()
|
||||||
|
})
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
for server_id in servers {
|
||||||
|
let status = self.language_server_statuses.get(&server_id);
|
||||||
|
let server = self.language_servers.get(&server_id);
|
||||||
|
if let Some((server, status)) = server.zip(status) {
|
||||||
|
if let LanguageServerState::Running { server, .. } = server {
|
||||||
|
for (token, progress) in &status.pending_work {
|
||||||
|
if progress.is_cancellable {
|
||||||
|
server
|
||||||
|
.notify::<lsp::notification::WorkDoneProgressCancel>(
|
||||||
|
WorkDoneProgressCancelParams {
|
||||||
|
token: lsp::NumberOrString::String(token.clone()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_errored_server(
|
fn check_errored_server(
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
|
@ -4211,35 +4242,7 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enqueue_language_server_progress(
|
|
||||||
&mut self,
|
|
||||||
message: BufferOrderedMessage,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
self.pending_language_server_update.replace(message);
|
|
||||||
self.flush_language_server_update.get_or_insert_with(|| {
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(SERVER_PROGRESS_DEBOUNCE_TIMEOUT)
|
|
||||||
.await;
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
this.flush_language_server_update.take();
|
|
||||||
if let Some(update) = this.pending_language_server_update.take() {
|
|
||||||
this.enqueue_buffer_ordered_message(update).ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
|
fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
|
||||||
if let Some(pending_message) = self.pending_language_server_update.take() {
|
|
||||||
self.flush_language_server_update.take();
|
|
||||||
self.buffer_ordered_messages_tx
|
|
||||||
.unbounded_send(pending_message)
|
|
||||||
.map_err(|e| anyhow!(e))?;
|
|
||||||
}
|
|
||||||
self.buffer_ordered_messages_tx
|
self.buffer_ordered_messages_tx
|
||||||
.unbounded_send(message)
|
.unbounded_send(message)
|
||||||
.map_err(|e| anyhow!(e))
|
.map_err(|e| anyhow!(e))
|
||||||
|
@ -4259,6 +4262,7 @@ impl Project {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lsp::ProgressParamsValue::WorkDone(progress) = progress.value;
|
let lsp::ProgressParamsValue::WorkDone(progress) = progress.value;
|
||||||
let language_server_status =
|
let language_server_status =
|
||||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
|
@ -4281,32 +4285,36 @@ impl Project {
|
||||||
lsp::WorkDoneProgress::Begin(report) => {
|
lsp::WorkDoneProgress::Begin(report) => {
|
||||||
if is_disk_based_diagnostics_progress {
|
if is_disk_based_diagnostics_progress {
|
||||||
self.disk_based_diagnostics_started(language_server_id, cx);
|
self.disk_based_diagnostics_started(language_server_id, cx);
|
||||||
} else {
|
|
||||||
self.on_lsp_work_start(
|
|
||||||
language_server_id,
|
|
||||||
token.clone(),
|
|
||||||
LanguageServerProgress {
|
|
||||||
message: report.message.clone(),
|
|
||||||
percentage: report.percentage.map(|p| p as usize),
|
|
||||||
last_update_at: Instant::now(),
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
self.on_lsp_work_start(
|
||||||
|
language_server_id,
|
||||||
|
token.clone(),
|
||||||
|
LanguageServerProgress {
|
||||||
|
title: Some(report.title),
|
||||||
|
is_disk_based_diagnostics_progress,
|
||||||
|
is_cancellable: report.cancellable.unwrap_or(false),
|
||||||
|
message: report.message.clone(),
|
||||||
|
percentage: report.percentage.map(|p| p as usize),
|
||||||
|
last_update_at: cx.background_executor().now(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
lsp::WorkDoneProgress::Report(report) => {
|
lsp::WorkDoneProgress::Report(report) => {
|
||||||
if !is_disk_based_diagnostics_progress {
|
if self.on_lsp_work_progress(
|
||||||
self.on_lsp_work_progress(
|
language_server_id,
|
||||||
language_server_id,
|
token.clone(),
|
||||||
token.clone(),
|
LanguageServerProgress {
|
||||||
LanguageServerProgress {
|
title: None,
|
||||||
message: report.message.clone(),
|
is_disk_based_diagnostics_progress,
|
||||||
percentage: report.percentage.map(|p| p as usize),
|
is_cancellable: report.cancellable.unwrap_or(false),
|
||||||
last_update_at: Instant::now(),
|
message: report.message.clone(),
|
||||||
},
|
percentage: report.percentage.map(|p| p as usize),
|
||||||
cx,
|
last_update_at: cx.background_executor().now(),
|
||||||
);
|
},
|
||||||
self.enqueue_language_server_progress(
|
cx,
|
||||||
|
) {
|
||||||
|
self.enqueue_buffer_ordered_message(
|
||||||
BufferOrderedMessage::LanguageServerUpdate {
|
BufferOrderedMessage::LanguageServerUpdate {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
message: proto::update_language_server::Variant::WorkProgress(
|
message: proto::update_language_server::Variant::WorkProgress(
|
||||||
|
@ -4317,17 +4325,15 @@ impl Project {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
cx,
|
)
|
||||||
);
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lsp::WorkDoneProgress::End(_) => {
|
lsp::WorkDoneProgress::End(_) => {
|
||||||
language_server_status.progress_tokens.remove(&token);
|
language_server_status.progress_tokens.remove(&token);
|
||||||
|
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||||
if is_disk_based_diagnostics_progress {
|
if is_disk_based_diagnostics_progress {
|
||||||
self.disk_based_diagnostics_finished(language_server_id, cx);
|
self.disk_based_diagnostics_finished(language_server_id, cx);
|
||||||
} else {
|
|
||||||
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4350,6 +4356,7 @@ impl Project {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
message: proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
message: proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
||||||
token,
|
token,
|
||||||
|
title: progress.title,
|
||||||
message: progress.message,
|
message: progress.message,
|
||||||
percentage: progress.percentage.map(|p| p as u32),
|
percentage: progress.percentage.map(|p| p as u32),
|
||||||
}),
|
}),
|
||||||
|
@ -4364,25 +4371,34 @@ impl Project {
|
||||||
token: String,
|
token: String,
|
||||||
progress: LanguageServerProgress,
|
progress: LanguageServerProgress,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) -> bool {
|
||||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
let entry = status
|
match status.pending_work.entry(token) {
|
||||||
.pending_work
|
btree_map::Entry::Vacant(entry) => {
|
||||||
.entry(token)
|
entry.insert(progress);
|
||||||
.or_insert(LanguageServerProgress {
|
cx.notify();
|
||||||
message: Default::default(),
|
return true;
|
||||||
percentage: Default::default(),
|
}
|
||||||
last_update_at: progress.last_update_at,
|
btree_map::Entry::Occupied(mut entry) => {
|
||||||
});
|
let entry = entry.get_mut();
|
||||||
if progress.message.is_some() {
|
if (progress.last_update_at - entry.last_update_at)
|
||||||
entry.message = progress.message;
|
>= SERVER_PROGRESS_THROTTLE_TIMEOUT
|
||||||
|
{
|
||||||
|
entry.last_update_at = progress.last_update_at;
|
||||||
|
if progress.message.is_some() {
|
||||||
|
entry.message = progress.message;
|
||||||
|
}
|
||||||
|
if progress.percentage.is_some() {
|
||||||
|
entry.percentage = progress.percentage;
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if progress.percentage.is_some() {
|
|
||||||
entry.percentage = progress.percentage;
|
|
||||||
}
|
|
||||||
entry.last_update_at = progress.last_update_at;
|
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_lsp_work_end(
|
fn on_lsp_work_end(
|
||||||
|
@ -4392,8 +4408,11 @@ impl Project {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
cx.emit(Event::RefreshInlayHints);
|
if let Some(work) = status.pending_work.remove(&token) {
|
||||||
status.pending_work.remove(&token);
|
if !work.is_disk_based_diagnostics_progress {
|
||||||
|
cx.emit(Event::RefreshInlayHints);
|
||||||
|
}
|
||||||
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7384,9 +7403,12 @@ impl Project {
|
||||||
language_server.server_id(),
|
language_server.server_id(),
|
||||||
id.to_string(),
|
id.to_string(),
|
||||||
LanguageServerProgress {
|
LanguageServerProgress {
|
||||||
|
is_disk_based_diagnostics_progress: false,
|
||||||
|
is_cancellable: false,
|
||||||
|
title: None,
|
||||||
message: status.clone(),
|
message: status.clone(),
|
||||||
percentage: None,
|
percentage: None,
|
||||||
last_update_at: Instant::now(),
|
last_update_at: cx.background_executor().now(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -9005,9 +9027,12 @@ impl Project {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
payload.token,
|
payload.token,
|
||||||
LanguageServerProgress {
|
LanguageServerProgress {
|
||||||
|
title: payload.title,
|
||||||
|
is_disk_based_diagnostics_progress: false,
|
||||||
|
is_cancellable: false,
|
||||||
message: payload.message,
|
message: payload.message,
|
||||||
percentage: payload.percentage.map(|p| p as usize),
|
percentage: payload.percentage.map(|p| p as usize),
|
||||||
last_update_at: Instant::now(),
|
last_update_at: cx.background_executor().now(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -9018,9 +9043,12 @@ impl Project {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
payload.token,
|
payload.token,
|
||||||
LanguageServerProgress {
|
LanguageServerProgress {
|
||||||
|
title: None,
|
||||||
|
is_disk_based_diagnostics_progress: false,
|
||||||
|
is_cancellable: false,
|
||||||
message: payload.message,
|
message: payload.message,
|
||||||
percentage: payload.percentage.map(|p| p as usize),
|
percentage: payload.percentage.map(|p| p as usize),
|
||||||
last_update_at: Instant::now(),
|
last_update_at: cx.background_executor().now(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ use language::{
|
||||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
||||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,
|
LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,
|
||||||
};
|
};
|
||||||
|
use lsp::NumberOrString;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -1461,6 +1462,69 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
|
||||||
assert_eq!(notification.version, 0);
|
assert_eq!(notification.version, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_cancel_language_server_work(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let progress_token = "the-progress-token";
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
|
language_registry.add(rust_lang());
|
||||||
|
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||||
|
"Rust",
|
||||||
|
FakeLspAdapter {
|
||||||
|
name: "the-language-server",
|
||||||
|
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||||
|
disk_based_diagnostics_progress_token: Some(progress_token.into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Simulate diagnostics starting to update.
|
||||||
|
let mut fake_server = fake_servers.next().await.unwrap();
|
||||||
|
fake_server
|
||||||
|
.start_progress_with(
|
||||||
|
"another-token",
|
||||||
|
lsp::WorkDoneProgressBegin {
|
||||||
|
cancellable: Some(false),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
fake_server
|
||||||
|
.start_progress_with(
|
||||||
|
progress_token,
|
||||||
|
lsp::WorkDoneProgressBegin {
|
||||||
|
cancellable: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.cancel_language_server_work_for_buffers([buffer.clone()], cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let cancel_notification = fake_server
|
||||||
|
.receive_notification::<lsp::notification::WorkDoneProgressCancel>()
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
cancel_notification.token,
|
||||||
|
NumberOrString::String(progress_token.into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
@ -3758,6 +3822,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_rename(cx: &mut gpui::TestAppContext) {
|
async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||||
|
// hi
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
|
|
@ -1199,6 +1199,7 @@ message UpdateLanguageServer {
|
||||||
|
|
||||||
message LspWorkStart {
|
message LspWorkStart {
|
||||||
string token = 1;
|
string token = 1;
|
||||||
|
optional string title = 4;
|
||||||
optional string message = 2;
|
optional string message = 2;
|
||||||
optional uint32 percentage = 3;
|
optional uint32 percentage = 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,7 @@ pub enum IconName {
|
||||||
Dash,
|
Dash,
|
||||||
Delete,
|
Delete,
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
Download,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
Envelope,
|
Envelope,
|
||||||
Escape,
|
Escape,
|
||||||
|
@ -248,6 +249,7 @@ impl IconName {
|
||||||
IconName::Dash => "icons/dash.svg",
|
IconName::Dash => "icons/dash.svg",
|
||||||
IconName::Delete => "icons/delete.svg",
|
IconName::Delete => "icons/delete.svg",
|
||||||
IconName::Disconnected => "icons/disconnected.svg",
|
IconName::Disconnected => "icons/disconnected.svg",
|
||||||
|
IconName::Download => "icons/download.svg",
|
||||||
IconName::Ellipsis => "icons/ellipsis.svg",
|
IconName::Ellipsis => "icons/ellipsis.svg",
|
||||||
IconName::Envelope => "icons/feedback.svg",
|
IconName::Envelope => "icons/feedback.svg",
|
||||||
IconName::Escape => "icons/escape.svg",
|
IconName::Escape => "icons/escape.svg",
|
||||||
|
|
|
@ -172,7 +172,7 @@ setTimeout(() => {
|
||||||
env: Object.assign({}, process.env, {
|
env: Object.assign({}, process.env, {
|
||||||
ZED_IMPERSONATE: users[i],
|
ZED_IMPERSONATE: users[i],
|
||||||
ZED_WINDOW_POSITION: position,
|
ZED_WINDOW_POSITION: position,
|
||||||
ZED_STATELESS: isStateful && i == 0 ? "1" : "",
|
ZED_STATELESS: isStateful && i == 0 ? "" : "1",
|
||||||
ZED_ALWAYS_ACTIVE: "1",
|
ZED_ALWAYS_ACTIVE: "1",
|
||||||
ZED_SERVER_URL: "http://localhost:3000",
|
ZED_SERVER_URL: "http://localhost:3000",
|
||||||
ZED_RPC_URL: "http://localhost:8080/rpc",
|
ZED_RPC_URL: "http://localhost:8080/rpc",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue