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
This commit is contained in:
Danilo Leal 2025-10-27 12:38:56 -03:00 committed by GitHub
parent ebf4a23b18
commit 7f17d4b61d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 184 additions and 145 deletions

View file

@ -1,5 +1,3 @@
mod extension_card;
mod feature_upsell;
pub use extension_card::*;
pub use feature_upsell::*;

View file

@ -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<SharedString>,
children: SmallVec<[AnyElement; 2]>,
}
impl FeatureUpsell {
pub fn new(text: impl Into<SharedString>) -> Self {
Self {
base: h_flex(),
text: text.into(),
docs_url: None,
children: SmallVec::new(),
}
}
pub fn docs_url(mut self, docs_url: impl Into<SharedString>) -> Self {
self.docs_url = Some(docs_url.into());
self
}
}
impl ParentElement for FeatureUpsell {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
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)
}
}),
)
},
))
}
}

View file

@ -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<Feature, Vec<&'static str>> {
static KEYWORDS_BY_FEATURE: OnceLock<BTreeMap<Feature, Vec<&'static str>>> = 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, Vec<&'static str>> {
],
),
(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<Self>) -> impl IntoElement {
let upsells_count = self.upsells.len();
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
} 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")
fn render_feature_upsell_banner(
&self,
label: SharedString,
docs_url: SharedString,
vim: bool,
cx: &mut Context<Self>,
) -> 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)
}
};
});
upsell.when(ix < upsells_count, |upsell| upsell.border_b_1())
}))
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 {
this.action_slot(docs_url_button)
}
}),
)
.into_any_element()
}
fn render_feature_upsells(&self, cx: &mut Context<Self>) -> 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);
}
container
}
}