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