716 lines · 24.3 KB · Rust
RawDownload
use crate::multibuffer_hint::MultibufferHint;use client::{Client, UserStore, zed_urls};use cloud_api_types::Plan;use db::kvp::KeyValueStore;use fs::Fs;use gpui::{    Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter,    FocusHandle, Focusable, Global, IntoElement, KeyContext, Render, ScrollHandle, SharedString,    Subscription, Task, WeakEntity, Window, actions,};use notifications::status_toast::StatusToast;use project::agent_server_store::AllAgentServersSettings;use schemars::JsonSchema;use serde::Deserialize;use settings::{SettingsStore, VsCodeSettingsSource};use std::sync::Arc;use ui::{    Divider, KeyBinding, ParentElement as _, StatefulInteractiveElement, Vector, VectorName,    WithScrollbar as _, prelude::*, rems_from_px,}; pub use workspace::welcome::ShowWelcome;use workspace::welcome::WelcomePage;use workspace::{    AppState, Workspace, WorkspaceId,    dock::DockPosition,    item::{Item, ItemEvent},    notifications::NotifyResultExt as _,    open_new, register_serializable_item, with_active_or_new_workspace,};use zed_actions::OpenOnboarding; mod base_keymap_picker;mod basics_page;pub mod multibuffer_hint;mod theme_preview; /// Imports settings from Visual Studio Code.#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]#[action(namespace = zed)]#[serde(deny_unknown_fields)]pub struct ImportVsCodeSettings {    #[serde(default)]    pub skip_prompt: bool,} /// Imports settings from Cursor editor.#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]#[action(namespace = zed)]#[serde(deny_unknown_fields)]pub struct ImportCursorSettings {    #[serde(default)]    pub skip_prompt: bool,} pub const FIRST_OPEN: &str = "first_open"; actions!(    onboarding,    [        /// Finish the onboarding process.        Finish,        /// Sign in while in the onboarding flow.        SignIn,        /// Open the user account in zed.dev while in the onboarding flow.        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                .with_local_workspace(window, cx, |workspace, window, cx| {                    let existing = workspace                        .active_pane()                        .read(cx)                        .items()                        .find_map(|item| item.downcast::<Onboarding>());                     if let Some(existing) = existing {                        workspace.activate_item(&existing, true, true, window, cx);                    } else {                        let settings_page = Onboarding::new(workspace, cx);                        workspace.add_item_to_active_pane(                            Box::new(settings_page),                            None,                            true,                            window,                            cx,                        )                    }                })                .detach();        });    });     cx.on_action(|_: &ShowWelcome, cx| {        with_active_or_new_workspace(cx, |workspace, window, cx| {            workspace                .with_local_workspace(window, cx, |workspace, window, cx| {                    let existing = workspace                        .active_pane()                        .read(cx)                        .items()                        .find_map(|item| item.downcast::<WelcomePage>());                     if let Some(existing) = existing {                        workspace.activate_item(&existing, true, true, window, cx);                    } else {                        let settings_page = cx                            .new(|cx| WelcomePage::new(workspace.weak_handle(), false, window, cx));                        workspace.add_item_to_active_pane(                            Box::new(settings_page),                            None,                            true,                            window,                            cx,                        )                    }                })                .detach();        });    });     cx.observe_new(|workspace: &mut Workspace, _window, _cx| {        workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| {            let fs = <dyn Fs>::global(cx);            let action = *action;             let workspace = cx.weak_entity();             window                .spawn(cx, async move |cx: &mut AsyncWindowContext| {                    handle_import_vscode_settings(                        workspace,                        VsCodeSettingsSource::VsCode,                        action.skip_prompt,                        fs,                        cx,                    )                    .await                })                .detach();        });         workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| {            let fs = <dyn Fs>::global(cx);            let action = *action;             let workspace = cx.weak_entity();             window                .spawn(cx, async move |cx: &mut AsyncWindowContext| {                    handle_import_vscode_settings(                        workspace,                        VsCodeSettingsSource::Cursor,                        action.skip_prompt,                        fs,                        cx,                    )                    .await                })                .detach();        });    })    .detach();     base_keymap_picker::init(cx);     register_serializable_item::<Onboarding>(cx);    register_serializable_item::<WelcomePage>(cx);} pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {    telemetry::event!("Onboarding Page Opened");    open_new(        Default::default(),        app_state,        cx,        |workspace, window, cx| {            {                workspace.toggle_dock(DockPosition::Left, window, cx);                let onboarding_page = Onboarding::new(workspace, cx);                workspace.add_item_to_center(Box::new(onboarding_page.clone()), window, cx);                 window.focus(&onboarding_page.focus_handle(cx), cx);                 cx.notify();            };            let kvp = KeyValueStore::global(cx);            db::write_and_log(cx, move || async move {                kvp.write_kvp(FIRST_OPEN.to_string(), "false".to_string())                    .await            });        },    )} struct Onboarding {    workspace: WeakEntity<Workspace>,    focus_handle: FocusHandle,    user_store: Entity<UserStore>,    scroll_handle: ScrollHandle,    _settings_subscription: Subscription,} impl Onboarding {    fn new(workspace: &Workspace, cx: &mut App) -> Entity<Self> {        let font_family_cache = theme::FontFamilyCache::global(cx);         let installed_agents = cx            .global::<SettingsStore>()            .get::<AllAgentServersSettings>(None)            .clone();        let client = Client::global(cx);        let status = *client.status().borrow();        let plan = workspace.user_store().read(cx).plan();        let zed_agent_state = if status.is_signed_out()            || matches!(                status,                client::Status::AuthenticationError | client::Status::ConnectionError            ) {            "signed_out"        } else if status.is_signing_in() {            "signing_in"        } else {            match plan {                Some(Plan::ZedPro) => "pro",                Some(Plan::ZedProTrial) => "trial",                Some(Plan::ZedBusiness) => "business",                Some(Plan::ZedVip) => "vip",                Some(Plan::ZedStudent) => "student",                Some(Plan::ZedFree) | None => "free",            }        };        let agents_installed = basics_page::FEATURED_AGENT_IDS            .iter()            .filter(|id| installed_agents.contains_key(**id))            .copied()            .collect::<Vec<_>>();        telemetry::event!(            "Welcome Agent Setup Viewed",            zed_agent = zed_agent_state,            agents_installed = agents_installed,        );         cx.new(|cx| {            cx.spawn(async move |this, cx| {                font_family_cache.prefetch(cx).await;                this.update(cx, |_, cx| {                    cx.notify();                })            })            .detach();             Self {                workspace: workspace.weak_handle(),                focus_handle: cx.focus_handle(),                scroll_handle: ScrollHandle::new(),                user_store: workspace.user_store().clone(),                _settings_subscription: cx                    .observe_global::<SettingsStore>(move |_, cx| cx.notify()),            }        })    }     fn on_finish(_: &Finish, _: &mut Window, cx: &mut App) {        telemetry::event!("Finish Setup");        go_to_welcome_page(cx);    }     fn handle_sign_in(&mut self, _: &SignIn, window: &mut Window, cx: &mut Context<Self>) {        let client = Client::global(cx);        let workspace = self.workspace.clone();         window            .spawn(cx, async move |mut cx| {                client                    .sign_in_with_optional_connect(true, &cx)                    .await                    .notify_workspace_async_err(workspace, &mut cx);            })            .detach();    }     fn handle_open_account(_: &OpenAccount, _: &mut Window, cx: &mut App) {        cx.open_url(&zed_urls::account_url(cx))    }     fn render_page(&mut self, cx: &mut Context<Self>) -> AnyElement {        crate::basics_page::render_basics_page(&self.user_store, cx).into_any_element()    }} impl Render for Onboarding {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        div()            .image_cache(gpui::retain_all("onboarding-page"))            .key_context({                let mut ctx = KeyContext::new_with_defaults();                ctx.add("Onboarding");                ctx.add("menu");                ctx            })            .track_focus(&self.focus_handle)            .size_full()            .bg(cx.theme().colors().editor_background)            .on_action(Self::on_finish)            .on_action(cx.listener(Self::handle_sign_in))            .on_action(Self::handle_open_account)            .on_action(cx.listener(|_, _: &menu::SelectNext, window, cx| {                window.focus_next(cx);                cx.notify();            }))            .on_action(cx.listener(|_, _: &menu::SelectPrevious, window, cx| {                window.focus_prev(cx);                cx.notify();            }))            .vertical_scrollbar_for(&self.scroll_handle, window, cx)            .child(                div()                    .id("page-content")                    .size_full()                    .overflow_y_scroll()                    .child(                        v_flex()                            .min_w_0()                            .max_w(rems_from_px(780.))                            .w_full()                            .mx_auto()                            .p_12()                            .gap_6()                            .child(                                h_flex()                                    .w_full()                                    .gap_4()                                    .justify_between()                                    .child(                                        h_flex()                                            .gap_4()                                            .child(Vector::square(VectorName::ZedLogo, rems(2.5)))                                            .child(                                                v_flex()                                                    .child(                                                        Headline::new("Welcome to Zed")                                                            .size(HeadlineSize::Small),                                                    )                                                    .child(                                                        Label::new("The editor for what's next")                                                            .color(Color::Muted)                                                            .size(LabelSize::Small)                                                            .italic(),                                                    ),                                            ),                                    )                                    .child({                                        Button::new("finish_setup", "Finish Setup")                                            .style(ButtonStyle::Filled)                                            .size(ButtonSize::Medium)                                            .width(rems_from_px(200.))                                            .key_binding(KeyBinding::for_action_in(                                                &Finish,                                                &self.focus_handle,                                                cx,                                            ))                                            .on_click(|_, window, cx| {                                                window.dispatch_action(Finish.boxed_clone(), cx);                                            })                                    }),                            )                            .child(Divider::horizontal().color(ui::DividerColor::BorderVariant))                            .child(self.render_page(cx)),                    )                    .track_scroll(&self.scroll_handle),            )    }} impl EventEmitter<ItemEvent> for Onboarding {} impl Focusable for Onboarding {    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {        self.focus_handle.clone()    }} impl Item for Onboarding {    type Event = ItemEvent;     fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {        "Onboarding".into()    }     fn telemetry_event_text(&self) -> Option<&'static str> {        Some("Onboarding Page Opened")    }     fn show_toolbar(&self) -> bool {        false    }     fn can_split(&self) -> bool {        true    }     fn clone_on_split(        &self,        _workspace_id: Option<WorkspaceId>,        _: &mut Window,        cx: &mut Context<Self>,    ) -> Task<Option<Entity<Self>>> {        Task::ready(Some(cx.new(|cx| Onboarding {            workspace: self.workspace.clone(),            user_store: self.user_store.clone(),            scroll_handle: ScrollHandle::new(),            focus_handle: cx.focus_handle(),            _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),        })))    }     fn to_item_events(event: &Self::Event, f: &mut dyn FnMut(workspace::item::ItemEvent)) {        f(*event)    }} fn go_to_welcome_page(cx: &mut App) {    with_active_or_new_workspace(cx, |workspace, window, cx| {        let Some((onboarding_id, onboarding_idx)) = workspace            .active_pane()            .read(cx)            .items()            .enumerate()            .find_map(|(idx, item)| {                let _ = item.downcast::<Onboarding>()?;                Some((item.item_id(), idx))            })        else {            return;        };         workspace.active_pane().update(cx, |pane, cx| {            // Get the index here to get around the borrow checker            let idx = pane.items().enumerate().find_map(|(idx, item)| {                let _ = item.downcast::<WelcomePage>()?;                Some(idx)            });             if let Some(idx) = idx {                pane.activate_item(idx, true, true, window, cx);            } else {                let item = Box::new(                    cx.new(|cx| WelcomePage::new(workspace.weak_handle(), false, window, cx)),                );                pane.add_item(item, true, true, Some(onboarding_idx), window, cx);            }             pane.remove_item(onboarding_id, false, false, window, cx);        });    });} pub async fn handle_import_vscode_settings(    workspace: WeakEntity<Workspace>,    source: VsCodeSettingsSource,    skip_prompt: bool,    fs: Arc<dyn Fs>,    cx: &mut AsyncWindowContext,) {    use util::truncate_and_remove_front;     let vscode_settings =        match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await {            Ok(vscode_settings) => vscode_settings,            Err(err) => {                zlog::error!("{err:?}");                let _ = cx.prompt(                    gpui::PromptLevel::Info,                    &format!("Could not find or load a {source} settings file"),                    None,                    &["OK"],                );                return;            }        };     if !skip_prompt {        let prompt = cx.prompt(            gpui::PromptLevel::Warning,            &format!(                "Importing {} settings may overwrite your existing settings. \                Will import settings from {}",

Lines 1–500 of 716.