diff --git a/Cargo.lock b/Cargo.lock index 60f20b957a..97b4dcebb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13338,6 +13338,7 @@ dependencies = [ "theme_selector", "tree-sitter-markdown", "tree-sitter-rust", + "ui", "urlencoding", "util", "uuid", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 05390aa950..26c4145388 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -94,6 +94,7 @@ terminal_view.workspace = true theme.workspace = true theme_selector.workspace = true urlencoding = "2.1.2" +ui.workspace = true util.workspace = true uuid.workspace = true vim.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a70104036d..868508fb78 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -168,6 +168,9 @@ fn init_ui(app_state: Arc, cx: &mut AppContext) -> Result<()> { SystemAppearance::init(cx); load_embedded_fonts(cx); + #[cfg(target_os = "linux")] + crate::zed::linux_prompts::init(cx); + theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); app_state.languages.set_theme(cx.theme().clone()); command_palette::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 970b6d7180..a127a2f984 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,5 +1,7 @@ mod app_menus; pub mod inline_completion_registry; +#[cfg(target_os = "linux")] +pub(crate) mod linux_prompts; #[cfg(not(target_os = "linux"))] pub(crate) mod only_instance; mod open_listener; @@ -262,9 +264,20 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { .register_action(move |_, _: &ResetBufferFontSize, cx| theme::reset_font_size(cx)) .register_action(|_, _: &install_cli::Install, cx| { cx.spawn(|workspace, mut cx| async move { + if cfg!(target_os = "linux") { + let prompt = cx.prompt( + PromptLevel::Warning, + "Could not install the CLI", + Some("If you installed Zed from our official release add ~/.local/bin to your PATH.\n\nIf you installed Zed from a different source you may need to create an alias/symlink manually."), + &["Ok"], + ); + cx.background_executor().spawn(prompt).detach(); + return Ok(()); + } let path = install_cli::install_cli(cx.deref()) .await .context("error creating CLI symlink")?; + workspace.update(&mut cx, |workspace, cx| { struct InstalledZedCli; diff --git a/crates/zed/src/zed/linux_prompts.rs b/crates/zed/src/zed/linux_prompts.rs new file mode 100644 index 0000000000..ee267e7756 --- /dev/null +++ b/crates/zed/src/zed/linux_prompts.rs @@ -0,0 +1,120 @@ +use gpui::{ + div, opaque_grey, AppContext, EventEmitter, FocusHandle, FocusableView, FontWeight, + InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse, + Render, RenderablePromptHandle, Styled, ViewContext, VisualContext, WindowContext, +}; +use ui::{h_flex, v_flex, ButtonCommon, ButtonStyle, Clickable, ElevationIndex, LabelSize}; +use workspace::ui::StyledExt; + +pub fn init(cx: &mut AppContext) { + cx.set_prompt_builder(fallback_prompt_renderer) +} +/// Use this function in conjunction with [AppContext::set_prompt_renderer] to force +/// GPUI to always use the fallback prompt renderer. +pub fn fallback_prompt_renderer( + level: PromptLevel, + message: &str, + detail: Option<&str>, + actions: &[&str], + handle: PromptHandle, + cx: &mut WindowContext, +) -> RenderablePromptHandle { + let renderer = cx.new_view({ + |cx| FallbackPromptRenderer { + _level: level, + message: message.to_string(), + detail: detail.map(ToString::to_string), + actions: actions.iter().map(ToString::to_string).collect(), + focus: cx.focus_handle(), + } + }); + + handle.with_view(renderer, cx) +} + +/// The default GPUI fallback for rendering prompts, when the platform doesn't support it. +pub struct FallbackPromptRenderer { + _level: PromptLevel, + message: String, + detail: Option, + actions: Vec, + focus: FocusHandle, +} + +impl Render for FallbackPromptRenderer { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let prompt = + v_flex() + .cursor_default() + .track_focus(&self.focus) + .elevation_3(cx) + .w_72() + .overflow_hidden() + .p_4() + .gap_4() + .font_family("Zed Sans") + .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() + .text_color(ui::Color::Muted.color(cx)) + .child(detail) + })) + .child(h_flex().justify_end().gap_2().children( + self.actions.iter().enumerate().map(|(ix, action)| { + ui::Button::new(ix, action.clone()) + .label_size(LabelSize::Large) + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) + .on_click(cx.listener(move |_, _, cx| { + cx.emit(PromptResponse(ix)); + })) + }), + )); + + div() + .size_full() + .occlude() + .child( + div() + .size_full() + .bg(opaque_grey(0.5, 0.6)) + .absolute() + .top_0() + .left_0(), + ) + .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 for FallbackPromptRenderer {} + +impl FocusableView for FallbackPromptRenderer { + fn focus_handle(&self, _: &crate::AppContext) -> FocusHandle { + self.focus.clone() + } +}