WIP

Joseph T. Lyons created

Change summary

crates/assistant/src/assistant_panel.rs     |   2 
crates/call/src/call.rs                     |  16 
crates/client/src/client.rs                 |   3 
crates/client/src/telemetry.rs              | 123 +++---
crates/client/src/user.rs                   |   1 
crates/collab_ui/src/collab_ui.rs           |   4 
crates/editor/src/editor.rs                 |   3 
crates/editor/src/items.rs                  |   1 
crates/theme_selector/src/theme_selector.rs |   2 
crates/welcome/src/base_keymap_picker.rs    |  11 
crates/welcome/src/base_keymap_setting.rs   |  14 
crates/welcome/src/welcome.rs               | 400 ++++++++++++++--------
crates/workspace/src/workspace.rs           |   2 
crates/zed/src/main.rs                      |  24 
14 files changed, 348 insertions(+), 258 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -3542,5 +3542,5 @@ fn report_assistant_event(
         .default_open_ai_model
         .clone();
 
-    telemetry.report_assistant_event(conversation_id, assistant_kind, model.full_name(), cx)
+    telemetry.report_assistant_event(conversation_id, assistant_kind, model.full_name())
 }

crates/call/src/call.rs 🔗

@@ -310,14 +310,14 @@ impl ActiveCall {
         })
     }
 
-    pub fn decline_incoming(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
+    pub fn decline_incoming(&mut self, _cx: &mut ModelContext<Self>) -> Result<()> {
         let call = self
             .incoming_call
             .0
             .borrow_mut()
             .take()
             .ok_or_else(|| anyhow!("no incoming call"))?;
-        report_call_event_for_room("decline incoming", call.room_id, None, &self.client, cx);
+        report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
         self.client.send(proto::DeclineCall {
             room_id: call.room_id,
         })?;
@@ -467,7 +467,7 @@ impl ActiveCall {
     pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
         if let Some(room) = self.room() {
             let room = room.read(cx);
-            report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
+            report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
         }
     }
 }
@@ -477,11 +477,10 @@ pub fn report_call_event_for_room(
     room_id: u64,
     channel_id: Option<u64>,
     client: &Arc<Client>,
-    cx: &mut AppContext,
 ) {
     let telemetry = client.telemetry();
 
-    telemetry.report_call_event(operation, Some(room_id), channel_id, cx)
+    telemetry.report_call_event(operation, Some(room_id), channel_id)
 }
 
 pub fn report_call_event_for_channel(
@@ -494,12 +493,7 @@ pub fn report_call_event_for_channel(
 
     let telemetry = client.telemetry();
 
-    telemetry.report_call_event(
-        operation,
-        room.map(|r| r.read(cx).id()),
-        Some(channel_id),
-        cx,
-    )
+    telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
 }
 
 #[cfg(test)]

crates/client/src/client.rs 🔗

@@ -501,8 +501,7 @@ impl Client {
                 }));
             }
             Status::SignedOut | Status::UpgradeRequired => {
-                cx.update(|cx| self.telemetry.set_authenticated_user_info(None, false, cx))
-                    .log_err();
+                self.telemetry.set_authenticated_user_info(None, false);
                 state._reconnect_task.take();
             }
             _ => {}

crates/client/src/telemetry.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use serde::Serialize;
-use settings::Settings;
+use settings::{Settings, SettingsStore};
 use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
 use sysinfo::{
     CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
@@ -17,10 +17,11 @@ use util::{channel::ReleaseChannel, TryFutureExt};
 pub struct Telemetry {
     http_client: Arc<dyn HttpClient>,
     executor: BackgroundExecutor,
-    state: Mutex<TelemetryState>,
+    state: Arc<Mutex<TelemetryState>>,
 }
 
 struct TelemetryState {
+    settings: TelemetrySettings,
     metrics_id: Option<Arc<str>>,      // Per logged-in user
     installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
     session_id: Option<Arc<str>>,      // Per app launch
@@ -139,45 +140,60 @@ impl Telemetry {
             None
         };
 
+        TelemetrySettings::register(cx);
+
+        let state = Arc::new(Mutex::new(TelemetryState {
+            settings: TelemetrySettings::get_global(cx).clone(),
+            app_metadata: cx.app_metadata(),
+            architecture: env::consts::ARCH,
+            release_channel,
+            installation_id: None,
+            metrics_id: None,
+            session_id: None,
+            clickhouse_events_queue: Default::default(),
+            flush_clickhouse_events_task: Default::default(),
+            log_file: None,
+            is_staff: None,
+            first_event_datetime: None,
+        }));
+
+        cx.observe_global::<SettingsStore>({
+            let state = state.clone();
+
+            move |cx| {
+                let mut state = state.lock();
+                state.settings = TelemetrySettings::get_global(cx).clone();
+            }
+        })
+        .detach();
+
         // TODO: Replace all hardware stuff with nested SystemSpecs json
         let this = Arc::new(Self {
             http_client: client,
             executor: cx.background_executor().clone(),
-            state: Mutex::new(TelemetryState {
-                app_metadata: cx.app_metadata(),
-                architecture: env::consts::ARCH,
-                release_channel,
-                installation_id: None,
-                metrics_id: None,
-                session_id: None,
-                clickhouse_events_queue: Default::default(),
-                flush_clickhouse_events_task: Default::default(),
-                log_file: None,
-                is_staff: None,
-                first_event_datetime: None,
-            }),
+            state,
         });
 
         // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
         // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
         std::mem::forget(cx.on_app_quit({
             let this = this.clone();
-            move |cx| this.shutdown_telemetry(cx)
+            move |_| this.shutdown_telemetry()
         }));
 
         this
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
+    fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> {
         Task::ready(())
     }
 
     // Skip calling this function in tests.
     // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
     #[cfg(not(any(test, feature = "test-support")))]
-    fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
-        self.report_app_event("close", true, cx);
+    fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> {
+        self.report_app_event("close", true);
         Task::ready(())
     }
 
@@ -197,7 +213,7 @@ impl Telemetry {
         drop(state);
 
         let this = self.clone();
-        cx.spawn(|cx| async move {
+        cx.spawn(|_cx| async move {
             // Avoiding calling `System::new_all()`, as there have been crashes related to it
             let refresh_kind = RefreshKind::new()
                 .with_memory() // For memory usage
@@ -226,11 +242,8 @@ impl Telemetry {
                     return;
                 };
 
-                cx.update(|cx| {
-                    this.report_memory_event(process.memory(), process.virtual_memory(), cx);
-                    this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32, cx);
-                })
-                .ok();
+                this.report_memory_event(process.memory(), process.virtual_memory());
+                this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32);
             }
         })
         .detach();
@@ -240,13 +253,13 @@ impl Telemetry {
         self: &Arc<Self>,
         metrics_id: Option<String>,
         is_staff: bool,
-        cx: &AppContext,
     ) {
-        if !TelemetrySettings::get_global(cx).metrics {
+        let mut state = self.state.lock();
+
+        if !state.settings.metrics {
             return;
         }
 
-        let mut state = self.state.lock();
         let metrics_id: Option<Arc<str>> = metrics_id.map(|id| id.into());
         state.metrics_id = metrics_id.clone();
         state.is_staff = Some(is_staff);
@@ -260,7 +273,6 @@ impl Telemetry {
         operation: &'static str,
         copilot_enabled: bool,
         copilot_enabled_for_language: bool,
-        cx: &AppContext,
     ) {
         let event = ClickhouseEvent::Editor {
             file_extension,
@@ -271,7 +283,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
     pub fn report_copilot_event(
@@ -279,7 +291,6 @@ impl Telemetry {
         suggestion_id: Option<String>,
         suggestion_accepted: bool,
         file_extension: Option<String>,
-        cx: &AppContext,
     ) {
         let event = ClickhouseEvent::Copilot {
             suggestion_id,
@@ -288,7 +299,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
     pub fn report_assistant_event(
@@ -296,7 +307,6 @@ impl Telemetry {
         conversation_id: Option<String>,
         kind: AssistantKind,
         model: &'static str,
-        cx: &AppContext,
     ) {
         let event = ClickhouseEvent::Assistant {
             conversation_id,
@@ -305,7 +315,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
     pub fn report_call_event(
@@ -313,7 +323,6 @@ impl Telemetry {
         operation: &'static str,
         room_id: Option<u64>,
         channel_id: Option<u64>,
-        cx: &AppContext,
     ) {
         let event = ClickhouseEvent::Call {
             operation,
@@ -322,29 +331,23 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
-    pub fn report_cpu_event(
-        self: &Arc<Self>,
-        usage_as_percentage: f32,
-        core_count: u32,
-        cx: &AppContext,
-    ) {
+    pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
         let event = ClickhouseEvent::Cpu {
             usage_as_percentage,
             core_count,
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
     pub fn report_memory_event(
         self: &Arc<Self>,
         memory_in_bytes: u64,
         virtual_memory_in_bytes: u64,
-        cx: &AppContext,
     ) {
         let event = ClickhouseEvent::Memory {
             memory_in_bytes,
@@ -352,36 +355,26 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
-    pub fn report_app_event(
-        self: &Arc<Self>,
-        operation: &'static str,
-        immediate_flush: bool,
-        cx: &AppContext,
-    ) {
+    pub fn report_app_event(self: &Arc<Self>, operation: &'static str, immediate_flush: bool) {
         let event = ClickhouseEvent::App {
             operation,
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, immediate_flush, cx)
+        self.report_clickhouse_event(event, immediate_flush)
     }
 
-    pub fn report_setting_event(
-        self: &Arc<Self>,
-        setting: &'static str,
-        value: String,
-        cx: &AppContext,
-    ) {
+    pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
         let event = ClickhouseEvent::Setting {
             setting,
             value,
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, false, cx)
+        self.report_clickhouse_event(event, false)
     }
 
     fn milliseconds_since_first_event(&self) -> i64 {
@@ -398,17 +391,13 @@ impl Telemetry {
         }
     }
 
-    fn report_clickhouse_event(
-        self: &Arc<Self>,
-        event: ClickhouseEvent,
-        immediate_flush: bool,
-        cx: &AppContext,
-    ) {
-        if !TelemetrySettings::get_global(cx).metrics {
+    fn report_clickhouse_event(self: &Arc<Self>, event: ClickhouseEvent, immediate_flush: bool) {
+        let mut state = self.state.lock();
+
+        if !state.settings.metrics {
             return;
         }
 
-        let mut state = self.state.lock();
         let signed_in = state.metrics_id.is_some();
         state
             .clickhouse_events_queue

crates/client/src/user.rs 🔗

@@ -164,7 +164,6 @@ impl UserStore {
                                         client.telemetry.set_authenticated_user_info(
                                             Some(info.metrics_id.clone()),
                                             info.staff,
-                                            cx,
                                         )
                                     }
                                 })?;

crates/collab_ui/src/collab_ui.rs 🔗

@@ -58,7 +58,6 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
                     room.id(),
                     room.channel_id(),
                     &client,
-                    cx,
                 );
                 Task::ready(room.unshare_screen(cx))
             } else {
@@ -67,7 +66,6 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
                     room.id(),
                     room.channel_id(),
                     &client,
-                    cx,
                 );
                 room.share_screen(cx)
             }
@@ -86,7 +84,7 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
             } else {
                 "disable microphone"
             };
-            report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
+            report_call_event_for_room(operation, room.id(), room.channel_id(), &client);
 
             room.toggle_mute(cx)
         })

crates/editor/src/editor.rs 🔗

@@ -8874,7 +8874,7 @@ impl Editor {
 
         let telemetry = project.read(cx).client().telemetry().clone();
 
-        telemetry.report_copilot_event(suggestion_id, suggestion_accepted, file_extension, cx)
+        telemetry.report_copilot_event(suggestion_id, suggestion_accepted, file_extension)
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -8926,7 +8926,6 @@ impl Editor {
             operation,
             copilot_enabled,
             copilot_enabled_for_language,
-            cx,
         )
     }
 

crates/editor/src/items.rs 🔗

@@ -866,6 +866,7 @@ impl Item for Editor {
     }
 
     fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
+        dbg!(event);
         match event {
             EditorEvent::Closed => f(ItemEvent::CloseItem),
 

crates/theme_selector/src/theme_selector.rs 🔗

@@ -182,7 +182,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
         let theme_name = cx.theme().name.clone();
 
         self.telemetry
-            .report_setting_event("theme", theme_name.to_string(), cx);
+            .report_setting_event("theme", theme_name.to_string());
 
         update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
             settings.theme = Some(theme_name.to_string());

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -1,4 +1,5 @@
 use super::base_keymap_setting::BaseKeymap;
+use client::telemetry::Telemetry;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
     actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
@@ -27,9 +28,10 @@ pub fn toggle(
     cx: &mut ViewContext<Workspace>,
 ) {
     let fs = workspace.app_state().fs.clone();
+    let telemetry = workspace.client().telemetry().clone();
     workspace.toggle_modal(cx, |cx| {
         BaseKeymapSelector::new(
-            BaseKeymapSelectorDelegate::new(cx.view().downgrade(), fs, cx),
+            BaseKeymapSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx),
             cx,
         )
     });
@@ -73,6 +75,7 @@ pub struct BaseKeymapSelectorDelegate {
     view: WeakView<BaseKeymapSelector>,
     matches: Vec<StringMatch>,
     selected_index: usize,
+    telemetry: Arc<Telemetry>,
     fs: Arc<dyn Fs>,
 }
 
@@ -80,6 +83,7 @@ impl BaseKeymapSelectorDelegate {
     fn new(
         weak_view: WeakView<BaseKeymapSelector>,
         fs: Arc<dyn Fs>,
+        telemetry: Arc<Telemetry>,
         cx: &mut ViewContext<BaseKeymapSelector>,
     ) -> Self {
         let base = BaseKeymap::get(None, cx);
@@ -91,6 +95,7 @@ impl BaseKeymapSelectorDelegate {
             view: weak_view,
             matches: Vec::new(),
             selected_index,
+            telemetry,
             fs,
         }
     }
@@ -172,6 +177,10 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
     fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
         if let Some(selection) = self.matches.get(self.selected_index) {
             let base_keymap = BaseKeymap::from_names(&selection.string);
+
+            self.telemetry
+                .report_setting_event("keymap", base_keymap.to_string());
+
             update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
                 *setting = Some(base_keymap)
             });

crates/welcome/src/base_keymap_setting.rs 🔗

@@ -1,3 +1,5 @@
+use std::fmt::{Display, Formatter};
+
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::Settings;
@@ -12,6 +14,18 @@ pub enum BaseKeymap {
     TextMate,
 }
 
+impl Display for BaseKeymap {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            BaseKeymap::VSCode => write!(f, "VSCode"),
+            BaseKeymap::JetBrains => write!(f, "JetBrains"),
+            BaseKeymap::SublimeText => write!(f, "Sublime Text"),
+            BaseKeymap::Atom => write!(f, "Atom"),
+            BaseKeymap::TextMate => write!(f, "TextMate"),
+        }
+    }
+}
+
 impl BaseKeymap {
     pub const OPTIONS: [(&'static str, Self); 5] = [
         ("VSCode (Default)", Self::VSCode),

crates/welcome/src/welcome.rs 🔗

@@ -1,7 +1,7 @@
 mod base_keymap_picker;
 mod base_keymap_setting;
 
-use client::TelemetrySettings;
+use client::{telemetry::Telemetry, TelemetrySettings};
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
@@ -14,7 +14,7 @@ use ui::{prelude::*, Checkbox};
 use vim::VimModeSetting;
 use workspace::{
     dock::DockPosition,
-    item::{Item, ItemEvent},
+    item::{Item, ItemEvent, ItemHandle},
     open_new, AppState, Welcome, Workspace, WorkspaceId,
 };
 
@@ -27,7 +27,7 @@ pub fn init(cx: &mut AppContext) {
 
     cx.observe_new_views(|workspace: &mut Workspace, _cx| {
         workspace.register_action(|workspace, _: &Welcome, cx| {
-            let welcome_page = cx.new_view(|cx| WelcomePage::new(workspace, cx));
+            let welcome_page = WelcomePage::new(workspace, cx);
             workspace.add_item(Box::new(welcome_page), cx)
         });
     })
@@ -39,7 +39,7 @@ pub fn init(cx: &mut AppContext) {
 pub fn show_welcome_view(app_state: &Arc<AppState>, cx: &mut AppContext) {
     open_new(&app_state, cx, |workspace, cx| {
         workspace.toggle_dock(DockPosition::Left, cx);
-        let welcome_page = cx.new_view(|cx| WelcomePage::new(workspace, cx));
+        let welcome_page = WelcomePage::new(workspace, cx);
         workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
         cx.focus_view(&welcome_page);
         cx.notify();
@@ -54,174 +54,248 @@ pub fn show_welcome_view(app_state: &Arc<AppState>, cx: &mut AppContext) {
 pub struct WelcomePage {
     workspace: WeakView<Workspace>,
     focus_handle: FocusHandle,
+    telemetry: Arc<Telemetry>,
     _settings_subscription: Subscription,
 }
 
 impl Render for WelcomePage {
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
-        h_stack()
-            .full()
-            .bg(cx.theme().colors().editor_background)
-            .track_focus(&self.focus_handle)
-            .child(
-                v_stack()
-                    .w_96()
-                    .gap_4()
-                    .mx_auto()
-                    .child(
-                        svg()
-                            .path("icons/logo_96.svg")
-                            .text_color(gpui::white())
-                            .w(px(96.))
-                            .h(px(96.))
-                            .mx_auto(),
-                    )
-                    .child(
-                        h_stack()
-                            .justify_center()
-                            .child(Label::new("Code at the speed of thought")),
-                    )
-                    .child(
-                        v_stack()
-                            .gap_2()
-                            .child(
-                                Button::new("choose-theme", "Choose a theme")
-                                    .full_width()
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.workspace
-                                            .update(cx, |workspace, cx| {
-                                                theme_selector::toggle(
-                                                    workspace,
-                                                    &Default::default(),
-                                                    cx,
-                                                )
-                                            })
-                                            .ok();
-                                    })),
-                            )
-                            .child(
-                                Button::new("choose-keymap", "Choose a keymap")
-                                    .full_width()
-                                    .on_click(cx.listener(|this, _, cx| {
-                                        this.workspace
-                                            .update(cx, |workspace, cx| {
-                                                base_keymap_picker::toggle(
-                                                    workspace,
-                                                    &Default::default(),
-                                                    cx,
-                                                )
-                                            })
-                                            .ok();
-                                    })),
-                            )
-                            .child(
-                                Button::new("install-cli", "Install the CLI")
-                                    .full_width()
-                                    .on_click(cx.listener(|_, _, cx| {
-                                        cx.app_mut()
-                                            .spawn(|cx| async move {
-                                                install_cli::install_cli(&cx).await
-                                            })
-                                            .detach_and_log_err(cx);
-                                    })),
-                            ),
-                    )
-                    .child(
-                        v_stack()
-                            .p_3()
-                            .gap_2()
-                            .bg(cx.theme().colors().elevated_surface_background)
-                            .border_1()
-                            .border_color(cx.theme().colors().border)
-                            .rounded_md()
-                            .child(
-                                h_stack()
-                                    .gap_2()
-                                    .child(
-                                        Checkbox::new(
-                                            "enable-vim",
-                                            if VimModeSetting::get_global(cx).0 {
-                                                ui::Selection::Selected
-                                            } else {
-                                                ui::Selection::Unselected
-                                            },
+        h_stack().full().track_focus(&self.focus_handle).child(
+            v_stack()
+                .w_96()
+                .gap_4()
+                .mx_auto()
+                .child(
+                    svg()
+                        .path("icons/logo_96.svg")
+                        .text_color(gpui::white())
+                        .w(px(96.))
+                        .h(px(96.))
+                        .mx_auto(),
+                )
+                .child(
+                    h_stack()
+                        .justify_center()
+                        .child(Label::new("Code at the speed of thought")),
+                )
+                .child(
+                    v_stack()
+                        .gap_2()
+                        .child(
+                            Button::new("choose-theme", "Choose a theme")
+                                .full_width()
+                                .on_click(cx.listener(|this, _, cx| {
+                                    this.telemetry
+                                        .report_app_event("welcome page button: theme", false);
+                                    this.workspace
+                                        .update(cx, |workspace, cx| {
+                                            theme_selector::toggle(
+                                                workspace,
+                                                &Default::default(),
+                                                cx,
+                                            )
+                                        })
+                                        .ok();
+                                })),
+                        )
+                        .child(
+                            Button::new("choose-keymap", "Choose a keymap")
+                                .full_width()
+                                .on_click(cx.listener(|this, _, cx| {
+                                    this.telemetry
+                                        .report_app_event("welcome page button: keymap", false);
+                                    this.workspace
+                                        .update(cx, |workspace, cx| {
+                                            base_keymap_picker::toggle(
+                                                workspace,
+                                                &Default::default(),
+                                                cx,
+                                            )
+                                        })
+                                        .ok();
+                                })),
+                        )
+                        .child(
+                            Button::new("install-cli", "Install the CLI")
+                                .full_width()
+                                .on_click(cx.listener(|this, _, cx| {
+                                    this.telemetry.report_app_event(
+                                        "welcome page button: install cli",
+                                        false,
+                                    );
+                                    cx.app_mut()
+                                        .spawn(
+                                            |cx| async move { install_cli::install_cli(&cx).await },
                                         )
-                                        .on_click(
-                                            cx.listener(move |this, selection, cx| {
-                                                this.update_settings::<VimModeSetting>(
-                                                    selection,
-                                                    cx,
-                                                    |setting, value| *setting = Some(value),
-                                                );
-                                            }),
-                                        ),
+                                        .detach_and_log_err(cx);
+                                })),
+                        ),
+                )
+                .child(
+                    v_stack()
+                        .p_3()
+                        .gap_2()
+                        .bg(cx.theme().colors().elevated_surface_background)
+                        .border_1()
+                        .border_color(cx.theme().colors().border)
+                        .rounded_md()
+                        .child(
+                            h_stack()
+                                .gap_2()
+                                .child(
+                                    Checkbox::new(
+                                        "enable-vim",
+                                        if VimModeSetting::get_global(cx).0 {
+                                            ui::Selection::Selected
+                                        } else {
+                                            ui::Selection::Unselected
+                                        },
                                     )
-                                    .child(Label::new("Enable vim mode")),
-                            )
-                            .child(
-                                h_stack()
-                                    .gap_2()
-                                    .child(
-                                        Checkbox::new(
-                                            "enable-telemetry",
-                                            if TelemetrySettings::get_global(cx).metrics {
-                                                ui::Selection::Selected
-                                            } else {
-                                                ui::Selection::Unselected
-                                            },
-                                        )
-                                        .on_click(
-                                            cx.listener(move |this, selection, cx| {
-                                                this.update_settings::<TelemetrySettings>(
-                                                    selection,
-                                                    cx,
-                                                    |settings, value| {
-                                                        settings.metrics = Some(value)
-                                                    },
-                                                );
-                                            }),
-                                        ),
+                                    .on_click(cx.listener(
+                                        move |this, selection, cx| {
+                                            this.telemetry.report_app_event(
+                                                "welcome page button: vim",
+                                                false,
+                                            );
+                                            this.update_settings::<VimModeSetting>(
+                                                selection,
+                                                cx,
+                                                |setting, value| *setting = Some(value),
+                                            );
+                                        },
+                                    )),
+                                )
+                                .child(Label::new("Enable vim mode")),
+                        )
+                        .child(
+                            h_stack()
+                                .gap_2()
+                                .child(
+                                    Checkbox::new(
+                                        "enable-telemetry",
+                                        if TelemetrySettings::get_global(cx).metrics {
+                                            ui::Selection::Selected
+                                        } else {
+                                            ui::Selection::Unselected
+                                        },
                                     )
-                                    .child(Label::new("Send anonymous usage data")),
-                            )
-                            .child(
-                                h_stack()
-                                    .gap_2()
-                                    .child(
-                                        Checkbox::new(
-                                            "enable-crash",
-                                            if TelemetrySettings::get_global(cx).diagnostics {
-                                                ui::Selection::Selected
-                                            } else {
-                                                ui::Selection::Unselected
-                                            },
-                                        )
-                                        .on_click(
-                                            cx.listener(move |this, selection, cx| {
-                                                this.update_settings::<TelemetrySettings>(
-                                                    selection,
-                                                    cx,
-                                                    |settings, value| {
-                                                        settings.diagnostics = Some(value)
-                                                    },
-                                                );
-                                            }),
-                                        ),
+                                    .on_click(cx.listener(
+                                        move |this, selection, cx| {
+                                            this.telemetry.report_app_event(
+                                                "welcome page button: user telemetry",
+                                                false,
+                                            );
+                                            this.update_settings::<TelemetrySettings>(
+                                                selection,
+                                                cx,
+                                                {
+                                                    let telemetry = this.telemetry.clone();
+
+                                                    move |settings, value| {
+                                                        settings.metrics = Some(value);
+
+                                                        telemetry.report_setting_event(
+                                                            "user telemetry",
+                                                            value.to_string(),
+                                                        );
+                                                    }
+                                                },
+                                            );
+                                        },
+                                    )),
+                                )
+                                .child(Label::new("Send anonymous usage data")),
+                        )
+                        .child(
+                            h_stack()
+                                .gap_2()
+                                .child(
+                                    Checkbox::new(
+                                        "enable-crash",
+                                        if TelemetrySettings::get_global(cx).diagnostics {
+                                            ui::Selection::Selected
+                                        } else {
+                                            ui::Selection::Unselected
+                                        },
                                     )
-                                    .child(Label::new("Send crash reports")),
-                            ),
-                    ),
-            )
+                                    .on_click(cx.listener(
+                                        move |this, selection, cx| {
+                                            this.telemetry.report_app_event(
+                                                "welcome page button: crash diagnostics",
+                                                false,
+                                            );
+                                            this.update_settings::<TelemetrySettings>(
+                                                selection,
+                                                cx,
+                                                {
+                                                    let telemetry = this.telemetry.clone();
+
+                                                    move |settings, value| {
+                                                        settings.diagnostics = Some(value);
+
+                                                        telemetry.report_setting_event(
+                                                            "crash diagnostics",
+                                                            value.to_string(),
+                                                        );
+                                                    }
+                                                },
+                                            );
+                                        },
+                                    )),
+                                )
+                                .child(Label::new("Send crash reports")),
+                        ),
+                ),
+        )
     }
 }
 
 impl WelcomePage {
-    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
-        WelcomePage {
+    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
+        let this = cx.new_view(|cx| WelcomePage {
             focus_handle: cx.focus_handle(),
             workspace: workspace.weak_handle(),
+            telemetry: workspace.client().telemetry().clone(),
             _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
-        }
+        });
+
+        this.on_release(
+            cx,
+            Box::new(|cx| {
+                this.update(cx, |this, _| {
+                    this.telemetry.report_app_event("close welcome page", false);
+                })
+            }),
+        )
+        .detach();
+
+        // this.subscribe_to_item_events(
+        //     cx,
+        //     Box::new(|event: ItemEvent, cx| {
+        //         // if event == ItemEvent::CloseItem {
+        //         dbg!(event);
+        //         // welcome.update(cx, |welcome, _| {
+        //         //     welcome
+        //         //         .telemetry
+        //         //         .report_app_event("close welcome page", false);
+        //         // })
+        //         // }
+        //     }),
+        // )
+        // .detach();
+
+        cx.subscribe(&this, |_, welcome, event, cx| {
+            if *event == ItemEvent::CloseItem {
+                welcome.update(cx, |welcome, _| {
+                    welcome
+                        .telemetry
+                        .report_app_event("close welcome page", false);
+                })
+            }
+        })
+        .detach();
+
+        this
     }
 
     fn update_settings<T: Settings>(
@@ -279,6 +353,7 @@ impl Item for WelcomePage {
         Some(cx.new_view(|cx| WelcomePage {
             focus_handle: cx.focus_handle(),
             workspace: self.workspace.clone(),
+            telemetry: self.telemetry.clone(),
             _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
         }))
     }
@@ -287,3 +362,16 @@ impl Item for WelcomePage {
         f(*event)
     }
 }
+
+// TODO
+//    - [X] get theme value
+//        - [X] In selector
+//        - [X] In main
+//    - [ ] get value of keymap selector
+//         - [X] In selector
+//         - [X] In main
+//    - [ ] get all button clicks
+//    - [ ] get value of usage data enabled
+//    - [ ] get value of crash reports enabled
+//    - [ ] get welcome screen close
+//    - [ ] test all events

crates/workspace/src/workspace.rs 🔗

@@ -1260,7 +1260,7 @@ impl Workspace {
     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
         self.client()
             .telemetry()
-            .report_app_event("open project", false, cx);
+            .report_app_event("open project", false);
         let paths = cx.prompt_for_paths(PathPromptOptions {
             files: true,
             directories: true,

crates/zed/src/main.rs 🔗

@@ -45,7 +45,7 @@ use util::{
     paths, ResultExt,
 };
 use uuid::Uuid;
-use welcome::{show_welcome_view, FIRST_OPEN};
+use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
 use workspace::{AppState, WorkspaceStore};
 use zed::{
     app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
@@ -171,17 +171,17 @@ fn main() {
         })
         .detach();
 
-        client.telemetry().start(installation_id, session_id, cx);
-        client
-            .telemetry()
-            .report_setting_event("theme", cx.theme().name.to_string(), cx);
-        let event_operation = match existing_installation_id_found {
-            Some(false) => "first open",
-            _ => "open",
-        };
-        client
-            .telemetry()
-            .report_app_event(event_operation, true, cx);
+        let telemetry = client.telemetry();
+        telemetry.start(installation_id, session_id, cx);
+        telemetry.report_setting_event("theme", cx.theme().name.to_string());
+        telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string());
+        telemetry.report_app_event(
+            match existing_installation_id_found {
+                Some(false) => "first open",
+                _ => "open",
+            },
+            true,
+        );
 
         let app_state = Arc::new(AppState {
             languages: languages.clone(),