mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Switch LSP prompts to use a non-blocking toast (#8312)
This fixes a major degradation in usability that some users ran into. Fixes https://github.com/zed-industries/zed/issues/8255 Fixes https://github.com/zed-industries/zed/issues/8229 Release Notes: - Switch from using platform prompts to toasts for LSP prompts. ([8255](https://github.com/zed-industries/zed/issues/8255), [8229](https://github.com/zed-industries/zed/issues/8229)) <img width="583" alt="Screenshot 2024-02-23 at 2 40 05 PM" src="https://github.com/zed-industries/zed/assets/2280405/1bfc027b-b7a8-4563-88b6-020e47869668"> Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
d993dd3b2c
commit
cab8b5a9a3
@ -399,6 +399,8 @@ fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
|
|||||||
("72", quote! { rems(18.) }, "288px (18rem)"),
|
("72", quote! { rems(18.) }, "288px (18rem)"),
|
||||||
("80", quote! { rems(20.) }, "320px (20rem)"),
|
("80", quote! { rems(20.) }, "320px (20rem)"),
|
||||||
("96", quote! { rems(24.) }, "384px (24rem)"),
|
("96", quote! { rems(24.) }, "384px (24rem)"),
|
||||||
|
("112", quote! { rems(28.) }, "448px (28rem)"),
|
||||||
|
("128", quote! { rems(32.) }, "512px (32rem)"),
|
||||||
("auto", quote! { auto() }, "Auto"),
|
("auto", quote! { auto() }, "Auto"),
|
||||||
("px", quote! { px(1.) }, "1px"),
|
("px", quote! { px(1.) }, "1px"),
|
||||||
("full", quote! { relative(1.) }, "100%"),
|
("full", quote! { relative(1.) }, "100%"),
|
||||||
|
@ -229,6 +229,7 @@ pub struct LanguageServerPromptRequest {
|
|||||||
pub level: PromptLevel,
|
pub level: PromptLevel,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub actions: Vec<MessageActionItem>,
|
pub actions: Vec<MessageActionItem>,
|
||||||
|
pub lsp_name: String,
|
||||||
response_channel: Sender<MessageActionItem>,
|
response_channel: Sender<MessageActionItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3022,6 +3023,7 @@ impl Project {
|
|||||||
cx.update(|cx| adapter.workspace_configuration(worktree_path, cx))?;
|
cx.update(|cx| adapter.workspace_configuration(worktree_path, cx))?;
|
||||||
let language_server = pending_server.task.await?;
|
let language_server = pending_server.task.await?;
|
||||||
|
|
||||||
|
let name = language_server.name();
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
@ -3160,8 +3162,10 @@ impl Project {
|
|||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
let name = name.to_string();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
let name = name.to_string();
|
||||||
async move {
|
async move {
|
||||||
if let Some(actions) = params.actions {
|
if let Some(actions) = params.actions {
|
||||||
let (tx, mut rx) = smol::channel::bounded(1);
|
let (tx, mut rx) = smol::channel::bounded(1);
|
||||||
@ -3174,6 +3178,7 @@ impl Project {
|
|||||||
message: params.message,
|
message: params.message,
|
||||||
actions,
|
actions,
|
||||||
response_channel: tx,
|
response_channel: tx,
|
||||||
|
lsp_name: name.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(_) = this.update(&mut cx, |_, cx| {
|
if let Ok(_) = this.update(&mut cx, |_, cx| {
|
||||||
@ -3211,6 +3216,7 @@ impl Project {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let mut initialization_options = adapter.adapter.initialization_options();
|
let mut initialization_options = adapter.adapter.initialization_options();
|
||||||
match (&mut initialization_options, override_options) {
|
match (&mut initialization_options, override_options) {
|
||||||
(Some(initialization_options), Some(override_options)) => {
|
(Some(initialization_options), Some(override_options)) => {
|
||||||
|
@ -7,6 +7,7 @@ use crate::prelude::*;
|
|||||||
pub enum LabelSize {
|
pub enum LabelSize {
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
Large,
|
||||||
Small,
|
Small,
|
||||||
XSmall,
|
XSmall,
|
||||||
}
|
}
|
||||||
@ -97,6 +98,7 @@ impl RenderOnce for LabelLike {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(|this| match self.size {
|
.map(|this| match self.size {
|
||||||
|
LabelSize::Large => this.text_ui_lg(),
|
||||||
LabelSize::Default => this.text_ui(),
|
LabelSize::Default => this.text_ui(),
|
||||||
LabelSize::Small => this.text_ui_sm(),
|
LabelSize::Small => this.text_ui_sm(),
|
||||||
LabelSize::XSmall => this.text_ui_xs(),
|
LabelSize::XSmall => this.text_ui_xs(),
|
||||||
|
@ -35,6 +35,17 @@ pub trait StyledExt: Styled + Sized {
|
|||||||
self.text_size(size.rems())
|
self.text_size(size.rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The large size for UI text.
|
||||||
|
///
|
||||||
|
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
|
||||||
|
///
|
||||||
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
|
///
|
||||||
|
/// Use `text_ui` for regular-sized text.
|
||||||
|
fn text_ui_lg(self) -> Self {
|
||||||
|
self.text_size(UiTextSize::Large.rems())
|
||||||
|
}
|
||||||
|
|
||||||
/// The default size for UI text.
|
/// The default size for UI text.
|
||||||
///
|
///
|
||||||
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
|
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
|
||||||
|
@ -13,6 +13,13 @@ pub enum UiTextSize {
|
|||||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
/// The large size for UI text.
|
||||||
|
///
|
||||||
|
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
|
||||||
|
///
|
||||||
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
|
Large,
|
||||||
|
|
||||||
/// The small size for UI text.
|
/// The small size for UI text.
|
||||||
///
|
///
|
||||||
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
|
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
|
||||||
@ -31,6 +38,7 @@ pub enum UiTextSize {
|
|||||||
impl UiTextSize {
|
impl UiTextSize {
|
||||||
pub fn rems(self) -> Rems {
|
pub fn rems(self) -> Rems {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Large => rems(16. / 16.),
|
||||||
Self::Default => rems(14. / 16.),
|
Self::Default => rems(14. / 16.),
|
||||||
Self::Small => rems(12. / 16.),
|
Self::Small => rems(12. / 16.),
|
||||||
Self::XSmall => rems(10. / 16.),
|
Self::XSmall => rems(10. / 16.),
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
use crate::{Toast, Workspace};
|
use crate::{Toast, Workspace};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Global,
|
svg, AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter,
|
||||||
PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext,
|
Global, PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
use language::DiagnosticSeverity;
|
||||||
|
|
||||||
use std::{any::TypeId, ops::DerefMut};
|
use std::{any::TypeId, ops::DerefMut};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(NotificationTracker::new());
|
cx.set_global(NotificationTracker::new());
|
||||||
@ -168,6 +172,105 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LanguageServerPrompt {
|
||||||
|
request: Option<project::LanguageServerPromptRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerPrompt {
|
||||||
|
pub fn new(request: project::LanguageServerPromptRequest) -> Self {
|
||||||
|
Self {
|
||||||
|
request: Some(request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn select_option(this: View<Self>, ix: usize, mut cx: AsyncWindowContext) {
|
||||||
|
util::async_maybe!({
|
||||||
|
let potential_future = this.update(&mut cx, |this, _| {
|
||||||
|
this.request.take().map(|request| request.respond(ix))
|
||||||
|
});
|
||||||
|
|
||||||
|
potential_future? // App Closed
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Response already sent"))?
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Stream already closed"))?;
|
||||||
|
|
||||||
|
this.update(&mut cx, |_, cx| cx.emit(DismissEvent))?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for LanguageServerPrompt {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let Some(request) = &self.request else {
|
||||||
|
return div().id("language_server_prompt_notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id("language_server_prompt_notification")
|
||||||
|
.elevation_3(cx)
|
||||||
|
.items_start()
|
||||||
|
.p_2()
|
||||||
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(format!("{}:", request.lsp_name))
|
||||||
|
.size(LabelSize::Default),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(Label::new(request.message.to_string()))
|
||||||
|
.children(request.actions.iter().enumerate().map(|(ix, action)| {
|
||||||
|
let this_handle = cx.view().clone();
|
||||||
|
ui::Button::new(ix, action.title.clone())
|
||||||
|
.size(ButtonSize::Large)
|
||||||
|
.on_click(move |_, cx| {
|
||||||
|
let this_handle = this_handle.clone();
|
||||||
|
cx.spawn(|cx| async move {
|
||||||
|
LanguageServerPrompt::select_option(this_handle, ix, cx).await
|
||||||
|
})
|
||||||
|
.detach()
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
ui::IconButton::new("close", ui::IconName::Close)
|
||||||
|
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
|
||||||
|
|
||||||
pub mod simple_message_notification {
|
pub mod simple_message_notification {
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
||||||
|
@ -59,7 +59,10 @@ use std::{
|
|||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp, env,
|
cmp,
|
||||||
|
collections::hash_map::DefaultHasher,
|
||||||
|
env,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{atomic::AtomicUsize, Arc, Weak},
|
sync::{atomic::AtomicUsize, Arc, Weak},
|
||||||
@ -579,24 +582,13 @@ impl Workspace {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
project::Event::LanguageServerPrompt(request) => {
|
project::Event::LanguageServerPrompt(request) => {
|
||||||
let request = request.clone();
|
let mut hasher = DefaultHasher::new();
|
||||||
|
request.message.as_str().hash(&mut hasher);
|
||||||
|
let id = hasher.finish();
|
||||||
|
|
||||||
cx.spawn(|_, mut cx| async move {
|
this.show_notification(id as usize, cx, |cx| {
|
||||||
let messages = request
|
cx.new_view(|_| notifications::LanguageServerPrompt::new(request.clone()))
|
||||||
.actions
|
});
|
||||||
.iter()
|
|
||||||
.map(|action| action.title.as_str())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let index = cx
|
|
||||||
.update(|cx| {
|
|
||||||
cx.prompt(request.level, "", Some(&request.message), &messages)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
request.respond(index).await;
|
|
||||||
|
|
||||||
Result::<(), anyhow::Error>::Ok(())
|
|
||||||
})
|
|
||||||
.detach()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -2766,7 +2758,7 @@ impl Workspace {
|
|||||||
.z_index(100)
|
.z_index(100)
|
||||||
.right_3()
|
.right_3()
|
||||||
.bottom_3()
|
.bottom_3()
|
||||||
.w_96()
|
.w_112()
|
||||||
.h_full()
|
.h_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
Loading…
Reference in New Issue
Block a user