Support built-in Zed prompts for all platforms (#26201)
This pull request does two things: 1. Adds a setting to force Zed to use the built-in prompts, instead of the system provided ones. I've personally found the system prompts on macOS often fail to respond to keyboard input, are slow to render initially, and don't match Zed's style. 2. Makes the previously Linux-only Zed provided prompts available to everybody using the above setting. Release Notes: - Added support for a built-in prompting system, regardless of platform. Use the new `use_system_prompts` setting to control whether to use the system provided prompts or Zed's built-in system. Note that on Linux, this setting has no effect, as Linux doesn't have a system prompting mechanism.
This commit is contained in:
parent
382f9f6151
commit
0f5a3afe94
13 changed files with 99 additions and 21 deletions
24
crates/ui_prompt/Cargo.toml
Normal file
24
crates/ui_prompt/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "ui_prompt"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/ui_prompt.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
1
crates/ui_prompt/LICENSE-GPL
Symbolic link
1
crates/ui_prompt/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
202
crates/ui_prompt/src/ui_prompt.rs
Normal file
202
crates/ui_prompt/src/ui_prompt.rs
Normal file
|
@ -0,0 +1,202 @@
|
|||
use gpui::{
|
||||
div, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
||||
InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse,
|
||||
Refineable, Render, RenderablePromptHandle, SharedString, Styled, TextStyleRefinement, Window,
|
||||
};
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
h_flex, v_flex, ActiveTheme, ButtonCommon, ButtonStyle, Clickable, ElevationIndex,
|
||||
FluentBuilder, LabelSize, StyledExt, TintColor,
|
||||
};
|
||||
use workspace::WorkspaceSettings;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
process_settings(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(process_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn process_settings(cx: &mut App) {
|
||||
let settings = WorkspaceSettings::get_global(cx);
|
||||
if settings.use_system_prompts && cfg!(not(any(target_os = "linux", target_os = "freebsd"))) {
|
||||
cx.reset_prompt_builder();
|
||||
} else {
|
||||
cx.set_prompt_builder(zed_prompt_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this function in conjunction with [App::set_prompt_builder] to force
|
||||
/// GPUI to use the internal prompt system.
|
||||
fn zed_prompt_renderer(
|
||||
level: PromptLevel,
|
||||
message: &str,
|
||||
detail: Option<&str>,
|
||||
actions: &[&str],
|
||||
handle: PromptHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> RenderablePromptHandle {
|
||||
let renderer = cx.new({
|
||||
|cx| ZedPromptRenderer {
|
||||
_level: level,
|
||||
message: message.to_string(),
|
||||
actions: actions.iter().map(ToString::to_string).collect(),
|
||||
focus: cx.focus_handle(),
|
||||
active_action_id: 0,
|
||||
detail: detail.filter(|text| !text.is_empty()).map(|text| {
|
||||
cx.new(|cx| {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let mut base_text_style = window.text_style();
|
||||
base_text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(settings.ui_font.family.clone()),
|
||||
font_size: Some(settings.ui_font_size(cx).into()),
|
||||
color: Some(ui::Color::Muted.color(cx)),
|
||||
..Default::default()
|
||||
});
|
||||
let markdown_style = MarkdownStyle {
|
||||
base_text_style,
|
||||
selection_background_color: { cx.theme().players().local().selection },
|
||||
..Default::default()
|
||||
};
|
||||
Markdown::new(SharedString::new(text), markdown_style, None, None, cx)
|
||||
})
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
handle.with_view(renderer, window, cx)
|
||||
}
|
||||
|
||||
pub struct ZedPromptRenderer {
|
||||
_level: PromptLevel,
|
||||
message: String,
|
||||
actions: Vec<String>,
|
||||
focus: FocusHandle,
|
||||
active_action_id: usize,
|
||||
detail: Option<Entity<Markdown>>,
|
||||
}
|
||||
|
||||
impl ZedPromptRenderer {
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(PromptResponse(self.active_action_id));
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(ix) = self.actions.iter().position(|a| a == "Cancel") {
|
||||
cx.emit(PromptResponse(ix));
|
||||
}
|
||||
}
|
||||
|
||||
fn select_first(
|
||||
&mut self,
|
||||
_: &menu::SelectFirst,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.active_action_id = self.actions.len().saturating_sub(1);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_action_id = 0;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.active_action_id > 0 {
|
||||
self.active_action_id -= 1;
|
||||
} else {
|
||||
self.active_action_id = self.actions.len().saturating_sub(1);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_previous(
|
||||
&mut self,
|
||||
_: &menu::SelectPrevious,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.active_action_id = (self.active_action_id + 1) % self.actions.len();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ZedPromptRenderer {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let font_family = settings.ui_font.family.clone();
|
||||
let prompt = v_flex()
|
||||
.key_context("Prompt")
|
||||
.cursor_default()
|
||||
.track_focus(&self.focus)
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_previous))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.elevation_3(cx)
|
||||
.w_72()
|
||||
.overflow_hidden()
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.font_family(font_family)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.child(self.message.clone())
|
||||
.text_color(ui::Color::Default.color(cx)),
|
||||
)
|
||||
.children(
|
||||
self.detail
|
||||
.clone()
|
||||
.map(|detail| div().w_full().text_xs().child(detail)),
|
||||
)
|
||||
.child(h_flex().justify_end().gap_2().children(
|
||||
self.actions.iter().enumerate().rev().map(|(ix, action)| {
|
||||
ui::Button::new(ix, action.clone())
|
||||
.label_size(LabelSize::Large)
|
||||
.style(ButtonStyle::Filled)
|
||||
.when(ix == self.active_action_id, |el| {
|
||||
el.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
})
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(cx.listener(move |_, _, _window, cx| {
|
||||
cx.emit(PromptResponse(ix));
|
||||
}))
|
||||
}),
|
||||
));
|
||||
|
||||
div().size_full().occlude().child(
|
||||
div()
|
||||
.size_full()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_around()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(prompt),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PromptResponse> for ZedPromptRenderer {}
|
||||
|
||||
impl Focusable for ZedPromptRenderer {
|
||||
fn focus_handle(&self, _: &crate::App) -> FocusHandle {
|
||||
self.focus.clone()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue