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