diff --git a/assets/settings/default.json b/assets/settings/default.json index 5b73d7643c84d344bd8f133f2b52427c0b952adf..3924e84d61a051c26b0b5e34719f71a53b92d631 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -79,6 +79,13 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // Control what info Zed sends to our servers + "telemetry": { + // Send debug info like crash reports. + "diagnostics": true, + // Send anonymized usage data like what languages you're using Zed with. + "metrics": true + }, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f0c64a1bb995f3a710301de16fd8c00e02dc0088..dd2be6e3185a53e1d75adaec7ef3c9d97c054d07 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -51,9 +51,17 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, + pub telemetry_defaults: TelemetrySettings, + pub telemetry_overrides: TelemetrySettings, pub staff_mode: bool, } +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettings { + diagnostics: Option, + metrics: Option, +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct FeatureFlags { pub experimental_themes: bool, @@ -302,6 +310,8 @@ pub struct SettingsFileContent { #[serde(default)] pub theme: Option, #[serde(default)] + pub telemetry: TelemetrySettings, + #[serde(default)] pub staff_mode: Option, } @@ -312,6 +322,7 @@ pub struct LspSettings { } impl Settings { + /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( assets: impl AssetSource, font_cache: &FontCache, @@ -363,11 +374,13 @@ impl Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - + telemetry_defaults: defaults.telemetry, + telemetry_overrides: Default::default(), staff_mode: false, } } + // Fill out the overrride and etc. settings from the user's settings.json pub fn set_user_settings( &mut self, data: SettingsFileContent, @@ -419,6 +432,7 @@ impl Settings { self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; self.language_overrides = data.languages; + self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; } @@ -489,6 +503,20 @@ impl Settings { .unwrap_or_else(|| R::default()) } + pub fn telemetry_diagnostics(&self) -> bool { + self.telemetry_overrides + .diagnostics + .or(self.telemetry_defaults.diagnostics) + .expect("missing default") + } + + pub fn telemetry_metrics(&self) -> bool { + self.telemetry_overrides + .metrics + .or(self.telemetry_defaults.metrics) + .expect("missing default") + } + pub fn terminal_scroll(&self) -> AlternateScroll { self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref()) } @@ -540,6 +568,8 @@ impl Settings { lsp: Default::default(), projects_online_by_default: true, theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), + telemetry_defaults: Default::default(), + telemetry_overrides: Default::default(), staff_mode: false, } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3861c53e792eb0fd943ede744a69ed1ab392e471..72115022d723d975629b26ac2159b0a55535d0de 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -18,7 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task, ViewContext}; +use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; @@ -50,10 +50,13 @@ fn main() { log::info!("========== starting zed =========="); let mut app = gpui::App::new(Assets).unwrap(); + let app_version = ZED_APP_VERSION .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); - init_panic_hook(app_version, http.clone(), app.background()); + init_panic_hook(app_version); + + app.background(); load_embedded_fonts(&app); @@ -61,7 +64,6 @@ fn main() { let themes = ThemeRegistry::new(Assets, app.font_cache()); let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { @@ -88,15 +90,6 @@ fn main() { cx.set_global(*RELEASE_CHANNEL); cx.set_global(HomeDir(paths::HOME.to_path_buf())); - let client = client::Client::new(http.clone(), cx); - let mut languages = LanguageRegistry::new(login_shell_env_loaded); - languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); - let languages = Arc::new(languages); - let init_languages = cx - .background() - .spawn(languages::init(languages.clone(), cx.background().clone())); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); //Setup settings global before binding actions @@ -105,7 +98,19 @@ fn main() { settings_file_content.clone(), fs.clone(), )); + watch_settings_file(default_settings, settings_file_content, themes.clone(), cx); + upload_previous_panics(http.clone(), cx); + + let client = client::Client::new(http.clone(), cx); + let mut languages = LanguageRegistry::new(login_shell_env_loaded); + languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); + let languages = Arc::new(languages); + let init_languages = cx + .background() + .spawn(languages::init(languages.clone(), cx.background().clone())); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); + watch_keymap_file(keymap_file, cx); context_menu::init(cx); @@ -251,65 +256,7 @@ fn init_logger() { } } -fn init_panic_hook(app_version: String, http: Arc, background: Arc) { - background - .spawn({ - async move { - let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); - let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; - - let mut components = filename.split('-'); - if components.next() != Some("zed") { - continue; - } - let version = if let Some(version) = components.next() { - version - } else { - continue; - }; - - let text = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - let body = serde_json::to_string(&json!({ - "text": text, - "version": version, - "token": ZED_SECRET_CLIENT_TOKEN, - })) - .unwrap(); - let request = Request::post(&panic_report_url) - .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "application/json") - .body(body.into())?; - let response = http.send(request).await.context("error sending panic")?; - if response.status().is_success() { - std::fs::remove_file(child_path) - .context("error removing panic after sending it successfully") - .log_err(); - } else { - return Err(anyhow!( - "error uploading panic to server: {}", - response.status() - )); - } - } - Ok::<_, anyhow::Error>(()) - } - .log_err() - }) - .detach(); - +fn init_panic_hook(app_version: String) { let is_pty = stdout_is_a_pty(); panic::set_hook(Box::new(move |info| { let backtrace = Backtrace::new(); @@ -358,6 +305,69 @@ fn init_panic_hook(app_version: String, http: Arc, background: A })); } +fn upload_previous_panics(http: Arc, cx: &mut MutableAppContext) { + let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + + cx.background() + .spawn({ + async move { + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + + if child_path.extension() != Some(OsStr::new("panic")) { + continue; + } + let filename = if let Some(filename) = child_path.file_name() { + filename.to_string_lossy() + } else { + continue; + }; + + let mut components = filename.split('-'); + if components.next() != Some("zed") { + continue; + } + let version = if let Some(version) = components.next() { + version + } else { + continue; + }; + + if diagnostics_telemetry { + let text = smol::fs::read_to_string(&child_path) + .await + .context("error reading panic file")?; + let body = serde_json::to_string(&json!({ + "text": text, + "version": version, + "token": ZED_SECRET_CLIENT_TOKEN, + })) + .unwrap(); + let request = Request::post(&panic_report_url) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(body.into())?; + let response = http.send(request).await.context("error sending panic")?; + if !response.status().is_success() { + log::error!("Error uploading panic to server: {}", response.status()); + } + } + + // We've done what we can, delete the file + std::fs::remove_file(child_path) + .context("error removing panic") + .log_err(); + } + Ok::<_, anyhow::Error>(()) + } + .log_err() + }) + .detach(); +} + async fn load_login_shell_environment() -> Result<()> { let marker = "ZED_LOGIN_SHELL_START"; let shell = env::var("SHELL").context(