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