welcome.rs

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