Improve lsp notifications (#10220)

1. They now will not go off-screen
2. You can scroll long messages.
3. Only one notification per language server is shown at a time
4. The title/text are now distinguished visually
5. You can copy the error message to the clipboard

Fixes: #10217
Fixes: #10190
Fixes: #10090

Release Notes:

- Fixed language server notifications being too large
([#10090](https://github.com/zed-industries/zed/issues/10090)).
This commit is contained in:
Conrad Irwin 2024-04-06 10:17:18 -06:00 committed by GitHub
parent 3aa242e076
commit 0325bda89a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 47 deletions

View File

@ -1,13 +1,14 @@
use crate::{Toast, Workspace};
use collections::HashMap;
use gpui::{
svg, AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter,
Global, PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext,
svg, AnyView, AppContext, AsyncWindowContext, ClipboardItem, DismissEvent, Entity, EntityId,
EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task, View, ViewContext,
VisualContext, WindowContext,
};
use language::DiagnosticSeverity;
use std::{any::TypeId, ops::DerefMut};
use ui::prelude::*;
use ui::{prelude::*, Tooltip};
use util::ResultExt;
pub fn init(cx: &mut AppContext) {
@ -100,22 +101,16 @@ impl Workspace {
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
) {
let type_id = TypeId::of::<V>();
if self
.notifications
.iter()
.all(|(existing_type_id, existing_id, _)| {
(*existing_type_id, *existing_id) != (type_id, id)
})
{
let notification = build_notification(cx);
cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
this.dismiss_notification_internal(type_id, id, cx);
})
.detach();
self.notifications
.push((type_id, id, Box::new(notification)));
cx.notify();
}
self.dismiss_notification_internal(type_id, id, cx);
let notification = build_notification(cx);
cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
this.dismiss_notification_internal(type_id, id, cx);
})
.detach();
self.notifications
.push((type_id, id, Box::new(notification)));
cx.notify();
}
pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
@ -174,12 +169,14 @@ impl Workspace {
pub struct LanguageServerPrompt {
request: Option<project::LanguageServerPromptRequest>,
scroll_handle: ScrollHandle,
}
impl LanguageServerPrompt {
pub fn new(request: project::LanguageServerPromptRequest) -> Self {
Self {
request: Some(request),
scroll_handle: ScrollHandle::new(),
}
}
@ -211,45 +208,88 @@ impl Render for LanguageServerPrompt {
h_flex()
.id("language_server_prompt_notification")
.occlude()
.elevation_3(cx)
.items_start()
.justify_between()
.p_2()
.gap_2()
.w_full()
.max_h(vh(0.8, cx))
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.group("")
.child(
v_flex()
.w_full()
.overflow_hidden()
.child(
h_flex()
.children(
match request.level {
PromptLevel::Info => None,
PromptLevel::Warning => Some(DiagnosticSeverity::WARNING),
PromptLevel::Critical => Some(DiagnosticSeverity::ERROR),
}
.map(|severity| {
svg()
.size(cx.text_style().font_size)
.flex_none()
.mr_1()
.map(|icon| {
if severity == DiagnosticSeverity::ERROR {
icon.path(IconName::ExclamationTriangle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::ExclamationTriangle.path())
.text_color(Color::Warning.color(cx))
.w_full()
.justify_between()
.child(
h_flex()
.flex_grow()
.children(
match request.level {
PromptLevel::Info => None,
PromptLevel::Warning => {
Some(DiagnosticSeverity::WARNING)
}
})
}),
PromptLevel::Critical => {
Some(DiagnosticSeverity::ERROR)
}
}
.map(|severity| {
svg()
.size(cx.text_style().font_size)
.flex_none()
.mr_1()
.mt(px(-2.0))
.map(|icon| {
if severity == DiagnosticSeverity::ERROR {
icon.path(
IconName::ExclamationTriangle.path(),
)
.text_color(Color::Error.color(cx))
} else {
icon.path(
IconName::ExclamationTriangle.path(),
)
.text_color(Color::Warning.color(cx))
}
})
}),
)
.child(
Label::new(request.lsp_name.clone())
.size(LabelSize::Default),
),
)
.child(
Label::new(format!("{}:", request.lsp_name))
.size(LabelSize::Default),
ui::IconButton::new("close", ui::IconName::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
),
)
.child(Label::new(request.message.to_string()))
.child(
v_flex()
.child(
h_flex().absolute().right_0().rounded_md().child(
ui::IconButton::new("copy", ui::IconName::Copy)
.on_click({
let message = request.message.clone();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(
message.clone(),
))
}
})
.tooltip(|cx| Tooltip::text("Copy", cx))
.visible_on_hover(""),
),
)
.child(Label::new(request.message.to_string()).size(LabelSize::Small)),
)
.children(request.actions.iter().enumerate().map(|(ix, action)| {
let this_handle = cx.view().clone();
ui::Button::new(ix, action.title.clone())
@ -263,10 +303,6 @@ impl Render for LanguageServerPrompt {
})
})),
)
.child(
ui::IconButton::new("close", ui::IconName::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
)
}
}

View File

@ -638,7 +638,7 @@ impl Workspace {
project::Event::LanguageServerPrompt(request) => {
let mut hasher = DefaultHasher::new();
request.message.as_str().hash(&mut hasher);
request.lsp_name.as_str().hash(&mut hasher);
let id = hasher.finish();
this.show_notification(id as usize, cx, |cx| {