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 crate::{Toast, Workspace};
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{
svg, AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, svg, AnyView, AppContext, AsyncWindowContext, ClipboardItem, DismissEvent, Entity, EntityId,
Global, PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task, View, ViewContext,
VisualContext, WindowContext,
}; };
use language::DiagnosticSeverity; use language::DiagnosticSeverity;
use std::{any::TypeId, ops::DerefMut}; use std::{any::TypeId, ops::DerefMut};
use ui::prelude::*; use ui::{prelude::*, Tooltip};
use util::ResultExt; use util::ResultExt;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
@ -100,22 +101,16 @@ impl Workspace {
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>, build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
) { ) {
let type_id = TypeId::of::<V>(); let type_id = TypeId::of::<V>();
if self self.dismiss_notification_internal(type_id, id, cx);
.notifications
.iter() let notification = build_notification(cx);
.all(|(existing_type_id, existing_id, _)| { cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
(*existing_type_id, *existing_id) != (type_id, id) this.dismiss_notification_internal(type_id, id, cx);
}) })
{ .detach();
let notification = build_notification(cx); self.notifications
cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| { .push((type_id, id, Box::new(notification)));
this.dismiss_notification_internal(type_id, id, cx); cx.notify();
})
.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>) pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
@ -174,12 +169,14 @@ impl Workspace {
pub struct LanguageServerPrompt { pub struct LanguageServerPrompt {
request: Option<project::LanguageServerPromptRequest>, request: Option<project::LanguageServerPromptRequest>,
scroll_handle: ScrollHandle,
} }
impl LanguageServerPrompt { impl LanguageServerPrompt {
pub fn new(request: project::LanguageServerPromptRequest) -> Self { pub fn new(request: project::LanguageServerPromptRequest) -> Self {
Self { Self {
request: Some(request), request: Some(request),
scroll_handle: ScrollHandle::new(),
} }
} }
@ -211,45 +208,88 @@ impl Render for LanguageServerPrompt {
h_flex() h_flex()
.id("language_server_prompt_notification") .id("language_server_prompt_notification")
.occlude()
.elevation_3(cx) .elevation_3(cx)
.items_start() .items_start()
.justify_between() .justify_between()
.p_2() .p_2()
.gap_2() .gap_2()
.w_full() .w_full()
.max_h(vh(0.8, cx))
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.group("")
.child( .child(
v_flex() v_flex()
.w_full()
.overflow_hidden() .overflow_hidden()
.child( .child(
h_flex() h_flex()
.children( .w_full()
match request.level { .justify_between()
PromptLevel::Info => None, .child(
PromptLevel::Warning => Some(DiagnosticSeverity::WARNING), h_flex()
PromptLevel::Critical => Some(DiagnosticSeverity::ERROR), .flex_grow()
} .children(
.map(|severity| { match request.level {
svg() PromptLevel::Info => None,
.size(cx.text_style().font_size) PromptLevel::Warning => {
.flex_none() Some(DiagnosticSeverity::WARNING)
.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))
} }
}) 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( .child(
Label::new(format!("{}:", request.lsp_name)) ui::IconButton::new("close", ui::IconName::Close)
.size(LabelSize::Default), .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)| { .children(request.actions.iter().enumerate().map(|(ix, action)| {
let this_handle = cx.view().clone(); let this_handle = cx.view().clone();
ui::Button::new(ix, action.title.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) => { project::Event::LanguageServerPrompt(request) => {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
request.message.as_str().hash(&mut hasher); request.lsp_name.as_str().hash(&mut hasher);
let id = hasher.finish(); let id = hasher.finish();
this.show_notification(id as usize, cx, |cx| { this.show_notification(id as usize, cx, |cx| {