From 6b0faa2d9cf78dd7ba657cfe56e55a0e18800576 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 28 Apr 2023 16:06:55 -0400 Subject: [PATCH 01/26] Rework telemetry code to support sending events to Clickhouse Co-Authored-By: Max Brunsfeld --- Cargo.lock | 1 + crates/client/src/client.rs | 35 +---- crates/client/src/telemetry.rs | 174 ++++++++++++++++++++++--- crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 28 +++- crates/editor/src/items.rs | 2 +- crates/feedback/src/feedback_editor.rs | 5 +- crates/zed/src/main.rs | 4 +- crates/zed/src/zed.rs | 2 +- 9 files changed, 192 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b6ebbe5edcf3b83762e7abf7ba6b1db1a74adff..54b2868acfeb0896d621d9661407443e88eb6b17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1972,6 +1972,7 @@ version = "0.1.0" dependencies = [ "aho-corasick", "anyhow", + "client", "clock", "collections", "context_menu", diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62135900a385b6c8fe674a224fa03ff87cb98156..18a0f32ed6de61de2ce668207f194bf7b1a03e3c 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -17,7 +17,7 @@ use futures::{ use gpui::{ actions, platform::AppVersion, - serde_json::{self, Value}, + serde_json::{self}, AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext, WeakViewHandle, }; @@ -27,7 +27,7 @@ use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use serde::Deserialize; -use settings::{Settings, TelemetrySettings}; +use settings::Settings; use std::{ any::TypeId, collections::HashMap, @@ -47,6 +47,7 @@ use util::http::HttpClient; use util::{ResultExt, TryFutureExt}; pub use rpc::*; +pub use telemetry::ClickhouseEvent; pub use user::*; lazy_static! { @@ -736,7 +737,7 @@ impl Client { read_from_keychain = credentials.is_some(); if read_from_keychain { cx.read(|cx| { - self.report_event( + self.telemetry().report_mixpanel_event( "read credentials from keychain", Default::default(), cx.global::().telemetry(), @@ -1116,7 +1117,7 @@ impl Client { .context("failed to decrypt access token")?; platform.activate(true); - telemetry.report_event( + telemetry.report_mixpanel_event( "authenticate with browser", Default::default(), metrics_enabled, @@ -1338,30 +1339,8 @@ impl Client { } } - pub fn start_telemetry(&self) { - self.telemetry.start(); - } - - pub fn report_event( - &self, - kind: &str, - properties: Value, - telemetry_settings: TelemetrySettings, - ) { - self.telemetry - .report_event(kind, properties.clone(), telemetry_settings); - } - - pub fn telemetry_log_file_path(&self) -> Option { - self.telemetry.log_file_path() - } - - pub fn metrics_id(&self) -> Option> { - self.telemetry.metrics_id() - } - - pub fn is_staff(&self) -> Option { - self.telemetry.is_staff() + pub fn telemetry(&self) -> &Arc { + &self.telemetry } } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7ee099dfabf8f67256e7cd945ff7981c4cd5d3e7..c8f6deada8a71dff2ec2ad0f278ab6f63f75ebe6 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,3 +1,4 @@ +use crate::{ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use db::kvp::KEY_VALUE_STORE; use gpui::{ executor::Background, @@ -35,20 +36,56 @@ struct TelemetryState { release_channel: Option<&'static str>, os_version: Option>, os_name: &'static str, - queue: Vec, - next_event_id: usize, - flush_task: Option>, + mixpanel_events_queue: Vec, // Mixpanel mixed events - will hopefully die soon + clickhouse_events_queue: Vec, + next_mixpanel_event_id: usize, + flush_mixpanel_events_task: Option>, + flush_clickhouse_events_task: Option>, log_file: Option, is_staff: Option, } const MIXPANEL_EVENTS_URL: &'static str = "https://api.mixpanel.com/track"; const MIXPANEL_ENGAGE_URL: &'static str = "https://api.mixpanel.com/engage#profile-set"; +const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events"; lazy_static! { static ref MIXPANEL_TOKEN: Option = std::env::var("ZED_MIXPANEL_TOKEN") .ok() .or_else(|| option_env!("ZED_MIXPANEL_TOKEN").map(|key| key.to_string())); + static ref CLICKHOUSE_EVENTS_URL: String = + format!("{}{}", *ZED_SERVER_URL, CLICKHOUSE_EVENTS_URL_PATH); +} + +#[derive(Serialize, Debug)] +struct ClickhouseEventRequestBody { + token: &'static str, + installation_id: Option>, + app_version: Option>, + os_name: &'static str, + os_version: Option>, + release_channel: Option<&'static str>, + events: Vec, +} + +#[derive(Serialize, Debug)] +struct ClickhouseEventWrapper { + time: u128, + signed_in: bool, + #[serde(flatten)] + event: ClickhouseEvent, +} + +#[derive(Serialize, Debug)] +#[serde(tag = "type")] +pub enum ClickhouseEvent { + Editor { + operation: &'static str, + file_extension: Option, + vim_mode: bool, + copilot_enabled: bool, + copilot_enabled_for_language: bool, + }, } #[derive(Serialize, Debug)] @@ -121,9 +158,11 @@ impl Telemetry { release_channel, device_id: None, metrics_id: None, - queue: Default::default(), - flush_task: Default::default(), - next_event_id: 0, + mixpanel_events_queue: Default::default(), + clickhouse_events_queue: Default::default(), + flush_mixpanel_events_task: Default::default(), + flush_clickhouse_events_task: Default::default(), + next_mixpanel_event_id: 0, log_file: None, is_staff: None, }), @@ -168,15 +207,24 @@ impl Telemetry { let device_id: Arc = device_id.into(); let mut state = this.state.lock(); state.device_id = Some(device_id.clone()); - for event in &mut state.queue { + + for event in &mut state.mixpanel_events_queue { event .properties .distinct_id .get_or_insert_with(|| device_id.clone()); } - if !state.queue.is_empty() { - drop(state); - this.flush(); + + let has_mixpanel_events = !state.mixpanel_events_queue.is_empty(); + let has_clickhouse_events = !state.clickhouse_events_queue.is_empty(); + drop(state); + + if has_mixpanel_events { + this.flush_mixpanel_events(); + } + + if has_clickhouse_events { + this.flush_clickhouse_events(); } anyhow::Ok(()) @@ -231,7 +279,42 @@ impl Telemetry { } } - pub fn report_event( + pub fn report_clickhouse_event( + self: &Arc, + event: ClickhouseEvent, + telemetry_settings: TelemetrySettings, + ) { + if !telemetry_settings.metrics() { + return; + } + + let mut state = self.state.lock(); + let signed_in = state.metrics_id.is_some(); + state.clickhouse_events_queue.push(ClickhouseEventWrapper { + time: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + signed_in, + event, + }); + + if state.device_id.is_some() { + if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN { + drop(state); + self.flush_clickhouse_events(); + } else { + let this = self.clone(); + let executor = self.executor.clone(); + state.flush_clickhouse_events_task = Some(self.executor.spawn(async move { + executor.timer(DEBOUNCE_INTERVAL).await; + this.flush_clickhouse_events(); + })); + } + } + } + + pub fn report_mixpanel_event( self: &Arc, kind: &str, properties: Value, @@ -243,7 +326,7 @@ impl Telemetry { let mut state = self.state.lock(); let event = MixpanelEvent { - event: kind.to_string(), + event: kind.into(), properties: MixpanelEventProperties { token: "", time: SystemTime::now() @@ -251,7 +334,7 @@ impl Telemetry { .unwrap() .as_millis(), distinct_id: state.device_id.clone(), - insert_id: post_inc(&mut state.next_event_id), + insert_id: post_inc(&mut state.next_mixpanel_event_id), event_properties: if let Value::Object(properties) = properties { Some(properties) } else { @@ -264,17 +347,17 @@ impl Telemetry { signed_in: state.metrics_id.is_some(), }, }; - state.queue.push(event); + state.mixpanel_events_queue.push(event); if state.device_id.is_some() { - if state.queue.len() >= MAX_QUEUE_LEN { + if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN { drop(state); - self.flush(); + self.flush_mixpanel_events(); } else { let this = self.clone(); let executor = self.executor.clone(); - state.flush_task = Some(self.executor.spawn(async move { + state.flush_mixpanel_events_task = Some(self.executor.spawn(async move { executor.timer(DEBOUNCE_INTERVAL).await; - this.flush(); + this.flush_mixpanel_events(); })); } } @@ -288,10 +371,10 @@ impl Telemetry { self.state.lock().is_staff } - fn flush(self: &Arc) { + fn flush_mixpanel_events(self: &Arc) { let mut state = self.state.lock(); - let mut events = mem::take(&mut state.queue); - state.flush_task.take(); + let mut events = mem::take(&mut state.mixpanel_events_queue); + state.flush_mixpanel_events_task.take(); drop(state); if let Some(token) = MIXPANEL_TOKEN.as_ref() { @@ -325,4 +408,53 @@ impl Telemetry { .detach(); } } + + fn flush_clickhouse_events(self: &Arc) { + let mut state = self.state.lock(); + let mut events = mem::take(&mut state.clickhouse_events_queue); + state.flush_clickhouse_events_task.take(); + drop(state); + + let this = self.clone(); + self.executor + .spawn( + async move { + let mut json_bytes = Vec::new(); + + if let Some(file) = &mut this.state.lock().log_file { + let file = file.as_file_mut(); + for event in &mut events { + json_bytes.clear(); + serde_json::to_writer(&mut json_bytes, event)?; + file.write_all(&json_bytes)?; + file.write(b"\n")?; + } + } + + { + let state = this.state.lock(); + json_bytes.clear(); + serde_json::to_writer( + &mut json_bytes, + &ClickhouseEventRequestBody { + token: ZED_SECRET_CLIENT_TOKEN, + installation_id: state.device_id.clone(), + app_version: state.app_version.clone(), + os_name: state.os_name, + os_version: state.os_version.clone(), + release_channel: state.release_channel, + events, + }, + )?; + } + + this.http_client + .post_json(CLICKHOUSE_EVENTS_URL.as_str(), json_bytes.into()) + .await?; + anyhow::Ok(()) + } + .log_err(), + ) + .detach(); + } } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index feb55e1b2fc09516cf80b8e191216a43bcaa3fe4..70b62737443b53e3d145dd140c66fb9fca6910ab 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -23,6 +23,7 @@ test-support = [ ] [dependencies] +client = { path = "../client" } clock = { path = "../clock" } copilot = { path = "../copilot" } db = { path = "../db" } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index eea418b211e27caf27ec1168cddd60ec62b022c2..7a6a9a8de81b7cae1a7d157b9a0757295ba63646 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,6 +22,7 @@ pub mod test; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; +use client::ClickhouseEvent; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; @@ -1295,7 +1296,7 @@ impl Editor { cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); } - this.report_event("open editor", cx); + this.report_editor_event("open", cx); this } @@ -6819,7 +6820,7 @@ impl Editor { .collect() } - fn report_event(&self, name: &str, cx: &AppContext) { + fn report_editor_event(&self, name: &'static str, cx: &AppContext) { if let Some((project, file)) = self.project.as_ref().zip( self.buffer .read(cx) @@ -6831,11 +6832,28 @@ impl Editor { let extension = Path::new(file.file_name(cx)) .extension() .and_then(|e| e.to_str()); - project.read(cx).client().report_event( - name, - json!({ "File Extension": extension, "Vim Mode": settings.vim_mode }), + let telemetry = project.read(cx).client().telemetry().clone(); + telemetry.report_mixpanel_event( + match name { + "open" => "open editor", + "save" => "save editor", + _ => name, + }, + json!({ "File Extension": extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }), settings.telemetry(), ); + let event = ClickhouseEvent::Editor { + file_extension: extension.map(ToString::to_string), + vim_mode: settings.vim_mode, + operation: name, + copilot_enabled: settings.features.copilot, + copilot_enabled_for_language: settings.show_copilot_suggestions( + self.language_at(0, cx) + .map(|language| language.name()) + .as_deref(), + ), + }; + telemetry.report_clickhouse_event(event, settings.telemetry()) } } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 83e971358d55eb187bf46d7f0f866f9cff065ebe..dcd49607fb95186caf302e8a29b7fb720b202451 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -636,7 +636,7 @@ impl Item for Editor { project: ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.report_event("save editor", cx); + self.report_editor_event("save", cx); let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); let buffers = self.buffer().clone().read(cx).all_buffers(); cx.spawn(|_, mut cx| async move { diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 7bf53280486f6d0c34f64d89b0959710ea9de09a..8f41762eed2d769020530f3af81c745806789d3a 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -164,8 +164,9 @@ impl FeedbackEditor { ) -> anyhow::Result<()> { let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); - let metrics_id = zed_client.metrics_id(); - let is_staff = zed_client.is_staff(); + let telemetry = zed_client.telemetry(); + let metrics_id = telemetry.metrics_id(); + let is_staff = telemetry.is_staff(); let http_client = zed_client.http_client(); let request = FeedbackRequestBody { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 679fd39e2f9455d461b946507bd800c23110bf27..2724e8587f362cf55fd7cfe7792c700385e4430f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -172,8 +172,8 @@ fn main() { }) .detach(); - client.start_telemetry(); - client.report_event( + client.telemetry().start(); + client.telemetry().report_mixpanel_event( "start app", Default::default(), cx.global::().telemetry(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 494739a9678f936fbb486aef4292321e3e4f5390..01a5decfd09df1c6bd1eb81270f2dffc683952cb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -573,7 +573,7 @@ fn open_telemetry_log_file( workspace.with_local_workspace(&app_state.clone(), cx, move |_, cx| { cx.spawn(|workspace, mut cx| async move { async fn fetch_log_string(app_state: &Arc) -> Option { - let path = app_state.client.telemetry_log_file_path()?; + let path = app_state.client.telemetry().log_file_path()?; app_state.fs.load(&path).await.log_err() } From 1bf85214a4579342dca1cdfb002a76053525f764 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 28 Apr 2023 16:42:36 -0400 Subject: [PATCH 02/26] Source ESLint server from Github rather than 3rd party NPM package --- crates/copilot/src/copilot.rs | 2 +- crates/node_runtime/src/node_runtime.rs | 46 ++++++++++--- crates/util/src/github.rs | 32 ++++++--- crates/zed/src/languages/c.rs | 2 +- crates/zed/src/languages/elixir.rs | 2 +- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/html.rs | 2 +- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/languages/lua.rs | 2 +- crates/zed/src/languages/python.rs | 2 +- crates/zed/src/languages/rust.rs | 8 +-- crates/zed/src/languages/typescript.rs | 90 +++++++++++++++---------- crates/zed/src/languages/yaml.rs | 2 +- 13 files changed, 128 insertions(+), 66 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 6bc3622ab170f35547a920c702a3ab8ae349548f..f7ff163424ecde9dfff5ff4709d0a69616c44099 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -932,7 +932,7 @@ async fn get_copilot_lsp(http: Arc) -> anyhow::Result { ///Check for the latest copilot language server and download it if we haven't already async fn fetch_latest(http: Arc) -> anyhow::Result { - let release = latest_github_release("zed-industries/copilot", http.clone()).await?; + let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?; let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name)); diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 079b6a5e45a97b920925777b5d6be96d1bca49e0..e2a8d0d0032e23ea6751855acce33d90bded46d8 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -5,7 +5,7 @@ use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; use parking_lot::Mutex; use serde::Deserialize; -use smol::{fs, io::BufReader}; +use smol::{fs, io::BufReader, process::Command}; use std::{ env::consts, path::{Path, PathBuf}, @@ -48,12 +48,41 @@ impl NodeRuntime { Ok(installation_path.join("bin/node")) } + pub async fn run_npm_subcommand( + &self, + directory: &Path, + subcommand: &str, + args: &[&str], + ) -> Result<()> { + let installation_path = self.install_if_needed().await?; + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + let output = Command::new(node_binary) + .arg(npm_file) + .arg(subcommand) + .args(args) + .current_dir(directory) + .output() + .await?; + + if !output.status.success() { + return Err(anyhow!( + "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + Ok(()) + } + pub async fn npm_package_latest_version(&self, name: &str) -> Result { let installation_path = self.install_if_needed().await?; let node_binary = installation_path.join("bin/node"); let npm_file = installation_path.join("bin/npm"); - let output = smol::process::Command::new(node_binary) + let output = Command::new(node_binary) .arg(npm_file) .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) @@ -64,11 +93,11 @@ impl NodeRuntime { .context("failed to run npm info")?; if !output.status.success() { - Err(anyhow!( + return Err(anyhow!( "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) - ))?; + )); } let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; @@ -80,14 +109,14 @@ impl NodeRuntime { pub async fn npm_install_packages( &self, - packages: impl IntoIterator, directory: &Path, + packages: impl IntoIterator, ) -> Result<()> { let installation_path = self.install_if_needed().await?; let node_binary = installation_path.join("bin/node"); let npm_file = installation_path.join("bin/npm"); - let output = smol::process::Command::new(node_binary) + let output = Command::new(node_binary) .arg(npm_file) .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) @@ -103,12 +132,13 @@ impl NodeRuntime { .output() .await .context("failed to run npm install")?; + if !output.status.success() { - Err(anyhow!( + return Err(anyhow!( "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) - ))?; + )); } Ok(()) } diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index 3bb4baa2937536f6c77d407ec22bea1c0320829f..b1e981ae4963e19829a91cc3cd961d604c9d1643 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -1,5 +1,5 @@ use crate::http::HttpClient; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use futures::AsyncReadExt; use serde::Deserialize; use std::sync::Arc; @@ -12,7 +12,10 @@ pub struct GitHubLspBinaryVersion { #[derive(Deserialize, Debug)] pub struct GithubRelease { pub name: String, + #[serde(rename = "prerelease")] + pub pre_release: bool, pub assets: Vec, + pub tarball_url: String, } #[derive(Deserialize, Debug)] @@ -23,16 +26,18 @@ pub struct GithubReleaseAsset { pub async fn latest_github_release( repo_name_with_owner: &str, + pre_release: bool, http: Arc, ) -> Result { let mut response = http .get( - &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"), + &format!("https://api.github.com/repos/{repo_name_with_owner}/releases"), Default::default(), true, ) .await .context("error fetching latest release")?; + let mut body = Vec::new(); response .body_mut() @@ -40,13 +45,20 @@ pub async fn latest_github_release( .await .context("error reading latest release")?; - let release = serde_json::from_slice::(body.as_slice()); - if release.is_err() { - log::error!( - "Github API response text: {:?}", - String::from_utf8_lossy(body.as_slice()) - ); - } + let releases = match serde_json::from_slice::>(body.as_slice()) { + Ok(releases) => releases, + + Err(_) => { + log::error!( + "Error deserializing Github API response text: {:?}", + String::from_utf8_lossy(body.as_slice()) + ); + return Err(anyhow!("error deserializing latest release")); + } + }; - release.context("error deserializing latest release") + releases + .into_iter() + .find(|release| release.pre_release == pre_release) + .ok_or(anyhow!("Failed to find a release")) } diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index e142028196deb8c5af3a19f32d5e5b3c1716c9af..84c5798b07d53bbcbe9dbb930cedb6f80db2c4ad 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -23,7 +23,7 @@ impl super::LspAdapter for CLspAdapter { &self, http: Arc, ) -> Result> { - let release = latest_github_release("clangd/clangd", http).await?; + let release = latest_github_release("clangd/clangd", false, http).await?; let asset_name = format!("clangd-mac-{}.zip", release.name); let asset = release .assets diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index a2debcdb2d5a3f6c08a476dd6a05bd36e9fe8315..2939a0fa5f942b9631b2ecd9e13d6c1e5c4de17e 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -24,7 +24,7 @@ impl LspAdapter for ElixirLspAdapter { &self, http: Arc, ) -> Result> { - let release = latest_github_release("elixir-lsp/elixir-ls", http).await?; + let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?; let asset_name = "elixir-ls.zip"; let asset = release .assets diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 760c5f353d0d98c6f58d85d5472a8c519a2c37da..ed24abb45c40b6b2cc2c2336a746557c03505745 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -33,7 +33,7 @@ impl super::LspAdapter for GoLspAdapter { &self, http: Arc, ) -> Result> { - let release = latest_github_release("golang/tools", http).await?; + let release = latest_github_release("golang/tools", false, http).await?; let version: Option = release.name.strip_prefix("gopls/v").map(str::to_string); if version.is_none() { log::warn!( diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index be5493b4cb8f8037375f9282f0c1ae5bd03240e6..68f780c3af73b3febd1fdcbeaba1a2a95bb36b37 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -57,8 +57,8 @@ impl LspAdapter for HtmlLspAdapter { if fs::metadata(&server_path).await.is_err() { self.node .npm_install_packages( - [("vscode-langservers-extracted", version.as_str())], &container_dir, + [("vscode-langservers-extracted", version.as_str())], ) .await?; } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 5c3edfba25538f02f597d4e66d6d78c1c7c548a4..d87d36abfef7139033d8f9526e877d2ba4efb826 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -76,8 +76,8 @@ impl LspAdapter for JsonLspAdapter { if fs::metadata(&server_path).await.is_err() { self.node .npm_install_packages( - [("vscode-json-languageserver", version.as_str())], &container_dir, + [("vscode-json-languageserver", version.as_str())], ) .await?; } diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 2a18138cb71193c9f07e2c02f3335959e85da123..f204eb2555f5327d0e70df60963db771acd772ab 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -30,7 +30,7 @@ impl super::LspAdapter for LuaLspAdapter { &self, http: Arc, ) -> Result> { - let release = latest_github_release("LuaLS/lua-language-server", http).await?; + let release = latest_github_release("LuaLS/lua-language-server", false, http).await?; let version = release.name.clone(); let platform = match consts::ARCH { "x86_64" => "x64", diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 08476c9c216277d7b80c20fabe5ac72795008df8..acd31e82059b0d04aa476340e30e87b4c1867a9d 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -53,7 +53,7 @@ impl LspAdapter for PythonLspAdapter { if fs::metadata(&server_path).await.is_err() { self.node - .npm_install_packages([("pyright", version.as_str())], &container_dir) + .npm_install_packages(&container_dir, [("pyright", version.as_str())]) .await?; } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 3808444ad9073da0f30b493d1b88262b19492cf8..92fb5bc3b2739cf8a63a2d3717c23e7642e50963 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -24,18 +24,17 @@ impl LspAdapter for RustLspAdapter { &self, http: Arc, ) -> Result> { - let release = latest_github_release("rust-analyzer/rust-analyzer", http).await?; + let release = latest_github_release("rust-analyzer/rust-analyzer", false, http).await?; let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { + Ok(Box::new(GitHubLspBinaryVersion { name: release.name, url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) + })) } async fn fetch_server_binary( @@ -77,6 +76,7 @@ impl LspAdapter for RustLspAdapter { while let Some(entry) = entries.next().await { last = Some(entry?.path()); } + anyhow::Ok(LanguageServerBinary { path: last.ok_or_else(|| anyhow!("no cached binary"))?, arguments: Default::default(), diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index e4a540dcd8efc1fcc315852aa3bed47a87f1965e..54d61e91cae5975198c34fedb93e8551ce0ddc08 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -1,4 +1,6 @@ use anyhow::{anyhow, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; @@ -6,7 +8,7 @@ use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use lsp::CodeActionKind; use node_runtime::NodeRuntime; use serde_json::{json, Value}; -use smol::fs; +use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ any::Any, ffi::OsString, @@ -14,8 +16,8 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::http::HttpClient; -use util::ResultExt; +use util::{fs::remove_matching, github::latest_github_release, http::HttpClient}; +use util::{github::GitHubLspBinaryVersion, ResultExt}; fn typescript_server_binary_arguments(server_path: &Path) -> Vec { vec![ @@ -69,24 +71,24 @@ impl LspAdapter for TypeScriptLspAdapter { async fn fetch_server_binary( &self, - versions: Box, + version: Box, _: Arc, container_dir: PathBuf, ) -> Result { - let versions = versions.downcast::().unwrap(); + let version = version.downcast::().unwrap(); let server_path = container_dir.join(Self::NEW_SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node .npm_install_packages( + &container_dir, [ - ("typescript", versions.typescript_version.as_str()), + ("typescript", version.typescript_version.as_str()), ( "typescript-language-server", - versions.server_version.as_str(), + version.server_version.as_str(), ), ], - &container_dir, ) .await?; } @@ -172,8 +174,7 @@ pub struct EsLintLspAdapter { } impl EsLintLspAdapter { - const SERVER_PATH: &'static str = - "node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslintServer.js"; + const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js"; #[allow(unused)] pub fn new(node: Arc) -> Self { @@ -228,30 +229,50 @@ impl LspAdapter for EsLintLspAdapter { async fn fetch_latest_server_version( &self, - _: Arc, + http: Arc, ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version("vscode-langservers-extracted") - .await?, - )) + // At the time of writing the latest vscode-eslint release was released in 2020 and requires + // special custom LSP protocol extensions be handled to fully initalize. Download the latest + // prerelease instead to sidestep this issue + let release = latest_github_release("microsoft/vscode-eslint", true, http).await?; + Ok(Box::new(GitHubLspBinaryVersion { + name: release.name, + url: release.tarball_url, + })) } async fn fetch_server_binary( &self, - versions: Box, - _: Arc, + version: Box, + http: Arc, container_dir: PathBuf, ) -> Result { - let version = versions.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let version = version.downcast::().unwrap(); + let destination_path = container_dir.join(format!("vscode-eslint-{}", version.name)); + let server_path = destination_path.join(Self::SERVER_PATH); if fs::metadata(&server_path).await.is_err() { + remove_matching(&container_dir, |entry| entry != destination_path).await; + + let mut response = http + .get(&version.url, Default::default(), true) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(&destination_path).await?; + + let mut dir = fs::read_dir(&destination_path).await?; + let first = dir.next().await.ok_or(anyhow!("missing first file"))??; + let repo_root = destination_path.join("vscode-eslint"); + fs::rename(first.path(), &repo_root).await?; + self.node - .npm_install_packages( - [("vscode-langservers-extracted", version.as_str())], - &container_dir, - ) + .run_npm_subcommand(&repo_root, "install", &[]) + .await?; + + self.node + .run_npm_subcommand(&repo_root, "run-script", &["compile"]) .await?; } @@ -263,18 +284,17 @@ impl LspAdapter for EsLintLspAdapter { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { - let server_path = container_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: eslint_server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - container_dir - )) + // This is unfortunate but we don't know what the version is to build a path directly + let mut dir = fs::read_dir(&container_dir).await?; + let first = dir.next().await.ok_or(anyhow!("missing first file"))??; + if !first.file_type().await?.is_dir() { + return Err(anyhow!("First entry is not a directory")); } + + Ok(LanguageServerBinary { + path: first.path().join(Self::SERVER_PATH), + arguments: Default::default(), + }) })() .await .log_err() diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index fadc74b698279d0b6320bf35c4abd28b666ea7c1..fed76cd5b9e399bdfed5a4756617395eb746852b 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -61,7 +61,7 @@ impl LspAdapter for YamlLspAdapter { if fs::metadata(&server_path).await.is_err() { self.node - .npm_install_packages([("yaml-language-server", version.as_str())], &container_dir) + .npm_install_packages(&container_dir, [("yaml-language-server", version.as_str())]) .await?; } From e566929d9eed8d7897396ab65d1a2989357fe19e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 29 Apr 2023 14:53:17 +0200 Subject: [PATCH 03/26] Fix panic when clicking on a definition This was introduced with #2420 and was caused by re-entrantly updating the workspace. Instead of passing the workspace reference from the outside, we now define the definition navigation as a method on the editor which solves the issue. Note that we also needed to introduce a `defer` call when navigating to a definition to prevent the workspace from reading the editor during `open_project_item`. --- crates/editor/src/editor.rs | 100 ++++++++++----------- crates/editor/src/element.rs | 26 ++---- crates/editor/src/link_go_to_definition.rs | 87 +++++++----------- 3 files changed, 86 insertions(+), 127 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7a6a9a8de81b7cae1a7d157b9a0757295ba63646..b88e38850e86c6a7726989c63528f189f4da99fb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1331,6 +1331,10 @@ impl Editor { &self.buffer } + fn workspace(&self, cx: &AppContext) -> Option> { + self.workspace.as_ref()?.0.upgrade(cx) + } + pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { self.buffer().read(cx).title(cx) } @@ -5558,93 +5562,77 @@ impl Editor { } } - pub fn go_to_definition( - workspace: &mut Workspace, - _: &GoToDefinition, - cx: &mut ViewContext, - ) { - Self::go_to_definition_of_kind(GotoDefinitionKind::Symbol, workspace, cx); + pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx); } - pub fn go_to_type_definition( - workspace: &mut Workspace, - _: &GoToTypeDefinition, - cx: &mut ViewContext, - ) { - Self::go_to_definition_of_kind(GotoDefinitionKind::Type, workspace, cx); + pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx); } - fn go_to_definition_of_kind( - kind: GotoDefinitionKind, - workspace: &mut Workspace, - cx: &mut ViewContext, - ) { - let active_item = workspace.active_item(cx); - let editor_handle = if let Some(editor) = active_item - .as_ref() - .and_then(|item| item.act_as::(cx)) - { - editor - } else { - return; - }; - - let editor = editor_handle.read(cx); - let buffer = editor.buffer.read(cx); - let head = editor.selections.newest::(cx).head(); + fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext) { + let Some(workspace) = self.workspace(cx) else { return }; + let buffer = self.buffer.read(cx); + let head = self.selections.newest::(cx).head(); let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) { text_anchor } else { return; }; - let project = workspace.project().clone(); + let project = workspace.read(cx).project().clone(); let definitions = project.update(cx, |project, cx| match kind { GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx), GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx), }); - cx.spawn_labeled("Fetching Definition...", |workspace, mut cx| async move { + cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move { let definitions = definitions.await?; - workspace.update(&mut cx, |workspace, cx| { - Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx); + editor.update(&mut cx, |editor, cx| { + editor.navigate_to_definitions(definitions, cx); })?; - Ok::<(), anyhow::Error>(()) }) .detach_and_log_err(cx); } pub fn navigate_to_definitions( - workspace: &mut Workspace, - editor_handle: ViewHandle, - definitions: Vec, - cx: &mut ViewContext, + &mut self, + mut definitions: Vec, + cx: &mut ViewContext, ) { - let pane = workspace.active_pane().clone(); + let Some(workspace) = self.workspace(cx) else { return }; + let pane = workspace.read(cx).active_pane().clone(); // If there is one definition, just open it directly - if let [definition] = definitions.as_slice() { + if definitions.len() == 1 { + let definition = definitions.pop().unwrap(); let range = definition .target .range .to_offset(definition.target.buffer.read(cx)); - let target_editor_handle = - workspace.open_project_item(definition.target.buffer.clone(), cx); - target_editor_handle.update(cx, |target_editor, cx| { - // When selecting a definition in a different buffer, disable the nav history - // to avoid creating a history entry at the previous cursor location. - if editor_handle != target_editor_handle { - pane.update(cx, |pane, _| pane.disable_history()); - } - target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); - - pane.update(cx, |pane, _| pane.enable_history()); - }); + } else { + cx.window_context().defer(move |cx| { + let target_editor: ViewHandle = workspace.update(cx, |workspace, cx| { + workspace.open_project_item(definition.target.buffer.clone(), cx) + }); + target_editor.update(cx, |target_editor, cx| { + // When selecting a definition in a different buffer, disable the nav history + // to avoid creating a history entry at the previous cursor location. + pane.update(cx, |pane, _| pane.disable_history()); + target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }); + pane.update(cx, |pane, _| pane.enable_history()); + }); + }); + } } else if !definitions.is_empty() { - let replica_id = editor_handle.read(cx).replica_id(cx); + let replica_id = self.replica_id(cx); let title = definitions .iter() .find(|definition| definition.origin.is_some()) @@ -5664,7 +5652,9 @@ impl Editor { .into_iter() .map(|definition| definition.target) .collect(); - Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) + workspace.update(cx, |workspace, cx| { + Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) + }) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 11d1ce8cae1ea2821332eb4ddbe37cdee4f6133b..75bd572d95cc4b4e5f3b397bede51b8958401eab 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -309,25 +309,17 @@ impl EditorElement { editor.select(SelectPhase::End, cx); } - if let Some(workspace) = editor - .workspace - .as_ref() - .and_then(|(workspace, _)| workspace.upgrade(cx)) - { - if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { - let (point, target_point) = position_map.point_for_position(text_bounds, position); - - if point == target_point { - workspace.update(cx, |workspace, cx| { - if shift { - go_to_fetched_type_definition(workspace, point, cx); - } else { - go_to_fetched_definition(workspace, point, cx); - } - }); + if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { + let (point, target_point) = position_map.point_for_position(text_bounds, position); - return true; + if point == target_point { + if shift { + go_to_fetched_type_definition(editor, point, cx); + } else { + go_to_fetched_definition(editor, point, cx); } + + return true; } } diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 69c45b9da814faf9ced23f9e3de184680c739474..b2105c1c8138fd884985a1a4365eb834aee1ba6b 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -1,15 +1,11 @@ use std::ops::Range; +use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase}; use gpui::{Task, ViewContext}; use language::{Bias, ToOffset}; use project::LocationLink; use settings::Settings; use util::TryFutureExt; -use workspace::Workspace; - -use crate::{ - Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, SelectPhase, -}; #[derive(Debug, Default)] pub struct LinkGoToDefinitionState { @@ -250,70 +246,51 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { } pub fn go_to_fetched_definition( - workspace: &mut Workspace, + editor: &mut Editor, point: DisplayPoint, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx); } pub fn go_to_fetched_type_definition( - workspace: &mut Workspace, + editor: &mut Editor, point: DisplayPoint, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx); } fn go_to_fetched_definition_of_kind( kind: LinkDefinitionKind, - workspace: &mut Workspace, + editor: &mut Editor, point: DisplayPoint, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { - let active_item = workspace.active_item(cx); - let editor_handle = if let Some(editor) = active_item - .as_ref() - .and_then(|item| item.act_as::(cx)) - { - editor - } else { - return; - }; - - let (cached_definitions, cached_definitions_kind) = editor_handle.update(cx, |editor, cx| { - let definitions = editor.link_go_to_definition_state.definitions.clone(); - hide_link_definition(editor, cx); - (definitions, editor.link_go_to_definition_state.kind) - }); + let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); + hide_link_definition(editor, cx); + let cached_definitions_kind = editor.link_go_to_definition_state.kind; let is_correct_kind = cached_definitions_kind == Some(kind); if !cached_definitions.is_empty() && is_correct_kind { - editor_handle.update(cx, |editor, cx| { - if !editor.focused { - cx.focus_self(); - } - }); + if !editor.focused { + cx.focus_self(); + } - Editor::navigate_to_definitions(workspace, editor_handle, cached_definitions, cx); + editor.navigate_to_definitions(cached_definitions, cx); } else { - editor_handle.update(cx, |editor, cx| { - editor.select( - SelectPhase::Begin { - position: point, - add: false, - click_count: 1, - }, - cx, - ); - }); + editor.select( + SelectPhase::Begin { + position: point, + add: false, + click_count: 1, + }, + cx, + ); match kind { - LinkDefinitionKind::Symbol => Editor::go_to_definition(workspace, &GoToDefinition, cx), - - LinkDefinitionKind::Type => { - Editor::go_to_type_definition(workspace, &GoToTypeDefinition, cx) - } + LinkDefinitionKind::Symbol => editor.go_to_definition(&Default::default(), cx), + LinkDefinitionKind::Type => editor.go_to_type_definition(&Default::default(), cx), } } } @@ -426,8 +403,8 @@ mod tests { ]))) }); - cx.update_workspace(|workspace, cx| { - go_to_fetched_type_definition(workspace, hover_point, cx); + cx.update_editor(|editor, cx| { + go_to_fetched_type_definition(editor, hover_point, cx); }); requests.next().await; cx.foreground().run_until_parked(); @@ -635,8 +612,8 @@ mod tests { "}); // Cmd click with existing definition doesn't re-request and dismisses highlight - cx.update_workspace(|workspace, cx| { - go_to_fetched_definition(workspace, hover_point, cx); + cx.update_editor(|editor, cx| { + go_to_fetched_definition(editor, hover_point, cx); }); // Assert selection moved to to definition cx.lsp @@ -676,8 +653,8 @@ mod tests { }, ]))) }); - cx.update_workspace(|workspace, cx| { - go_to_fetched_definition(workspace, hover_point, cx); + cx.update_editor(|editor, cx| { + go_to_fetched_definition(editor, hover_point, cx); }); requests.next().await; cx.foreground().run_until_parked(); From 029538fe21576f9a9a466714dec844d12e44dd16 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 11:45:35 +0200 Subject: [PATCH 04/26] Make `dispatch_global_action` private --- crates/gpui/src/app.rs | 2 +- crates/zed/src/main.rs | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c201febed5a22cdc63567db9fb54d0c248949d0c..a949b64c04ab27377dcdcced7536071a2de499b7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1048,7 +1048,7 @@ impl AppContext { } } - pub fn dispatch_global_action(&mut self, action: A) { + fn dispatch_global_action(&mut self, action: A) { self.dispatch_global_action_any(&action); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2724e8587f362cf55fd7cfe7792c700385e4430f..58a53b9e402a2fc1eef7954b9e3aba523c475843 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -29,8 +29,16 @@ use settings::{ use simplelog::ConfigBuilder; use smol::process::Command; use std::{ - env, ffi::OsStr, fs::OpenOptions, io::Write as _, os::unix::prelude::OsStrExt, panic, - path::PathBuf, sync::Arc, thread, time::Duration, + env, + ffi::OsStr, + fs::OpenOptions, + io::Write as _, + os::unix::prelude::OsStrExt, + panic, + path::PathBuf, + sync::{Arc, Weak}, + thread, + time::Duration, }; use terminal_view::{get_working_directory, TerminalView}; use util::http::{self, HttpClient}; @@ -104,7 +112,13 @@ fn main() { .log_err(); } }) - .on_reopen(move |cx| cx.dispatch_global_action(NewFile)); + .on_reopen(move |cx| { + if cx.has_global::>() { + if let Some(app_state) = cx.global::>().upgrade() { + workspace::open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); + } + } + }); app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); @@ -190,6 +204,7 @@ fn main() { dock_default_item_factory, background_actions, }); + cx.set_global(Arc::downgrade(&app_state)); auto_update::init(http, client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); @@ -274,7 +289,7 @@ async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncApp cx.update(|cx| show_welcome_experience(app_state, cx)); } else { cx.update(|cx| { - cx.dispatch_global_action(NewFile); + workspace::open_new(app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); }); } } From d815fc88ae917d2180b17966ad815b06531ac87e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 14:09:39 +0200 Subject: [PATCH 05/26] Remove `ViewContext::dispatch_any_action` --- .../src/activity_indicator.rs | 40 ++--- crates/copilot/src/copilot.rs | 2 +- crates/copilot_button/src/copilot_button.rs | 16 +- crates/editor/src/editor.rs | 12 +- crates/gpui/src/app.rs | 9 +- crates/recent_projects/src/recent_projects.rs | 2 +- crates/search/src/buffer_search.rs | 24 +-- crates/search/src/project_search.rs | 11 +- crates/workspace/src/notifications.rs | 84 +++++----- crates/workspace/src/workspace.rs | 144 ++++++++++++------ crates/zed/src/zed.rs | 55 +------ 11 files changed, 197 insertions(+), 202 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 4ea031985eb42332aaef3cdcc45d9f35f55e9fcd..d5ee1364b37100a7e71e4f6365bbee523e630bda 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -5,7 +5,7 @@ use gpui::{ actions, anyhow, elements::*, platform::{CursorStyle, MouseButton}, - Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle, + AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use project::{LanguageServerProgress, Project}; @@ -45,7 +45,7 @@ struct PendingWork<'a> { struct Content { icon: Option<&'static str>, message: String, - action: Option>, + on_click: Option)>>, } pub fn init(cx: &mut AppContext) { @@ -199,7 +199,7 @@ impl ActivityIndicator { return Content { icon: None, message, - action: None, + on_click: None, }; } @@ -230,7 +230,7 @@ impl ActivityIndicator { downloading.join(", "), if downloading.len() > 1 { "s" } else { "" } ), - action: None, + on_click: None, }; } else if !checking_for_update.is_empty() { return Content { @@ -244,7 +244,7 @@ impl ActivityIndicator { "" } ), - action: None, + on_click: None, }; } else if !failed.is_empty() { return Content { @@ -254,7 +254,9 @@ impl ActivityIndicator { failed.join(", "), if failed.len() > 1 { "s" } else { "" } ), - action: Some(Box::new(ShowErrorMessage)), + on_click: Some(Arc::new(|this, cx| { + this.show_error_message(&Default::default(), cx) + })), }; } @@ -264,27 +266,31 @@ impl ActivityIndicator { AutoUpdateStatus::Checking => Content { icon: Some(DOWNLOAD_ICON), message: "Checking for Zed updates…".to_string(), - action: None, + on_click: None, }, AutoUpdateStatus::Downloading => Content { icon: Some(DOWNLOAD_ICON), message: "Downloading Zed update…".to_string(), - action: None, + on_click: None, }, AutoUpdateStatus::Installing => Content { icon: Some(DOWNLOAD_ICON), message: "Installing Zed update…".to_string(), - action: None, + on_click: None, }, AutoUpdateStatus::Updated => Content { icon: None, message: "Click to restart and update Zed".to_string(), - action: Some(Box::new(workspace::Restart)), + on_click: Some(Arc::new(|_, cx| { + workspace::restart(&Default::default(), cx) + })), }, AutoUpdateStatus::Errored => Content { icon: Some(WARNING_ICON), message: "Auto update failed".to_string(), - action: Some(Box::new(DismissErrorMessage)), + on_click: Some(Arc::new(|this, cx| { + this.dismiss_error_message(&Default::default(), cx) + })), }, AutoUpdateStatus::Idle => Default::default(), }; @@ -294,7 +300,7 @@ impl ActivityIndicator { return Content { icon: None, message: most_recent_active_task.to_string(), - action: None, + on_click: None, }; } @@ -315,7 +321,7 @@ impl View for ActivityIndicator { let Content { icon, message, - action, + on_click, } = self.content_to_render(cx); let mut element = MouseEventHandler::::new(0, cx, |state, cx| { @@ -325,7 +331,7 @@ impl View for ActivityIndicator { .workspace .status_bar .lsp_status; - let style = if state.hovered() && action.is_some() { + let style = if state.hovered() && on_click.is_some() { theme.hover.as_ref().unwrap_or(&theme.default) } else { &theme.default @@ -353,12 +359,10 @@ impl View for ActivityIndicator { .aligned() }); - if let Some(action) = action { + if let Some(on_click) = on_click.clone() { element = element .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_any_action(action.boxed_clone()) - }); + .on_click(MouseButton::Left, move |_, this, cx| on_click(this, cx)); } element.into_any() diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index f7ff163424ecde9dfff5ff4709d0a69616c44099..ea25355065979593b9c61557a24ce1693a9f5142 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -560,7 +560,7 @@ impl Copilot { } } - fn reinstall(&mut self, cx: &mut ModelContext) -> Task<()> { + pub fn reinstall(&mut self, cx: &mut ModelContext) -> Task<()> { let start_task = cx .spawn({ let http = self.http.clone(); diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index a597bb7e47c1bd724a076e54802291408190243b..832bdaf3dae071f03f87721a8f3245982ec47230 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -1,5 +1,5 @@ use context_menu::{ContextMenu, ContextMenuItem}; -use copilot::{Copilot, Reinstall, SignOut, Status}; +use copilot::{Copilot, SignOut, Status}; use editor::Editor; use gpui::{ elements::*, @@ -103,11 +103,21 @@ impl View for CopilotButton { { workspace.update(cx, |workspace, cx| { workspace.show_toast( - Toast::new_action( + Toast::new( COPILOT_ERROR_TOAST_ID, format!("Copilot can't be started: {}", e), + ) + .on_click( "Reinstall Copilot", - Reinstall, + |cx| { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| { + copilot.reinstall(cx) + }) + .detach(); + } + }, ), cx, ); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b88e38850e86c6a7726989c63528f189f4da99fb..d8c2e81ce16f234ea5a1f2b378784446a7993eb0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3199,11 +3199,13 @@ impl Editor { .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) .on_click(MouseButton::Left, { - move |_, _, cx| { - cx.dispatch_any_action(match fold_status { - FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }), - FoldStatus::Foldable => Box::new(FoldAt { buffer_row }), - }); + move |_, editor, cx| match fold_status { + FoldStatus::Folded => { + editor.unfold_at(&UnfoldAt { buffer_row }, cx); + } + FoldStatus::Foldable => { + editor.fold_at(&FoldAt { buffer_row }, cx); + } } }) .into_any() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a949b64c04ab27377dcdcced7536071a2de499b7..7343a7245d5d05183eb821b5aca5953570262c31 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1745,7 +1745,7 @@ impl AppContext { self.pending_effects.push_back(Effect::RefreshWindows); } - pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) { + fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) { self.dispatch_any_action_at(window_id, view_id, Box::new(action)); } @@ -3196,13 +3196,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> { .dispatch_action_at(window_id, view_id, action) } - pub fn dispatch_any_action(&mut self, action: Box) { - let window_id = self.window_id; - let view_id = self.view_id; - self.window_context - .dispatch_any_action_at(window_id, view_id, action) - } - pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext)) { let handle = self.handle(); self.window_context diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 6429448f75498a579fad8cb3f2a112155acc6827..414b3e932348ee185f4f3b46190d24c4c19b61db 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -61,7 +61,7 @@ fn toggle(app_state: Weak, cx: &mut ViewContext) -> Option< }); } else { workspace.show_notification(0, cx, |cx| { - cx.add_view(|_| MessageNotification::new_message("No recent projects to open.")) + cx.add_view(|_| MessageNotification::new("No recent projects to open.")) }) } })?; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 91ca99c5c35d997c2d464ed011bb1250788963b4..ee5a2e8332e4cc90cab44642fd3b7df246849341 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -338,8 +338,8 @@ impl BufferSearchBar { .contained() .with_style(style.container) }) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_any_action(option.to_toggle_action()) + .on_click(MouseButton::Left, move |_, this, cx| { + this.toggle_search_option(option, cx); }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( @@ -386,8 +386,10 @@ impl BufferSearchBar { .with_style(style.container) }) .on_click(MouseButton::Left, { - let action = action.boxed_clone(); - move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()) + move |_, this, cx| match direction { + Direction::Prev => this.select_prev_match(&Default::default(), cx), + Direction::Next => this.select_next_match(&Default::default(), cx), + } }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( @@ -405,7 +407,6 @@ impl BufferSearchBar { theme: &theme::Search, cx: &mut ViewContext, ) -> AnyElement { - let action = Box::new(Dismiss); let tooltip = "Dismiss Buffer Search"; let tooltip_style = cx.global::().theme.tooltip.clone(); @@ -422,12 +423,17 @@ impl BufferSearchBar { .contained() .with_style(style.container) }) - .on_click(MouseButton::Left, { - let action = action.boxed_clone(); - move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()) + .on_click(MouseButton::Left, move |_, this, cx| { + this.dismiss(&Default::default(), cx) }) .with_cursor_style(CursorStyle::PointingHand) - .with_tooltip::(0, tooltip.to_string(), Some(action), tooltip_style, cx) + .with_tooltip::( + 0, + tooltip.to_string(), + Some(Box::new(Dismiss)), + tooltip_style, + cx, + ) .into_any() } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index ac478a8a2c0c0c7e272dc7fbdf44464fa851866c..ea29f9cfda8f219465103db148b4019f1c0c6203 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -788,9 +788,10 @@ impl ProjectSearchBar { .contained() .with_style(style.container) }) - .on_click(MouseButton::Left, { - let action = action.boxed_clone(); - move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(search) = this.active_project_search.as_ref() { + search.update(cx, |search, cx| search.select_match(direction, cx)); + } }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( @@ -822,8 +823,8 @@ impl ProjectSearchBar { .contained() .with_style(style.container) }) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_any_action(option.to_toggle_action()) + .on_click(MouseButton::Left, move |_, this, cx| { + this.toggle_search_option(option, cx); }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 57749a5c2b3fd9200ddbfbb6253e481c883a8320..455ffb2bb08f59df838b2491c129af8d07c8f185 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -114,17 +114,14 @@ impl Workspace { pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { self.dismiss_notification::(toast.id, cx); self.show_notification(toast.id, cx, |cx| { - cx.add_view(|_cx| match &toast.click { - Some((click_msg, action)) => { - simple_message_notification::MessageNotification::new_boxed_action( - toast.msg.clone(), - action.boxed_clone(), - click_msg.clone(), - ) - } - None => { - simple_message_notification::MessageNotification::new_message(toast.msg.clone()) + cx.add_view(|_cx| match toast.on_click.as_ref() { + Some((click_msg, on_click)) => { + let on_click = on_click.clone(); + simple_message_notification::MessageNotification::new(toast.msg.clone()) + .with_click_message(click_msg.clone()) + .on_click(move |cx| on_click(cx)) } + None => simple_message_notification::MessageNotification::new(toast.msg.clone()), }) }) } @@ -152,19 +149,17 @@ impl Workspace { } pub mod simple_message_notification { - - use std::borrow::Cow; - use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, impl_actions, platform::{CursorStyle, MouseButton}, - Action, AppContext, Element, Entity, View, ViewContext, + AppContext, Element, Entity, View, ViewContext, }; use menu::Cancel; use serde::Deserialize; use settings::Settings; + use std::{borrow::Cow, sync::Arc}; use crate::Workspace; @@ -194,7 +189,7 @@ pub mod simple_message_notification { pub struct MessageNotification { message: Cow<'static, str>, - click_action: Option>, + on_click: Option)>>, click_message: Option>, } @@ -207,36 +202,31 @@ pub mod simple_message_notification { } impl MessageNotification { - pub fn new_message>>(message: S) -> MessageNotification { + pub fn new(message: S) -> MessageNotification + where + S: Into>, + { Self { message: message.into(), - click_action: None, + on_click: None, click_message: None, } } - pub fn new_boxed_action>, S2: Into>>( - message: S1, - click_action: Box, - click_message: S2, - ) -> Self { - Self { - message: message.into(), - click_action: Some(click_action), - click_message: Some(click_message.into()), - } + pub fn with_click_message(mut self, message: S) -> Self + where + S: Into>, + { + self.click_message = Some(message.into()); + self } - pub fn new>, A: Action, S2: Into>>( - message: S1, - click_action: A, - click_message: S2, - ) -> Self { - Self { - message: message.into(), - click_action: Some(Box::new(click_action) as Box), - click_message: Some(click_message.into()), - } + pub fn on_click(mut self, on_click: F) -> Self + where + F: 'static + Fn(&mut ViewContext), + { + self.on_click = Some(Arc::new(on_click)); + self } pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { @@ -255,14 +245,10 @@ pub mod simple_message_notification { enum MessageNotificationTag {} - let click_action = self - .click_action - .as_ref() - .map(|action| action.boxed_clone()); - let click_message = self.click_message.as_ref().map(|message| message.clone()); + let click_message = self.click_message.clone(); let message = self.message.clone(); - - let has_click_action = click_action.is_some(); + let on_click = self.on_click.clone(); + let has_click_action = on_click.is_some(); MouseEventHandler::::new(0, cx, |state, cx| { Flex::column() @@ -326,10 +312,10 @@ pub mod simple_message_notification { // Since we're not using a proper overlay, we have to capture these extra events .on_down(MouseButton::Left, |_, _, _| {}) .on_up(MouseButton::Left, |_, _, _| {}) - .on_click(MouseButton::Left, move |_, _, cx| { - if let Some(click_action) = click_action.as_ref() { - cx.dispatch_any_action(click_action.boxed_clone()); - cx.dispatch_action(CancelMessageNotification) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(on_click) = on_click.as_ref() { + on_click(cx); + this.dismiss(&Default::default(), cx); } }) .with_cursor_style(if has_click_action { @@ -372,7 +358,7 @@ where Err(err) => { workspace.show_notification(0, cx, |cx| { cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new_message(format!( + simple_message_notification::MessageNotification::new(format!( "Error: {:?}", err, )) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1a622babb34d12cee2669103497718fb84822774..6a1f7aa8bbe3a365ba1fe33b76cda20bfd5cfade 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -43,8 +43,9 @@ use gpui::{ CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions, }, - Action, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, - ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, + SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -59,7 +60,7 @@ use std::{ }; use crate::{ - notifications::simple_message_notification::{MessageNotification, OsOpen}, + notifications::simple_message_notification::MessageNotification, persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace}, }; use lazy_static::lazy_static; @@ -137,7 +138,7 @@ pub struct ActivatePane(pub usize); pub struct Toast { id: usize, msg: Cow<'static, str>, - click: Option<(Cow<'static, str>, Box)>, + on_click: Option<(Cow<'static, str>, Arc)>, } impl Toast { @@ -145,21 +146,17 @@ impl Toast { Toast { id, msg: msg.into(), - click: None, + on_click: None, } } - pub fn new_action>, I2: Into>>( - id: usize, - msg: I1, - click_msg: I2, - action: impl Action, - ) -> Self { - Toast { - id, - msg: msg.into(), - click: Some((click_msg.into(), Box::new(action))), - } + pub fn on_click(mut self, message: M, on_click: F) -> Self + where + M: Into>, + F: Fn(&mut WindowContext) + 'static, + { + self.on_click = Some((message.into(), Arc::new(on_click))); + self } } @@ -167,7 +164,7 @@ impl PartialEq for Toast { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.msg == other.msg - && self.click.is_some() == other.click.is_some() + && self.on_click.is_some() == other.on_click.is_some() } } @@ -176,10 +173,7 @@ impl Clone for Toast { Toast { id: self.id, msg: self.msg.to_owned(), - click: self - .click - .as_ref() - .map(|(msg, click)| (msg.to_owned(), click.boxed_clone())), + on_click: self.on_click.clone(), } } } @@ -260,6 +254,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_async_action(Workspace::follow_next_collaborator); cx.add_async_action(Workspace::close); cx.add_global_action(Workspace::close_global); + cx.add_global_action(restart); cx.add_async_action(Workspace::save_all); cx.add_action(Workspace::add_folder_to_project); cx.add_action( @@ -303,9 +298,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { } else { workspace.show_notification(1, cx, |cx| { cx.add_view(|_| { - MessageNotification::new_message( - "Successfully installed the `zed` binary", - ) + MessageNotification::new("Successfully installed the `zed` binary") }) }); } @@ -2668,36 +2661,37 @@ impl Workspace { } fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { - workspace.update(cx, |workspace, cx| { - if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { - workspace.show_notification_once(0, cx, |cx| { - cx.add_view(|_| { - MessageNotification::new( - "Failed to load any database file.", - OsOpen::new("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()), - "Click to let us know about this error" - ) - }) - }); - } else { - let backup_path = (*db::BACKUP_DB_PATH).read(); - if let Some(backup_path) = &*backup_path { + const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; + + workspace + .update(cx, |workspace, cx| { + if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { workspace.show_notification_once(0, cx, |cx| { cx.add_view(|_| { - let backup_path = backup_path.to_string_lossy(); - MessageNotification::new( - format!( - "Database file was corrupted. Old database backed up to {}", - backup_path - ), - OsOpen::new(backup_path.to_string()), - "Click to show old database in finder", - ) + MessageNotification::new("Failed to load any database file.") + .with_click_message("Click to let us know about this error") + .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) }) }); + } else { + let backup_path = (*db::BACKUP_DB_PATH).read(); + if let Some(backup_path) = backup_path.clone() { + workspace.show_notification_once(0, cx, move |cx| { + cx.add_view(move |_| { + MessageNotification::new(format!( + "Database file was corrupted. Old database backed up to {}", + backup_path.display() + )) + .with_click_message("Click to show old database in finder") + .on_click(move |cx| { + cx.platform().open_url(&backup_path.to_string_lossy()) + }) + }) + }); + } } - } - }).log_err(); + }) + .log_err(); } impl Entity for Workspace { @@ -3062,6 +3056,58 @@ pub fn join_remote_project( }) } +pub fn restart(_: &Restart, cx: &mut AppContext) { + let mut workspaces = cx + .window_ids() + .filter_map(|window_id| { + Some( + cx.root_view(window_id)? + .clone() + .downcast::()? + .downgrade(), + ) + }) + .collect::>(); + + // If multiple windows have unsaved changes, and need a save prompt, + // prompt in the active window before switching to a different window. + workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + + let should_confirm = cx.global::().confirm_quit; + cx.spawn(|mut cx| async move { + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { + let answer = cx.prompt( + workspace.window_id(), + PromptLevel::Info, + "Are you sure you want to restart?", + &["Restart", "Cancel"], + ); + + if let Some(mut answer) = answer { + let answer = answer.next().await; + if answer != Some(0) { + return Ok(()); + } + } + } + + // If the user cancels any save prompt, then keep the app open. + for workspace in workspaces { + if !workspace + .update(&mut cx, |workspace, cx| { + workspace.prepare_to_close(true, cx) + })? + .await? + { + return Ok(()); + } + } + cx.platform().restart(); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); +} + fn parse_pixel_position_env_var(value: &str) -> Option { let mut parts = value.split(','); let width: usize = parts.next()?.parse().ok()?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 01a5decfd09df1c6bd1eb81270f2dffc683952cb..28b17c297d9458da382191fa8eaadca88885846f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -35,7 +35,7 @@ use terminal_view::terminal_button::TerminalButton; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{sidebar::SidebarSide, AppState, Restart, Workspace}; +use workspace::{sidebar::SidebarSide, AppState, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -113,7 +113,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { }, ); cx.add_global_action(quit); - cx.add_global_action(restart); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { cx.update_global::(|settings, cx| { @@ -370,58 +369,6 @@ pub fn build_window_options( } } -fn restart(_: &Restart, cx: &mut gpui::AppContext) { - let mut workspaces = cx - .window_ids() - .filter_map(|window_id| { - Some( - cx.root_view(window_id)? - .clone() - .downcast::()? - .downgrade(), - ) - }) - .collect::>(); - - // If multiple windows have unsaved changes, and need a save prompt, - // prompt in the active window before switching to a different window. - workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); - - let should_confirm = cx.global::().confirm_quit; - cx.spawn(|mut cx| async move { - if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { - let answer = cx.prompt( - workspace.window_id(), - PromptLevel::Info, - "Are you sure you want to restart?", - &["Restart", "Cancel"], - ); - - if let Some(mut answer) = answer { - let answer = answer.next().await; - if answer != Some(0) { - return Ok(()); - } - } - } - - // If the user cancels any save prompt, then keep the app open. - for workspace in workspaces { - if !workspace - .update(&mut cx, |workspace, cx| { - workspace.prepare_to_close(true, cx) - })? - .await? - { - return Ok(()); - } - } - cx.platform().restart(); - anyhow::Ok(()) - }) - .detach_and_log_err(cx); -} - fn quit(_: &Quit, cx: &mut gpui::AppContext) { let mut workspaces = cx .window_ids() From c4472b0786af91237b22c5aed82c8b257165b1b1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 15:48:41 +0200 Subject: [PATCH 06/26] Remove `ViewContext::dispatch_action` --- crates/auto_update/src/auto_update.rs | 40 +++-- crates/auto_update/src/update_notification.rs | 6 +- crates/breadcrumbs/src/breadcrumbs.rs | 16 +- crates/collab_ui/src/collab_titlebar_item.rs | 64 ++----- crates/collab_ui/src/collab_ui.rs | 1 - .../src/collaborator_list_popover.rs | 161 ----------------- crates/collab_ui/src/contact_list.rs | 56 +++--- crates/collab_ui/src/contacts_popover.rs | 10 +- .../collab_ui/src/sharing_status_indicator.rs | 5 +- crates/context_menu/src/context_menu.rs | 8 +- crates/copilot/src/sign_in.rs | 6 +- crates/diagnostics/src/items.rs | 25 ++- crates/editor/src/editor.rs | 42 +++-- crates/editor/src/element.rs | 15 +- crates/editor/src/hover_popover.rs | 13 +- crates/feedback/src/deploy_feedback_button.rs | 19 +- crates/feedback/src/feedback.rs | 57 ++---- crates/feedback/src/feedback_editor.rs | 67 +++---- crates/feedback/src/feedback_info_text.rs | 4 +- crates/feedback/src/submit_feedback_button.rs | 24 ++- crates/gpui/src/app.rs | 11 -- crates/gpui/src/views/select.rs | 20 +-- .../src/active_buffer_language.rs | 22 +-- .../src/language_selector.rs | 14 +- crates/outline/src/outline.rs | 2 +- crates/project_panel/src/project_panel.rs | 23 ++- crates/recent_projects/src/recent_projects.rs | 39 ++--- crates/terminal_view/src/terminal_button.rs | 12 +- crates/theme/src/ui.rs | 19 +- crates/theme_selector/src/theme_selector.rs | 14 +- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/welcome.rs | 48 ++++-- crates/workspace/src/dock.rs | 8 +- .../workspace/src/dock/toggle_dock_button.rs | 22 ++- crates/workspace/src/notifications.rs | 4 +- crates/workspace/src/pane.rs | 46 +++-- crates/workspace/src/sidebar.rs | 6 +- crates/workspace/src/toolbar.rs | 40 ++++- crates/workspace/src/workspace.rs | 68 +++----- crates/zed/src/main.rs | 22 ++- crates/zed/src/zed.rs | 163 ++++++++++-------- 41 files changed, 574 insertions(+), 670 deletions(-) delete mode 100644 crates/collab_ui/src/collaborator_list_popover.rs diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 02cbab21d0d02e93ba0f95adb838be3c64f2d6ec..2b3c7e1c63a18957f3d4d30f54d5b90b5e8420fe 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -51,9 +51,8 @@ impl Entity for AutoUpdater { pub fn init(http_client: Arc, server_url: String, cx: &mut AppContext) { if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) { - let server_url = server_url; let auto_updater = cx.add_model(|cx| { - let updater = AutoUpdater::new(version, http_client, server_url.clone()); + let updater = AutoUpdater::new(version, http_client, server_url); let mut update_subscription = cx .global::() @@ -74,25 +73,32 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo updater }); cx.set_global(Some(auto_updater)); - cx.add_global_action(|_: &Check, cx| { - if let Some(updater) = AutoUpdater::get(cx) { - updater.update(cx, |updater, cx| updater.poll(cx)); - } - }); - cx.add_global_action(move |_: &ViewReleaseNotes, cx| { - let latest_release_url = if cx.has_global::() - && *cx.global::() == ReleaseChannel::Preview - { - format!("{server_url}/releases/preview/latest") - } else { - format!("{server_url}/releases/latest") - }; - cx.platform().open_url(&latest_release_url); - }); + cx.add_global_action(check); + cx.add_global_action(view_release_notes); cx.add_action(UpdateNotification::dismiss); } } +pub fn check(_: &Check, cx: &mut AppContext) { + if let Some(updater) = AutoUpdater::get(cx) { + updater.update(cx, |updater, cx| updater.poll(cx)); + } +} + +fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { + if let Some(auto_updater) = AutoUpdater::get(cx) { + let server_url = &auto_updater.read(cx).server_url; + let latest_release_url = if cx.has_global::() + && *cx.global::() == ReleaseChannel::Preview + { + format!("{server_url}/releases/preview/latest") + } else { + format!("{server_url}/releases/latest") + }; + cx.platform().open_url(&latest_release_url); + } +} + pub fn notify_of_any_new_update( workspace: WeakViewHandle, cx: &mut AppContext, diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index c0b88fdf5e38be391e459f21f11e5c8aa9b2c3f6..b48ac2a413b00e2f24a41ba742883019b507d961 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -63,8 +63,8 @@ impl View for UpdateNotification { .with_height(style.button_width) }) .with_padding(Padding::uniform(5.)) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(Cancel) + .on_click(MouseButton::Left, move |_, this, cx| { + this.dismiss(&Default::default(), cx) }) .aligned() .constrained() @@ -84,7 +84,7 @@ impl View for UpdateNotification { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(ViewReleaseNotes) + crate::view_release_notes(&Default::default(), cx) }) .into_any_named("update notification") } diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index c09706f378d9ca560fed5b68f9ad182606212b3b..f3be60f8de7fb343da80012271b3439524d36d4e 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -1,13 +1,13 @@ use gpui::{ elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext, - ViewHandle, + ViewHandle, WeakViewHandle, }; use itertools::Itertools; use search::ProjectSearchView; use settings::Settings; use workspace::{ item::{ItemEvent, ItemHandle}, - ToolbarItemLocation, ToolbarItemView, + ToolbarItemLocation, ToolbarItemView, Workspace, }; pub enum Event { @@ -19,15 +19,17 @@ pub struct Breadcrumbs { active_item: Option>, project_search: Option>, subscription: Option, + workspace: WeakViewHandle, } impl Breadcrumbs { - pub fn new() -> Self { + pub fn new(workspace: &Workspace) -> Self { Self { pane_focused: false, active_item: Default::default(), subscription: Default::default(), project_search: Default::default(), + workspace: workspace.weak_handle(), } } } @@ -85,8 +87,12 @@ impl View for Breadcrumbs { let style = style.style_for(state, false); crumbs.with_style(style.container) }) - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(outline::Toggle); + .on_click(MouseButton::Left, |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + outline::toggle(workspace, &Default::default(), cx) + }) + } }) .with_tooltip::( 0, diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 95fe26fa11c47c89489e523f1bbdd25e80481a1c..a9894dade1b69cb072edba1f0e37cd34ab5056ad 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,7 +1,6 @@ use crate::{ - collaborator_list_popover, collaborator_list_popover::CollaboratorListPopover, contact_notification::ContactNotification, contacts_popover, face_pile::FacePile, - ToggleScreenSharing, + toggle_screen_sharing, ToggleScreenSharing, }; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, ContactEventKind, SignIn, SignOut, User, UserStore}; @@ -27,7 +26,6 @@ use workspace::{FollowNextCollaborator, Workspace}; actions!( collab, [ - ToggleCollaboratorList, ToggleContactsMenu, ToggleUserMenu, ShareProject, @@ -36,7 +34,6 @@ actions!( ); pub fn init(cx: &mut AppContext) { - cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover); cx.add_action(CollabTitlebarItem::toggle_contacts_popover); cx.add_action(CollabTitlebarItem::share_project); cx.add_action(CollabTitlebarItem::unshare_project); @@ -48,7 +45,6 @@ pub struct CollabTitlebarItem { user_store: ModelHandle, contacts_popover: Option>, user_menu: ViewHandle, - collaborator_list_popover: Option>, _subscriptions: Vec, } @@ -172,7 +168,6 @@ impl CollabTitlebarItem { menu.set_position_mode(OverlayPositionMode::Local); menu }), - collaborator_list_popover: None, _subscriptions: subscriptions, } } @@ -217,36 +212,6 @@ impl CollabTitlebarItem { } } - pub fn toggle_collaborator_list_popover( - &mut self, - _: &ToggleCollaboratorList, - cx: &mut ViewContext, - ) { - match self.collaborator_list_popover.take() { - Some(_) => {} - None => { - if let Some(workspace) = self.workspace.upgrade(cx) { - let user_store = workspace.read(cx).user_store().clone(); - let view = cx.add_view(|cx| CollaboratorListPopover::new(user_store, cx)); - - cx.subscribe(&view, |this, _, event, cx| { - match event { - collaborator_list_popover::Event::Dismissed => { - this.collaborator_list_popover = None; - } - } - - cx.notify(); - }) - .detach(); - - self.collaborator_list_popover = Some(view); - } - } - } - cx.notify(); - } - pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext) { if self.contacts_popover.take().is_none() { if let Some(workspace) = self.workspace.upgrade(cx) { @@ -357,8 +322,8 @@ impl CollabTitlebarItem { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ToggleContactsMenu); + .on_click(MouseButton::Left, move |_, this, cx| { + this.toggle_contacts_popover(&Default::default(), cx) }) .with_tooltip::( 0, @@ -405,7 +370,7 @@ impl CollabTitlebarItem { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ToggleScreenSharing); + toggle_screen_sharing(&Default::default(), cx) }) .with_tooltip::( 0, @@ -451,11 +416,11 @@ impl CollabTitlebarItem { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { + .on_click(MouseButton::Left, move |_, this, cx| { if is_shared { - cx.dispatch_action(UnshareProject); + this.unshare_project(&Default::default(), cx); } else { - cx.dispatch_action(ShareProject); + this.share_project(&Default::default(), cx); } }) .with_tooltip::( @@ -496,8 +461,8 @@ impl CollabTitlebarItem { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ToggleUserMenu); + .on_click(MouseButton::Left, move |_, this, cx| { + this.toggle_user_menu(&Default::default(), cx) }) .with_tooltip::( 0, @@ -527,8 +492,13 @@ impl CollabTitlebarItem { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(SignIn); + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let client = workspace.read(cx).app_state().client.clone(); + cx.app_context() + .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await }) + .detach_and_log_err(cx); + } }) .into_any() } @@ -862,7 +832,7 @@ impl CollabTitlebarItem { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(auto_update::Check); + auto_update::check(&Default::default(), cx); }) .into_any(), ), diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3f3998fb6dcaf994c8ce730dcd539225e06d16df..c0734388b1512b2e9cac5014c460bf1a8c09650b 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,5 +1,4 @@ mod collab_titlebar_item; -mod collaborator_list_popover; mod contact_finder; mod contact_list; mod contact_notification; diff --git a/crates/collab_ui/src/collaborator_list_popover.rs b/crates/collab_ui/src/collaborator_list_popover.rs deleted file mode 100644 index 68206444415bd1b18909aaf73f11348e6c56dc1b..0000000000000000000000000000000000000000 --- a/crates/collab_ui/src/collaborator_list_popover.rs +++ /dev/null @@ -1,161 +0,0 @@ -use call::ActiveCall; -use client::UserStore; -use gpui::Action; -use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext}; -use settings::Settings; - -use crate::collab_titlebar_item::ToggleCollaboratorList; - -pub(crate) enum Event { - Dismissed, -} - -enum Collaborator { - SelfUser { username: String }, - RemoteUser { username: String }, -} - -actions!(collaborator_list_popover, [NoOp]); - -pub(crate) struct CollaboratorListPopover { - list_state: ListState, -} - -impl Entity for CollaboratorListPopover { - type Event = Event; -} - -impl View for CollaboratorListPopover { - fn ui_name() -> &'static str { - "CollaboratorListPopover" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); - - MouseEventHandler::::new(0, cx, |_, _| { - List::new(self.list_state.clone()) - .contained() - .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key - .constrained() - .with_width(theme.contacts_popover.width) - .with_height(theme.contacts_popover.height) - }) - .on_down_out(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ToggleCollaboratorList); - }) - .into_any() - } - - fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); - } -} - -impl CollaboratorListPopover { - pub fn new(user_store: ModelHandle, cx: &mut ViewContext) -> Self { - let active_call = ActiveCall::global(cx); - - let mut collaborators = user_store - .read(cx) - .current_user() - .map(|u| Collaborator::SelfUser { - username: u.github_login.clone(), - }) - .into_iter() - .collect::>(); - - //TODO: What should the canonical sort here look like, consult contacts list implementation - if let Some(room) = active_call.read(cx).room() { - for participant in room.read(cx).remote_participants() { - collaborators.push(Collaborator::RemoteUser { - username: participant.1.user.github_login.clone(), - }); - } - } - - Self { - list_state: ListState::new( - collaborators.len(), - Orientation::Top, - 0., - move |_, index, cx| match &collaborators[index] { - Collaborator::SelfUser { username } => render_collaborator_list_entry( - index, - username, - None::, - None, - Svg::new("icons/chevron_right_12.svg"), - NoOp, - "Leave call".to_owned(), - cx, - ), - - Collaborator::RemoteUser { username } => render_collaborator_list_entry( - index, - username, - Some(NoOp), - Some(format!("Follow {username}")), - Svg::new("icons/x_mark_12.svg"), - NoOp, - format!("Remove {username} from call"), - cx, - ), - }, - ), - } - } -} - -fn render_collaborator_list_entry( - index: usize, - username: &str, - username_action: Option, - username_tooltip: Option, - icon: Svg, - icon_action: IA, - icon_tooltip: String, - cx: &mut ViewContext, -) -> AnyElement { - enum Username {} - enum UsernameTooltip {} - enum Icon {} - enum IconTooltip {} - - let theme = &cx.global::().theme; - let username_theme = theme.contact_list.contact_username.text.clone(); - let tooltip_theme = theme.tooltip.clone(); - - let username = - MouseEventHandler::::new(index, cx, |_, _| { - Label::new(username.to_owned(), username_theme.clone()) - }) - .on_click(MouseButton::Left, move |_, _, cx| { - if let Some(username_action) = username_action.clone() { - cx.dispatch_action(username_action); - } - }); - - Flex::row() - .with_child(if let Some(username_tooltip) = username_tooltip { - username - .with_tooltip::( - index, - username_tooltip, - None, - tooltip_theme.clone(), - cx, - ) - .into_any() - } else { - username.into_any() - }) - .with_child( - MouseEventHandler::::new(index, cx, |_, _| icon) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(icon_action.clone()) - }) - .with_tooltip::(index, icon_tooltip, None, tooltip_theme, cx), - ) - .into_any() -} diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 319df337d78d8e6e18e376bb32c817dda68404b3..0429182bf37be56c93ceedcc7daab64f7fbbb3d3 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -1,4 +1,3 @@ -use crate::contacts_popover; use call::ActiveCall; use client::{proto::PeerId, Contact, User, UserStore}; use editor::{Cancel, Editor}; @@ -140,6 +139,7 @@ pub struct RespondToContactRequest { } pub enum Event { + ToggleContactFinder, Dismissed, } @@ -1116,11 +1116,14 @@ impl ContactList { ) .with_padding(Padding::uniform(2.)) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(RemoveContact { - user_id, - github_login: github_login.clone(), - }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.remove_contact( + &RemoveContact { + user_id, + github_login: github_login.clone(), + }, + cx, + ); }) .flex_float(), ) @@ -1203,11 +1206,14 @@ impl ContactList { render_icon_button(button_style, "icons/x_mark_8.svg").aligned() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(RespondToContactRequest { - user_id, - accept: false, - }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.respond_to_contact_request( + &RespondToContactRequest { + user_id, + accept: false, + }, + cx, + ); }) .contained() .with_margin_right(button_spacing), @@ -1225,11 +1231,14 @@ impl ContactList { .flex_float() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(RespondToContactRequest { - user_id, - accept: true, - }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.respond_to_contact_request( + &RespondToContactRequest { + user_id, + accept: true, + }, + cx, + ); }), ); } else { @@ -1246,11 +1255,14 @@ impl ContactList { }) .with_padding(Padding::uniform(2.)) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(RemoveContact { - user_id, - github_login: github_login.clone(), - }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.remove_contact( + &RemoveContact { + user_id, + github_login: github_login.clone(), + }, + cx, + ); }) .flex_float(), ); @@ -1318,7 +1330,7 @@ impl View for ContactList { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(contacts_popover::ToggleContactFinder) + cx.emit(Event::ToggleContactFinder) }) .with_tooltip::( 0, diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index 60f0bf0e73b706ddf2db1e651c7e46c1d1902723..b35eb09b3104223b1bb19faf9e9a7907f5edef70 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -1,7 +1,6 @@ use crate::{ contact_finder::{build_contact_finder, ContactFinder}, contact_list::ContactList, - ToggleContactsMenu, }; use client::UserStore; use gpui::{ @@ -72,8 +71,11 @@ impl ContactsPopover { let child = cx .add_view(|cx| ContactList::new(&workspace, cx).with_editor_text(editor_text, cx)); cx.focus(&child); - self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event { + self._subscription = Some(cx.subscribe(&child, |this, _, event, cx| match event { crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed), + crate::contact_list::Event::ToggleContactFinder => { + this.toggle_contact_finder(&Default::default(), cx) + } })); self.child = Child::ContactList(child); cx.notify(); @@ -106,9 +108,7 @@ impl View for ContactsPopover { .with_width(theme.contacts_popover.width) .with_height(theme.contacts_popover.height) }) - .on_down_out(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ToggleContactsMenu); - }) + .on_down_out(MouseButton::Left, move |_, _, cx| cx.emit(Event::Dismissed)) .into_any() } diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 42c3c886ad6cdf1d034fb9ff045a5c302b942992..447b561b953e3f08d67961e4106f557f1b64a96b 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -1,3 +1,4 @@ +use crate::toggle_screen_sharing; use call::ActiveCall; use gpui::{ color::Color, @@ -7,8 +8,6 @@ use gpui::{ }; use settings::Settings; -use crate::ToggleScreenSharing; - pub fn init(cx: &mut AppContext) { let active_call = ActiveCall::global(cx); @@ -54,7 +53,7 @@ impl View for SharingStatusIndicator { .aligned() }) .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(ToggleScreenSharing); + toggle_screen_sharing(&Default::default(), cx) }) .into_any() } diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 6f66d710cb77d3b50ccfdd06c912b898768a5176..3a3ca9b66bbfa917ad71c215b8fdf67c334e1651 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -485,7 +485,11 @@ impl ContextMenu { .contained() .with_style(style.container) }) - .on_down_out(MouseButton::Left, |_, _, cx| cx.dispatch_action(Cancel)) - .on_down_out(MouseButton::Right, |_, _, cx| cx.dispatch_action(Cancel)) + .on_down_out(MouseButton::Left, |_, this, cx| { + this.cancel(&Default::default(), cx); + }) + .on_down_out(MouseButton::Right, |_, this, cx| { + this.cancel(&Default::default(), cx); + }) } } diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index fdb4828cd0df93e2f609c9e3d3b7614acee49385..02a5b347d47cb9f84fc9fb4d407b5549391bf91e 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -196,7 +196,7 @@ impl CopilotCodeVerification { .contained() .with_style(style.auth.prompting.hint.container.clone()), ) - .with_child(theme::ui::cta_button_with_click::( + .with_child(theme::ui::cta_button::( if connect_clicked { "Waiting for connection..." } else { @@ -250,7 +250,7 @@ impl CopilotCodeVerification { .contained() .with_style(enabled_style.hint.container), ) - .with_child(theme::ui::cta_button_with_click::( + .with_child(theme::ui::cta_button::( "Done", style.auth.content_width, &style.auth.cta_button, @@ -304,7 +304,7 @@ impl CopilotCodeVerification { .contained() .with_style(unauthorized_style.warning.container), ) - .with_child(theme::ui::cta_button_with_click::( + .with_child(theme::ui::cta_button::( "Subscribe on GitHub", style.auth.content_width, &style.auth.cta_button, diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 19b1506509d8faa9e610b9da5dbcbbb70bb3a393..f0ceacc6194326a221454edeeb07f385421f5054 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -3,18 +3,19 @@ use editor::{Editor, GoToDiagnostic}; use gpui::{ elements::*, platform::{CursorStyle, MouseButton}, - serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, - WeakViewHandle, + serde_json, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::Diagnostic; use lsp::LanguageServerId; -use project::Project; use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; +use workspace::{item::ItemHandle, StatusItemView, Workspace}; + +use crate::ProjectDiagnosticsEditor; pub struct DiagnosticIndicator { summary: project::DiagnosticSummary, active_editor: Option>, + workspace: WeakViewHandle, current_diagnostic: Option, in_progress_checks: HashSet, _observe_active_editor: Option, @@ -25,7 +26,8 @@ pub fn init(cx: &mut AppContext) { } impl DiagnosticIndicator { - pub fn new(project: &ModelHandle, cx: &mut ViewContext) -> Self { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let project = workspace.project(); cx.subscribe(project, |this, project, event, cx| match event { project::Event::DiskBasedDiagnosticsStarted { language_server_id } => { this.in_progress_checks.insert(*language_server_id); @@ -46,6 +48,7 @@ impl DiagnosticIndicator { .language_servers_running_disk_based_diagnostics() .collect(), active_editor: None, + workspace: workspace.weak_handle(), current_diagnostic: None, _observe_active_editor: None, } @@ -163,8 +166,12 @@ impl View for DiagnosticIndicator { }) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(crate::Deploy) + .on_click(MouseButton::Left, |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx) + }) + } }) .with_tooltip::( 0, @@ -200,8 +207,8 @@ impl View for DiagnosticIndicator { .with_margin_left(item_spacing) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(GoToDiagnostic) + .on_click(MouseButton::Left, |_, this, cx| { + this.go_to_next_diagnostic(&Default::default(), cx) }), ); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d8c2e81ce16f234ea5a1f2b378784446a7993eb0..0849c0ef93525ef13ad7c16725d77272f6287ecc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -809,10 +809,13 @@ impl CompletionsMenu { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_down(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ConfirmCompletion { - item_ix: Some(item_ix), - }); + .on_down(MouseButton::Left, move |_, this, cx| { + this.confirm_completion( + &ConfirmCompletion { + item_ix: Some(item_ix), + }, + cx, + ); }) .into_any(), ); @@ -970,9 +973,23 @@ impl CodeActionsMenu { .with_style(item_style) }) .with_cursor_style(CursorStyle::PointingHand) - .on_down(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ConfirmCodeAction { - item_ix: Some(item_ix), + .on_down(MouseButton::Left, move |_, this, cx| { + let workspace = this + .workspace + .as_ref() + .and_then(|(workspace, _)| workspace.upgrade(cx)); + cx.window_context().defer(move |cx| { + if let Some(workspace) = workspace { + workspace.update(cx, |workspace, cx| { + if let Some(task) = Editor::confirm_code_action( + workspace, + &Default::default(), + cx, + ) { + task.detach_and_log_err(cx); + } + }); + } }); }) .into_any(), @@ -3138,10 +3155,13 @@ impl Editor { }) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) - .on_down(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(ToggleCodeActions { - deployed_from_indicator: true, - }); + .on_down(MouseButton::Left, |_, this, cx| { + this.toggle_code_actions( + &ToggleCodeActions { + deployed_from_indicator: true, + }, + cx, + ); }) .into_any(), ) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 75bd572d95cc4b4e5f3b397bede51b8958401eab..7c43885763e09286c5916b0d89883491050bc086 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -211,10 +211,13 @@ impl EditorElement { enum GutterHandlers {} scene.push_mouse_region( MouseRegion::new::(cx.view_id(), cx.view_id() + 1, gutter_bounds) - .on_hover(|hover, _: &mut Editor, cx| { - cx.dispatch_action(GutterHover { - hovered: hover.started, - }) + .on_hover(|hover, editor: &mut Editor, cx| { + editor.gutter_hover( + &GutterHover { + hovered: hover.started, + }, + cx, + ); }), ) } @@ -754,8 +757,8 @@ impl EditorElement { scene.push_mouse_region( MouseRegion::new::(cx.view_id(), *id as usize, bound) - .on_click(MouseButton::Left, move |_, _: &mut Editor, cx| { - cx.dispatch_action(UnfoldAt { buffer_row }) + .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| { + editor.unfold_at(&UnfoldAt { buffer_row }, cx) }) .with_notify_on_hover(true) .with_notify_on_click(true), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 2932fa547edbb766b7d725f5c7eb75528307bf6a..438c662ed1125209640b6481097f9b34fd0787c6 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,3 +1,7 @@ +use crate::{ + display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot, + EditorStyle, RangeToAnchorExt, +}; use futures::FutureExt; use gpui::{ actions, @@ -12,11 +16,6 @@ use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; -use crate::{ - display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot, - EditorStyle, GoToDiagnostic, RangeToAnchorExt, -}; - pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; @@ -668,8 +667,8 @@ impl DiagnosticPopover { ..Default::default() }) .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(GoToDiagnostic) + .on_click(MouseButton::Left, |_, this, cx| { + this.go_to_diagnostic(&Default::default(), cx) }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 9536477c74eb0ae373a85466c6df9c15cdf21247..b464d0088707e73be0f71a1c2a333823ce76f13d 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,15 +1,16 @@ use gpui::{ elements::*, platform::{CursorStyle, MouseButton}, - Entity, View, ViewContext, + Entity, View, ViewContext, WeakViewHandle, }; use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; +use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; pub struct DeployFeedbackButton { active: bool, + workspace: WeakViewHandle, } impl Entity for DeployFeedbackButton { @@ -17,8 +18,11 @@ impl Entity for DeployFeedbackButton { } impl DeployFeedbackButton { - pub fn new() -> Self { - DeployFeedbackButton { active: false } + pub fn new(workspace: &Workspace) -> Self { + DeployFeedbackButton { + active: false, + workspace: workspace.weak_handle(), + } } } @@ -52,9 +56,12 @@ impl View for DeployFeedbackButton { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, _, cx| { + .on_click(MouseButton::Left, move |_, this, cx| { if !active { - cx.dispatch_action(GiveFeedback) + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace + .update(cx, |workspace, cx| FeedbackEditor::deploy(workspace, cx)) + } } }) .with_tooltip::( diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index a8860f7bc50590eb34e8f1c4d6cac07757bfd381..7cbb3a673be167d91582d13b89f854fef7007b41 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -3,20 +3,10 @@ pub mod feedback_editor; pub mod feedback_info_text; pub mod submit_feedback_button; -use std::sync::Arc; - mod system_specs; -use gpui::{actions, impl_actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext}; -use serde::Deserialize; +use gpui::{actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext}; use system_specs::SystemSpecs; -use workspace::{AppState, Workspace}; - -#[derive(Deserialize, Clone, PartialEq)] -pub struct OpenBrowser { - pub url: Arc, -} - -impl_actions!(zed, [OpenBrowser]); +use workspace::Workspace; actions!( zed, @@ -28,29 +18,20 @@ actions!( ] ); -pub fn init(app_state: Arc, cx: &mut AppContext) { - let system_specs = SystemSpecs::new(&cx); - let system_specs_text = system_specs.to_string(); - - feedback_editor::init(system_specs, app_state, cx); - - cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); - - let url = format!( - "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", - urlencoding::encode(&system_specs_text) - ); +pub fn init(cx: &mut AppContext) { + feedback_editor::init(cx); cx.add_action( move |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { + let specs = SystemSpecs::new(&cx).to_string(); cx.prompt( PromptLevel::Info, - &format!("Copied into clipboard:\n\n{system_specs_text}"), + &format!("Copied into clipboard:\n\n{specs}"), &["OK"], ); - let item = ClipboardItem::new(system_specs_text.clone()); + let item = ClipboardItem::new(specs.clone()); cx.write_to_clipboard(item); }, ); @@ -58,24 +39,24 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action( |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; - cx.dispatch_action(OpenBrowser { - url: url.into(), - }); + cx.platform().open_url(url); }, ); cx.add_action( move |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { - cx.dispatch_action(OpenBrowser { - url: url.clone().into(), - }); + let url = format!( + "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + urlencoding::encode(&SystemSpecs::new(&cx).to_string()) + ); + cx.platform().open_url(&url); }, ); - cx.add_action( - |_: &mut Workspace, _: &OpenZedCommunityRepo, cx: &mut ViewContext| { - let url = "https://github.com/zed-industries/community"; - cx.dispatch_action(OpenBrowser { url: url.into() }); - }, - ); + cx.add_global_action(open_zed_community_repo); +} + +pub fn open_zed_community_repo(_: &OpenZedCommunityRepo, cx: &mut AppContext) { + let url = "https://github.com/zed-industries/community"; + cx.platform().open_url(&url); } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 8f41762eed2d769020530f3af81c745806789d3a..253cc511ee569e81455ea4301012dda9e38c609f 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -1,10 +1,4 @@ -use std::{ - any::TypeId, - borrow::Cow, - ops::{Range, RangeInclusive}, - sync::Arc, -}; - +use crate::system_specs::SystemSpecs; use anyhow::bail; use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use editor::{Anchor, Editor}; @@ -19,40 +13,34 @@ use gpui::{ use isahc::Request; use language::Buffer; use postage::prelude::Stream; - use project::Project; use serde::Serialize; +use std::{ + any::TypeId, + borrow::Cow, + ops::{Range, RangeInclusive}, + sync::Arc, +}; use util::ResultExt; use workspace::{ - item::{Item, ItemHandle}, + item::{Item, ItemEvent, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - AppState, Workspace, + smallvec::SmallVec, + Workspace, }; -use crate::{submit_feedback_button::SubmitFeedbackButton, system_specs::SystemSpecs}; - const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; actions!(feedback, [GiveFeedback, SubmitFeedback]); -pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut AppContext) { +pub fn init(cx: &mut AppContext) { cx.add_action({ move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext| { - FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx); + FeedbackEditor::deploy(workspace, cx); } }); - - cx.add_async_action( - |submit_feedback_button: &mut SubmitFeedbackButton, _: &SubmitFeedback, cx| { - if let Some(active_item) = submit_feedback_button.active_item.as_ref() { - Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx))) - } else { - None - } - }, - ); } #[derive(Serialize)] @@ -94,7 +82,7 @@ impl FeedbackEditor { } } - fn handle_save(&mut self, cx: &mut ViewContext) -> Task> { + pub fn submit(&mut self, cx: &mut ViewContext) -> Task> { let feedback_text = self.editor.read(cx).text(cx); let feedback_char_count = feedback_text.chars().count(); let feedback_text = feedback_text.trim().to_string(); @@ -133,10 +121,8 @@ impl FeedbackEditor { if answer == Some(0) { match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await { Ok(_) => { - this.update(&mut cx, |_, cx| { - cx.dispatch_action(workspace::CloseActiveItem); - }) - .log_err(); + this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed)) + .log_err(); } Err(error) => { log::error!("{}", error); @@ -198,22 +184,21 @@ impl FeedbackEditor { } impl FeedbackEditor { - pub fn deploy( - system_specs: SystemSpecs, - _: &mut Workspace, - app_state: Arc, - cx: &mut ViewContext, - ) { - let markdown = app_state.languages.language_for_name("Markdown"); + pub fn deploy(workspace: &mut Workspace, cx: &mut ViewContext) { + let markdown = workspace + .app_state() + .languages + .language_for_name("Markdown"); cx.spawn(|workspace, mut cx| async move { let markdown = markdown.await.log_err(); workspace .update(&mut cx, |workspace, cx| { - workspace.with_local_workspace(&app_state, cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { let project = workspace.project().clone(); let buffer = project .update(cx, |project, cx| project.create_buffer("", markdown, cx)) .expect("creating buffers on a local workspace always succeeds"); + let system_specs = SystemSpecs::new(cx); let feedback_editor = cx .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx)); workspace.add_item(Box::new(feedback_editor), cx); @@ -291,7 +276,7 @@ impl Item for FeedbackEditor { _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.handle_save(cx) + self.submit(cx) } fn save_as( @@ -300,7 +285,7 @@ impl Item for FeedbackEditor { _: std::path::PathBuf, cx: &mut ViewContext, ) -> Task> { - self.handle_save(cx) + self.submit(cx) } fn reload( @@ -353,6 +338,10 @@ impl Item for FeedbackEditor { None } } + + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + Editor::to_item_events(event) + } } impl SearchableItem for FeedbackEditor { diff --git a/crates/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs index b557c4f7e1fa49b549e5c6bf4ab5e0f6ad3ec8be..9aee4e0e68b6c2ec4f1d590373559eb97b4b8412 100644 --- a/crates/feedback/src/feedback_info_text.rs +++ b/crates/feedback/src/feedback_info_text.rs @@ -6,7 +6,7 @@ use gpui::{ use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; -use crate::{feedback_editor::FeedbackEditor, OpenZedCommunityRepo}; +use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo}; pub struct FeedbackInfoText { active_item: Option>, @@ -57,7 +57,7 @@ impl View for FeedbackInfoText { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(OpenZedCommunityRepo) + open_zed_community_repo(&Default::default(), cx) }), ) .with_child( diff --git a/crates/feedback/src/submit_feedback_button.rs b/crates/feedback/src/submit_feedback_button.rs index 918c74bed8fa0aa4d83847f69c4b2d2eb5e5bb8f..ccd58c3dc97dbcdad2920722a4e4fb0882edbb2f 100644 --- a/crates/feedback/src/submit_feedback_button.rs +++ b/crates/feedback/src/submit_feedback_button.rs @@ -1,12 +1,16 @@ +use crate::feedback_editor::{FeedbackEditor, SubmitFeedback}; +use anyhow::Result; use gpui::{ elements::{Label, MouseEventHandler}, platform::{CursorStyle, MouseButton}, - AnyElement, Element, Entity, View, ViewContext, ViewHandle, + AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle, }; use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; -use crate::feedback_editor::{FeedbackEditor, SubmitFeedback}; +pub fn init(cx: &mut AppContext) { + cx.add_async_action(SubmitFeedbackButton::submit); +} pub struct SubmitFeedbackButton { pub(crate) active_item: Option>, @@ -18,6 +22,18 @@ impl SubmitFeedbackButton { active_item: Default::default(), } } + + pub fn submit( + &mut self, + _: &SubmitFeedback, + cx: &mut ViewContext, + ) -> Option>> { + if let Some(active_item) = self.active_item.as_ref() { + Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.submit(cx))) + } else { + None + } + } } impl Entity for SubmitFeedbackButton { @@ -39,8 +55,8 @@ impl View for SubmitFeedbackButton { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(SubmitFeedback) + .on_click(MouseButton::Left, |_, this, cx| { + this.submit(&Default::default(), cx); }) .aligned() .contained() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 7343a7245d5d05183eb821b5aca5953570262c31..02d6c1a2ac9dc11ce7cdf8a035eeb450173883a1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1745,10 +1745,6 @@ impl AppContext { self.pending_effects.push_back(Effect::RefreshWindows); } - fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) { - self.dispatch_any_action_at(window_id, view_id, Box::new(action)); - } - pub fn dispatch_any_action_at( &mut self, window_id: usize, @@ -3189,13 +3185,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> { self.window_context.notify_view(window_id, view_id); } - pub fn dispatch_action(&mut self, action: impl Action) { - let window_id = self.window_id; - let view_id = self.view_id; - self.window_context - .dispatch_action_at(window_id, view_id, action) - } - pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext)) { let handle = self.handle(); self.window_context diff --git a/crates/gpui/src/views/select.rs b/crates/gpui/src/views/select.rs index 285f37639e9ae5ebcf0fad9d78c642d54e7181c5..f3be9de3ec32dc30ecf9a598962034b5f2bbd4ea 100644 --- a/crates/gpui/src/views/select.rs +++ b/crates/gpui/src/views/select.rs @@ -1,8 +1,8 @@ use serde::Deserialize; use crate::{ - actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext, - View, ViewContext, WeakViewHandle, + actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, View, + ViewContext, WeakViewHandle, }; pub struct Select { @@ -116,10 +116,9 @@ impl View for Select { .contained() .with_style(style.header) }) - .on_click( - MouseButton::Left, - move |_, _, cx: &mut EventContext| cx.dispatch_action(ToggleSelect), - ), + .on_click(MouseButton::Left, move |_, this, cx| { + this.toggle(&Default::default(), cx); + }), ); if self.is_open { result.add_child(Overlay::new( @@ -143,12 +142,9 @@ impl View for Select { cx, ) }) - .on_click( - MouseButton::Left, - move |_, _, cx: &mut EventContext| { - cx.dispatch_action(SelectItem(ix)) - }, - ) + .on_click(MouseButton::Left, move |_, this, cx| { + this.select_item(&SelectItem(ix), cx); + }) .into_any() })) }, diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index 17e53b378c19aed464a5e7ef4b65262fe2ccaa03..425f4c8dd7f80ea4edc4fa88338a238d8db2c194 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -2,27 +2,23 @@ use editor::Editor; use gpui::{ elements::*, platform::{CursorStyle, MouseButton}, - Entity, Subscription, View, ViewContext, ViewHandle, + Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; use std::sync::Arc; -use workspace::{item::ItemHandle, StatusItemView}; +use workspace::{item::ItemHandle, StatusItemView, Workspace}; pub struct ActiveBufferLanguage { active_language: Option>>, + workspace: WeakViewHandle, _observe_active_editor: Option, } -impl Default for ActiveBufferLanguage { - fn default() -> Self { - Self::new() - } -} - impl ActiveBufferLanguage { - pub fn new() -> Self { + pub fn new(workspace: &Workspace) -> Self { Self { active_language: None, + workspace: workspace.weak_handle(), _observe_active_editor: None, } } @@ -66,8 +62,12 @@ impl View for ActiveBufferLanguage { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(crate::Toggle) + .on_click(MouseButton::Left, |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + crate::toggle(workspace, &Default::default(), cx) + }); + } }) .into_any() } else { diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 29da7c926dcfe6967595e05d4bc8949ee446273f..fd43111443373773593573d6189ec407ec165e73 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -11,21 +11,18 @@ use project::Project; use settings::Settings; use std::sync::Arc; use util::ResultExt; -use workspace::{AppState, Workspace}; +use workspace::Workspace; actions!(language_selector, [Toggle]); -pub fn init(app_state: Arc, cx: &mut AppContext) { +pub fn init(cx: &mut AppContext) { Picker::::init(cx); - cx.add_action({ - let language_registry = app_state.languages.clone(); - move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx) - }); + cx.add_action(toggle); } -fn toggle( +pub fn toggle( workspace: &mut Workspace, - registry: Arc, + _: &Toggle, cx: &mut ViewContext, ) -> Option<()> { let (_, buffer, _) = workspace @@ -34,6 +31,7 @@ fn toggle( .read(cx) .active_excerpt(cx)?; workspace.toggle_modal(cx, |workspace, cx| { + let registry = workspace.app_state().languages.clone(); cx.add_view(|cx| { Picker::new( LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry), diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index b2154e7bb2a36ceb27c17826eff2ab7b886e6d17..6ecaf370e40b1c846a1775bb27c421468fcf5066 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -24,7 +24,7 @@ pub fn init(cx: &mut AppContext) { OutlineView::init(cx); } -fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { +pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { if let Some(editor) = workspace .active_item(cx) .and_then(|item| item.downcast::()) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0ca187bfe57b293e488f602b857cdb7845862c2c..373417b167047ac80cd2d04aa8d7eb22068fa1df 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,7 +13,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, + ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -44,6 +44,7 @@ pub struct ProjectPanel { clipboard_entry: Option, context_menu: ViewHandle, dragged_entry_destination: Option>, + workspace: WeakViewHandle, } #[derive(Copy, Clone)] @@ -137,7 +138,8 @@ pub enum Event { } impl ProjectPanel { - pub fn new(project: ModelHandle, cx: &mut ViewContext) -> ViewHandle { + pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { + let project = workspace.project().clone(); let project_panel = cx.add_view(|cx: &mut ViewContext| { cx.observe(&project, |this, _, cx| { this.update_visible_entries(None, cx); @@ -206,6 +208,7 @@ impl ProjectPanel { clipboard_entry: None, context_menu: cx.add_view(ContextMenu::new), dragged_entry_destination: None, + workspace: workspace.weak_handle(), }; this.update_visible_entries(None, cx); this @@ -1296,8 +1299,14 @@ impl View for ProjectPanel { ) } }) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(workspace::Open) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + if let Some(task) = workspace.open(&Default::default(), cx) { + task.detach_and_log_err(cx); + } + }) + } }) .with_cursor_style(CursorStyle::PointingHand), ) @@ -1400,7 +1409,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..50, cx), &[ @@ -1492,7 +1501,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); select_path(&panel, "root1", cx); assert_eq!( @@ -1785,7 +1794,7 @@ mod tests { let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); panel.update(cx, |panel, cx| { panel.select_next(&Default::default(), cx); diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 414b3e932348ee185f4f3b46190d24c4c19b61db..644e74d87887ffd82124fff90fc7d4eccdd9bac0 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -11,24 +11,24 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; use settings::Settings; -use std::sync::{Arc, Weak}; +use std::sync::Arc; use workspace::{ - notifications::simple_message_notification::MessageNotification, AppState, Workspace, - WorkspaceLocation, WORKSPACE_DB, + notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation, + WORKSPACE_DB, }; actions!(projects, [OpenRecent]); -pub fn init(cx: &mut AppContext, app_state: Weak) { - cx.add_async_action( - move |_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext| { - toggle(app_state.clone(), cx) - }, - ); +pub fn init(cx: &mut AppContext) { + cx.add_async_action(toggle); RecentProjects::init(cx); } -fn toggle(app_state: Weak, cx: &mut ViewContext) -> Option>> { +fn toggle( + _: &mut Workspace, + _: &OpenRecent, + cx: &mut ViewContext, +) -> Option>> { Some(cx.spawn(|workspace, mut cx| async move { let workspace_locations: Vec<_> = cx .background() @@ -49,11 +49,7 @@ fn toggle(app_state: Weak, cx: &mut ViewContext) -> Option< let workspace = cx.weak_handle(); cx.add_view(|cx| { RecentProjects::new( - RecentProjectsDelegate::new( - workspace, - workspace_locations, - app_state.clone(), - ), + RecentProjectsDelegate::new(workspace, workspace_locations), cx, ) .with_max_size(800., 1200.) @@ -74,7 +70,6 @@ type RecentProjects = Picker; struct RecentProjectsDelegate { workspace: WeakViewHandle, workspace_locations: Vec, - app_state: Weak, selected_match_index: usize, matches: Vec, } @@ -83,12 +78,10 @@ impl RecentProjectsDelegate { fn new( workspace: WeakViewHandle, workspace_locations: Vec, - app_state: Weak, ) -> Self { Self { workspace, workspace_locations, - app_state, selected_match_index: 0, matches: Default::default(), } @@ -155,20 +148,16 @@ impl PickerDelegate for RecentProjectsDelegate { } fn confirm(&mut self, cx: &mut ViewContext) { - if let Some(((selected_match, workspace), app_state)) = self + if let Some((selected_match, workspace)) = self .matches .get(self.selected_index()) .zip(self.workspace.upgrade(cx)) - .zip(self.app_state.upgrade()) { let workspace_location = &self.workspace_locations[selected_match.candidate_id]; workspace .update(cx, |workspace, cx| { - workspace.open_workspace_for_paths( - workspace_location.paths().as_ref().clone(), - app_state, - cx, - ) + workspace + .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx) }) .detach_and_log_err(cx); cx.emit(PickerEvent::Dismiss); diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index 6349cbbfa43f879c665b28a09932e27f901199fe..8edf03f527321551d8a1047e38abfe1023e6d9f8 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -7,7 +7,11 @@ use gpui::{ }; use settings::Settings; use std::any::TypeId; -use workspace::{dock::FocusDock, item::ItemHandle, NewTerminal, StatusItemView, Workspace}; +use workspace::{ + dock::{Dock, FocusDock}, + item::ItemHandle, + NewTerminal, StatusItemView, Workspace, +}; pub struct TerminalButton { workspace: WeakViewHandle, @@ -80,7 +84,11 @@ impl View for TerminalButton { this.deploy_terminal_menu(cx); } else { if !active { - cx.dispatch_action(FocusDock); + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + Dock::focus_dock(workspace, &Default::default(), cx) + }) + } } }; }) diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 1198e81e92b634488d3fc29e13221648dc18f3fc..b86bfca8c42ae05900d76d86e19544531c245899 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -156,24 +156,7 @@ pub fn keystroke_label( pub type ButtonStyle = Interactive; -pub fn cta_button( - label: L, - action: A, - max_width: f32, - style: &ButtonStyle, - cx: &mut ViewContext, -) -> MouseEventHandler -where - L: Into>, - A: 'static + Action + Clone, - V: View, -{ - cta_button_with_click::(label, max_width, style, cx, move |_, _, cx| { - cx.dispatch_action(action.clone()) - }) -} - -pub fn cta_button_with_click( +pub fn cta_button( label: L, max_width: f32, style: &ButtonStyle, diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 1f2d73df144e797cc24403e56e3e3c803ec9e4ca..21332114e22bf81bf9147568c471bc0e3d77316b 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -6,20 +6,18 @@ use staff_mode::StaffMode; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry}; use util::ResultExt; -use workspace::{AppState, Workspace}; +use workspace::Workspace; actions!(theme_selector, [Toggle, Reload]); -pub fn init(app_state: Arc, cx: &mut AppContext) { - cx.add_action({ - let theme_registry = app_state.themes.clone(); - move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx) - }); +pub fn init(cx: &mut AppContext) { + cx.add_action(toggle); ThemeSelector::init(cx); } -fn toggle(workspace: &mut Workspace, themes: Arc, cx: &mut ViewContext) { - workspace.toggle_modal(cx, |_, cx| { +pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + workspace.toggle_modal(cx, |workspace, cx| { + let themes = workspace.app_state().themes.clone(); cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx)) }); } diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 7347a559a9028b981b8290c5453f59d852f1f277..260c279e181a8677f08124024ac3f7db537e8eed 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -18,7 +18,7 @@ pub fn init(cx: &mut AppContext) { BaseKeymapSelector::init(cx); } -fn toggle( +pub fn toggle( workspace: &mut Workspace, _: &ToggleBaseKeymapSelector, cx: &mut ViewContext, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 629e6f3989f28b8d36ad05a157ebcce1c9e866c5..a3d91adc910f658ab2bfd3b9af063ae4bc81142b 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -5,7 +5,7 @@ use std::{borrow::Cow, sync::Arc}; use db::kvp::KEY_VALUE_STORE; use gpui::{ elements::{Flex, Label, ParentElement}, - AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, + AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, }; use settings::{settings_file::SettingsFile, Settings}; @@ -20,7 +20,7 @@ pub const FIRST_OPEN: &str = "first_open"; pub fn init(cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { - let welcome_page = cx.add_view(WelcomePage::new); + let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item(Box::new(welcome_page), cx) }); @@ -30,7 +30,7 @@ pub fn init(cx: &mut AppContext) { pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { open_new(&app_state, cx, |workspace, cx| { workspace.toggle_sidebar(SidebarSide::Left, cx); - let welcome_page = cx.add_view(|cx| WelcomePage::new(cx)); + let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); cx.focus(&welcome_page); cx.notify(); @@ -43,6 +43,7 @@ pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { } pub struct WelcomePage { + workspace: WeakViewHandle, _settings_subscription: Subscription, } @@ -97,26 +98,46 @@ impl View for WelcomePage { ) .with_child( Flex::column() - .with_child(theme::ui::cta_button( + .with_child(theme::ui::cta_button::( "Choose a theme", - theme_selector::Toggle, width, &theme.welcome.button, cx, + |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + theme_selector::toggle(workspace, &Default::default(), cx) + }) + } + }, )) - .with_child(theme::ui::cta_button( + .with_child(theme::ui::cta_button::( "Choose a keymap", - ToggleBaseKeymapSelector, width, &theme.welcome.button, cx, + |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + base_keymap_picker::toggle( + workspace, + &Default::default(), + cx, + ) + }) + } + }, )) - .with_child(theme::ui::cta_button( + .with_child(theme::ui::cta_button::( "Install the CLI", - install_cli::Install, width, &theme.welcome.button, cx, + |_, _, cx| { + cx.app_context() + .spawn(|cx| async move { install_cli::install_cli(&cx).await }) + .detach_and_log_err(cx); + }, )) .contained() .with_style(theme.welcome.button_group) @@ -190,8 +211,9 @@ impl View for WelcomePage { } impl WelcomePage { - pub fn new(cx: &mut ViewContext) -> Self { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { WelcomePage { + workspace: workspace.weak_handle(), _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), } } @@ -220,11 +242,15 @@ impl Item for WelcomePage { fn show_toolbar(&self) -> bool { false } + fn clone_on_split( &self, _workspace_id: WorkspaceId, cx: &mut ViewContext, ) -> Option { - Some(WelcomePage::new(cx)) + Some(WelcomePage { + workspace: self.workspace.clone(), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + }) } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 33cd833019eb7a473c6cee8aeaa329f45bad93fa..8ac432dc4779a1ccb0132bcd3264fd414356edd7 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -271,11 +271,11 @@ impl Dock { } } - fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext) { + pub fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext) { Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx); } - fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { + pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext) { Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx); } @@ -374,8 +374,8 @@ impl Dock { .with_background_color(style.wash_color) }) .capture_all() - .on_down(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(HideDock); + .on_down(MouseButton::Left, |_, workspace, cx| { + Dock::hide_dock(workspace, &Default::default(), cx) }) .with_cursor_style(CursorStyle::Arrow), ) diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs index bf851839383b67f651c0d192c9b1e51c9a05443c..1fda55b7834967207a0f4463e548c8ad219bf84e 100644 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ b/crates/workspace/src/dock/toggle_dock_button.rs @@ -1,3 +1,5 @@ +use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock}; +use crate::{handle_dropped_item, StatusItemView, Workspace}; use gpui::{ elements::{Empty, MouseEventHandler, Svg}, platform::CursorStyle, @@ -6,10 +8,6 @@ use gpui::{ }; use settings::Settings; -use crate::{handle_dropped_item, StatusItemView, Workspace}; - -use super::{icon_for_dock_anchor, FocusDock, HideDock}; - pub struct ToggleDockButton { workspace: WeakViewHandle, } @@ -82,8 +80,12 @@ impl View for ToggleDockButton { if dock_position.is_visible() { button - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(HideDock); + .on_click(MouseButton::Left, |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + Dock::hide_dock(workspace, &Default::default(), cx) + }) + } }) .with_tooltip::( 0, @@ -94,8 +96,12 @@ impl View for ToggleDockButton { ) } else { button - .on_click(MouseButton::Left, |_, _, cx| { - cx.dispatch_action(FocusDock); + .on_click(MouseButton::Left, |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + Dock::focus_dock(workspace, &Default::default(), cx) + }) + } }) .with_tooltip::( 0, diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 455ffb2bb08f59df838b2491c129af8d07c8f185..7881603bbc6c47bc97edc931633d957965949181 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -278,8 +278,8 @@ pub mod simple_message_notification { .with_height(style.button_width) }) .with_padding(Padding::uniform(5.)) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(CancelMessageNotification) + .on_click(MouseButton::Left, move |_, this, cx| { + this.dismiss(&Default::default(), cx); }) .with_cursor_style(CursorStyle::PointingHand) .aligned() diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 41f4d5d111cea8e522ddc9eedb4558a679409bcc..2631f72fd4573bd8a944ecc0a6d94eeb00887e3d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,7 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock}, + dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, Workspace, @@ -259,6 +259,10 @@ impl Pane { } } + pub(crate) fn workspace(&self) -> &WeakViewHandle { + &self.workspace + } + pub fn is_active(&self) -> bool { self.is_active } @@ -1340,8 +1344,8 @@ impl Pane { cx, ) }) - .on_down(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(ActivateItem(ix)); + .on_down(MouseButton::Left, move |_, this, cx| { + this.activate_item(ix, true, true, cx); }) .on_click(MouseButton::Middle, { let item_id = item.id(); @@ -1639,7 +1643,13 @@ impl Pane { 3, "icons/x_mark_8.svg", cx, - |_, cx| cx.dispatch_action(HideDock), + |this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + Dock::hide_dock(workspace, &Default::default(), cx) + }) + } + }, None, ) })) @@ -1693,8 +1703,8 @@ impl View for Pane { }) .on_down( MouseButton::Left, - move |_, _, cx| { - cx.dispatch_action(ActivateItem(active_item_index)); + move |_, this, cx| { + this.activate_item(active_item_index, true, true, cx); }, ), ); @@ -1759,15 +1769,27 @@ impl View for Pane { }) .on_down( MouseButton::Navigate(NavigationDirection::Back), - move |_, _, cx| { - let pane = cx.weak_handle(); - cx.dispatch_action(GoBack { pane: Some(pane) }); + move |_, pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + Pane::go_back(workspace, Some(pane), cx).detach_and_log_err(cx) + }) + }) + } }, ) .on_down(MouseButton::Navigate(NavigationDirection::Forward), { - move |_, _, cx| { - let pane = cx.weak_handle(); - cx.dispatch_action(GoForward { pane: Some(pane) }) + move |_, pane, cx| { + if let Some(workspace) = pane.workspace.upgrade(cx) { + let pane = cx.weak_handle(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + Pane::go_forward(workspace, Some(pane), cx).detach_and_log_err(cx) + }) + }) + } } }) .into_any_named("pane") diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 2581c87f4211bd7dbc867cb3e1a4e9336c8b29c1..2b114d83eccf6e7a68ae4d8db0ec96e41330d798 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -279,9 +279,9 @@ impl View for SidebarButtons { .with_style(style.container) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let action = action.clone(); - move |_, _, cx| cx.dispatch_action(action.clone()) + .on_click(MouseButton::Left, move |_, this, cx| { + this.sidebar + .update(cx, |sidebar, cx| sidebar.toggle_item(ix, cx)); }) .with_tooltip::( ix, diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index e9cc90f64d164e09834e409ad85f85d44128b7a2..a940bd09b29f86075dbee28a375baa4443ed0005 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -130,8 +130,20 @@ impl View for Toolbar { tooltip_style.clone(), enable_go_backward, spacing, - super::GoBack { - pane: Some(pane.clone()), + { + let pane = pane.clone(); + move |toolbar, cx| { + if let Some(workspace) = toolbar + .pane + .upgrade(cx) + .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) + { + workspace.update(cx, |workspace, cx| { + Pane::go_back(workspace, Some(pane.clone()), cx) + .detach_and_log_err(cx); + }); + } + } }, super::GoBack { pane: None }, "Go Back", @@ -143,7 +155,21 @@ impl View for Toolbar { tooltip_style, enable_go_forward, spacing, - super::GoForward { pane: Some(pane) }, + { + let pane = pane.clone(); + move |toolbar, cx| { + if let Some(workspace) = toolbar + .pane + .upgrade(cx) + .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) + { + workspace.update(cx, |workspace, cx| { + Pane::go_forward(workspace, Some(pane.clone()), cx) + .detach_and_log_err(cx); + }); + } + } + }, super::GoForward { pane: None }, "Go Forward", cx, @@ -161,13 +187,13 @@ impl View for Toolbar { } #[allow(clippy::too_many_arguments)] -fn nav_button( +fn nav_button)>( svg_path: &'static str, style: theme::Interactive, tooltip_style: TooltipStyle, enabled: bool, spacing: f32, - action: A, + on_click: F, tooltip_action: A, action_name: &str, cx: &mut ViewContext, @@ -195,8 +221,8 @@ fn nav_button( } else { CursorStyle::default() }) - .on_click(MouseButton::Left, move |_, _, cx| { - cx.dispatch_action(action.clone()) + .on_click(MouseButton::Left, move |_, toolbar, cx| { + on_click(toolbar, cx) }) .with_tooltip::( 0, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6a1f7aa8bbe3a365ba1fe33b76cda20bfd5cfade..2a7748af0f18f2e67f378cd6401ff413089e288c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -208,48 +208,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { } } }); - cx.add_action({ - let app_state = Arc::downgrade(&app_state); - move |_, _: &Open, cx: &mut ViewContext| { - let mut paths = cx.prompt_for_paths(PathPromptOptions { - files: true, - directories: true, - multiple: true, - }); - - if let Some(app_state) = app_state.upgrade() { - cx.spawn(|this, mut cx| async move { - if let Some(paths) = paths.recv().await.flatten() { - if let Some(task) = this - .update(&mut cx, |this, cx| { - this.open_workspace_for_paths(paths, app_state, cx) - }) - .log_err() - { - task.await.log_err(); - } - } - }) - .detach(); - } - } - }); - cx.add_global_action({ - let app_state = Arc::downgrade(&app_state); - move |_: &NewWindow, cx: &mut AppContext| { - if let Some(app_state) = app_state.upgrade() { - open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); - } - } - }); - cx.add_global_action({ - let app_state = Arc::downgrade(&app_state); - move |_: &NewFile, cx: &mut AppContext| { - if let Some(app_state) = app_state.upgrade() { - open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); - } - } - }); + cx.add_async_action(Workspace::open); cx.add_async_action(Workspace::follow_next_collaborator); cx.add_async_action(Workspace::close); @@ -913,7 +872,6 @@ impl Workspace { /// to the callback. Otherwise, a new empty window will be created. pub fn with_local_workspace( &mut self, - app_state: &Arc, cx: &mut ViewContext, callback: F, ) -> Task> @@ -924,7 +882,7 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(Ok(callback(self, cx)))) } else { - let task = Self::new_local(Vec::new(), app_state.clone(), None, cx); + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await; workspace.update(&mut cx, callback) @@ -1093,10 +1051,29 @@ impl Workspace { }) } + pub fn open(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { + let mut paths = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }); + + Some(cx.spawn(|this, mut cx| async move { + if let Some(paths) = paths.recv().await.flatten() { + if let Some(task) = this + .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx)) + .log_err() + { + task.await? + } + } + Ok(()) + })) + } + pub fn open_workspace_for_paths( &mut self, paths: Vec, - app_state: Arc, cx: &mut ViewContext, ) -> Task> { let window_id = cx.window_id(); @@ -1108,6 +1085,7 @@ impl Workspace { } else { Some(self.prepare_to_close(false, cx)) }; + let app_state = self.app_state.clone(); cx.spawn(|_, mut cx| async move { let window_id_to_replace = if let Some(close_task) = close_task { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 58a53b9e402a2fc1eef7954b9e3aba523c475843..7a66953cff29bf3768a6f38e5ad6f94b1f119821 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -10,6 +10,7 @@ use cli::{ }; use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; +use editor::Editor; use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, @@ -51,8 +52,7 @@ use staff_mode::StaffMode; use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ - self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, - Workspace, + self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace, }; use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings}; @@ -115,7 +115,10 @@ fn main() { .on_reopen(move |cx| { if cx.has_global::>() { if let Some(app_state) = cx.global::>().upgrade() { - workspace::open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); } } }); @@ -208,14 +211,14 @@ fn main() { auto_update::init(http, client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); - recent_projects::init(cx, Arc::downgrade(&app_state)); + recent_projects::init(cx); journal::init(app_state.clone(), cx); - language_selector::init(app_state.clone(), cx); - theme_selector::init(app_state.clone(), cx); + language_selector::init(cx); + theme_selector::init(cx); zed::init(&app_state, cx); collab_ui::init(&app_state, cx); - feedback::init(app_state.clone(), cx); + feedback::init(cx); welcome::init(cx); cx.set_menus(menus::menus()); @@ -289,7 +292,10 @@ async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncApp cx.update(|cx| show_welcome_experience(app_state, cx)); } else { cx.update(|cx| { - workspace::open_new(app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach(); + workspace::open_new(app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); }); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 28b17c297d9458da382191fa8eaadca88885846f..9e0b55d4234be00aed3ed0ceb2a1b16bd101a7a4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,7 +20,7 @@ use gpui::{ geometry::vector::vec2f, impl_actions, platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions}, - AssetSource, ViewContext, + AppContext, AssetSource, ViewContext, }; use language::Rope; pub use lsp; @@ -35,7 +35,7 @@ use terminal_view::terminal_button::TerminalButton; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; -use workspace::{sidebar::SidebarSide, AppState, Workspace}; +use workspace::{open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -147,10 +147,9 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { }) .detach_and_log_err(cx); }); - cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { - open_config_file(&paths::SETTINGS, app_state.clone(), cx, || { + cx.add_action( + move |workspace: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { + open_config_file(workspace, &paths::SETTINGS, cx, || { str::from_utf8( Assets .load("settings/initial_user_settings.json") @@ -160,73 +159,68 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { .unwrap() .into() }); - } - }); - cx.add_action({ - let app_state = app_state.clone(); + }, + ); + cx.add_action( move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext| { - open_log_file(workspace, app_state.clone(), cx); - } - }); - cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { + open_log_file(workspace, cx); + }, + ); + cx.add_action( + move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { open_bundled_file( - app_state.clone(), + workspace, "licenses.md", "Open Source License Attribution", "Markdown", cx, ); - } - }); - cx.add_action({ - let app_state = app_state.clone(); + }, + ); + cx.add_action( move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext| { - open_telemetry_log_file(workspace, app_state.clone(), cx); - } - }); - cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { - open_config_file(&paths::KEYMAP, app_state.clone(), cx, Default::default); - } - }); - cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext| { + open_telemetry_log_file(workspace, cx); + }, + ); + cx.add_action( + move |workspace: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { + open_config_file(workspace, &paths::KEYMAP, cx, Default::default); + }, + ); + cx.add_action( + move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext| { open_bundled_file( - app_state.clone(), + workspace, "keymaps/default.json", "Default Key Bindings", "JSON", cx, ); - } - }); - cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext| { + }, + ); + cx.add_action( + move |workspace: &mut Workspace, + _: &OpenDefaultSettings, + cx: &mut ViewContext| { open_bundled_file( - app_state.clone(), + workspace, "settings/default.json", "Default Settings", "JSON", cx, ); - } - }); + }, + ); cx.add_action({ - let app_state = app_state.clone(); - move |_: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { - let app_state = app_state.clone(); + move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { + let app_state = workspace.app_state().clone(); let markdown = app_state.languages.language_for_name("JSON"); let content = to_string_pretty(&cx.debug_elements()).unwrap(); cx.spawn(|workspace, mut cx| async move { let markdown = markdown.await.log_err(); workspace .update(&mut cx, |workspace, cx| { - workspace.with_local_workspace(&app_state, cx, move |workspace, cx| { + workspace.with_local_workspace(cx, move |workspace, cx| { let project = workspace.project().clone(); let buffer = project @@ -258,6 +252,28 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx); }, ); + cx.add_global_action({ + let app_state = Arc::downgrade(&app_state); + move |_: &NewWindow, cx: &mut AppContext| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }); + cx.add_global_action({ + let app_state = Arc::downgrade(&app_state); + move |_: &NewFile, cx: &mut AppContext| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }); activity_indicator::init(cx); lsp_log::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); @@ -275,7 +291,7 @@ pub fn initialize_workspace( if let workspace::Event::PaneAdded(pane) = event { pane.update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { - let breadcrumbs = cx.add_view(|_| Breadcrumbs::new()); + let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace)); toolbar.add_item(breadcrumbs, cx); let buffer_search_bar = cx.add_view(BufferSearchBar::new); toolbar.add_item(buffer_search_bar, cx); @@ -304,7 +320,7 @@ pub fn initialize_workspace( }); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let project_panel = ProjectPanel::new(workspace.project().clone(), cx); + let project_panel = ProjectPanel::new(workspace, cx); workspace.left_sidebar().update(cx, |sidebar, cx| { sidebar.add_item( "icons/folder_tree_16.svg", @@ -317,12 +333,13 @@ pub fn initialize_workspace( let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); let diagnostic_summary = - cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx)); + cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); - let active_buffer_language = cx.add_view(|_| language_selector::ActiveBufferLanguage::new()); + let active_buffer_language = + cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace)); let feedback_button = - cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new()); + cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); @@ -428,13 +445,13 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { } fn open_config_file( + workspace: &mut Workspace, path: &'static Path, - app_state: Arc, cx: &mut ViewContext, default_content: impl 'static + Send + FnOnce() -> Rope, ) { + let fs = workspace.app_state().fs.clone(); cx.spawn(|workspace, mut cx| async move { - let fs = &app_state.fs; if !fs.is_file(path).await { fs.create_file(path, Default::default()).await?; fs.save(path, &default_content(), Default::default()) @@ -443,7 +460,7 @@ fn open_config_file( workspace .update(&mut cx, |workspace, cx| { - workspace.with_local_workspace(&app_state, cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { workspace.open_paths(vec![path.to_path_buf()], false, cx) }) })? @@ -454,20 +471,15 @@ fn open_config_file( .detach_and_log_err(cx) } -fn open_log_file( - workspace: &mut Workspace, - app_state: Arc, - cx: &mut ViewContext, -) { +fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { const MAX_LINES: usize = 1000; workspace - .with_local_workspace(&app_state.clone(), cx, move |_, cx| { + .with_local_workspace(cx, move |workspace, cx| { + let fs = workspace.app_state().fs.clone(); cx.spawn(|workspace, mut cx| async move { - let (old_log, new_log) = futures::join!( - app_state.fs.load(&paths::OLD_LOG), - app_state.fs.load(&paths::LOG) - ); + let (old_log, new_log) = + futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG)); let mut lines = VecDeque::with_capacity(MAX_LINES); for line in old_log @@ -512,12 +524,9 @@ fn open_log_file( .detach(); } -fn open_telemetry_log_file( - workspace: &mut Workspace, - app_state: Arc, - cx: &mut ViewContext, -) { - workspace.with_local_workspace(&app_state.clone(), cx, move |_, cx| { +fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { + workspace.with_local_workspace(cx, move |workspace, cx| { + let app_state = workspace.app_state().clone(); cx.spawn(|workspace, mut cx| async move { async fn fetch_log_string(app_state: &Arc) -> Option { let path = app_state.client.telemetry().log_file_path()?; @@ -573,18 +582,18 @@ fn open_telemetry_log_file( } fn open_bundled_file( - app_state: Arc, + workspace: &mut Workspace, asset_path: &'static str, title: &'static str, language: &'static str, cx: &mut ViewContext, ) { - let language = app_state.languages.language_for_name(language); + let language = workspace.app_state().languages.language_for_name(language); cx.spawn(|workspace, mut cx| async move { let language = language.await.log_err(); workspace .update(&mut cx, |workspace, cx| { - workspace.with_local_workspace(&app_state, cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { let project = workspace.project(); let buffer = project.update(cx, |project, cx| { let text = Assets::get(asset_path) @@ -815,8 +824,12 @@ mod tests { #[gpui::test] async fn test_new_empty_workspace(cx: &mut TestAppContext) { let app_state = init(cx); - cx.update(|cx| open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile))) - .await; + cx.update(|cx| { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + }) + .await; let window_id = *cx.window_ids().first().unwrap(); let workspace = cx From eb2cce98a715ae078579efdf90a9776604649da9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 16:40:57 +0200 Subject: [PATCH 07/26] Move `dispatch_action_any_action_at` to `AsyncAppContext` --- crates/command_palette/src/command_palette.rs | 8 +-- crates/context_menu/src/context_menu.rs | 27 ++++++---- crates/gpui/src/app.rs | 51 ++++++------------- 3 files changed, 37 insertions(+), 49 deletions(-) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 1d9ac62c2cefffb7162aa473df23fedfb8a091cc..5b1559bd29a27246e1afc3c093fbb74727e24dab 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -167,9 +167,11 @@ impl PickerDelegate for CommandPaletteDelegate { let focused_view_id = self.focused_view_id; let action_ix = self.matches[self.selected_ix].candidate_id; let action = self.actions.remove(action_ix).action; - cx.defer(move |_, cx| { - cx.dispatch_any_action_at(window_id, focused_view_id, action); - }); + cx.app_context() + .spawn(move |mut cx| async move { + cx.dispatch_action(window_id, focused_view_id, action.as_ref()) + }) + .detach_and_log_err(cx); } cx.emit(PickerEvent::Dismiss); } diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 3a3ca9b66bbfa917ad71c215b8fdf67c334e1651..e0429bd01b6203ad2a75d7709b2d1f0c0c5e1e2e 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -227,11 +227,13 @@ impl ContextMenu { match action { ContextMenuItemAction::Action(action) => { let window_id = cx.window_id(); - cx.dispatch_any_action_at( - window_id, - self.parent_view_id, - action.boxed_clone(), - ); + let view_id = self.parent_view_id; + let action = action.boxed_clone(); + cx.app_context() + .spawn(|mut cx| async move { + cx.dispatch_action(window_id, view_id, action.as_ref()) + }) + .detach_and_log_err(cx); } ContextMenuItemAction::Handler(handler) => handler(cx), } @@ -459,11 +461,16 @@ impl ContextMenu { let window_id = cx.window_id(); match &action { ContextMenuItemAction::Action(action) => { - cx.dispatch_any_action_at( - window_id, - view_id, - action.boxed_clone(), - ); + let action = action.boxed_clone(); + cx.app_context() + .spawn(|mut cx| async move { + cx.dispatch_action( + window_id, + view_id, + action.as_ref(), + ) + }) + .detach_and_log_err(cx); } ContextMenuItemAction::Handler(handler) => handler(cx), } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 02d6c1a2ac9dc11ce7cdf8a035eeb450173883a1..c757e7f383bd4d29a3b17d754f334f54ac651af1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -309,6 +309,20 @@ impl AsyncAppContext { self.0.borrow_mut().update_window(window_id, callback) } + pub fn dispatch_action( + &mut self, + window_id: usize, + view_id: usize, + action: &dyn Action, + ) -> Result<()> { + self.0 + .borrow_mut() + .update_window(window_id, |window| { + window.handle_dispatch_action_from_effect(Some(view_id), action); + }) + .ok_or_else(|| anyhow!("window not found")) + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where T: Entity, @@ -1619,17 +1633,7 @@ impl AppContext { Effect::RefreshWindows => { refreshing = true; } - Effect::DispatchActionFrom { - window_id, - view_id, - action, - } => { - self.handle_dispatch_action_from_effect( - window_id, - Some(view_id), - action.as_ref(), - ); - } + Effect::ActionDispatchNotification { action_id } => { self.handle_action_dispatch_notification_effect(action_id) } @@ -1745,19 +1749,6 @@ impl AppContext { self.pending_effects.push_back(Effect::RefreshWindows); } - pub fn dispatch_any_action_at( - &mut self, - window_id: usize, - view_id: usize, - action: Box, - ) { - self.pending_effects.push_back(Effect::DispatchActionFrom { - window_id, - view_id, - action, - }); - } - fn perform_window_refresh(&mut self) { let window_ids = self.windows.keys().cloned().collect::>(); for window_id in window_ids { @@ -2155,11 +2146,6 @@ pub enum Effect { result: MatchResult, }, RefreshWindows, - DispatchActionFrom { - window_id: usize, - view_id: usize, - action: Box, - }, ActionDispatchNotification { action_id: TypeId, }, @@ -2248,13 +2234,6 @@ impl Debug for Effect { .field("view_id", view_id) .field("subscription_id", subscription_id) .finish(), - Effect::DispatchActionFrom { - window_id, view_id, .. - } => f - .debug_struct("Effect::DispatchActionFrom") - .field("window_id", window_id) - .field("view_id", view_id) - .finish(), Effect::ActionDispatchNotification { action_id, .. } => f .debug_struct("Effect::ActionDispatchNotification") .field("action_id", action_id) From 6c931ab9da7f73344fd2698b237a1d31483cdeb1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 16:49:17 +0200 Subject: [PATCH 08/26] Inline test-only `AppContext` methods --- crates/gpui/src/app.rs | 15 --------------- crates/gpui/src/app/test_app_context.rs | 12 +++++++----- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c757e7f383bd4d29a3b17d754f334f54ac651af1..589ec8f1af7665e897ce5d840a642d4992c0e6f3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1062,10 +1062,6 @@ impl AppContext { } } - fn dispatch_global_action(&mut self, action: A) { - self.dispatch_global_action_any(&action); - } - fn dispatch_global_action_any(&mut self, action: &dyn Action) -> bool { self.update(|this| { if let Some((name, mut handler)) = this.global_actions.remove_entry(&action.id()) { @@ -1907,17 +1903,6 @@ impl AppContext { }); } - fn handle_dispatch_action_from_effect( - &mut self, - window_id: usize, - view_id: Option, - action: &dyn Action, - ) { - self.update_window(window_id, |cx| { - cx.handle_dispatch_action_from_effect(view_id, action) - }); - } - fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) { self.action_dispatch_observations .clone() diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 3a03a81c4707e65982795868c9eb060f5173fced..79f35cd923ce7631804929c73044cdc62ff3d9df 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -72,14 +72,16 @@ impl TestAppContext { } pub fn dispatch_action(&self, window_id: usize, action: A) { - let mut cx = self.cx.borrow_mut(); - if let Some(view_id) = cx.windows.get(&window_id).and_then(|w| w.focused_view_id) { - cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action); - } + self.cx + .borrow_mut() + .update_window(window_id, |window| { + window.handle_dispatch_action_from_effect(window.focused_view_id(), &action); + }) + .expect("window not found"); } pub fn dispatch_global_action(&self, action: A) { - self.cx.borrow_mut().dispatch_global_action(action); + self.cx.borrow_mut().dispatch_global_action_any(&action); } pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) { From e3b2407ebfb18a9dc2f59892a9e9966e72338d40 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 16:58:07 +0200 Subject: [PATCH 09/26] Run until parked now that the command palette spawns to dispatch action --- crates/command_palette/src/command_palette.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 5b1559bd29a27246e1afc3c093fbb74727e24dab..441fbb84a6812cc7b7952ab514e9f3f51fbbc253 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -268,9 +268,11 @@ impl std::fmt::Debug for Command { #[cfg(test)] mod tests { + use std::sync::Arc; + use super::*; use editor::Editor; - use gpui::TestAppContext; + use gpui::{executor::Deterministic, TestAppContext}; use project::Project; use workspace::{AppState, Workspace}; @@ -291,7 +293,8 @@ mod tests { } #[gpui::test] - async fn test_command_palette(cx: &mut TestAppContext) { + async fn test_command_palette(deterministic: Arc, cx: &mut TestAppContext) { + deterministic.forbid_parking(); let app_state = cx.update(AppState::test); cx.update(|cx| { @@ -333,7 +336,7 @@ mod tests { assert_eq!(palette.delegate().matches[0].string, "editor: backspace"); palette.confirm(&Default::default(), cx); }); - + deterministic.run_until_parked(); editor.read_with(cx, |editor, cx| { assert_eq!(editor.text(cx), "ab"); }); From 780ece551e58fcd6cebc4fb7af727bdf54826945 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 1 May 2023 17:06:05 +0200 Subject: [PATCH 10/26] Defer hiding the dock and going back/forward when Pane is on the stack --- crates/workspace/src/pane.rs | 8 +++++--- crates/workspace/src/toolbar.rs | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2631f72fd4573bd8a944ecc0a6d94eeb00887e3d..8bd42fed045ec80f51ea7b9432f588dcef6ca2a6 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1645,9 +1645,11 @@ impl Pane { cx, |this, cx| { if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - Dock::hide_dock(workspace, &Default::default(), cx) - }) + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + Dock::hide_dock(workspace, &Default::default(), cx) + }) + }); } }, None, diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index a940bd09b29f86075dbee28a375baa4443ed0005..eac9963d38ddcedbaea32c9722b8df2667d2299e 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -138,10 +138,13 @@ impl View for Toolbar { .upgrade(cx) .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) { - workspace.update(cx, |workspace, cx| { - Pane::go_back(workspace, Some(pane.clone()), cx) - .detach_and_log_err(cx); - }); + let pane = pane.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + Pane::go_back(workspace, Some(pane.clone()), cx) + .detach_and_log_err(cx); + }); + }) } } }, @@ -163,9 +166,12 @@ impl View for Toolbar { .upgrade(cx) .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) { - workspace.update(cx, |workspace, cx| { - Pane::go_forward(workspace, Some(pane.clone()), cx) - .detach_and_log_err(cx); + let pane = pane.clone(); + cx.window_context().defer(move |cx| { + workspace.update(cx, |workspace, cx| { + Pane::go_forward(workspace, Some(pane.clone()), cx) + .detach_and_log_err(cx); + }); }); } } From 4966a4a681d4a79a8222eca1434f45e6f33fe925 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 1 May 2023 13:14:35 -0400 Subject: [PATCH 11/26] Reduce hardcoded ESLint workspace configuration --- crates/zed/src/languages/typescript.rs | 33 ++++---------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 54d61e91cae5975198c34fedb93e8551ce0ddc08..429a5d942151cd9dbd3661b2ebb6bbc16a6505e3 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -188,35 +188,10 @@ impl LspAdapter for EsLintLspAdapter { Some( future::ready(json!({ "": { - "validate": "on", - "packageManager": "npm", - "useESLintClass": false, - "experimental": { - "useFlatConfig": false - }, - "codeActionOnSave": { - "mode": "all" - }, - "format": false, - "quiet": false, - "onIgnoredFiles": "off", - "options": {}, - "rulesCustomizations": [], - "run": "onType", - "problems": { - "shortenToSingleLine": false - }, - "nodePath": null, - "codeAction": { - "disableRuleComment": { - "enable": true, - "location": "separateLine", - "commentStyle": "line" - }, - "showDocumentation": { - "enable": true - } - } + "validate": "on", + "rulesCustomizations": [], + "run": "onType", + "nodePath": null, } })) .boxed(), From 40ab5c1fb9ef08101f0e7fcd5eb656b1973aabf8 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 1 May 2023 13:15:41 -0400 Subject: [PATCH 12/26] Remove underline from diagnostic source --- styles/src/styleTree/hoverPopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts index fadd62db1d4cc1732a14b4459f2c3eea28891e71..b01402e069804f0ae464a49777de432c9271d175 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/styleTree/hoverPopover.ts @@ -40,7 +40,7 @@ export default function HoverPopover(colorScheme: ColorScheme) { padding: { top: 4 }, }, prose: text(layer, "sans", { size: "sm" }), - diagnosticSourceHighlight: { underline: true, color: foreground(layer, "accent") }, + diagnosticSourceHighlight: { color: foreground(layer, "accent") }, highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better } } From f62ba2eec7ba0bfbd616ae41e8c196dc32b4533a Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 1 May 2023 16:29:51 -0400 Subject: [PATCH 13/26] use installation_id over device_id --- crates/client/src/telemetry.rs | 51 ++++++++++++++------------ crates/feedback/src/feedback_editor.rs | 3 ++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index c8f6deada8a71dff2ec2ad0f278ab6f63f75ebe6..7151dcd7bb0606efc59a5dec68669a4d7273d651 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -30,13 +30,13 @@ pub struct Telemetry { #[derive(Default)] struct TelemetryState { - metrics_id: Option>, - device_id: Option>, + metrics_id: Option>, // Per logged-in user + installation_id: Option>, // Per app installation app_version: Option>, release_channel: Option<&'static str>, os_version: Option>, os_name: &'static str, - mixpanel_events_queue: Vec, // Mixpanel mixed events - will hopefully die soon + mixpanel_events_queue: Vec, clickhouse_events_queue: Vec, next_mixpanel_event_id: usize, flush_mixpanel_events_task: Option>, @@ -100,7 +100,8 @@ struct MixpanelEventProperties { #[serde(skip_serializing_if = "str::is_empty")] token: &'static str, time: u128, - distinct_id: Option>, + #[serde(rename = "distinct_id")] + installation_id: Option>, #[serde(rename = "$insert_id")] insert_id: usize, // Custom fields @@ -123,7 +124,7 @@ struct MixpanelEngageRequest { #[serde(rename = "$token")] token: &'static str, #[serde(rename = "$distinct_id")] - distinct_id: Arc, + installation_id: Arc, #[serde(rename = "$set")] set: Value, } @@ -156,7 +157,7 @@ impl Telemetry { os_name: platform.os_name().into(), app_version: platform.app_version().ok().map(|v| v.to_string().into()), release_channel, - device_id: None, + installation_id: None, metrics_id: None, mixpanel_events_queue: Default::default(), clickhouse_events_queue: Default::default(), @@ -193,26 +194,26 @@ impl Telemetry { self.executor .spawn( async move { - let device_id = - if let Ok(Some(device_id)) = KEY_VALUE_STORE.read_kvp("device_id") { - device_id + let installation_id = + if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp("device_id") { + installation_id } else { - let device_id = Uuid::new_v4().to_string(); + let installation_id = Uuid::new_v4().to_string(); KEY_VALUE_STORE - .write_kvp("device_id".to_string(), device_id.clone()) + .write_kvp("device_id".to_string(), installation_id.clone()) .await?; - device_id + installation_id }; - let device_id: Arc = device_id.into(); + let installation_id: Arc = installation_id.into(); let mut state = this.state.lock(); - state.device_id = Some(device_id.clone()); + state.installation_id = Some(installation_id.clone()); for event in &mut state.mixpanel_events_queue { event .properties - .distinct_id - .get_or_insert_with(|| device_id.clone()); + .installation_id + .get_or_insert_with(|| installation_id.clone()); } let has_mixpanel_events = !state.mixpanel_events_queue.is_empty(); @@ -248,19 +249,19 @@ impl Telemetry { let this = self.clone(); let mut state = self.state.lock(); - let device_id = state.device_id.clone(); + let installation_id = state.installation_id.clone(); let metrics_id: Option> = metrics_id.map(|id| id.into()); state.metrics_id = metrics_id.clone(); state.is_staff = Some(is_staff); drop(state); - if let Some((token, device_id)) = MIXPANEL_TOKEN.as_ref().zip(device_id) { + if let Some((token, installation_id)) = MIXPANEL_TOKEN.as_ref().zip(installation_id) { self.executor .spawn( async move { let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest { token, - distinct_id: device_id, + installation_id, set: json!({ "Staff": is_staff, "ID": metrics_id, @@ -299,7 +300,7 @@ impl Telemetry { event, }); - if state.device_id.is_some() { + if state.installation_id.is_some() { if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN { drop(state); self.flush_clickhouse_events(); @@ -333,7 +334,7 @@ impl Telemetry { .duration_since(UNIX_EPOCH) .unwrap() .as_millis(), - distinct_id: state.device_id.clone(), + installation_id: state.installation_id.clone(), insert_id: post_inc(&mut state.next_mixpanel_event_id), event_properties: if let Value::Object(properties) = properties { Some(properties) @@ -348,7 +349,7 @@ impl Telemetry { }, }; state.mixpanel_events_queue.push(event); - if state.device_id.is_some() { + if state.installation_id.is_some() { if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN { drop(state); self.flush_mixpanel_events(); @@ -367,6 +368,10 @@ impl Telemetry { self.state.lock().metrics_id.clone() } + pub fn installation_id(self: &Arc) -> Option> { + self.state.lock().installation_id.clone() + } + pub fn is_staff(self: &Arc) -> Option { self.state.lock().is_staff } @@ -438,7 +443,7 @@ impl Telemetry { &mut json_bytes, &ClickhouseEventRequestBody { token: ZED_SECRET_CLIENT_TOKEN, - installation_id: state.device_id.clone(), + installation_id: state.installation_id.clone(), app_version: state.app_version.clone(), os_name: state.os_name, os_version: state.os_version.clone(), diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 253cc511ee569e81455ea4301012dda9e38c609f..2e513f6f06da5c0bb1522cc1ba2baa3c9f320cea 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -47,6 +47,7 @@ pub fn init(cx: &mut AppContext) { struct FeedbackRequestBody<'a> { feedback_text: &'a str, metrics_id: Option>, + installation_id: Option>, system_specs: SystemSpecs, is_staff: bool, token: &'a str, @@ -152,12 +153,14 @@ impl FeedbackEditor { let telemetry = zed_client.telemetry(); let metrics_id = telemetry.metrics_id(); + let installation_id = telemetry.installation_id(); let is_staff = telemetry.is_staff(); let http_client = zed_client.http_client(); let request = FeedbackRequestBody { feedback_text: &feedback_text, metrics_id, + installation_id, system_specs, is_staff: is_staff.unwrap_or(false), token: ZED_SECRET_CLIENT_TOKEN, From f7de0ad8ae3b17a148f7c460491edbcfe67c9dcf Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 1 May 2023 16:48:27 -0400 Subject: [PATCH 14/26] Show diagnostic source in diagnostic multibuffer headers --- crates/diagnostics/src/diagnostics.rs | 13 +++++++++++-- crates/theme/src/theme.rs | 1 + styles/src/styleTree/editor.ts | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 6973d83f9b0a351e2d1b294829c89b34f7acdd03..22f67265ecc2fec017938cba5e0e6e335da7fb91 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -697,8 +697,18 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { icon.constrained() .with_width(icon_width) .aligned() - .contained(), + .contained() + .with_margin_right(cx.gutter_padding), ) + .with_children(diagnostic.source.as_ref().map(|source| { + Label::new( + format!("{source}: "), + style.source.label.clone().with_font_size(font_size), + ) + .contained() + .with_style(style.message.container) + .aligned() + })) .with_child( Label::new( message.clone(), @@ -707,7 +717,6 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .with_highlights(highlights.clone()) .contained() .with_style(style.message.container) - .with_margin_left(cx.gutter_padding) .aligned(), ) .with_children(diagnostic.code.clone().map(|code| { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d2962847729f75e580a95a39d29a0ef979ba26f5..1211f5374227617da02efe73f808e6dbee487e90 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -659,6 +659,7 @@ pub struct DiagnosticPathHeader { pub struct DiagnosticHeader { #[serde(flatten)] pub container: ContainerStyle, + pub source: ContainedLabel, pub message: ContainedLabel, pub code: ContainedText, pub text_scale_factor: f32, diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 84ef51406ea17e97ee3cdb91017043794130e228..cd0adf6bc7f830a3633a335733aa4193553355f4 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -176,6 +176,9 @@ export default function editor(colorScheme: ColorScheme) { left: 10, }, }, + source: { + text: text(colorScheme.middle, "sans", { size: "sm", weight: "bold", }), + }, message: { highlightText: text(colorScheme.middle, "sans", { size: "sm", From 4c1cba6def2d599c6df0b4aab7ce0b307393e0ec Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 2 May 2023 10:09:57 +0200 Subject: [PATCH 15/26] Remove unnecessary `Element` impl for `RootElement` --- crates/gpui/src/elements.rs | 58 ------------------------------------- 1 file changed, 58 deletions(-) diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 09b9c4058994c92cd16d502aae74f6c779ff660e..dbeb9c218a189f9640062ca6b779720dd1f9b831 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -45,7 +45,6 @@ use std::{ mem, ops::{Deref, DerefMut, Range}, }; -use util::ResultExt; pub trait Element: 'static { type LayoutState; @@ -717,63 +716,6 @@ impl AnyRootElement for RootElement { } } -impl Element for RootElement { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - _view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, ()) { - let size = AnyRootElement::layout(self, constraint, cx) - .log_err() - .unwrap_or_else(|| Vector2F::zero()); - (size, ()) - } - - fn paint( - &mut self, - scene: &mut SceneBuilder, - bounds: RectF, - visible_bounds: RectF, - _layout: &mut Self::LayoutState, - _view: &mut V, - cx: &mut ViewContext, - ) { - AnyRootElement::paint(self, scene, bounds.origin(), visible_bounds, cx).log_err(); - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _bounds: RectF, - _visible_bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - _view: &V, - cx: &ViewContext, - ) -> Option { - AnyRootElement::rect_for_text_range(self, range_utf16, cx) - .log_err() - .flatten() - } - - fn debug( - &self, - _bounds: RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - _view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - AnyRootElement::debug(self, cx) - .log_err() - .unwrap_or_default() - } -} - pub trait ParentElement<'a, V: View>: Extend> + Sized { fn add_children>(&mut self, children: impl IntoIterator) { self.extend(children.into_iter().map(|child| child.into_any())); From 794446bf8bd43afee0a2fb9269910c9d0c36b5ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 2 May 2023 11:03:01 +0200 Subject: [PATCH 16/26] Move `debug_elements` to `AsyncAppContext` Previously, `debug_elements` was available on `WindowContext`. If that method was called while having a borrow out to a view, it would panic because the view would already have been borrowed. By moving it to an `AsyncAppContext` we ensure the method can't be called while a view is being used. --- crates/gpui/src/app.rs | 9 +++++++++ crates/gpui/src/app/window.rs | 19 +------------------ crates/gpui/src/elements.rs | 7 ++++++- crates/zed/src/zed.rs | 8 +++++++- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 589ec8f1af7665e897ce5d840a642d4992c0e6f3..3cc90dd4ce1fe3534fd7f6d5c531dd8a58b84d83 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -43,6 +43,7 @@ use window_input_handler::WindowInputHandler; use crate::{ elements::{AnyElement, AnyRootElement, RootElement}, executor::{self, Task}, + json, keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult}, platform::{ self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, @@ -309,6 +310,14 @@ impl AsyncAppContext { self.0.borrow_mut().update_window(window_id, callback) } + pub fn debug_elements(&self, window_id: usize) -> Option { + self.0.borrow().read_window(window_id, |cx| { + let root_view = cx.window.root_view(); + let root_element = cx.window.rendered_views.get(&root_view.id())?; + root_element.debug(cx).log_err() + })? + } + pub fn dispatch_action( &mut self, window_id: usize, diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index f54c18c755c097ccdb5e473fce25e3db007d9157..49befafbec4bfca2fac1782f2d5c8d249b24a951 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1,7 +1,7 @@ use crate::{ elements::AnyRootElement, geometry::rect::RectF, - json::{self, ToJson}, + json::ToJson, keymap_matcher::{Binding, Keystroke, MatchResult}, platform::{ self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, @@ -975,17 +975,6 @@ impl<'a> WindowContext<'a> { .flatten() } - pub fn debug_elements(&self) -> Option { - let view = self.window.root_view(); - Some(json!({ - "root_view": view.debug_json(self), - "root_element": self.window.rendered_views.get(&view.id()) - .and_then(|root_element| { - root_element.debug(self).log_err() - }) - })) - } - pub fn set_window_title(&mut self, title: &str) { self.window.platform_window.set_title(title); } @@ -1454,13 +1443,7 @@ impl Element for ChildView { ) -> serde_json::Value { json!({ "type": "ChildView", - "view_id": self.view_id, "bounds": bounds.to_json(), - "view": if let Some(view) = cx.views.get(&(cx.window_id, self.view_id)) { - view.debug_json(cx) - } else { - json!(null) - }, "child": if let Some(element) = cx.window.rendered_views.get(&self.view_id) { element.debug(&cx.window_context).log_err().unwrap_or_else(|| json!(null)) } else { diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index dbeb9c218a189f9640062ca6b779720dd1f9b831..7de0bc10f59ec0619b596b3a9a52d619a9104943 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -708,7 +708,12 @@ impl AnyRootElement for RootElement { .ok_or_else(|| anyhow!("debug called on a root element for a dropped view"))?; let view = view.read(cx); let view_context = ViewContext::immutable(cx, self.view.id()); - Ok(self.element.debug(view, &view_context)) + Ok(serde_json::json!({ + "view_id": self.view.id(), + "view_name": V::ui_name(), + "view": view.debug_json(cx), + "element": self.element.debug(view, &view_context) + })) } fn name(&self) -> Option<&str> { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9e0b55d4234be00aed3ed0ceb2a1b16bd101a7a4..5160426bdd7a224c9849a26f54811d430bba007a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,6 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use anyhow::anyhow; use feedback::{ feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton, }; @@ -215,9 +216,14 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { let app_state = workspace.app_state().clone(); let markdown = app_state.languages.language_for_name("JSON"); - let content = to_string_pretty(&cx.debug_elements()).unwrap(); + let window_id = cx.window_id(); cx.spawn(|workspace, mut cx| async move { let markdown = markdown.await.log_err(); + let content = to_string_pretty( + &cx.debug_elements(window_id) + .ok_or_else(|| anyhow!("could not debug elements for {window_id}"))?, + ) + .unwrap(); workspace .update(&mut cx, |workspace, cx| { workspace.with_local_workspace(cx, move |workspace, cx| { From f5278c49b0e9f1d78e9d2ff7d7888f9931d8695b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 2 May 2023 12:12:57 +0300 Subject: [PATCH 17/26] Clarify GH Token scope requirements --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d23744aac0fcab30ed790f98ab86989f2f33633c..6908cebf24c3b5781933854daa9566572973a92d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml) -Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true. +Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true. ## Development tips @@ -31,7 +31,8 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea * Set up a local `zed` database and seed it with some initial users: - Create a personal GitHub token to run `script/bootstrap` once successfully. Then delete that token. + Create a personal GitHub token to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope). + Then delete that token. ``` GITHUB_TOKEN=<$token> script/bootstrap From f985fac6f9274f89663c2224e37dff961df3df7d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 2 May 2023 11:47:05 +0200 Subject: [PATCH 18/26] Fix panic when showing contacts popover via keybinding --- crates/collab_ui/src/collab_titlebar_item.rs | 109 ++++++++++--------- crates/collab_ui/src/contact_list.rs | 12 +- crates/collab_ui/src/contacts_popover.rs | 49 ++++++--- crates/zed/src/zed.rs | 5 +- 4 files changed, 99 insertions(+), 76 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index a9894dade1b69cb072edba1f0e37cd34ab5056ad..97fb82a5d6b7cbce8ab7373c9dcb78e6a7b8d3e9 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -3,7 +3,7 @@ use crate::{ toggle_screen_sharing, ToggleScreenSharing, }; use call::{ActiveCall, ParticipantLocation, Room}; -use client::{proto::PeerId, ContactEventKind, SignIn, SignOut, User, UserStore}; +use client::{proto::PeerId, Client, ContactEventKind, SignIn, SignOut, User, UserStore}; use clock::ReplicaId; use contacts_popover::ContactsPopover; use context_menu::{ContextMenu, ContextMenuItem}; @@ -17,6 +17,7 @@ use gpui::{ AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; +use project::Project; use settings::Settings; use std::{ops::Range, sync::Arc}; use theme::{AvatarStyle, Theme}; @@ -41,8 +42,10 @@ pub fn init(cx: &mut AppContext) { } pub struct CollabTitlebarItem { - workspace: WeakViewHandle, + project: ModelHandle, user_store: ModelHandle, + client: Arc, + workspace: WeakViewHandle, contacts_popover: Option>, user_menu: ViewHandle, _subscriptions: Vec, @@ -64,7 +67,7 @@ impl View for CollabTitlebarItem { return Empty::new().into_any(); }; - let project = workspace.read(cx).project().read(cx); + let project = self.project.read(cx); let mut project_title = String::new(); for (i, name) in project.worktree_root_names(cx).enumerate() { if i > 0 { @@ -89,8 +92,8 @@ impl View for CollabTitlebarItem { .left(), ); - let user = workspace.read(cx).user_store().read(cx).current_user(); - let peer_id = workspace.read(cx).client().peer_id(); + let user = self.user_store.read(cx).current_user(); + let peer_id = self.client.peer_id(); if let Some(((user, peer_id), room)) = user .zip(peer_id) .zip(ActiveCall::global(cx).read(cx).room().cloned()) @@ -124,13 +127,16 @@ impl View for CollabTitlebarItem { impl CollabTitlebarItem { pub fn new( - workspace: &ViewHandle, - user_store: ModelHandle, + workspace: &Workspace, + workspace_handle: &ViewHandle, cx: &mut ViewContext, ) -> Self { + let project = workspace.project().clone(); + let user_store = workspace.app_state().user_store.clone(); + let client = workspace.app_state().client.clone(); let active_call = ActiveCall::global(cx); let mut subscriptions = Vec::new(); - subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify())); + subscriptions.push(cx.observe(workspace_handle, |_, _, cx| cx.notify())); subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(|this, active, cx| { this.window_activation_changed(active, cx) @@ -160,8 +166,10 @@ impl CollabTitlebarItem { ); Self { - workspace: workspace.downgrade(), - user_store: user_store.clone(), + workspace: workspace.weak_handle(), + project, + user_store, + client, contacts_popover: None, user_menu: cx.add_view(|cx| { let mut menu = ContextMenu::new(cx); @@ -173,16 +181,14 @@ impl CollabTitlebarItem { } fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let project = if active { - Some(workspace.read(cx).project().clone()) - } else { - None - }; - ActiveCall::global(cx) - .update(cx, |call, cx| call.set_location(project.as_ref(), cx)) - .detach_and_log_err(cx); - } + let project = if active { + Some(self.project.clone()) + } else { + None + }; + ActiveCall::global(cx) + .update(cx, |call, cx| call.set_location(project.as_ref(), cx)) + .detach_and_log_err(cx); } fn active_call_changed(&mut self, cx: &mut ViewContext) { @@ -193,41 +199,42 @@ impl CollabTitlebarItem { } fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let active_call = ActiveCall::global(cx); - let project = workspace.read(cx).project().clone(); - active_call - .update(cx, |call, cx| call.share_project(project, cx)) - .detach_and_log_err(cx); - } + let active_call = ActiveCall::global(cx); + let project = self.project.clone(); + active_call + .update(cx, |call, cx| call.share_project(project, cx)) + .detach_and_log_err(cx); } fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let active_call = ActiveCall::global(cx); - let project = workspace.read(cx).project().clone(); - active_call - .update(cx, |call, cx| call.unshare_project(project, cx)) - .log_err(); - } + let active_call = ActiveCall::global(cx); + let project = self.project.clone(); + active_call + .update(cx, |call, cx| call.unshare_project(project, cx)) + .log_err(); } pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext) { if self.contacts_popover.take().is_none() { - if let Some(workspace) = self.workspace.upgrade(cx) { - let view = cx.add_view(|cx| ContactsPopover::new(&workspace, cx)); - cx.subscribe(&view, |this, _, event, cx| { - match event { - contacts_popover::Event::Dismissed => { - this.contacts_popover = None; - } + let view = cx.add_view(|cx| { + ContactsPopover::new( + self.project.clone(), + self.user_store.clone(), + self.workspace.clone(), + cx, + ) + }); + cx.subscribe(&view, |this, _, event, cx| { + match event { + contacts_popover::Event::Dismissed => { + this.contacts_popover = None; } + } - cx.notify(); - }) - .detach(); - self.contacts_popover = Some(view); - } + cx.notify(); + }) + .detach(); + self.contacts_popover = Some(view); } cx.notify(); @@ -493,12 +500,10 @@ impl CollabTitlebarItem { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - let client = workspace.read(cx).app_state().client.clone(); - cx.app_context() - .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await }) - .detach_and_log_err(cx); - } + let client = this.client.clone(); + cx.app_context() + .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await }) + .detach_and_log_err(cx); }) .into_any() } diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 0429182bf37be56c93ceedcc7daab64f7fbbb3d3..87aa41b7a4658af0c171b8d9b39eeb4431c0cc77 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -157,7 +157,12 @@ pub struct ContactList { } impl ContactList { - pub fn new(workspace: &ViewHandle, cx: &mut ViewContext) -> Self { + pub fn new( + project: ModelHandle, + user_store: ModelHandle, + workspace: WeakViewHandle, + cx: &mut ViewContext, + ) -> Self { let filter_editor = cx.add_view(|cx| { let mut editor = Editor::single_line( Some(Arc::new(|theme| { @@ -262,7 +267,6 @@ impl ContactList { }); let active_call = ActiveCall::global(cx); - let user_store = workspace.read(cx).user_store().clone(); let mut subscriptions = Vec::new(); subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx))); subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx))); @@ -275,8 +279,8 @@ impl ContactList { match_candidates: Default::default(), filter_editor, _subscriptions: subscriptions, - project: workspace.read(cx).project().clone(), - workspace: workspace.downgrade(), + project, + workspace, user_store, }; this.update_entries(cx); diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index b35eb09b3104223b1bb19faf9e9a7907f5edef70..35734d81f48c132aa41259f47420680977ea4ed8 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -8,6 +8,7 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use picker::PickerEvent; +use project::Project; use settings::Settings; use workspace::Workspace; @@ -28,17 +29,26 @@ enum Child { pub struct ContactsPopover { child: Child, + project: ModelHandle, user_store: ModelHandle, workspace: WeakViewHandle, _subscription: Option, } impl ContactsPopover { - pub fn new(workspace: &ViewHandle, cx: &mut ViewContext) -> Self { + pub fn new( + project: ModelHandle, + user_store: ModelHandle, + workspace: WeakViewHandle, + cx: &mut ViewContext, + ) -> Self { let mut this = Self { - child: Child::ContactList(cx.add_view(|cx| ContactList::new(workspace, cx))), - user_store: workspace.read(cx).user_store().clone(), - workspace: workspace.downgrade(), + child: Child::ContactList(cx.add_view(|cx| { + ContactList::new(project.clone(), user_store.clone(), workspace.clone(), cx) + })), + project, + user_store, + workspace, _subscription: None, }; this.show_contact_list(String::new(), cx); @@ -67,19 +77,24 @@ impl ContactsPopover { } fn show_contact_list(&mut self, editor_text: String, cx: &mut ViewContext) { - if let Some(workspace) = self.workspace.upgrade(cx) { - let child = cx - .add_view(|cx| ContactList::new(&workspace, cx).with_editor_text(editor_text, cx)); - cx.focus(&child); - self._subscription = Some(cx.subscribe(&child, |this, _, event, cx| match event { - crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed), - crate::contact_list::Event::ToggleContactFinder => { - this.toggle_contact_finder(&Default::default(), cx) - } - })); - self.child = Child::ContactList(child); - cx.notify(); - } + let child = cx.add_view(|cx| { + ContactList::new( + self.project.clone(), + self.user_store.clone(), + self.workspace.clone(), + cx, + ) + .with_editor_text(editor_text, cx) + }); + cx.focus(&child); + self._subscription = Some(cx.subscribe(&child, |this, _, event, cx| match event { + crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed), + crate::contact_list::Event::ToggleContactFinder => { + this.toggle_contact_finder(&Default::default(), cx) + } + })); + self.child = Child::ContactList(child); + cx.notify(); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5160426bdd7a224c9849a26f54811d430bba007a..ec4904daebbca8cb8cb758c5e66bb1d96b66dd0f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -321,9 +321,8 @@ pub fn initialize_workspace( cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); - let collab_titlebar_item = cx.add_view(|cx| { - CollabTitlebarItem::new(&workspace_handle, app_state.user_store.clone(), cx) - }); + let collab_titlebar_item = + cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let project_panel = ProjectPanel::new(workspace, cx); From 94f17755333412fd24d89bf798cc707d7164b33e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 2 May 2023 13:46:49 +0200 Subject: [PATCH 19/26] Fix broken styling in contact finder This regressed as part of #2372, where we forgot to theme the contact finder picker differently from the rest of the app. --- crates/collab_ui/src/contact_finder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index b07d6d7e2b130eb76e6d5c033c0c540f76aacf27..8530867f1479dd653a90b974f2840307bd461ecf 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -23,6 +23,7 @@ pub fn build_contact_finder( }, cx, ) + .with_theme(|theme| theme.contact_finder.picker.clone()) } pub struct ContactFinderDelegate { From 185c1650df6fef9cdc33da9d5b8791c3abf2c32b Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 2 May 2023 09:08:07 -0400 Subject: [PATCH 20/26] Show diagnostic source in inline diagnostic --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/editor.rs | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 22f67265ecc2fec017938cba5e0e6e335da7fb91..17d142ba4b1bf869021475178db439e76c12a510 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -677,7 +677,7 @@ impl Item for ProjectDiagnosticsEditor { } fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { - let (message, highlights) = highlight_diagnostic_message(&diagnostic.message); + let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message); Arc::new(move |cx| { let settings = cx.global::(); let theme = &settings.theme.editor; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0849c0ef93525ef13ad7c16725d77272f6287ecc..df2ca6f43d8b67d9e206cdbfaf8eb817ac89725d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7509,8 +7509,16 @@ impl Deref for EditorStyle { pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> RenderBlock { let mut highlighted_lines = Vec::new(); - for line in diagnostic.message.lines() { - highlighted_lines.push(highlight_diagnostic_message(line)); + for (index, line) in diagnostic.message.lines().enumerate() { + let line = match &diagnostic.source { + Some(source) if index == 0 => { + let source_highlight = Vec::from_iter(0..source.len()); + highlight_diagnostic_message(source_highlight, &format!("{source}: {line}")) + } + + _ => highlight_diagnostic_message(Vec::new(), line), + }; + highlighted_lines.push(line); } Arc::new(move |cx: &mut BlockContext| { @@ -7534,11 +7542,14 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend }) } -pub fn highlight_diagnostic_message(message: &str) -> (String, Vec) { +pub fn highlight_diagnostic_message( + inital_highlights: Vec, + message: &str, +) -> (String, Vec) { let mut message_without_backticks = String::new(); let mut prev_offset = 0; let mut inside_block = false; - let mut highlights = Vec::new(); + let mut highlights = inital_highlights; for (match_ix, (offset, _)) in message .match_indices('`') .chain([(message.len(), "")]) From 70f8cf4cf6e0fa9d0a8765e6353604ae0be1296c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 2 May 2023 19:28:58 +0200 Subject: [PATCH 21/26] Move methods querying window state into `AsyncAppContext` --- .../src/project_shared_notification.rs | 4 +- .../collab_ui/src/sharing_status_indicator.rs | 4 +- crates/copilot/src/sign_in.rs | 15 ++- crates/gpui/src/app.rs | 67 ++++++------- crates/gpui/src/app/test_app_context.rs | 6 +- crates/gpui/src/test.rs | 2 +- crates/gpui_macros/src/gpui_macros.rs | 4 +- crates/workspace/src/workspace.rs | 94 ++++++++++--------- crates/zed/src/zed.rs | 33 +++---- 9 files changed, 116 insertions(+), 113 deletions(-) diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 1304688ca174d388d1019719568880697dfe637e..8a41368276dded678f6df7b60732175eaf5c8fd0 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -58,14 +58,14 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { room::Event::RemoteProjectUnshared { project_id } => { if let Some(window_ids) = notification_windows.remove(&project_id) { for window_id in window_ids { - cx.remove_window(window_id); + cx.update_window(window_id, |cx| cx.remove_window()); } } } room::Event::Left => { for (_, window_ids) in notification_windows.drain() { for window_id in window_ids { - cx.remove_window(window_id); + cx.update_window(window_id, |cx| cx.remove_window()); } } } diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 447b561b953e3f08d67961e4106f557f1b64a96b..9fbe57af65678c140e2fd360673f6eaa7f1cc6d6 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -19,10 +19,10 @@ pub fn init(cx: &mut AppContext) { status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } } else if let Some((window_id, _)) = status_indicator.take() { - cx.remove_status_bar_item(window_id); + cx.update_window(window_id, |cx| cx.remove_window()); } } else if let Some((window_id, _)) = status_indicator.take() { - cx.remove_status_bar_item(window_id); + cx.update_window(window_id, |cx| cx.remove_window()); } }) .detach(); diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 02a5b347d47cb9f84fc9fb4d407b5549391bf91e..da3c96956e613941f96d3e58077236f43876f25e 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -27,14 +27,13 @@ pub fn init(cx: &mut AppContext) { crate::Status::SigningIn { prompt } => { if let Some(code_verification_handle) = code_verification.as_mut() { let window_id = code_verification_handle.window_id(); - if cx.has_window(window_id) { - cx.update_window(window_id, |cx| { - code_verification_handle.update(cx, |code_verification, cx| { - code_verification.set_status(status, cx) - }); - cx.activate_window(); + let updated = cx.update_window(window_id, |cx| { + code_verification_handle.update(cx, |code_verification, cx| { + code_verification.set_status(status.clone(), cx) }); - } else { + cx.activate_window(); + }); + if updated.is_none() { code_verification = Some(create_copilot_auth_window(cx, &status)); } } else if let Some(_prompt) = prompt { @@ -56,7 +55,7 @@ pub fn init(cx: &mut AppContext) { } _ => { if let Some(code_verification) = code_verification.take() { - cx.remove_window(code_verification.window_id()); + cx.update_window(code_verification.window_id(), |cx| cx.remove_window()); } } } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3cc90dd4ce1fe3534fd7f6d5c531dd8a58b84d83..4d84f7c070fd179ec3220054b1f3fff2041ed3e5 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -302,6 +302,14 @@ impl AsyncAppContext { self.0.borrow_mut().update(callback) } + pub fn read_window T>( + &self, + window_id: usize, + callback: F, + ) -> Option { + self.0.borrow_mut().read_window(window_id, callback) + } + pub fn update_window T>( &mut self, window_id: usize, @@ -332,6 +340,22 @@ impl AsyncAppContext { .ok_or_else(|| anyhow!("window not found")) } + pub fn has_window(&self, window_id: usize) -> bool { + self.read(|cx| cx.windows.contains_key(&window_id)) + } + + pub fn window_is_active(&self, window_id: usize) -> bool { + self.read(|cx| cx.windows.get(&window_id).map_or(false, |w| w.is_active)) + } + + pub fn root_view(&self, window_id: usize) -> Option { + self.read(|cx| cx.windows.get(&window_id).map(|w| w.root_view().clone())) + } + + pub fn window_ids(&self) -> Vec { + self.read(|cx| cx.windows.keys().copied().collect()) + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where T: Entity, @@ -353,7 +377,7 @@ impl AsyncAppContext { } pub fn remove_window(&mut self, window_id: usize) { - self.update(|cx| cx.remove_window(window_id)) + self.update_window(window_id, |cx| cx.remove_window()); } pub fn activate_window(&mut self, window_id: usize) { @@ -552,7 +576,7 @@ impl AppContext { App(self.weak_self.as_ref().unwrap().upgrade().unwrap()) } - pub fn quit(&mut self) { + fn quit(&mut self) { let mut futures = Vec::new(); self.update(|cx| { @@ -569,7 +593,8 @@ impl AppContext { } }); - self.remove_all_windows(); + self.windows.clear(); + self.flush_effects(); let futures = futures::future::join_all(futures); if self @@ -581,11 +606,6 @@ impl AppContext { } } - pub fn remove_all_windows(&mut self) { - self.windows.clear(); - self.flush_effects(); - } - pub fn foreground(&self) -> &Rc { &self.foreground } @@ -702,24 +722,6 @@ impl AppContext { } } - pub fn has_window(&self, window_id: usize) -> bool { - self.window_ids() - .find(|window| window == &window_id) - .is_some() - } - - pub fn window_is_active(&self, window_id: usize) -> bool { - self.windows.get(&window_id).map_or(false, |w| w.is_active) - } - - pub fn root_view(&self, window_id: usize) -> Option<&AnyViewHandle> { - self.windows.get(&window_id).map(|w| w.root_view()) - } - - pub fn window_ids(&self) -> impl Iterator + '_ { - self.windows.keys().copied() - } - pub fn view_ui_name(&self, window_id: usize, view_id: usize) -> Option<&'static str> { Some(self.views.get(&(window_id, view_id))?.ui_name()) } @@ -1285,15 +1287,6 @@ impl AppContext { }) } - pub fn remove_status_bar_item(&mut self, id: usize) { - self.remove_window(id); - } - - pub fn remove_window(&mut self, window_id: usize) { - self.windows.remove(&window_id); - self.flush_effects(); - } - pub fn build_window( &mut self, window_id: usize, @@ -1352,7 +1345,7 @@ impl AppContext { { let mut app = self.upgrade(); platform_window.on_close(Box::new(move || { - app.update(|cx| cx.remove_window(window_id)); + app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window())); })); } @@ -4663,7 +4656,7 @@ mod tests { assert!(model_release_observed.get()); drop(view); - cx.remove_window(window_id); + cx.update_window(window_id, |cx| cx.remove_window()); assert!(view_released.get()); assert!(view_release_observed.get()); } diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 79f35cd923ce7631804929c73044cdc62ff3d9df..2d079a604287dc63cf0d36832e6b5ad87a3d7afa 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -182,7 +182,11 @@ impl TestAppContext { } pub fn window_ids(&self) -> Vec { - self.cx.borrow().window_ids().collect() + self.cx.borrow().windows.keys().copied().collect() + } + + pub fn remove_all_windows(&mut self) { + self.update(|cx| cx.windows.clear()); } pub fn read T>(&self, callback: F) -> T { diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 3b2a5e996037d12abb0210c777659a96f6b280c8..def8ba2ce5ba665b84ff5f25d4c7fd6f0c0e8781 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -100,7 +100,7 @@ pub fn run_test( test_fn(cx, foreground_platform.clone(), deterministic.clone(), seed); }); - cx.update(|cx| cx.remove_all_windows()); + cx.remove_all_windows(); deterministic.run_until_parked(); cx.update(|cx| cx.clear_globals()); diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index ca15fa14a2c77c57aa34c243fc0076e440785713..e976245e06b3bc29e904d6e02db35f476498e1e0 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -137,7 +137,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { ); )); cx_teardowns.extend(quote!( - #cx_varname.update(|cx| cx.remove_all_windows()); + #cx_varname.remove_all_windows(); deterministic.run_until_parked(); #cx_varname.update(|cx| cx.clear_globals()); )); @@ -212,7 +212,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { ); )); cx_teardowns.extend(quote!( - #cx_varname.update(|cx| cx.remove_all_windows()); + #cx_varname.remove_all_windows(); deterministic.run_until_parked(); #cx_varname.update(|cx| cx.clear_globals()); )); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2a7748af0f18f2e67f378cd6401ff413089e288c..5a008537376d86306d099cf10ebdfe2ee81afa9f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -918,12 +918,18 @@ impl Workspace { } pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { - let id = cx.window_ids().find(|&id| cx.window_is_active(id)); - if let Some(id) = id { - //This can only get called when the window's project connection has been lost - //so we don't need to prompt the user for anything and instead just close the window - cx.remove_window(id); - } + cx.spawn(|mut cx| async move { + let id = cx + .window_ids() + .into_iter() + .find(|&id| cx.window_is_active(id)); + if let Some(id) = id { + //This can only get called when the window's project connection has been lost + //so we don't need to prompt the user for anything and instead just close the window + cx.remove_window(id); + } + }) + .detach(); } pub fn close( @@ -948,19 +954,14 @@ impl Workspace { ) -> Task> { let active_call = self.active_call().cloned(); let window_id = cx.window_id(); - let workspace_count = cx - .window_ids() - .collect::>() - .into_iter() - .filter_map(|window_id| { - cx.app_context() - .root_view(window_id)? - .clone() - .downcast::() - }) - .count(); cx.spawn(|this, mut cx| async move { + let workspace_count = cx + .window_ids() + .into_iter() + .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::()) + .count(); + if let Some(active_call) = active_call { if !quitting && workspace_count == 1 @@ -2849,10 +2850,10 @@ impl std::fmt::Debug for OpenPaths { pub struct WorkspaceCreated(WeakViewHandle); pub fn activate_workspace_for_project( - cx: &mut AppContext, + cx: &mut AsyncAppContext, predicate: impl Fn(&mut Project, &mut ModelContext) -> bool, ) -> Option> { - for window_id in cx.window_ids().collect::>() { + for window_id in cx.window_ids() { let handle = cx .update_window(window_id, |cx| { if let Some(workspace_handle) = cx.root_view().clone().downcast::() { @@ -2891,13 +2892,14 @@ pub fn open_paths( > { log::info!("open paths {:?}", abs_paths); - // Open paths in existing workspace if possible - let existing = - activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx)); - let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); cx.spawn(|mut cx| async move { + // Open paths in existing workspace if possible + let existing = activate_workspace_for_project(&mut cx, |project, cx| { + project.contains_paths(&abs_paths, cx) + }); + if let Some(existing) = existing { Ok(( existing.clone(), @@ -2960,13 +2962,16 @@ pub fn join_remote_project( cx: &mut AppContext, ) -> Task> { cx.spawn(|mut cx| async move { - let existing_workspace = cx.update(|cx| { - cx.window_ids() - .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::()) - .find(|workspace| { + let existing_workspace = cx + .window_ids() + .into_iter() + .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::()) + .find(|workspace| { + cx.read_window(workspace.window_id(), |cx| { workspace.read(cx).project().read(cx).remote_id() == Some(project_id) }) - }); + .unwrap_or(false) + }); let workspace = if let Some(existing_workspace) = existing_workspace { existing_workspace.downgrade() @@ -3035,24 +3040,25 @@ pub fn join_remote_project( } pub fn restart(_: &Restart, cx: &mut AppContext) { - let mut workspaces = cx - .window_ids() - .filter_map(|window_id| { - Some( - cx.root_view(window_id)? - .clone() - .downcast::()? - .downgrade(), - ) - }) - .collect::>(); - - // If multiple windows have unsaved changes, and need a save prompt, - // prompt in the active window before switching to a different window. - workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); - let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { + let mut workspaces = cx + .window_ids() + .into_iter() + .filter_map(|window_id| { + Some( + cx.root_view(window_id)? + .clone() + .downcast::()? + .downgrade(), + ) + }) + .collect::>(); + + // If multiple windows have unsaved changes, and need a save prompt, + // prompt in the active window before switching to a different window. + workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { let answer = cx.prompt( workspace.window_id(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ec4904daebbca8cb8cb758c5e66bb1d96b66dd0f..a28f1a9fb35e15679e4e55bd866c8793dad3bf56 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -392,24 +392,25 @@ pub fn build_window_options( } fn quit(_: &Quit, cx: &mut gpui::AppContext) { - let mut workspaces = cx - .window_ids() - .filter_map(|window_id| { - Some( - cx.root_view(window_id)? - .clone() - .downcast::()? - .downgrade(), - ) - }) - .collect::>(); - - // If multiple windows have unsaved changes, and need a save prompt, - // prompt in the active window before switching to a different window. - workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); - let should_confirm = cx.global::().confirm_quit; cx.spawn(|mut cx| async move { + let mut workspaces = cx + .window_ids() + .into_iter() + .filter_map(|window_id| { + Some( + cx.root_view(window_id)? + .clone() + .downcast::()? + .downgrade(), + ) + }) + .collect::>(); + + // If multiple windows have unsaved changes, and need a save prompt, + // prompt in the active window before switching to a different window. + workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id())); + if let (true, Some(workspace)) = (should_confirm, workspaces.first()) { let answer = cx.prompt( workspace.window_id(), From 1398a1206299cbaf5e14b9de30d2fbfe83f04334 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 2 May 2023 17:35:23 +0300 Subject: [PATCH 22/26] More keybindings in macOs modals with buttons Closes https://github.com/zed-industries/community/issues/1095 by forcing the non-Cancel button to get a focus. Due to the way macOs handles buttons on modals, the focus gain had to be achieved via certain button addition order, rather than conventional "setFocus"-ish API, see the related comment for details. Co-authored-by: Antonio Scandurra --- crates/gpui/src/platform/mac/window.rs | 39 ++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index d96f9bc4aea5eb8c3f68917e8ee38a25478bad8a..bcff08d0056a0c01ada1fa589a264ab910b3adc4 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -699,6 +699,31 @@ impl platform::Window for Window { msg: &str, answers: &[&str], ) -> oneshot::Receiver { + // macOs applies overrides to modal window buttons after they are added. + // Two most important for this logic are: + // * Buttons with "Cancel" title will be displayed as the last buttons in the modal + // * Last button added to the modal via `addButtonWithTitle` stays focused + // * Focused buttons react on "space"/" " keypresses + // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus + // + // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion + // ``` + // By default, the first button has a key equivalent of Return, + // any button with a title of “Cancel” has a key equivalent of Escape, + // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button). + // ``` + // + // To avoid situations when the last element added is "Cancel" and it gets the focus + // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button + // last, so it gets focus and a Space shortcut. + // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key. + let latest_non_cancel_label = answers + .iter() + .enumerate() + .rev() + .find(|(_, &label)| label != "Cancel") + .filter(|&(label_index, _)| label_index > 0); + unsafe { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; @@ -709,10 +734,20 @@ impl platform::Window for Window { }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; - for (ix, answer) in answers.iter().enumerate() { + + for (ix, answer) in answers + .iter() + .enumerate() + .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix)) + { + let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; + let _: () = msg_send![button, setTag: ix as NSInteger]; + } + if let Some((ix, answer)) = latest_non_cancel_label { let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; let _: () = msg_send![button, setTag: ix as NSInteger]; } + let (done_tx, done_rx) = oneshot::channel(); let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |answer: NSInteger| { @@ -720,7 +755,7 @@ impl platform::Window for Window { let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap()); } }); - let block = block.copy(); + let native_window = self.0.borrow().native_window; self.0 .borrow() From 2b95aba99c763e400b15b6896dd7200730f794fb Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 2 May 2023 17:02:41 -0400 Subject: [PATCH 23/26] Add download and upload metadata to update request --- crates/auto_update/src/auto_update.rs | 30 +++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 2b3c7e1c63a18957f3d4d30f54d5b90b5e8420fe..68d3776e1c8dd410fa86c4f5c3fadb86097c4599 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,13 +1,15 @@ mod update_notification; use anyhow::{anyhow, Context, Result}; -use client::{ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakViewHandle, }; +use isahc::AsyncBody; use serde::Deserialize; +use serde_derive::Serialize; use settings::Settings; use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; @@ -21,6 +23,13 @@ const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]); +#[derive(Serialize)] +struct UpdateRequestBody { + installation_id: Option>, + release_channel: Option<&'static str>, + telemetry: bool, +} + #[derive(Clone, Copy, PartialEq, Eq)] pub enum AutoUpdateStatus { Idle, @@ -247,7 +256,24 @@ impl AutoUpdater { mounted_app_path.push("/"); let mut dmg_file = File::create(&dmg_path).await?; - let mut response = client.get(&release.url, Default::default(), true).await?; + + let (installation_id, release_channel, telemetry) = cx.read(|cx| { + let installation_id = cx.global::>().telemetry().installation_id(); + let release_channel = cx + .has_global::() + .then(|| cx.global::().display_name()); + let telemetry = cx.global::().telemetry().metrics(); + + (installation_id, release_channel, telemetry) + }); + + let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody { + installation_id, + release_channel, + telemetry, + })?); + + let mut response = client.get(&release.url, request_body, true).await?; smol::io::copy(response.body_mut(), &mut dmg_file).await?; log::info!("downloaded update. path:{:?}", dmg_path); From 69a4fffae24346f79e606a85f8153906f076b79f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 2 May 2023 23:22:55 -0400 Subject: [PATCH 24/26] Update `post_json` to take in a bool for allowing for redirects --- crates/client/src/telemetry.rs | 6 +++--- crates/util/src/http.rs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7151dcd7bb0606efc59a5dec68669a4d7273d651..5dfb6595d1567e26f59cc69ea57892b7d826c896 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -270,7 +270,7 @@ impl Telemetry { }])?; this.http_client - .post_json(MIXPANEL_ENGAGE_URL, json_bytes.into()) + .post_json(MIXPANEL_ENGAGE_URL, json_bytes.into(), false) .await?; anyhow::Ok(()) } @@ -404,7 +404,7 @@ impl Telemetry { json_bytes.clear(); serde_json::to_writer(&mut json_bytes, &events)?; this.http_client - .post_json(MIXPANEL_EVENTS_URL, json_bytes.into()) + .post_json(MIXPANEL_EVENTS_URL, json_bytes.into(), false) .await?; anyhow::Ok(()) } @@ -454,7 +454,7 @@ impl Telemetry { } this.http_client - .post_json(CLICKHOUSE_EVENTS_URL.as_str(), json_bytes.into()) + .post_json(CLICKHOUSE_EVENTS_URL.as_str(), json_bytes.into(), false) .await?; anyhow::Ok(()) } diff --git a/crates/util/src/http.rs b/crates/util/src/http.rs index e29768a53e891e165dd859fd27be5325f35cdfa5..e7f39552b0f832d0734dfaffce39775f262ca8ac 100644 --- a/crates/util/src/http.rs +++ b/crates/util/src/http.rs @@ -40,8 +40,14 @@ pub trait HttpClient: Send + Sync { &'a self, uri: &str, body: AsyncBody, + follow_redirects: bool, ) -> BoxFuture<'a, Result, Error>> { let request = isahc::Request::builder() + .redirect_policy(if follow_redirects { + RedirectPolicy::Follow + } else { + RedirectPolicy::None + }) .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") From 41d4454f4581639e324db86e36924adcddf24d6b Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 2 May 2023 23:23:43 -0400 Subject: [PATCH 25/26] Use post_json so that the Content-Type is set to application/json --- crates/auto_update/src/auto_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 68d3776e1c8dd410fa86c4f5c3fadb86097c4599..abf95ff45a07fc9ea06bb97459a63d0cb2beb84f 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -273,7 +273,7 @@ impl AutoUpdater { telemetry, })?); - let mut response = client.get(&release.url, request_body, true).await?; + let mut response = client.post_json(&release.url, request_body, true).await?; smol::io::copy(response.body_mut(), &mut dmg_file).await?; log::info!("downloaded update. path:{:?}", dmg_path); From 376aa1235f55b6fa185ccea906a98fbc212204aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 May 2023 10:37:57 +0200 Subject: [PATCH 26/26] Fix "IncomingCallNotification was dropped" error when accepting a call This was caused by accepting the call, which caused the notification to be removed. When `active_call.accept_incoming()` finally completed, we would try to get the app state from it in order to join the project, but couldn't becuase the view would have already been dropped. This commit fixes the bug by capturing a weak handle to the app state when accepting the call as opposed to trying to read it from the view when the accept completes. --- .../src/incoming_call_notification.rs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 2e3048a4f8e6bb3a0c34dada2d82acbc00e43720..35484b33090fb6eacc4cb41de8e233b92a19a16d 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -78,24 +78,26 @@ impl IncomingCallNotification { let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx)); let caller_user_id = self.call.calling_user.id; let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id); - cx.spawn(|this, mut cx| async move { - join.await?; - if let Some(project_id) = initial_project_id { - this.update(&mut cx, |this, cx| { - if let Some(app_state) = this.app_state.upgrade() { - workspace::join_remote_project( - project_id, - caller_user_id, - app_state, - cx, - ) - .detach_and_log_err(cx); - } - })?; - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + let app_state = self.app_state.clone(); + cx.app_context() + .spawn(|mut cx| async move { + join.await?; + if let Some(project_id) = initial_project_id { + cx.update(|cx| { + if let Some(app_state) = app_state.upgrade() { + workspace::join_remote_project( + project_id, + caller_user_id, + app_state, + cx, + ) + .detach_and_log_err(cx); + } + }); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } else { active_call.update(cx, |active_call, _| { active_call.decline_incoming().log_err();