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 futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
||||
ViewContext, VisualContext as _,
|
||||
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
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 workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
|
||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
||||
const WARNING_ICON: &str = "icons/warning.svg";
|
||||
|
||||
pub enum Event {
|
||||
ShowError { lsp_name: Arc<str>, error: String },
|
||||
}
|
||||
|
@ -35,14 +32,13 @@ struct LspStatus {
|
|||
}
|
||||
|
||||
struct PendingWork<'a> {
|
||||
language_server_name: &'a str,
|
||||
progress_token: &'a str,
|
||||
progress: &'a LanguageServerProgress,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Content {
|
||||
icon: Option<&'static str>,
|
||||
icon: Option<gpui::AnyElement>,
|
||||
message: String,
|
||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||
}
|
||||
|
@ -159,7 +155,6 @@ impl ActivityIndicator {
|
|||
.pending_work
|
||||
.iter()
|
||||
.map(|(token, progress)| PendingWork {
|
||||
language_server_name: status.name.as_str(),
|
||||
progress_token: token.as_str(),
|
||||
progress,
|
||||
})
|
||||
|
@ -175,31 +170,41 @@ impl ActivityIndicator {
|
|||
// Show any language server has pending activity.
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
language_server_name,
|
||||
progress_token,
|
||||
progress,
|
||||
}) = pending_work.next()
|
||||
{
|
||||
let mut message = language_server_name.to_string();
|
||||
|
||||
message.push_str(": ");
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
message.push_str(progress_message);
|
||||
} else {
|
||||
message.push_str(progress_token);
|
||||
}
|
||||
let mut message = progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(progress_token)
|
||||
.to_string();
|
||||
|
||||
if let Some(percentage) = progress.percentage {
|
||||
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();
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
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,
|
||||
on_click: None,
|
||||
};
|
||||
|
@ -222,7 +227,11 @@ impl ActivityIndicator {
|
|||
|
||||
if !downloading.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Downloading {}...", downloading.join(", "),),
|
||||
on_click: None,
|
||||
};
|
||||
|
@ -230,7 +239,11 @@ impl ActivityIndicator {
|
|||
|
||||
if !checking_for_update.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!(
|
||||
"Checking for updates to {}...",
|
||||
checking_for_update.join(", "),
|
||||
|
@ -241,7 +254,11 @@ impl ActivityIndicator {
|
|||
|
||||
if !failed.is_empty() {
|
||||
return Content {
|
||||
icon: Some(WARNING_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!(
|
||||
"Failed to download {}. Click to show error.",
|
||||
failed.join(", "),
|
||||
|
@ -255,7 +272,11 @@ impl ActivityIndicator {
|
|||
// Show any formatting failure
|
||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||
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),
|
||||
on_click: Some(Arc::new(|_, cx| {
|
||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||
|
@ -267,17 +288,29 @@ impl ActivityIndicator {
|
|||
if let Some(updater) = &self.auto_updater {
|
||||
return match &updater.read(cx).status() {
|
||||
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(),
|
||||
on_click: None,
|
||||
},
|
||||
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(),
|
||||
on_click: None,
|
||||
},
|
||||
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(),
|
||||
on_click: None,
|
||||
},
|
||||
|
@ -292,7 +325,11 @@ impl ActivityIndicator {
|
|||
})),
|
||||
},
|
||||
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(),
|
||||
on_click: Some(Arc::new(|this, 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() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Updating {extension_id} extension…"),
|
||||
on_click: None,
|
||||
};
|
||||
|
@ -338,7 +379,8 @@ impl Render for ActivityIndicator {
|
|||
}
|
||||
|
||||
result
|
||||
.children(content.icon.map(|icon| svg().path(icon)))
|
||||
.gap_2()
|
||||
.children(content.icon)
|
||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue