Cargo.lock 🔗
@@ -20101,6 +20101,7 @@ dependencies = [
"itertools 0.14.0",
"language",
"log",
+ "markdown",
"menu",
"node_runtime",
"parking_lot",
Mayank Verma and Danilo Leal created
Closes #43657
Release Notes:
- Improved LSP notification messages by adding markdown rendering with
clickable URLs, inline code, etc.
<table>
<tr>
<td>Before</td>
<td>After</td>
</tr>
<tr>
<td><img width="408" height="153" alt="screenshot-notification-before"
src="https://github.com/user-attachments/assets/53b026de-335f-4c39-937f-590c3b7ea571"
/></td>
<td><img width="408" height="153" alt="screenshot-notification-after"
src="https://github.com/user-attachments/assets/9d6a7baa-8304-4a52-a5d0-0bacf7ea69f9"
/></td>
</tr>
</table>
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Cargo.lock | 1
crates/workspace/Cargo.toml | 1
crates/workspace/src/notifications.rs | 77 ++++++++++++++++++++++++----
3 files changed, 67 insertions(+), 12 deletions(-)
@@ -20101,6 +20101,7 @@ dependencies = [
"itertools 0.14.0",
"language",
"log",
+ "markdown",
"menu",
"node_runtime",
"parking_lot",
@@ -45,6 +45,7 @@ itertools.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
+markdown.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
postage.workspace = true
@@ -3,9 +3,12 @@ use anyhow::Context as _;
use gpui::{
AnyView, App, AppContext as _, AsyncWindowContext, ClickEvent, ClipboardItem, Context,
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle,
- Task, svg,
+ Task, TextStyleRefinement, UnderlineStyle, svg,
};
+use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use parking_lot::Mutex;
+use settings::Settings;
+use theme::ThemeSettings;
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
@@ -216,6 +219,7 @@ pub struct LanguageServerPrompt {
focus_handle: FocusHandle,
request: Option<project::LanguageServerPromptRequest>,
scroll_handle: ScrollHandle,
+ markdown: Entity<Markdown>,
}
impl Focusable for LanguageServerPrompt {
@@ -228,10 +232,13 @@ impl Notification for LanguageServerPrompt {}
impl LanguageServerPrompt {
pub fn new(request: project::LanguageServerPromptRequest, cx: &mut App) -> Self {
+ let markdown = cx.new(|cx| Markdown::new(request.message.clone().into(), None, None, cx));
+
Self {
focus_handle: cx.focus_handle(),
request: Some(request),
scroll_handle: ScrollHandle::new(),
+ markdown,
}
}
@@ -262,7 +269,7 @@ impl Render for LanguageServerPrompt {
};
let (icon, color) = match request.level {
- PromptLevel::Info => (IconName::Info, Color::Accent),
+ PromptLevel::Info => (IconName::Info, Color::Muted),
PromptLevel::Warning => (IconName::Warning, Color::Warning),
PromptLevel::Critical => (IconName::XCircle, Color::Error),
};
@@ -291,16 +298,15 @@ impl Render for LanguageServerPrompt {
.child(
h_flex()
.justify_between()
- .items_start()
.child(
h_flex()
.gap_2()
- .child(Icon::new(icon).color(color))
+ .child(Icon::new(icon).color(color).size(IconSize::Small))
.child(Label::new(request.lsp_name.clone())),
)
.child(
h_flex()
- .gap_2()
+ .gap_1()
.child(
IconButton::new("copy", IconName::Copy)
.on_click({
@@ -317,15 +323,17 @@ impl Render for LanguageServerPrompt {
IconButton::new(close_id, close_icon)
.tooltip(move |_window, cx| {
if suppress {
- Tooltip::for_action(
- "Suppress.\nClose with click.",
- &SuppressNotification,
+ Tooltip::with_meta(
+ "Suppress",
+ Some(&SuppressNotification),
+ "Click to close",
cx,
)
} else {
- Tooltip::for_action(
- "Close.\nSuppress with shift-click.",
- &menu::Cancel,
+ Tooltip::with_meta(
+ "Close",
+ Some(&menu::Cancel),
+ "Suppress with shift-click",
cx,
)
}
@@ -342,7 +350,16 @@ impl Render for LanguageServerPrompt {
),
),
)
- .child(Label::new(request.message.to_string()).size(LabelSize::Small))
+ .child(
+ MarkdownElement::new(self.markdown.clone(), markdown_style(window, cx))
+ .text_size(TextSize::Small.rems(cx))
+ .code_block_renderer(markdown::CodeBlockRenderer::Default {
+ copy_button: false,
+ copy_button_on_hover: false,
+ border: false,
+ })
+ .on_url_click(|link, _, cx| cx.open_url(&link)),
+ )
.children(request.actions.iter().enumerate().map(|(ix, action)| {
let this_handle = cx.entity();
Button::new(ix, action.title.clone())
@@ -369,6 +386,42 @@ fn workspace_error_notification_id() -> NotificationId {
NotificationId::unique::<WorkspaceErrorNotification>()
}
+fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
+ let settings = ThemeSettings::get_global(cx);
+ let ui_font_family = settings.ui_font.family.clone();
+ let ui_font_fallbacks = settings.ui_font.fallbacks.clone();
+ let buffer_font_family = settings.buffer_font.family.clone();
+ let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone();
+
+ let mut base_text_style = window.text_style();
+ base_text_style.refine(&TextStyleRefinement {
+ font_family: Some(ui_font_family),
+ font_fallbacks: ui_font_fallbacks,
+ color: Some(cx.theme().colors().text),
+ ..Default::default()
+ });
+
+ MarkdownStyle {
+ base_text_style,
+ selection_background_color: cx.theme().colors().element_selection_background,
+ inline_code: TextStyleRefinement {
+ background_color: Some(cx.theme().colors().editor_background.opacity(0.5)),
+ font_family: Some(buffer_font_family),
+ font_fallbacks: buffer_font_fallbacks,
+ ..Default::default()
+ },
+ link: TextStyleRefinement {
+ underline: Some(UnderlineStyle {
+ thickness: px(1.),
+ color: Some(cx.theme().colors().text_accent),
+ wavy: false,
+ }),
+ ..Default::default()
+ },
+ ..Default::default()
+ }
+}
+
#[derive(Debug, Clone)]
pub struct ErrorMessagePrompt {
message: SharedString,