mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-07 12:25:32 +03:00
Make LSP task cancellation discoverable (#13226)
Release Notes: - Added the ability to cancel a cargo check by clicking on the status bar item.
This commit is contained in:
parent
84a44bef8a
commit
89d2ace713
@ -3,15 +3,18 @@ use editor::Editor;
|
||||
use extension::ExtensionStore;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
|
||||
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
|
||||
VisualContext as _,
|
||||
};
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, ContextMenu};
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
@ -24,6 +27,7 @@ pub struct ActivityIndicator {
|
||||
statuses: Vec<LspStatus>,
|
||||
project: Model<Project>,
|
||||
auto_updater: Option<Model<AutoUpdater>>,
|
||||
context_menu: Option<View<ContextMenu>>,
|
||||
}
|
||||
|
||||
struct LspStatus {
|
||||
@ -32,6 +36,7 @@ struct LspStatus {
|
||||
}
|
||||
|
||||
struct PendingWork<'a> {
|
||||
language_server_id: LanguageServerId,
|
||||
progress_token: &'a str,
|
||||
progress: &'a LanguageServerProgress,
|
||||
}
|
||||
@ -74,6 +79,7 @@ impl ActivityIndicator {
|
||||
statuses: Default::default(),
|
||||
project: project.clone(),
|
||||
auto_updater,
|
||||
context_menu: None,
|
||||
}
|
||||
});
|
||||
|
||||
@ -147,7 +153,7 @@ impl ActivityIndicator {
|
||||
.read(cx)
|
||||
.language_server_statuses()
|
||||
.rev()
|
||||
.filter_map(|status| {
|
||||
.filter_map(|(server_id, status)| {
|
||||
if status.pending_work.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@ -155,6 +161,7 @@ impl ActivityIndicator {
|
||||
.pending_work
|
||||
.iter()
|
||||
.map(|(token, progress)| PendingWork {
|
||||
language_server_id: server_id,
|
||||
progress_token: token.as_str(),
|
||||
progress,
|
||||
})
|
||||
@ -172,6 +179,7 @@ impl ActivityIndicator {
|
||||
if let Some(PendingWork {
|
||||
progress_token,
|
||||
progress,
|
||||
..
|
||||
}) = pending_work.next()
|
||||
{
|
||||
let mut message = progress
|
||||
@ -206,7 +214,7 @@ impl ActivityIndicator {
|
||||
.into_any_element(),
|
||||
),
|
||||
message,
|
||||
on_click: None,
|
||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -357,6 +365,75 @@ impl ActivityIndicator {
|
||||
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.context_menu.take().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.build_lsp_work_context_menu(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let mut has_work = false;
|
||||
let this = cx.view().downgrade();
|
||||
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
|
||||
for work in self.pending_language_server_work(cx) {
|
||||
has_work = true;
|
||||
|
||||
let this = this.clone();
|
||||
let title = SharedString::from(
|
||||
work.progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(work.progress_token)
|
||||
.to_string(),
|
||||
);
|
||||
if work.progress.is_cancellable {
|
||||
let language_server_id = work.language_server_id;
|
||||
let token = work.progress_token.to_string();
|
||||
menu = menu.custom_entry(
|
||||
move |_| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(title.clone()))
|
||||
.child(Icon::new(IconName::XCircle))
|
||||
.into_any_element()
|
||||
},
|
||||
move |cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.cancel_language_server_work(
|
||||
language_server_id,
|
||||
Some(token.clone()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
this.context_menu.take();
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
menu = menu.label(title.clone());
|
||||
}
|
||||
}
|
||||
menu
|
||||
});
|
||||
|
||||
if has_work {
|
||||
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
||||
this.context_menu.take();
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
cx.focus_view(&context_menu);
|
||||
self.context_menu = Some(context_menu);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for ActivityIndicator {}
|
||||
@ -382,6 +459,14 @@ impl Render for ActivityIndicator {
|
||||
.gap_2()
|
||||
.children(content.icon)
|
||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||
.children(self.context_menu.as_ref().map(|menu| {
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1020,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
executor.run_until_parked();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
@ -1037,7 +1037,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
});
|
||||
|
||||
@ -1054,7 +1054,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
executor.run_until_parked();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
@ -1064,7 +1064,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
});
|
||||
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
|
@ -4772,7 +4772,7 @@ async fn test_references(
|
||||
// User is informed that a request is pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||
assert_eq!(
|
||||
status.pending_work.values().next().unwrap().message,
|
||||
@ -4802,7 +4802,7 @@ async fn test_references(
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
// User is informed that a request is no longer pending.
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert!(status.pending_work.is_empty());
|
||||
|
||||
assert_eq!(references.len(), 3);
|
||||
@ -4830,7 +4830,7 @@ async fn test_references(
|
||||
// User is informed that a request is pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||
assert_eq!(
|
||||
status.pending_work.values().next().unwrap().message,
|
||||
@ -4847,7 +4847,7 @@ async fn test_references(
|
||||
// User is informed that the request is no longer pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert!(status.pending_work.is_empty());
|
||||
});
|
||||
}
|
||||
|
@ -4149,11 +4149,26 @@ impl Project {
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for server_id in servers {
|
||||
self.cancel_language_server_work(server_id, None, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel_language_server_work(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
token_to_cancel: Option<String>,
|
||||
_cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
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 let Some(token_to_cancel) = token_to_cancel.as_ref() {
|
||||
if token != token_to_cancel {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if progress.is_cancellable {
|
||||
server
|
||||
.notify::<lsp::notification::WorkDoneProgressCancel>(
|
||||
@ -4167,7 +4182,6 @@ impl Project {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_errored_server(
|
||||
language: Arc<Language>,
|
||||
@ -4580,8 +4594,10 @@ impl Project {
|
||||
|
||||
pub fn language_server_statuses(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
|
||||
self.language_server_statuses.values()
|
||||
) -> impl DoubleEndedIterator<Item = (LanguageServerId, &LanguageServerStatus)> {
|
||||
self.language_server_statuses
|
||||
.iter()
|
||||
.map(|(key, value)| (*key, value))
|
||||
}
|
||||
|
||||
pub fn last_formatting_failure(&self) -> Option<&str> {
|
||||
|
@ -14,6 +14,7 @@ use theme::ThemeSettings;
|
||||
enum ContextMenuItem {
|
||||
Separator,
|
||||
Header(SharedString),
|
||||
Label(SharedString),
|
||||
Entry {
|
||||
toggled: Option<bool>,
|
||||
label: SharedString,
|
||||
@ -147,6 +148,12 @@ impl ContextMenu {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||
let label = label.into();
|
||||
self.items.push(ContextMenuItem::Label(label));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
toggled: None,
|
||||
@ -284,6 +291,7 @@ impl ContextMenuItem {
|
||||
fn is_selectable(&self) -> bool {
|
||||
match self {
|
||||
ContextMenuItem::Separator => false,
|
||||
ContextMenuItem::Label { .. } => false,
|
||||
ContextMenuItem::Header(_) => false,
|
||||
ContextMenuItem::Entry { .. } => true,
|
||||
ContextMenuItem::CustomEntry { selectable, .. } => *selectable,
|
||||
@ -333,6 +341,11 @@ impl Render for ContextMenu {
|
||||
.inset(true)
|
||||
.into_any_element()
|
||||
}
|
||||
ContextMenuItem::Label(label) => ListItem::new(ix)
|
||||
.inset(true)
|
||||
.disabled(true)
|
||||
.child(Label::new(label.clone()))
|
||||
.into_any_element(),
|
||||
ContextMenuItem::Entry {
|
||||
toggled,
|
||||
label,
|
||||
|
Loading…
Reference in New Issue
Block a user