From 7f17d4b61d4078cb15a0def2c418f0485c9f29ed Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:38:56 -0300 Subject: [PATCH] Add Tailwind CSS and Ruff to built-in features list (#41285) Closes https://github.com/zed-industries/zed/issues/41168 This PR adds both Tailwind CSS and Ruff (linter for Python) as built-in features; a banner mentioning this should show up now for these two when searching for them in the extensions UI. There will also be a corresponding zed.dev site PR adding a "Ruff is built-in" card to the zed.dev/extensions page. Release Notes: - N/A --- crates/extensions_ui/src/components.rs | 2 - .../src/components/feature_upsell.rs | 77 ------ crates/extensions_ui/src/extensions_ui.rs | 246 +++++++++++++----- 3 files changed, 182 insertions(+), 143 deletions(-) delete mode 100644 crates/extensions_ui/src/components/feature_upsell.rs diff --git a/crates/extensions_ui/src/components.rs b/crates/extensions_ui/src/components.rs index 957980e49f8f4774ce7eb601503db79ce74baceb..bf11abd679c657c6533f5e9e075b1b69c01e8622 100644 --- a/crates/extensions_ui/src/components.rs +++ b/crates/extensions_ui/src/components.rs @@ -1,5 +1,3 @@ mod extension_card; -mod feature_upsell; pub use extension_card::*; -pub use feature_upsell::*; diff --git a/crates/extensions_ui/src/components/feature_upsell.rs b/crates/extensions_ui/src/components/feature_upsell.rs deleted file mode 100644 index 0515dd46d30ce9f7e87331f99542940c3efa837a..0000000000000000000000000000000000000000 --- a/crates/extensions_ui/src/components/feature_upsell.rs +++ /dev/null @@ -1,77 +0,0 @@ -use gpui::{AnyElement, Div, StyleRefinement}; -use smallvec::SmallVec; -use ui::prelude::*; - -#[derive(IntoElement)] -pub struct FeatureUpsell { - base: Div, - text: SharedString, - docs_url: Option, - children: SmallVec<[AnyElement; 2]>, -} - -impl FeatureUpsell { - pub fn new(text: impl Into) -> Self { - Self { - base: h_flex(), - text: text.into(), - docs_url: None, - children: SmallVec::new(), - } - } - - pub fn docs_url(mut self, docs_url: impl Into) -> Self { - self.docs_url = Some(docs_url.into()); - self - } -} - -impl ParentElement for FeatureUpsell { - fn extend(&mut self, elements: impl IntoIterator) { - self.children.extend(elements) - } -} - -// Style methods. -impl FeatureUpsell { - fn style(&mut self) -> &mut StyleRefinement { - self.base.style() - } - - gpui::border_style_methods!({ - visibility: pub - }); -} - -impl RenderOnce for FeatureUpsell { - fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - self.base - .py_2() - .px_4() - .justify_between() - .flex_wrap() - .border_color(cx.theme().colors().border_variant) - .child(Label::new(self.text)) - .child(h_flex().gap_2().children(self.children).when_some( - self.docs_url, - |el, docs_url| { - el.child( - Button::new("open_docs", "View Documentation") - .icon(IconName::ArrowUpRight) - .icon_size(IconSize::Small) - .icon_position(IconPosition::End) - .on_click({ - move |_event, _window, cx| { - telemetry::event!( - "Documentation Viewed", - source = "Feature Upsell", - url = docs_url, - ); - cx.open_url(&docs_url) - } - }), - ) - }, - )) - } -} diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 1fc1384a133946651f16b3b9bdba742c2882b9a8..cf59f7d200962b2e541c429c7918f622d6e06587 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -24,8 +24,8 @@ use settings::{Settings, SettingsContent}; use strum::IntoEnumIterator as _; use theme::ThemeSettings; use ui::{ - CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, ToggleButton, Tooltip, - WithScrollbar, prelude::*, + Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButton, + Tooltip, WithScrollbar, prelude::*, }; use vim_mode_setting::VimModeSetting; use workspace::{ @@ -34,7 +34,7 @@ use workspace::{ }; use zed_actions::ExtensionCategoryFilter; -use crate::components::{ExtensionCard, FeatureUpsell}; +use crate::components::ExtensionCard; use crate::extension_version_selector::{ ExtensionVersionSelector, ExtensionVersionSelectorDelegate, }; @@ -225,9 +225,9 @@ impl ExtensionFilter { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] enum Feature { + ExtensionRuff, + ExtensionTailwind, Git, - OpenIn, - Vim, LanguageBash, LanguageC, LanguageCpp, @@ -236,13 +236,28 @@ enum Feature { LanguageReact, LanguageRust, LanguageTypescript, + OpenIn, + Vim, } fn keywords_by_feature() -> &'static BTreeMap> { static KEYWORDS_BY_FEATURE: OnceLock>> = OnceLock::new(); KEYWORDS_BY_FEATURE.get_or_init(|| { BTreeMap::from_iter([ + (Feature::ExtensionRuff, vec!["ruff"]), + (Feature::ExtensionTailwind, vec!["tail", "tailwind"]), (Feature::Git, vec!["git"]), + (Feature::LanguageBash, vec!["sh", "bash"]), + (Feature::LanguageC, vec!["c", "clang"]), + (Feature::LanguageCpp, vec!["c++", "cpp", "clang"]), + (Feature::LanguageGo, vec!["go", "golang"]), + (Feature::LanguagePython, vec!["python", "py"]), + (Feature::LanguageReact, vec!["react"]), + (Feature::LanguageRust, vec!["rust", "rs"]), + ( + Feature::LanguageTypescript, + vec!["type", "typescript", "ts"], + ), ( Feature::OpenIn, vec![ @@ -257,17 +272,6 @@ fn keywords_by_feature() -> &'static BTreeMap> { ], ), (Feature::Vim, vec!["vim"]), - (Feature::LanguageBash, vec!["sh", "bash"]), - (Feature::LanguageC, vec!["c", "clang"]), - (Feature::LanguageCpp, vec!["c++", "cpp", "clang"]), - (Feature::LanguageGo, vec!["go", "golang"]), - (Feature::LanguagePython, vec!["python", "py"]), - (Feature::LanguageReact, vec!["react"]), - (Feature::LanguageRust, vec!["rust", "rs"]), - ( - Feature::LanguageTypescript, - vec!["type", "typescript", "ts"], - ), ]) }) } @@ -1336,58 +1340,172 @@ impl ExtensionsPage { } } - fn render_feature_upsells(&self, cx: &mut Context) -> impl IntoElement { - let upsells_count = self.upsells.len(); + fn render_feature_upsell_banner( + &self, + label: SharedString, + docs_url: SharedString, + vim: bool, + cx: &mut Context, + ) -> impl IntoElement { + let docs_url_button = Button::new("open_docs", "View Documentation") + .icon(IconName::ArrowUpRight) + .icon_size(IconSize::Small) + .icon_position(IconPosition::End) + .on_click({ + move |_event, _window, cx| { + telemetry::event!( + "Documentation Viewed", + source = "Feature Upsell", + url = docs_url, + ); + cx.open_url(&docs_url) + } + }); - v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| { - let upsell = match feature { - Feature::Git => FeatureUpsell::new( - "Zed comes with basic Git support. More Git features are coming in the future.", - ) - .docs_url("https://zed.dev/docs/git"), - Feature::OpenIn => FeatureUpsell::new( - "Zed supports linking to a source line on GitHub and others.", - ) - .docs_url("https://zed.dev/docs/git#git-integrations"), - Feature::Vim => FeatureUpsell::new("Vim support is built-in to Zed!") - .docs_url("https://zed.dev/docs/vim") - .child(CheckboxWithLabel::new( - "enable-vim", - Label::new("Enable vim mode"), - if VimModeSetting::get_global(cx).0 { - ui::ToggleState::Selected + div() + .pt_4() + .px_4() + .child( + Banner::new() + .severity(Severity::Success) + .child(Label::new(label).mt_0p5()) + .map(|this| { + if vim { + this.action_slot( + h_flex() + .gap_1() + .child(docs_url_button) + .child(Divider::vertical().color(ui::DividerColor::Border)) + .child( + h_flex() + .pl_1() + .gap_1() + .child(Label::new("Enable Vim mode")) + .child( + Switch::new( + "enable-vim", + if VimModeSetting::get_global(cx).0 { + ui::ToggleState::Selected + } else { + ui::ToggleState::Unselected + }, + ) + .on_click(cx.listener( + move |this, selection, _, cx| { + telemetry::event!( + "Vim Mode Toggled", + source = "Feature Upsell" + ); + this.update_settings( + selection, + cx, + |setting, value| { + setting.vim_mode = Some(value) + }, + ); + }, + )) + .color(ui::SwitchColor::Accent), + ), + ), + ) } else { - ui::ToggleState::Unselected - }, - cx.listener(move |this, selection, _, cx| { - telemetry::event!("Vim Mode Toggled", source = "Feature Upsell"); - this.update_settings(selection, cx, |setting, value| { - setting.vim_mode = Some(value) - }); - }), - )), - Feature::LanguageBash => FeatureUpsell::new("Shell support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/bash"), - Feature::LanguageC => FeatureUpsell::new("C support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/c"), - Feature::LanguageCpp => FeatureUpsell::new("C++ support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/cpp"), - Feature::LanguageGo => FeatureUpsell::new("Go support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/go"), - Feature::LanguagePython => FeatureUpsell::new("Python support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/python"), - Feature::LanguageReact => FeatureUpsell::new("React support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/typescript"), - Feature::LanguageRust => FeatureUpsell::new("Rust support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/rust"), - Feature::LanguageTypescript => { - FeatureUpsell::new("Typescript support is built-in to Zed!") - .docs_url("https://zed.dev/docs/languages/typescript") - } + this.action_slot(docs_url_button) + } + }), + ) + .into_any_element() + } + + fn render_feature_upsells(&self, cx: &mut Context) -> impl IntoElement { + let mut container = v_flex(); + + for feature in &self.upsells { + let banner = match feature { + Feature::ExtensionRuff => self.render_feature_upsell_banner( + "Ruff (linter for Python) support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/python#code-formatting--linting".into(), + false, + cx, + ), + Feature::ExtensionTailwind => self.render_feature_upsell_banner( + "Tailwind CSS support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/tailwindcss".into(), + false, + cx, + ), + Feature::Git => self.render_feature_upsell_banner( + "Zed comes with basic Git support—more features are coming in the future." + .into(), + "https://zed.dev/docs/git".into(), + false, + cx, + ), + Feature::LanguageBash => self.render_feature_upsell_banner( + "Shell support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/bash".into(), + false, + cx, + ), + Feature::LanguageC => self.render_feature_upsell_banner( + "C support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/c".into(), + false, + cx, + ), + Feature::LanguageCpp => self.render_feature_upsell_banner( + "C++ support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/cpp".into(), + false, + cx, + ), + Feature::LanguageGo => self.render_feature_upsell_banner( + "Go support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/go".into(), + false, + cx, + ), + Feature::LanguagePython => self.render_feature_upsell_banner( + "Python support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/python".into(), + false, + cx, + ), + Feature::LanguageReact => self.render_feature_upsell_banner( + "React support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/typescript".into(), + false, + cx, + ), + Feature::LanguageRust => self.render_feature_upsell_banner( + "Rust support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/rust".into(), + false, + cx, + ), + Feature::LanguageTypescript => self.render_feature_upsell_banner( + "Typescript support is built-in to Zed!".into(), + "https://zed.dev/docs/languages/typescript".into(), + false, + cx, + ), + Feature::OpenIn => self.render_feature_upsell_banner( + "Zed supports linking to a source line on GitHub and others.".into(), + "https://zed.dev/docs/git#git-integrations".into(), + false, + cx, + ), + Feature::Vim => self.render_feature_upsell_banner( + "Vim support is built-in to Zed!".into(), + "https://zed.dev/docs/vim".into(), + true, + cx, + ), }; + container = container.child(banner); + } - upsell.when(ix < upsells_count, |upsell| upsell.border_b_1()) - })) + container } }