welcome.rs

  1use client::{TelemetrySettings, telemetry::Telemetry};
  2use db::kvp::KEY_VALUE_STORE;
  3use gpui::{
  4    Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
  5    ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, svg,
  6};
  7use language::language_settings::{EditPredictionProvider, all_language_settings};
  8use project::DisableAiSettings;
  9use settings::{Settings, SettingsStore};
 10use std::sync::Arc;
 11use ui::{CheckboxWithLabel, ElevationIndex, Tooltip, prelude::*};
 12use util::ResultExt;
 13use vim_mode_setting::VimModeSetting;
 14use workspace::{
 15    AppState, Welcome, Workspace, WorkspaceId,
 16    dock::DockPosition,
 17    item::{Item, ItemEvent},
 18    open_new,
 19};
 20
 21pub use multibuffer_hint::*;
 22
 23mod base_keymap_picker;
 24mod multibuffer_hint;
 25mod welcome_ui;
 26
 27actions!(
 28    welcome,
 29    [
 30        /// Resets the welcome screen hints to their initial state.
 31        ResetHints
 32    ]
 33);
 34
 35pub const FIRST_OPEN: &str = "first_open";
 36pub const DOCS_URL: &str = "https://zed.dev/docs/";
 37
 38pub fn init(cx: &mut App) {
 39    cx.observe_new(|workspace: &mut Workspace, _, _cx| {
 40        workspace.register_action(|workspace, _: &Welcome, window, cx| {
 41            let welcome_page = WelcomePage::new(workspace, cx);
 42            workspace.add_item_to_active_pane(Box::new(welcome_page), None, true, window, cx)
 43        });
 44        workspace
 45            .register_action(|_workspace, _: &ResetHints, _, cx| MultibufferHint::set_count(0, cx));
 46    })
 47    .detach();
 48
 49    base_keymap_picker::init(cx);
 50}
 51
 52pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {
 53    open_new(
 54        Default::default(),
 55        app_state,
 56        cx,
 57        |workspace, window, cx| {
 58            workspace.toggle_dock(DockPosition::Left, window, cx);
 59            let welcome_page = WelcomePage::new(workspace, cx);
 60            workspace.add_item_to_center(Box::new(welcome_page.clone()), window, cx);
 61
 62            window.focus(&welcome_page.focus_handle(cx));
 63
 64            cx.notify();
 65
 66            db::write_and_log(cx, || {
 67                KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
 68            });
 69        },
 70    )
 71}
 72
 73pub struct WelcomePage {
 74    workspace: WeakEntity<Workspace>,
 75    focus_handle: FocusHandle,
 76    telemetry: Arc<Telemetry>,
 77    _settings_subscription: Subscription,
 78}
 79
 80impl Render for WelcomePage {
 81    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 82        let edit_prediction_provider_is_zed =
 83            all_language_settings(None, cx).edit_predictions.provider
 84                == EditPredictionProvider::Zed;
 85
 86        let edit_prediction_label = if edit_prediction_provider_is_zed {
 87            "Edit Prediction Enabled"
 88        } else {
 89            "Try Edit Prediction"
 90        };
 91
 92        h_flex()
 93            .size_full()
 94            .bg(cx.theme().colors().editor_background)
 95            .key_context("Welcome")
 96            .track_focus(&self.focus_handle(cx))
 97            .child(
 98                v_flex()
 99                    .gap_8()
100                    .mx_auto()
101                    .child(
102                        v_flex()
103                            .w_full()
104                            .child(
105                                svg()
106                                    .path("icons/logo_96.svg")
107                                    .text_color(cx.theme().colors().icon_disabled)
108                                    .w(px(40.))
109                                    .h(px(40.))
110                                    .mx_auto()
111                                    .mb_4(),
112                            )
113                            .child(
114                                h_flex()
115                                    .w_full()
116                                    .justify_center()
117                                    .child(Headline::new("Welcome to Zed")),
118                            )
119                            .child(
120                                h_flex().w_full().justify_center().child(
121                                    Label::new("The editor for what's next")
122                                        .color(Color::Muted)
123                                        .italic(),
124                                ),
125                            ),
126                    )
127                    .child(
128                        h_flex()
129                            .items_start()
130                            .gap_8()
131                            .child(
132                                v_flex()
133                                    .gap_2()
134                                    .pr_8()
135                                    .border_r_1()
136                                    .border_color(cx.theme().colors().border_variant)
137                                    .child(
138                                        self.section_label( cx).child(
139                                            Label::new("Get Started")
140                                                .size(LabelSize::XSmall)
141                                                .color(Color::Muted),
142                                        ),
143                                    )
144                                    .child(
145                                        Button::new("choose-theme", "Choose a Theme")
146                                            .icon(IconName::SwatchBook)
147                                            .icon_size(IconSize::XSmall)
148                                            .icon_color(Color::Muted)
149                                            .icon_position(IconPosition::Start)
150                                            .on_click(cx.listener(|this, _, window, cx| {
151                                                telemetry::event!("Welcome Theme Changed");
152                                                this.workspace
153                                                    .update(cx, |_workspace, cx| {
154                                                        window.dispatch_action(zed_actions::theme_selector::Toggle::default().boxed_clone(), cx);
155                                                    })
156                                                    .ok();
157                                            })),
158                                    )
159                                    .child(
160                                        Button::new("choose-keymap", "Choose a Keymap")
161                                            .icon(IconName::Keyboard)
162                                            .icon_size(IconSize::XSmall)
163                                            .icon_color(Color::Muted)
164                                            .icon_position(IconPosition::Start)
165                                            .on_click(cx.listener(|this, _, window, cx| {
166                                                telemetry::event!("Welcome Keymap Changed");
167                                                this.workspace
168                                                    .update(cx, |workspace, cx| {
169                                                        base_keymap_picker::toggle(
170                                                            workspace,
171                                                            &Default::default(),
172                                                            window, cx,
173                                                        )
174                                                    })
175                                                    .ok();
176                                            })),
177                                    )
178                                    .when(!DisableAiSettings::get_global(cx).disable_ai, |parent| {
179                                        parent.child(
180                                            Button::new(
181                                                "edit_prediction_onboarding",
182                                                edit_prediction_label,
183                                            )
184                                            .disabled(edit_prediction_provider_is_zed)
185                                            .icon(IconName::ZedPredict)
186                                            .icon_size(IconSize::XSmall)
187                                            .icon_color(Color::Muted)
188                                            .icon_position(IconPosition::Start)
189                                            .on_click(
190                                                cx.listener(|_, _, window, cx| {
191                                                    telemetry::event!("Welcome Screen Try Edit Prediction clicked");
192                                                    window.dispatch_action(zed_actions::OpenZedPredictOnboarding.boxed_clone(), cx);
193                                                }),
194                                            ),
195                                        )
196                                    })
197                                    .child(
198                                        Button::new("edit settings", "Edit Settings")
199                                            .icon(IconName::Settings)
200                                            .icon_size(IconSize::XSmall)
201                                            .icon_color(Color::Muted)
202                                            .icon_position(IconPosition::Start)
203                                            .on_click(cx.listener(|_, _, window, cx| {
204                                                telemetry::event!("Welcome Settings Edited");
205                                                window.dispatch_action(Box::new(
206                                                    zed_actions::OpenSettings,
207                                                ), cx);
208                                            })),
209                                    )
210
211                            )
212                            .child(
213                                v_flex()
214                                    .gap_2()
215                                    .child(
216                                        self.section_label(cx).child(
217                                            Label::new("Resources")
218                                                .size(LabelSize::XSmall)
219                                                .color(Color::Muted),
220                                        ),
221                                    )
222                                    .when(cfg!(target_os = "macos"), |el| {
223                                        el.child(
224                                            Button::new("install-cli", "Install the CLI")
225                                                .icon(IconName::Terminal)
226                                                .icon_size(IconSize::XSmall)
227                                                .icon_color(Color::Muted)
228                                                .icon_position(IconPosition::Start)
229                                                .on_click(cx.listener(|this, _, window, cx| {
230                                                    telemetry::event!("Welcome CLI Installed");
231                                                    this.workspace.update(cx, |_, cx|{
232                                                        install_cli::install_cli(window, cx);
233                                                    }).log_err();
234                                                })),
235                                        )
236                                    })
237                                    .child(
238                                        Button::new("view-docs", "View Documentation")
239                                            .icon(IconName::FileCode)
240                                            .icon_size(IconSize::XSmall)
241                                            .icon_color(Color::Muted)
242                                            .icon_position(IconPosition::Start)
243                                            .on_click(cx.listener(|_, _, _, cx| {
244                                                telemetry::event!("Welcome Documentation Viewed");
245                                                cx.open_url(DOCS_URL);
246                                            })),
247                                    )
248                                    .child(
249                                        Button::new("explore-extensions", "Explore Extensions")
250                                            .icon(IconName::Blocks)
251                                            .icon_size(IconSize::XSmall)
252                                            .icon_color(Color::Muted)
253                                            .icon_position(IconPosition::Start)
254                                            .on_click(cx.listener(|_, _, window, cx| {
255                                                telemetry::event!("Welcome Extensions Page Opened");
256                                                window.dispatch_action(Box::new(
257                                                    zed_actions::Extensions::default(),
258                                                ), cx);
259                                            })),
260                                    )
261                            ),
262                    )
263                    .child(
264                        v_container()
265                            .px_2()
266                            .gap_2()
267                            .child(
268                                h_flex()
269                                    .justify_between()
270                                    .child(
271                                        CheckboxWithLabel::new(
272                                            "enable-vim",
273                                            Label::new("Enable Vim Mode"),
274                                            if VimModeSetting::get_global(cx).0 {
275                                                ui::ToggleState::Selected
276                                            } else {
277                                                ui::ToggleState::Unselected
278                                            },
279                                            cx.listener(move |this, selection, _window, cx| {
280                                                telemetry::event!("Welcome Vim Mode Toggled");
281                                                this.update_settings::<VimModeSetting>(
282                                                    selection,
283                                                    cx,
284                                                    |setting, value| *setting = Some(value),
285                                                );
286                                            }),
287                                        )
288                                        .fill()
289                                        .elevation(ElevationIndex::ElevatedSurface),
290                                    )
291                                    .child(
292                                        IconButton::new("vim-mode", IconName::Info)
293                                            .icon_size(IconSize::XSmall)
294                                            .icon_color(Color::Muted)
295                                            .tooltip(
296                                                Tooltip::text(
297                                                    "You can also toggle Vim Mode via the command palette or Editor Controls menu.")
298                                            ),
299                                    ),
300                            )
301                            .child(
302                                CheckboxWithLabel::new(
303                                    "enable-crash",
304                                    Label::new("Send Crash Reports"),
305                                    if TelemetrySettings::get_global(cx).diagnostics {
306                                        ui::ToggleState::Selected
307                                    } else {
308                                        ui::ToggleState::Unselected
309                                    },
310                                    cx.listener(move |this, selection, _window, cx| {
311                                        telemetry::event!("Welcome Diagnostic Telemetry Toggled");
312                                        this.update_settings::<TelemetrySettings>(selection, cx, {
313                                            move |settings, value| {
314                                                settings.diagnostics = Some(value);
315                                                telemetry::event!(
316                                                    "Settings Changed",
317                                                    setting = "diagnostic telemetry",
318                                                    value
319                                                );
320                                            }
321                                        });
322                                    }),
323                                )
324                                .fill()
325                                .elevation(ElevationIndex::ElevatedSurface),
326                            )
327                            .child(
328                                CheckboxWithLabel::new(
329                                    "enable-telemetry",
330                                    Label::new("Send Telemetry"),
331                                    if TelemetrySettings::get_global(cx).metrics {
332                                        ui::ToggleState::Selected
333                                    } else {
334                                        ui::ToggleState::Unselected
335                                    },
336                                    cx.listener(move |this, selection, _window, cx| {
337                                        telemetry::event!("Welcome Metric Telemetry Toggled");
338                                        this.update_settings::<TelemetrySettings>(selection, cx, {
339                                            move |settings, value| {
340                                                settings.metrics = Some(value);
341                                                telemetry::event!(
342                                                    "Settings Changed",
343                                                    setting = "metric telemetry",
344                                                    value
345                                                );
346                                            }
347                                        });
348                                    }),
349                                )
350                                .fill()
351                                .elevation(ElevationIndex::ElevatedSurface),
352                            ),
353                    ),
354            )
355    }
356}
357
358impl WelcomePage {
359    pub fn new(workspace: &Workspace, cx: &mut Context<Workspace>) -> Entity<Self> {
360        let this = cx.new(|cx| {
361            cx.on_release(|_: &mut Self, _| {
362                telemetry::event!("Welcome Page Closed");
363            })
364            .detach();
365
366            WelcomePage {
367                focus_handle: cx.focus_handle(),
368                workspace: workspace.weak_handle(),
369                telemetry: workspace.client().telemetry().clone(),
370                _settings_subscription: cx
371                    .observe_global::<SettingsStore>(move |_, cx| cx.notify()),
372            }
373        });
374
375        this
376    }
377
378    fn section_label(&self, cx: &mut App) -> Div {
379        div()
380            .pl_1()
381            .font_buffer(cx)
382            .text_color(Color::Muted.color(cx))
383    }
384
385    fn update_settings<T: Settings>(
386        &mut self,
387        selection: &ToggleState,
388        cx: &mut Context<Self>,
389        callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
390    ) {
391        if let Some(workspace) = self.workspace.upgrade() {
392            let fs = workspace.read(cx).app_state().fs.clone();
393            let selection = *selection;
394            settings::update_settings_file::<T>(fs, cx, move |settings, _| {
395                let value = match selection {
396                    ToggleState::Unselected => false,
397                    ToggleState::Selected => true,
398                    _ => return,
399                };
400
401                callback(settings, value)
402            });
403        }
404    }
405}
406
407impl EventEmitter<ItemEvent> for WelcomePage {}
408
409impl Focusable for WelcomePage {
410    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
411        self.focus_handle.clone()
412    }
413}
414
415impl Item for WelcomePage {
416    type Event = ItemEvent;
417
418    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
419        "Welcome".into()
420    }
421
422    fn telemetry_event_text(&self) -> Option<&'static str> {
423        Some("Welcome Page Opened")
424    }
425
426    fn show_toolbar(&self) -> bool {
427        false
428    }
429
430    fn clone_on_split(
431        &self,
432        _workspace_id: Option<WorkspaceId>,
433        _: &mut Window,
434        cx: &mut Context<Self>,
435    ) -> Option<Entity<Self>> {
436        Some(cx.new(|cx| WelcomePage {
437            focus_handle: cx.focus_handle(),
438            workspace: self.workspace.clone(),
439            telemetry: self.telemetry.clone(),
440            _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
441        }))
442    }
443
444    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
445        f(*event)
446    }
447}