ZIm/crates/workspace/src/lsp_status.rs
Nathan Sobo 893f15ddab Switch MouseEventHandler to use MouseRegions
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
2022-05-26 20:00:01 -06:00

222 lines
7.7 KiB
Rust

use crate::{ItemHandle, StatusItemView};
use futures::StreamExt;
use gpui::{actions, AppContext, EventContext};
use gpui::{
elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext,
View, ViewContext,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project};
use settings::Settings;
use smallvec::SmallVec;
use std::cmp::Reverse;
use std::fmt::Write;
use std::sync::Arc;
actions!(lsp_status, [DismissErrorMessage]);
pub struct LspStatus {
checking_for_update: Vec<String>,
downloading: Vec<String>,
failed: Vec<String>,
project: ModelHandle<Project>,
}
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(LspStatus::dismiss_error_message);
}
impl LspStatus {
pub fn new(
project: &ModelHandle<Project>,
languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
) -> Self {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn_weak(|this, mut cx| async move {
while let Some((language, event)) = status_events.next().await {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
for vector in [
&mut this.checking_for_update,
&mut this.downloading,
&mut this.failed,
] {
vector.retain(|name| name != language.name().as_ref());
}
match event {
LanguageServerBinaryStatus::CheckingForUpdate => {
this.checking_for_update.push(language.name().to_string());
}
LanguageServerBinaryStatus::Downloading => {
this.downloading.push(language.name().to_string());
}
LanguageServerBinaryStatus::Failed => {
this.failed.push(language.name().to_string());
}
LanguageServerBinaryStatus::Downloaded
| LanguageServerBinaryStatus::Cached => {}
}
cx.notify();
});
} else {
break;
}
}
})
.detach();
cx.observe(project, |_, _, cx| cx.notify()).detach();
Self {
checking_for_update: Default::default(),
downloading: Default::default(),
failed: Default::default(),
project: project.clone(),
}
}
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
self.failed.clear();
cx.notify();
}
fn pending_language_server_work<'a>(
&self,
cx: &'a AppContext,
) -> impl Iterator<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
self.project
.read(cx)
.language_server_statuses()
.rev()
.filter_map(|status| {
if status.pending_work.is_empty() {
None
} else {
let mut pending_work = status
.pending_work
.iter()
.map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
.collect::<SmallVec<[_; 4]>>();
pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
Some(pending_work)
}
})
.flatten()
}
}
impl Entity for LspStatus {
type Event = ();
}
impl View for LspStatus {
fn ui_name() -> &'static str {
"LspStatus"
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let mut message;
let mut icon = None;
let mut handler = None;
let mut pending_work = self.pending_language_server_work(cx);
if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
message = lang_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);
}
if let Some(percentage) = progress.percentage {
write!(&mut message, " ({}%)", percentage).unwrap();
}
let additional_work_count = pending_work.count();
if additional_work_count > 0 {
write!(&mut message, " + {} more", additional_work_count).unwrap();
}
} else {
drop(pending_work);
if !self.downloading.is_empty() {
icon = Some("icons/download-solid-14.svg");
message = format!(
"Downloading {} language server{}...",
self.downloading.join(", "),
if self.downloading.len() > 1 { "s" } else { "" }
);
} else if !self.checking_for_update.is_empty() {
icon = Some("icons/download-solid-14.svg");
message = format!(
"Checking for updates to {} language server{}...",
self.checking_for_update.join(", "),
if self.checking_for_update.len() > 1 {
"s"
} else {
""
}
);
} else if !self.failed.is_empty() {
icon = Some("icons/warning-solid-14.svg");
message = format!(
"Failed to download {} language server{}. Click to dismiss.",
self.failed.join(", "),
if self.failed.len() > 1 { "s" } else { "" }
);
handler =
Some(|_, _, cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage));
} else {
return Empty::new().boxed();
}
}
let mut element = MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
let theme = &cx
.global::<Settings>()
.theme
.workspace
.status_bar
.lsp_status;
let style = if state.hovered && handler.is_some() {
theme.hover.as_ref().unwrap_or(&theme.default)
} else {
&theme.default
};
Flex::row()
.with_children(icon.map(|path| {
Svg::new(path)
.with_color(style.icon_color)
.constrained()
.with_width(style.icon_width)
.contained()
.with_margin_right(style.icon_spacing)
.aligned()
.named("warning-icon")
}))
.with_child(Label::new(message, style.message.clone()).aligned().boxed())
.constrained()
.with_height(style.height)
.contained()
.with_style(style.container)
.aligned()
.boxed()
});
if let Some(handler) = handler {
element = element
.with_cursor_style(CursorStyle::PointingHand)
.on_click(handler);
}
element.boxed()
}
}
impl StatusItemView for LspStatus {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
}