onboarding: Remove feature flag and old welcome crate (#36110)

Release Notes:

- N/A

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Danilo Leal 2025-08-13 13:18:24 -03:00 committed by GitHub
parent a7442d8880
commit d9a94a5496
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 26 additions and 574 deletions

View file

@ -18,12 +18,10 @@ default = []
ai_onboarding.workspace = true
anyhow.workspace = true
client.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
db.workspace = true
documented.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
fuzzy.workspace = true
git.workspace = true

View file

@ -0,0 +1,229 @@
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{
App, Context, DismissEvent, Entity, EventEmitter, Focusable, Render, Task, WeakEntity, Window,
actions,
};
use picker::{Picker, PickerDelegate};
use project::Fs;
use settings::{BaseKeymap, Settings, update_settings_file};
use std::sync::Arc;
use ui::{ListItem, ListItemSpacing, prelude::*};
use util::ResultExt;
use workspace::{ModalView, Workspace, ui::HighlightedLabel};
actions!(
zed,
[
/// Toggles the base keymap selector modal.
ToggleBaseKeymapSelector
]
);
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(toggle);
})
.detach();
}
pub fn toggle(
workspace: &mut Workspace,
_: &ToggleBaseKeymapSelector,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let fs = workspace.app_state().fs.clone();
workspace.toggle_modal(window, cx, |window, cx| {
BaseKeymapSelector::new(
BaseKeymapSelectorDelegate::new(cx.entity().downgrade(), fs, cx),
window,
cx,
)
});
}
pub struct BaseKeymapSelector {
picker: Entity<Picker<BaseKeymapSelectorDelegate>>,
}
impl Focusable for BaseKeymapSelector {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
impl ModalView for BaseKeymapSelector {}
impl BaseKeymapSelector {
pub fn new(
delegate: BaseKeymapSelectorDelegate,
window: &mut Window,
cx: &mut Context<BaseKeymapSelector>,
) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl Render for BaseKeymapSelector {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
pub struct BaseKeymapSelectorDelegate {
selector: WeakEntity<BaseKeymapSelector>,
matches: Vec<StringMatch>,
selected_index: usize,
fs: Arc<dyn Fs>,
}
impl BaseKeymapSelectorDelegate {
fn new(
selector: WeakEntity<BaseKeymapSelector>,
fs: Arc<dyn Fs>,
cx: &mut Context<BaseKeymapSelector>,
) -> Self {
let base = BaseKeymap::get(None, cx);
let selected_index = BaseKeymap::OPTIONS
.iter()
.position(|(_, value)| value == base)
.unwrap_or(0);
Self {
selector,
matches: Vec::new(),
selected_index,
fs,
}
}
}
impl PickerDelegate for BaseKeymapSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select a base keymap...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
) -> Task<()> {
let background = cx.background_executor().clone();
let candidates = BaseKeymap::names()
.enumerate()
.map(|(id, name)| StringMatchCandidate::new(id, name))
.collect::<Vec<_>>();
cx.spawn_in(window, async move |this, cx| {
let matches = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.0,
})
.collect()
} else {
match_strings(
&candidates,
&query,
false,
true,
100,
&Default::default(),
background,
)
.await
};
this.update(cx, |this, _| {
this.delegate.matches = matches;
this.delegate.selected_index = this
.delegate
.selected_index
.min(this.delegate.matches.len().saturating_sub(1));
})
.log_err();
})
}
fn confirm(
&mut self,
_: bool,
_: &mut Window,
cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
) {
if let Some(selection) = self.matches.get(self.selected_index) {
let base_keymap = BaseKeymap::from_names(&selection.string);
telemetry::event!(
"Settings Changed",
setting = "keymap",
value = base_keymap.to_string()
);
update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
*setting = Some(base_keymap)
});
}
self.selector
.update(cx, |_, cx| {
cx.emit(DismissEvent);
})
.ok();
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>) {
self.selector
.update(cx, |_, cx| {
cx.emit(DismissEvent);
})
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let keymap_match = &self.matches[ix];
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(HighlightedLabel::new(
keymap_match.string.clone(),
keymap_match.positions.clone(),
)),
)
}
}

View file

@ -0,0 +1,184 @@
use std::collections::HashSet;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use db::kvp::KEY_VALUE_STORE;
use gpui::{App, EntityId, EventEmitter, Subscription};
use ui::{IconButtonShape, Tooltip, prelude::*};
use workspace::item::{ItemEvent, ItemHandle};
use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub struct MultibufferHint {
shown_on: HashSet<EntityId>,
active_item: Option<Box<dyn ItemHandle>>,
subscription: Option<Subscription>,
}
const NUMBER_OF_HINTS: usize = 10;
const SHOWN_COUNT_KEY: &str = "MULTIBUFFER_HINT_SHOWN_COUNT";
impl Default for MultibufferHint {
fn default() -> Self {
Self::new()
}
}
impl MultibufferHint {
pub fn new() -> Self {
Self {
shown_on: Default::default(),
active_item: None,
subscription: None,
}
}
}
impl MultibufferHint {
fn counter() -> &'static AtomicUsize {
static SHOWN_COUNT: OnceLock<AtomicUsize> = OnceLock::new();
SHOWN_COUNT.get_or_init(|| {
let value: usize = KEY_VALUE_STORE
.read_kvp(SHOWN_COUNT_KEY)
.ok()
.flatten()
.and_then(|v| v.parse().ok())
.unwrap_or(0);
AtomicUsize::new(value)
})
}
fn shown_count() -> usize {
Self::counter().load(Ordering::Relaxed)
}
fn increment_count(cx: &mut App) {
Self::set_count(Self::shown_count() + 1, cx)
}
pub(crate) fn set_count(count: usize, cx: &mut App) {
Self::counter().store(count, Ordering::Relaxed);
db::write_and_log(cx, move || {
KEY_VALUE_STORE.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count))
});
}
fn dismiss(&mut self, cx: &mut App) {
Self::set_count(NUMBER_OF_HINTS, cx)
}
/// Determines the toolbar location for this [`MultibufferHint`].
fn determine_toolbar_location(&mut self, cx: &mut Context<Self>) -> ToolbarItemLocation {
if Self::shown_count() >= NUMBER_OF_HINTS {
return ToolbarItemLocation::Hidden;
}
let Some(active_pane_item) = self.active_item.as_ref() else {
return ToolbarItemLocation::Hidden;
};
if active_pane_item.is_singleton(cx)
|| active_pane_item.breadcrumbs(cx.theme(), cx).is_none()
|| !active_pane_item.can_save(cx)
{
return ToolbarItemLocation::Hidden;
}
if self.shown_on.insert(active_pane_item.item_id()) {
Self::increment_count(cx);
}
ToolbarItemLocation::Secondary
}
}
impl EventEmitter<ToolbarItemEvent> for MultibufferHint {}
impl ToolbarItemView for MultibufferHint {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
window: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation {
cx.notify();
self.active_item = active_pane_item.map(|item| item.boxed_clone());
let Some(active_pane_item) = active_pane_item else {
return ToolbarItemLocation::Hidden;
};
let this = cx.entity().downgrade();
self.subscription = Some(active_pane_item.subscribe_to_item_events(
window,
cx,
Box::new(move |event, _, cx| {
if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |this, cx| {
cx.notify();
let location = this.determine_toolbar_location(cx);
cx.emit(ToolbarItemEvent::ChangeLocation(location))
})
.ok();
}
}),
));
self.determine_toolbar_location(cx)
}
}
impl Render for MultibufferHint {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.px_2()
.py_0p5()
.justify_between()
.bg(cx.theme().status().info_background.opacity(0.5))
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_sm()
.overflow_hidden()
.child(
h_flex()
.gap_0p5()
.child(
h_flex()
.gap_2()
.child(
Icon::new(IconName::Info)
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child(Label::new(
"Edit and save files directly in the results multibuffer!",
)),
)
.child(
Button::new("open_docs", "Learn More")
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::End)
.on_click(move |_event, _, cx| {
cx.open_url("https://zed.dev/docs/multibuffers")
}),
),
)
.child(
IconButton::new("dismiss", IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.on_click(cx.listener(|this, _event, _, cx| {
this.dismiss(cx);
cx.emit(ToolbarItemEvent::ChangeLocation(
ToolbarItemLocation::Hidden,
))
}))
.tooltip(Tooltip::text("Dismiss Hint")),
)
.into_any_element()
}
}

View file

@ -1,8 +1,7 @@
use crate::welcome::{ShowWelcome, WelcomePage};
pub use crate::welcome::ShowWelcome;
use crate::{multibuffer_hint::MultibufferHint, welcome::WelcomePage};
use client::{Client, UserStore, zed_urls};
use command_palette_hooks::CommandPaletteFilter;
use db::kvp::KEY_VALUE_STORE;
use feature_flags::{FeatureFlag, FeatureFlagViewExt as _};
use fs::Fs;
use gpui::{
Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter,
@ -27,17 +26,13 @@ use workspace::{
};
mod ai_setup_page;
mod base_keymap_picker;
mod basics_page;
mod editing_page;
pub mod multibuffer_hint;
mod theme_preview;
mod welcome;
pub struct OnBoardingFeatureFlag {}
impl FeatureFlag for OnBoardingFeatureFlag {
const NAME: &'static str = "onboarding";
}
/// Imports settings from Visual Studio Code.
#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = zed)]
@ -57,6 +52,7 @@ pub struct ImportCursorSettings {
}
pub const FIRST_OPEN: &str = "first_open";
pub const DOCS_URL: &str = "https://zed.dev/docs/";
actions!(
zed,
@ -80,11 +76,19 @@ actions!(
/// Sign in while in the onboarding flow.
SignIn,
/// Open the user account in zed.dev while in the onboarding flow.
OpenAccount
OpenAccount,
/// Resets the welcome screen hints to their initial state.
ResetHints
]
);
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
workspace
.register_action(|_workspace, _: &ResetHints, _, cx| MultibufferHint::set_count(0, cx));
})
.detach();
cx.on_action(|_: &OpenOnboarding, cx| {
with_active_or_new_workspace(cx, |workspace, window, cx| {
workspace
@ -182,34 +186,8 @@ pub fn init(cx: &mut App) {
})
.detach();
cx.observe_new::<Workspace>(|_, window, cx| {
let Some(window) = window else {
return;
};
base_keymap_picker::init(cx);
let onboarding_actions = [
std::any::TypeId::of::<OpenOnboarding>(),
std::any::TypeId::of::<ShowWelcome>(),
];
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&onboarding_actions);
});
cx.observe_flag::<OnBoardingFeatureFlag, _>(window, move |is_enabled, _, _, cx| {
if is_enabled {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.show_action_types(onboarding_actions.iter());
});
} else {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&onboarding_actions);
});
}
})
.detach();
})
.detach();
register_serializable_item::<Onboarding>(cx);
}