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,13 +101,8 @@ 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()
.all(|(existing_type_id, existing_id, _)| {
(*existing_type_id, *existing_id) != (type_id, id)
})
{
let notification = build_notification(cx); let notification = build_notification(cx);
cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| { cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
this.dismiss_notification_internal(type_id, id, cx); this.dismiss_notification_internal(type_id, id, cx);
@ -116,7 +112,6 @@ impl Workspace {
.push((type_id, id, Box::new(notification))); .push((type_id, id, Box::new(notification)));
cx.notify(); 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>)
where where
@ -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()
.w_full()
.justify_between()
.child(
h_flex()
.flex_grow()
.children( .children(
match request.level { match request.level {
PromptLevel::Info => None, PromptLevel::Info => None,
PromptLevel::Warning => Some(DiagnosticSeverity::WARNING), PromptLevel::Warning => {
PromptLevel::Critical => Some(DiagnosticSeverity::ERROR), Some(DiagnosticSeverity::WARNING)
}
PromptLevel::Critical => {
Some(DiagnosticSeverity::ERROR)
}
} }
.map(|severity| { .map(|severity| {
svg() svg()
.size(cx.text_style().font_size) .size(cx.text_style().font_size)
.flex_none() .flex_none()
.mr_1() .mr_1()
.mt(px(-2.0))
.map(|icon| { .map(|icon| {
if severity == DiagnosticSeverity::ERROR { if severity == DiagnosticSeverity::ERROR {
icon.path(IconName::ExclamationTriangle.path()) icon.path(
IconName::ExclamationTriangle.path(),
)
.text_color(Color::Error.color(cx)) .text_color(Color::Error.color(cx))
} else { } else {
icon.path(IconName::ExclamationTriangle.path()) icon.path(
IconName::ExclamationTriangle.path(),
)
.text_color(Color::Warning.color(cx)) .text_color(Color::Warning.color(cx))
} }
}) })
}), }),
) )
.child( .child(
Label::new(format!("{}:", request.lsp_name)) Label::new(request.lsp_name.clone())
.size(LabelSize::Default), .size(LabelSize::Default),
), ),
) )
.child(Label::new(request.message.to_string())) .child(
ui::IconButton::new("close", ui::IconName::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
),
)
.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| {