From a63eccf18839ecbfd35f45bd2da87b0c73a4cb2d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 4 Oct 2023 22:46:28 -0600 Subject: [PATCH 01/18] Add url schemes to Zed --- crates/util/src/channel.rs | 26 ++++++++++++++++++++++++++ crates/zed/Cargo.toml | 3 +++ 2 files changed, 29 insertions(+) diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 274fd576a050076511c8c1253b7187fbd437e8c3..89d42ffba6bdfc2b31b36fb7c78aeed5217f6823 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,6 +1,7 @@ use std::env; use lazy_static::lazy_static; +use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -15,6 +16,23 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; + + static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "zed-dev:/", + "preview" => "zed-preview:/", + "stable" => "zed:/", + // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); + static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "http://localhost:3000/dev/", + "preview" => "https://zed.dev/preview/", + "stable" => "https://zed.dev/", + // NOTE: this must be kept in sync with https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -41,4 +59,12 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static Url { + &URL_SCHEME + } + + pub fn link_prefix(&self) -> &'static Url { + &LINK_PREFIX + } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d4ac972a5dfce65a1688fc2517347ea7138bd31a..7eb14559be8ab64e44eabf55eaf8837338473d06 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -162,6 +162,7 @@ identifier = "dev.zed.Zed-Dev" name = "Zed Dev" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-dev"] [package.metadata.bundle-preview] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] @@ -169,6 +170,7 @@ identifier = "dev.zed.Zed-Preview" name = "Zed Preview" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-preview"] [package.metadata.bundle-stable] @@ -177,3 +179,4 @@ identifier = "dev.zed.Zed" name = "Zed" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed"] From b258ee5f77f9e84e2d0f7d7aad802dca9076b321 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 11:04:23 -0600 Subject: [PATCH 02/18] Fix ./script/bundle -l --- crates/zed/Cargo.toml | 2 +- script/bundle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7eb14559be8ab64e44eabf55eaf8837338473d06..3c93462d4b9182fb362d623455d52c9212d8cc9d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle-dev] +[package.metadata.bundle] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/script/bundle b/script/bundle index 49da1072ceaff28bff27e101caadf85e58d2e2c4..4882189c225f577b0ca82c3ce82631e0191037b5 100755 --- a/script/bundle +++ b/script/bundle @@ -16,7 +16,7 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode and print the app bundle's path. + -d Compile in debug mode (doesn't currently work without -l) -l Compile for local architecture only and copy bundle to /Applications. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. @@ -92,7 +92,7 @@ sed \ Cargo.toml if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) + app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi From 13192fa03ca4548bd29abed93597c6ac1274340b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 14:23:14 -0600 Subject: [PATCH 03/18] Code to allow opening zed:/channel/1234 Refactored a bit how url arguments are handled to avoid adding too much extra complexity to main. --- crates/cli/src/main.rs | 1 + crates/collab_ui/src/collab_panel.rs | 83 ++++++---------- crates/util/src/channel.rs | 17 +--- crates/workspace/src/workspace.rs | 82 ++++++++++++++++ crates/zed/src/main.rs | 137 +++++++++++---------------- crates/zed/src/open_url.rs | 101 ++++++++++++++++++++ 6 files changed, 272 insertions(+), 149 deletions(-) create mode 100644 crates/zed/src/open_url.rs diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2f742814a8dbfdfeb7719fbe906858448d8253f8..69cfb7102bbe1b7ef4bb2d182794027e30a45148 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -182,6 +182,7 @@ impl Bundle { kCFStringEncodingUTF8, ptr::null(), )); + // equivalent to: open zed-cli:... -a /Applications/Zed\ Preview.app let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); LSOpenFromURLSpec( &LSLaunchURLSpec { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 951c8bf70ca647644943b3804f45cc6646eb395b..3d66e8450ae0435bdc8d69908b43402992575458 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1969,18 +1969,21 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new(channel.name.clone(), style.text.clone()) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new( + channel.name.clone().to_owned() + channel_id.to_string().as_str(), + style.text.clone(), + ) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = @@ -3187,49 +3190,19 @@ impl CollabPanel { } fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { - let workspace = self.workspace.clone(); - let window = cx.window(); - let active_call = ActiveCall::global(cx); - cx.spawn(|_, mut cx| async move { - if active_call.read_with(&mut cx, |active_call, cx| { - if let Some(room) = active_call.room() { - let room = room.read(cx); - room.is_sharing_project() && room.remote_participants().len() > 0 - } else { - false - } - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); - - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return anyhow::Ok(()); - } - } - } - - let room = active_call - .update(&mut cx, |call, cx| call.join_channel(channel_id, cx)) - .await?; - - let task = room.update(&mut cx, |room, cx| { - let workspace = workspace.upgrade(cx)?; - let (project, host) = room.most_active_project()?; - let app_state = workspace.read(cx).app_state().clone(); - Some(workspace::join_remote_project(project, host, app_state, cx)) - }); - if let Some(task) = task { - task.await?; - } - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + let Some(workspace) = self.workspace.upgrade(cx) else { + return; + }; + let Some(handle) = cx.window().downcast::() else { + return; + }; + workspace::join_channel( + channel_id, + workspace.read(cx).app_state().clone(), + Some(handle), + cx, + ) + .detach_and_log_err(cx) } fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext) { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 89d42ffba6bdfc2b31b36fb7c78aeed5217f6823..761b17e6af0d74f67f603808a7f515bf4ffdf9b7 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -17,15 +17,14 @@ lazy_static! { _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { "dev" => "zed-dev:/", "preview" => "zed-preview:/", "stable" => "zed:/", - // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. _ => unreachable!(), - }) - .unwrap(); - static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + }.to_string(); + pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { "dev" => "http://localhost:3000/dev/", "preview" => "https://zed.dev/preview/", "stable" => "https://zed.dev/", @@ -59,12 +58,4 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } - - pub fn url_scheme(&self) -> &'static Url { - &URL_SCHEME - } - - pub fn link_prefix(&self) -> &'static Url { - &LINK_PREFIX - } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f7bb4092291f2b677d8c4b0e0fbc0bdd887ad535..5ec847b28b337ca52879511c8531a9a4e0e380b5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4154,6 +4154,88 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| async move { + let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { + let Some(room) = active_call.room().map( |room| room.read(cx) ) else { + return false + }; + + room.is_sharing_project() && room.remote_participants().len() > 0 && + room.channel_id() != Some(channel_id) + }); + + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(&mut cx, |cx| { + cx.window() + }) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + &mut cx, + ); + + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(()); + } + } + } + } + } + + let room = active_call.update(&mut cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }).await?; + + let task = room.update(&mut cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)) + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(()); + } + + if requesting_window.is_some() { + return anyhow::Ok(()); + } + + // find an existing workspace to focus and show call controls + for window in cx.windows() { + let found = window.update(&mut cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + + if found.unwrap_or(false) { + return anyhow::Ok(()) + } + } + + // no open workspaces + cx.update(|cx| { + Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) + }).await; + + return anyhow::Ok(()); + }) +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index d6f3be2b464f3453ca83f602b56d974989d53cf6..c491d406aff75a17d873d5186b75d397f67f51b3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -45,7 +45,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{ReleaseChannel, URL_SCHEME_PREFIX}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -61,6 +61,10 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; +use crate::open_url::{OpenListener, OpenRequest}; + +mod open_url; + fn main() { let http = http::client(); init_paths(); @@ -92,29 +96,20 @@ fn main() { }) }; - let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); - let cli_connections_tx = Arc::new(cli_connections_tx); - let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); - let open_paths_tx = Arc::new(open_paths_tx); - let urls_callback_triggered = Arc::new(AtomicBool::new(false)); - - let callback_cli_connections_tx = Arc::clone(&cli_connections_tx); - let callback_open_paths_tx = Arc::clone(&open_paths_tx); - let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered); - app.on_open_urls(move |urls, _| { - callback_urls_callback_triggered.store(true, Ordering::Release); - open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx); - }) - .on_reopen(move |cx| { - if cx.has_global::>() { - if let Some(app_state) = cx.global::>().upgrade() { - workspace::open_new(&app_state, cx, |workspace, cx| { - Editor::new_file(workspace, &Default::default(), cx) - }) - .detach(); + let (listener, mut open_rx) = OpenListener::new(); + let listener = Arc::new(listener); + let callback_listener = listener.clone(); + app.on_open_urls(move |urls, _| callback_listener.open_urls(urls)) + .on_reopen(move |cx| { + if cx.has_global::>() { + if let Some(app_state) = cx.global::>().upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } } - } - }); + }); app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); @@ -226,41 +221,52 @@ fn main() { // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() - && !urls_callback_triggered.load(Ordering::Acquire) + && !listener.triggered.load(Ordering::Acquire) { - open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx) + listener.open_urls(collect_url_args()) } - if let Ok(Some(connection)) = cli_connections_rx.try_next() { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } else if let Ok(Some(paths)) = open_paths_rx.try_next() { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } else { - cx.spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach() - } - - cx.spawn(|cx| { - let app_state = app_state.clone(); - async move { - while let Some(connection) = cli_connections_rx.next().await { - handle_cli_connection(connection, app_state.clone(), cx.clone()).await; - } + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); } - }) - .detach(); + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach(), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } cx.spawn(|mut cx| { let app_state = app_state.clone(); async move { - while let Some(paths) = open_paths_rx.next().await { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| { + handle_cli_connection(connection, app_state.clone(), cx) + }) + .detach(); + } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), + } } } }) @@ -297,37 +303,6 @@ async fn installation_id() -> Result { } } -fn open_urls( - urls: Vec, - cli_connections_tx: &mpsc::UnboundedSender<( - mpsc::Receiver, - IpcSender, - )>, - open_paths_tx: &mpsc::UnboundedSender>, -) { - if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - if let Some(cli_connection) = connect_to_cli(server_name).log_err() { - cli_connections_tx - .unbounded_send(cli_connection) - .map_err(|_| anyhow!("no listener for cli connections")) - .log_err(); - }; - } else { - let paths: Vec<_> = urls - .iter() - .flat_map(|url| url.strip_prefix("file://")) - .map(|url| { - let decoded = urlencoding::decode_binary(url.as_bytes()); - PathBuf::from(OsStr::from_bytes(decoded.as_ref())) - }) - .collect(); - open_paths_tx - .unbounded_send(paths) - .map_err(|_| anyhow!("no listener for open urls requests")) - .log_err(); - } -} - async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace::last_opened_workspace_paths().await { cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx)) diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs new file mode 100644 index 0000000000000000000000000000000000000000..f421633d5b886d691456aeac777533e2d0ed0dcb --- /dev/null +++ b/crates/zed/src/open_url.rs @@ -0,0 +1,101 @@ +use anyhow::anyhow; +use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use futures::channel::mpsc; +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use std::ffi::OsStr; +use std::os::unix::prelude::OsStrExt; +use std::sync::atomic::Ordering; +use std::{path::PathBuf, sync::atomic::AtomicBool}; +use util::channel::URL_SCHEME_PREFIX; +use util::ResultExt; + +use crate::{connect_to_cli, handle_cli_connection}; + +pub enum OpenRequest { + Paths { + paths: Vec, + }, + CliConnection { + connection: (mpsc::Receiver, IpcSender), + }, + JoinChannel { + channel_id: u64, + }, +} + +pub struct OpenListener { + tx: UnboundedSender, + pub triggered: AtomicBool, +} + +impl OpenListener { + pub fn new() -> (Self, UnboundedReceiver) { + let (tx, rx) = mpsc::unbounded(); + ( + OpenListener { + tx, + triggered: AtomicBool::new(false), + }, + rx, + ) + } + + pub fn open_urls(&self, urls: Vec) { + self.triggered.store(true, Ordering::Release); + dbg!(&urls); + let request = if let Some(server_name) = + urls.first().and_then(|url| url.strip_prefix("zed-cli://")) + { + self.handle_cli_connection(server_name) + } else if let Some(request_path) = urls + .first() + .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) + { + self.handle_zed_url_scheme(request_path) + } else { + self.handle_file_urls(urls) + }; + + if let Some(request) = request { + self.tx + .unbounded_send(request) + .map_err(|_| anyhow!("no listener for open requests")) + .log_err(); + } + } + + fn handle_cli_connection(&self, server_name: &str) -> Option { + if let Some(connection) = connect_to_cli(server_name).log_err() { + return Some(OpenRequest::CliConnection { connection }); + } + + None + } + + fn handle_zed_url_scheme(&self, request_path: &str) -> Option { + let mut parts = request_path.split("/"); + if parts.next() == Some("channel") { + if let Some(slug) = parts.next() { + if let Some(id_str) = slug.split("-").last() { + if let Ok(channel_id) = id_str.parse::() { + return Some(OpenRequest::JoinChannel { channel_id }); + } + } + } + } + None + } + + fn handle_file_urls(&self, urls: Vec) -> Option { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|url| { + let decoded = urlencoding::decode_binary(url.as_bytes()); + PathBuf::from(OsStr::from_bytes(decoded.as_ref())) + }) + .collect(); + + Some(OpenRequest::Paths { paths }) + } +} From 31062d424f385c34ccc3496e74dbd0d57978cf90 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 5 Oct 2023 16:41:08 -0700 Subject: [PATCH 04/18] make bundle script incremental when using debug or local builds --- crates/zed/resources/zed.entitlements | 10 ++-------- crates/zed/src/main.rs | 6 ++---- crates/zed/src/open_url.rs | 2 +- script/bundle | 27 +++++++++++++++++++-------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index f40a8a253ad082bc08e48ed8684b2d553d5065e8..dca0ff676633b9bac7b69114c416536f99262599 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -10,14 +10,8 @@ com.apple.security.device.camera - com.apple.security.personal-information.addressbook - - com.apple.security.personal-information.calendars - - com.apple.security.personal-information.location - - com.apple.security.personal-information.photos-library - + com.apple.security.keychain-access-groups + MQ55VZLNZQ.dev.zed.Shared diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c491d406aff75a17d873d5186b75d397f67f51b3..9d0451ecfa97cf7da8001ccbcee03fe56a274d5f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -32,12 +32,10 @@ use std::{ ffi::OsStr, fs::OpenOptions, io::{IsTerminal, Write as _}, - os::unix::prelude::OsStrExt, panic, path::{Path, PathBuf}, - str, sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, + atomic::{AtomicU32, Ordering}, Arc, Weak, }, thread, @@ -45,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::{ReleaseChannel, URL_SCHEME_PREFIX}, + channel::ReleaseChannel, http::{self, HttpClient}, paths::PathLikeWithPosition, }; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index f421633d5b886d691456aeac777533e2d0ed0dcb..1c741a02c89d3ec20d88d866c2d2c4b469385ab2 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -9,7 +9,7 @@ use std::{path::PathBuf, sync::atomic::AtomicBool}; use util::channel::URL_SCHEME_PREFIX; use util::ResultExt; -use crate::{connect_to_cli, handle_cli_connection}; +use crate::connect_to_cli; pub enum OpenRequest { Paths { diff --git a/script/bundle b/script/bundle index 4882189c225f577b0ca82c3ce82631e0191037b5..94741d290fc95839362de6c256b41431f216d7e3 100755 --- a/script/bundle +++ b/script/bundle @@ -5,6 +5,7 @@ set -e build_flag="--release" target_dir="release" open_result=false +local_arch=false local_only=false overwrite_local_app=false bundle_name="" @@ -16,8 +17,8 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode (doesn't currently work without -l) - -l Compile for local architecture only and copy bundle to /Applications. + -d Compile in debug mode + -l Compile for local architecture and copy bundle to /Applications, implies -d. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. -h Display this help and exit. @@ -32,10 +33,20 @@ do case "${flag}" in o) open_result=true;; d) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true build_flag=""; + local_arch=true + target_dir="debug" + ;; + l) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true + build_flag="" + local_arch=true + local_only=true target_dir="debug" ;; - l) local_only=true;; f) overwrite_local_app=true;; h) help_info @@ -67,7 +78,7 @@ version_info=$(rustc --version --verbose) host_line=$(echo "$version_info" | grep host) local_target_triple=${host_line#*: } -if [ "$local_only" = true ]; then +if [ "$local_arch" = true ]; then echo "Building for local target only." cargo build ${build_flag} --package zed cargo build ${build_flag} --package cli @@ -91,8 +102,8 @@ sed \ "s/package.metadata.bundle-${channel}/package.metadata.bundle/" \ Cargo.toml -if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) +if [ "$local_arch" = true ]; then + app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi @@ -101,7 +112,7 @@ mv Cargo.toml.backup Cargo.toml popd echo "Bundled ${app_path}" -if [ "$local_only" = false ]; then +if [ "$local_arch" = false ]; then echo "Creating fat binaries" lipo \ -create \ @@ -136,7 +147,7 @@ else codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v fi -if [ "$target_dir" = "debug" ]; then +if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then if [ "$open_result" = true ]; then open "$app_path" else From b58c42cd536e2317a009ed48464a8f691199b731 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 13:47:35 -0600 Subject: [PATCH 05/18] TEMP --- .cargo/config.toml | 2 +- crates/util/src/channel.rs | 49 ++++++---- crates/workspace/src/workspace.rs | 149 +++++++++++++++++++++--------- crates/zed/src/main.rs | 107 ++++++++++----------- crates/zed/src/open_url.rs | 7 +- 5 files changed, 191 insertions(+), 123 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be080072d89d16a199e2d60d527eeacd07..e22bdb0f2c70a1ffda714674253cc533e9e7c1d1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 761b17e6af0d74f67f603808a7f515bf4ffdf9b7..2364dcaad48d79272dddf9bbabbf67c0d590270a 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,7 +1,6 @@ use std::env; use lazy_static::lazy_static; -use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -16,22 +15,6 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - - pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "zed-dev:/", - "preview" => "zed-preview:/", - "stable" => "zed:/", - // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. - _ => unreachable!(), - }.to_string(); - pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "http://localhost:3000/dev/", - "preview" => "https://zed.dev/preview/", - "stable" => "https://zed.dev/", - // NOTE: this must be kept in sync with https://zed.dev. - _ => unreachable!(), - }) - .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -58,4 +41,36 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "zed-dev:/", + ReleaseChannel::Preview => "zed-preview:/", + ReleaseChannel::Stable => "zed:/", + } + } + + pub fn link_prefix(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "https://zed.dev/dev/", + ReleaseChannel::Preview => "https://zed.dev/preview/", + ReleaseChannel::Stable => "https://zed.dev/", + } + } +} + +pub fn parse_zed_link(link: &str) -> Option<&str> { + for release in [ + ReleaseChannel::Dev, + ReleaseChannel::Preview, + ReleaseChannel::Stable, + ] { + if let Some(stripped) = link.strip_prefix(release.link_prefix()) { + return Some(stripped); + } + if let Some(stripped) = link.strip_prefix(release.url_scheme()) { + return Some(stripped); + } + } + None } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5ec847b28b337ca52879511c8531a9a4e0e380b5..1002ae29dc2059335f79d0d67e6631dad1eecf6e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,14 +15,14 @@ use call::ActiveCall; use channel::ChannelStore; use client::{ proto::{self, PeerId}, - Client, TypedEnvelope, UserStore, + Client, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - FutureExt, StreamExt, + select_biased, FutureExt, StreamExt, }; use gpui::{ actions, @@ -4154,57 +4154,119 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } -pub fn join_channel( +async fn join_channel_internal( channel_id: u64, app_state: Arc, requesting_window: Option>, - cx: &mut AppContext, -) -> Task> { - let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| async move { - let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { - let Some(room) = active_call.room().map( |room| room.read(cx) ) else { - return false - }; + active_call: &ModelHandle, + cx: &mut AsyncAppContext, +) -> Result { + let should_prompt = active_call.read_with(cx, |active_call, cx| { + let Some(room) = active_call.room().map(|room| room.read(cx)) else { + return false; + }; - room.is_sharing_project() && room.remote_participants().len() > 0 && - room.channel_id() != Some(channel_id) - }); + room.is_sharing_project() + && room.remote_participants().len() > 0 + && room.channel_id() != Some(channel_id) + }); - if should_prompt { - if let Some(workspace) = requesting_window { - if let Some(window) = workspace.update(&mut cx, |cx| { - cx.window() - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(cx, |cx| cx.window()) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + cx, + ); - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return Ok(()); - } + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(false); } } + } else { + return Ok(false); // unreachable!() hopefully } + } else { + return Ok(false); // unreachable!() hopefully } + } - let room = active_call.update(&mut cx, |active_call, cx| { - active_call.join_channel(channel_id, cx) - }).await?; + let client = cx.read(|cx| active_call.read(cx).client()); - let task = room.update(&mut cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { - return Some(join_remote_project(project, host, app_state.clone(), cx)) + let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); + let mut client_status = client.status(); + + 'outer: loop { + select_biased! { + _ = timer => { + return Err(anyhow!("connecting timed out")) + }, + status = client_status.recv().fuse() => { + let Some(status) = status else { + return Err(anyhow!("unexpected error reading connection status")) + }; + + match status { + Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => { + if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + timer = cx.background().timer(Duration::from_secs(5)).fuse(); + } else { + return Err(anyhow!("not signed in")) + } + }, + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) + } } + } + } - None - }); - if let Some(task) = task { - task.await?; + let room = active_call + .update(cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }) + .await?; + + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(true); + } + + anyhow::Ok(false) +} + +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| { + let result = join_channel_internal( + channel_id, + app_state, + requesting_window, + &active_call, + &mut cx, + ) + .await; + + // join channel succeeded, and opened a window + if Some(true) = result { return anyhow::Ok(()); } @@ -4223,16 +4285,15 @@ pub fn join_channel( }); if found.unwrap_or(false) { - return anyhow::Ok(()) + return anyhow::Ok(()); } } // no open workspaces - cx.update(|cx| { - Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) - }).await; + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; - return anyhow::Ok(()); + return connected.map(|_| ()); }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9d0451ecfa97cf7da8001ccbcee03fe56a274d5f..861121a1cf20c4037a4110cf3246982cb059477f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{parse_zed_link, ReleaseChannel}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -206,12 +206,9 @@ fn main() { if stdout_is_a_pty() { cx.platform().activate(true); - let paths = collect_path_args(); - if paths.is_empty() { - cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await }) - .detach() - } else { - workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx); + let urls = collect_url_args(); + if !urls.is_empty() { + listener.open_urls(urls) } } else { upload_previous_panics(http.clone(), cx); @@ -223,53 +220,51 @@ fn main() { { listener.open_urls(collect_url_args()) } + } - match open_rx.try_next() { - Ok(Some(OpenRequest::Paths { paths })) => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - Ok(Some(OpenRequest::CliConnection { connection })) => { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach(), - Ok(None) | Err(_) => cx - .spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach(), + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach_and_log_err(cx), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } - cx.spawn(|mut cx| { - let app_state = app_state.clone(); - async move { - while let Some(request) = open_rx.next().await { - match request { - OpenRequest::Paths { paths } => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - OpenRequest::CliConnection { connection } => { - cx.spawn(|cx| { - handle_cli_connection(connection, app_state.clone(), cx) - }) + cx.spawn(|mut cx| { + let app_state = app_state.clone(); + async move { + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); - } - OpenRequest::JoinChannel { channel_id } => cx - .update(|cx| { - workspace::join_channel(channel_id, app_state.clone(), None, cx) - }) - .detach(), } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), } } - }) - .detach(); - } + } + }) + .detach(); cx.spawn(|cx| async move { if stdout_is_a_pty() { @@ -608,23 +603,23 @@ fn stdout_is_a_pty() -> bool { std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal() } -fn collect_path_args() -> Vec { +fn collect_url_args() -> Vec { env::args() .skip(1) - .filter_map(|arg| match std::fs::canonicalize(arg) { - Ok(path) => Some(path), + .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) { + Ok(path) => Some(format!("file://{}", path.to_string_lossy())), Err(error) => { - log::error!("error parsing path argument: {}", error); - None + if let Some(_) = parse_zed_link(&arg) { + Some(arg) + } else { + log::error!("error parsing path argument: {}", error); + None + } } }) .collect() } -fn collect_url_args() -> Vec { - env::args().skip(1).collect() -} - fn load_embedded_fonts(app: &App) { let font_paths = Assets.list("fonts"); let embedded_fonts = Mutex::new(Vec::new()); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 1c741a02c89d3ec20d88d866c2d2c4b469385ab2..6f90953de2740617017556f803cd87a670e99c77 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -6,7 +6,7 @@ use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; -use util::channel::URL_SCHEME_PREFIX; +use util::channel::parse_zed_link; use util::ResultExt; use crate::connect_to_cli; @@ -47,10 +47,7 @@ impl OpenListener { urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { self.handle_cli_connection(server_name) - } else if let Some(request_path) = urls - .first() - .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) - { + } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url)) { self.handle_zed_url_scheme(request_path) } else { self.handle_file_urls(urls) From 4128e2ffcbb5317ffc668c0c851e3a51474649e2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 15:18:25 -0600 Subject: [PATCH 06/18] Fix panic if the host is not there. --- crates/call/src/room.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 72db174d7256b0a5686bee0210bb5c37464bc97d..2543697bc0f86e1d69cb0cb0b51dc2ff63cf53ad 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -602,6 +602,7 @@ impl Room { pub fn most_active_project(&self) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); + for participant in self.remote_participants.values() { match participant.location { ParticipantLocation::SharedProject { project_id } => { @@ -619,8 +620,8 @@ impl Room { pairs.sort_by_key(|(_, count)| *count as i32); pairs - .first() - .map(|(project_id, _)| (*project_id, hosts[&project_id])) + .iter() + .find_map(|(project_id, _)| hosts.get(project_id).map(|host| (*project_id, *host))) } async fn handle_room_updated( From 63a230f92e04e839f52855419dfb7e3477910866 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:11:45 -0600 Subject: [PATCH 07/18] Make joining on boot work --- crates/call/src/room.rs | 20 +++++- crates/client/src/client.rs | 3 +- crates/workspace/src/workspace.rs | 110 +++++++++++++++++------------- crates/zed/src/main.rs | 49 ++++++++----- crates/zed/src/open_url.rs | 1 - 5 files changed, 118 insertions(+), 65 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 2543697bc0f86e1d69cb0cb0b51dc2ff63cf53ad..5419e00f02e093419862d0c7cbaf4dab70c7b0f0 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -18,7 +18,7 @@ use live_kit_client::{ LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, }; -use postage::stream::Stream; +use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -70,6 +70,8 @@ pub struct Room { user_store: ModelHandle, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, subscriptions: Vec, + room_update_completed_tx: watch::Sender>, + room_update_completed_rx: watch::Receiver>, pending_room_update: Option>, maintain_connection: Option>>, } @@ -211,6 +213,8 @@ impl Room { Audio::play_sound(Sound::Joined, cx); + let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); + Self { id, channel_id, @@ -230,6 +234,8 @@ impl Room { user_store, follows_by_leader_id_project_id: Default::default(), maintain_connection: Some(maintain_connection), + room_update_completed_tx, + room_update_completed_rx, } } @@ -856,6 +862,7 @@ impl Room { }); this.check_invariants(); + this.room_update_completed_tx.try_send(Some(())).ok(); cx.notify(); }); })); @@ -864,6 +871,17 @@ impl Room { Ok(()) } + pub fn next_room_update(&mut self) -> impl Future { + let mut done_rx = self.room_update_completed_rx.clone(); + async move { + while let Some(result) = done_rx.next().await { + if result.is_some() { + break; + } + } + } + } + fn remote_video_track_updated( &mut self, change: RemoteVideoTrackUpdate, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 5767ac54b7893f7425dfd56202b7512d17314f0f..62cd60c55fd339965232448d0a306cc3dd457ff4 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,8 @@ impl Client { } } } - _ = status_rx.next().fuse() => { + status = status_rx.next().fuse() => { + dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1002ae29dc2059335f79d0d67e6631dad1eecf6e..a1249173090a8af506d16efe1b8c25657b358e34 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -22,7 +22,7 @@ use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - select_biased, FutureExt, StreamExt, + FutureExt, StreamExt, }; use gpui::{ actions, @@ -36,9 +36,9 @@ use gpui::{ CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions, }, - AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, - ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, WindowHandle, + AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, + Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, WindowHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; @@ -4156,7 +4156,7 @@ pub async fn last_opened_workspace_paths() -> Option { async fn join_channel_internal( channel_id: u64, - app_state: Arc, + app_state: &Arc, requesting_window: Option>, active_call: &ModelHandle, cx: &mut AsyncAppContext, @@ -4196,33 +4196,24 @@ async fn join_channel_internal( let client = cx.read(|cx| active_call.read(cx).client()); - let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); let mut client_status = client.status(); + // this loop will terminate within client::CONNECTION_TIMEOUT seconds. 'outer: loop { - select_biased! { - _ = timer => { - return Err(anyhow!("connecting timed out")) - }, - status = client_status.recv().fuse() => { - let Some(status) = status else { - return Err(anyhow!("unexpected error reading connection status")) - }; + let Some(status) = client_status.recv().await else { + return Err(anyhow!("error connecting")); + }; - match status { - Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, - Status::Connected { .. } => break 'outer, - Status::SignedOut => { - if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - timer = cx.background().timer(Duration::from_secs(5)).fuse(); - } else { - return Err(anyhow!("not signed in")) - } - }, - Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), - | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) - } + match status { + Status::Connecting + | Status::Authenticating + | Status::Reconnecting + | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => return Err(anyhow!("not signed in")), + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { + return Err(anyhow!("zed is offline")) } } } @@ -4233,6 +4224,8 @@ async fn join_channel_internal( }) .await?; + room.update(cx, |room, cx| room.next_room_update()).await; + let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4255,10 +4248,10 @@ pub fn join_channel( cx: &mut AppContext, ) -> Task> { let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| { + cx.spawn(|mut cx| async move { let result = join_channel_internal( channel_id, - app_state, + &app_state, requesting_window, &active_call, &mut cx, @@ -4266,7 +4259,7 @@ pub fn join_channel( .await; // join channel succeeded, and opened a window - if Some(true) = result { + if matches!(result, Ok(true)) { return anyhow::Ok(()); } @@ -4275,28 +4268,53 @@ pub fn join_channel( } // find an existing workspace to focus and show call controls - for window in cx.windows() { - let found = window.update(&mut cx, |cx| { - let is_workspace = cx.root_view().clone().downcast::().is_some(); - if is_workspace { - cx.activate_window(); - } - is_workspace - }); + let mut active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + // no open workspaces, make one to show the error in (blergh) + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; + } - if found.unwrap_or(false) { - return anyhow::Ok(()); - } + active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + return result.map(|_| ()); // unreachable!() assuming new_local always opens a window } - // no open workspaces - cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) - .await; + if let Err(err) = result { + let prompt = active_window.unwrap().prompt( + PromptLevel::Critical, + &format!("Failed to join channel: {}", err), + &["Ok"], + &mut cx, + ); + if let Some(mut prompt) = prompt { + prompt.next().await; + } else { + return Err(err); + } + } - return connected.map(|_| ()); + // return ok, we showed the error to the user. + return anyhow::Ok(()); }) } +pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { + for window in cx.windows() { + let found = window.update(cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + if found == Some(true) { + return Some(window); + } + } + None +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 861121a1cf20c4037a4110cf3246982cb059477f..52aaf639ea4c875bfcecf9aea3ffc6fb8bfe2dc2 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,7 +8,9 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; -use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{ + self, Client, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, +}; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Editor}; use futures::{ @@ -33,7 +35,7 @@ use std::{ fs::OpenOptions, io::{IsTerminal, Write as _}, panic, - path::{Path, PathBuf}, + path::Path, sync::{ atomic::{AtomicU32, Ordering}, Arc, Weak, @@ -222,6 +224,8 @@ fn main() { } } + let mut triggered_authentication = false; + match open_rx.try_next() { Ok(Some(OpenRequest::Paths { paths })) => { cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) @@ -231,9 +235,18 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach_and_log_err(cx), + Ok(Some(OpenRequest::JoinChannel { channel_id })) => { + triggered_authentication = true; + let app_state = app_state.clone(); + let client = client.clone(); + cx.spawn(|mut cx| async move { + // ignore errors here, we'll show a generic "not signed in" + let _ = authenticate(client, &cx).await; + cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx)) + .await + }) + .detach_and_log_err(cx) + } Ok(None) | Err(_) => cx .spawn({ let app_state = app_state.clone(); @@ -266,20 +279,24 @@ fn main() { }) .detach(); - cx.spawn(|cx| async move { - if stdout_is_a_pty() { - if client::IMPERSONATE_LOGIN.is_some() { - client.authenticate_and_connect(false, &cx).await?; - } - } else if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - } - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); + if !triggered_authentication { + cx.spawn(|cx| async move { authenticate(client, &cx).await }) + .detach_and_log_err(cx); + } }); } +async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { + if stdout_is_a_pty() { + if client::IMPERSONATE_LOGIN.is_some() { + client.authenticate_and_connect(false, &cx).await?; + } + } else if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + } + Ok::<_, anyhow::Error>(()) +} + async fn installation_id() -> Result { let legacy_key_name = "device_id"; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 6f90953de2740617017556f803cd87a670e99c77..3e4902b9785cd08d4e38bbc14c6287936106bf8f 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -42,7 +42,6 @@ impl OpenListener { pub fn open_urls(&self, urls: Vec) { self.triggered.store(true, Ordering::Release); - dbg!(&urls); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { From f6bc229d1d4f4d46139ec268241fb69d925fceed Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:48:29 -0600 Subject: [PATCH 08/18] More progress and some debug logs to remove --- assets/settings/default.json | 2 +- crates/util/src/channel.rs | 6 +++--- crates/workspace/src/workspace.rs | 21 +++++++++++++++++++-- crates/zed/src/main.rs | 10 ++++++++++ crates/zed/src/open_url.rs | 13 +++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb0b8143f7e42981a71966327edd0f54..cc724657c0c20411b17d0ad0ea293b0a08ddc6f5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -76,7 +76,7 @@ // Settings related to calls in Zed "calls": { // Join calls with the microphone muted by default - "mute_on_join": true + "mute_on_join": false }, // Scrollbar related settings "scrollbar": { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 2364dcaad48d79272dddf9bbabbf67c0d590270a..47c6a570a1f84c4f0fb16c24df650082d5aee60e 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -44,9 +44,9 @@ impl ReleaseChannel { pub fn url_scheme(&self) -> &'static str { match self { - ReleaseChannel::Dev => "zed-dev:/", - ReleaseChannel::Preview => "zed-preview:/", - ReleaseChannel::Stable => "zed:/", + ReleaseChannel::Dev => "zed-dev://", + ReleaseChannel::Preview => "zed-preview://", + ReleaseChannel::Stable => "zed://", } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a1249173090a8af506d16efe1b8c25657b358e34..1644f86b61c25a0ebede73a133e4f805585f659f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,6 +47,7 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, + fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4161,6 +4162,7 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { + dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4193,6 +4195,7 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } + dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4218,13 +4221,17 @@ async fn join_channel_internal( } } + dbg!("past here"); + let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) }) .await?; - room.update(cx, |room, cx| room.next_room_update()).await; + room.update(cx, |room, _| room.next_room_update()).await; + + dbg!("wow"); let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { @@ -4237,7 +4244,16 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - + use std::io::Write; + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "no jokes" + ) + .unwrap(); anyhow::Ok(false) } @@ -4257,6 +4273,7 @@ pub fn join_channel( &mut cx, ) .await; + dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 52aaf639ea4c875bfcecf9aea3ffc6fb8bfe2dc2..f25a1e14be0bfb6e790b7179a2ca5e0c96bd6cf3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -66,6 +66,15 @@ use crate::open_url::{OpenListener, OpenRequest}; mod open_url; fn main() { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "HELLO" + ) + .unwrap(); let http = http::client(); init_paths(); init_logger(); @@ -270,6 +279,7 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { + dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 3e4902b9785cd08d4e38bbc14c6287936106bf8f..8cad217a8c952358d9aa613e6cdc2f934fd361de 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -3,6 +3,8 @@ use cli::{ipc::IpcSender, CliRequest, CliResponse}; use futures::channel::mpsc; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::ffi::OsStr; +use std::fs::OpenOptions; +use std::io::Write; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; @@ -41,6 +43,16 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "{:?}", + &urls, + ) + .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) @@ -79,6 +91,7 @@ impl OpenListener { } } } + log::error!("invalid zed url: {}", request_path); None } From 6de69de868fcee8214edca76b8a09d2210885646 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 6 Oct 2023 16:04:45 -0700 Subject: [PATCH 09/18] Remove change to linker args --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c70a1ffda714674253cc533e9e7c1d1..9da6b3be080072d89d16a199e2d60d527eeacd07 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] From 66120fb97a3dca9292a3c978152246f675198adb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 22:24:41 -0600 Subject: [PATCH 10/18] Try universal link entitlement too --- crates/zed/resources/zed.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index dca0ff676633b9bac7b69114c416536f99262599..9f5414243de13b87e419849ab63c68eb45ab1ce0 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.associated-domains + applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit From 34b75379485f82f531cba5325997a5e42600a379 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 23:15:37 -0600 Subject: [PATCH 11/18] Add universal links support to mac platform --- crates/gpui/src/platform/mac/platform.rs | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 656f2e44750641d752fce2ff4f639cce59601c25..47ad72eeb233f7813af1067fccb616ca002d72c7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -140,6 +140,10 @@ unsafe fn build_classes() { sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); + decl.add_method( + sel!(application:continueUserActivity:restorationHandler:), + continue_user_activity as extern "C" fn(&mut Object, Sel, id, id, id), + ); decl.register() } } @@ -1009,6 +1013,28 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } } +extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { + dbg!("yay!"); + let url = unsafe { + let url: id = msg_send!(user_activity, webpageURL); + if url == nil { + log::error!("got unexpected user activity"); + None + } else { + Some( + CStr::from_ptr(url.absoluteString().UTF8String()) + .to_string_lossy() + .to_string(), + ) + } + }; + dbg!(&url); + let platform = unsafe { get_foreground_platform(this) }; + if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { + callback(url.into_iter().collect()); + } +} + extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_foreground_platform(this); From 5dbda7023517b60720d250e4109c8bb08f7c6da7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 08:59:25 -0600 Subject: [PATCH 12/18] Fix ./script/bundle to allow passing key --- crates/client/src/client.rs | 1 - crates/gpui/src/platform/mac/platform.rs | 2 -- crates/workspace/src/workspace.rs | 7 ------- crates/zed/src/main.rs | 1 - script/bundle | 10 ++++++++-- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62cd60c55fd339965232448d0a306cc3dd457ff4..22e8bc06d4e430ae922d44ca4a3dc87948ebf6cd 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -799,7 +799,6 @@ impl Client { } } status = status_rx.next().fuse() => { - dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 47ad72eeb233f7813af1067fccb616ca002d72c7..24ad7747590d9c218ec9b86e99e248f3ff6d3cb7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1014,7 +1014,6 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { - dbg!("yay!"); let url = unsafe { let url: id = msg_send!(user_activity, webpageURL); if url == nil { @@ -1028,7 +1027,6 @@ extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_acti ) } }; - dbg!(&url); let platform = unsafe { get_foreground_platform(this) }; if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { callback(url.into_iter().collect()); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1644f86b61c25a0ebede73a133e4f805585f659f..7773abc19cc379402cd875acd12bc7e6f79098db 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,7 +4162,6 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4195,7 +4194,6 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } - dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4221,8 +4219,6 @@ async fn join_channel_internal( } } - dbg!("past here"); - let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) @@ -4231,8 +4227,6 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; - dbg!("wow"); - let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4273,7 +4267,6 @@ pub fn join_channel( &mut cx, ) .await; - dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f25a1e14be0bfb6e790b7179a2ca5e0c96bd6cf3..004e5769d57666d1df0fb8e972aa445ddecf7980 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -279,7 +279,6 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { - dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/script/bundle b/script/bundle index 94741d290fc95839362de6c256b41431f216d7e3..a1d0b305c8fc3b4732c3dac7a4722f3ae3838ae9 100755 --- a/script/bundle +++ b/script/bundle @@ -128,7 +128,11 @@ fi echo "Copying WebRTC.framework into the frameworks folder" mkdir "${app_path}/Contents/Frameworks" -cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +if [ "$local_arch" = false ]; then + cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +else + cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +fi if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" @@ -144,7 +148,9 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR else echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD" echo "Performing an ad-hoc signature, but this bundle should not be distributed" - codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v + echo "If you see 'The application cannot be opened for an unexpected reason,' you likely don't have the necessary entitlements to run the application in your signing keychain" + echo "You will need to download a new signing key from developer.apple.com, add it to keychain, and export MACOS_SIGNING_KEY=" + codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v fi if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then From 8f4d81903c6b0322c7026c04b0df2830fbc02f10 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:28:49 -0600 Subject: [PATCH 13/18] Add "Copy Link" to channel right click menu --- crates/channel/src/channel_store.rs | 21 +++++++++++++++++++++ crates/collab_ui/src/collab_panel.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index bd72c92c7db768c558f7bc1b39a371f01f5dfd6c..cd2153ad5c56658a1d80eb6543200069ef63314c 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -4,6 +4,7 @@ use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat}; use anyhow::{anyhow, Result}; use client::{Client, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; +use db::RELEASE_CHANNEL; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use rpc::{ @@ -47,6 +48,26 @@ pub struct Channel { pub unseen_message_id: Option, } +impl Channel { + pub fn link(&self) -> String { + RELEASE_CHANNEL.link_prefix().to_owned() + + "channel/" + + &self.slug() + + "-" + + &self.id.to_string() + } + + pub fn slug(&self) -> String { + let slug: String = self + .name + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '-' }) + .collect(); + + slug.trim_matches(|c| c == '-').to_string() + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] pub struct ChannelPath(Arc<[ChannelId]>); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 3d66e8450ae0435bdc8d69908b43402992575458..9ec05e07c81fe0b48e19cf567f4ff137e62d527c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -34,8 +34,8 @@ use gpui::{ }, impl_actions, platform::{CursorStyle, MouseButton, PromptLevel}, - serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, FontCache, ModelHandle, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + serde_json, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, FontCache, + ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -100,6 +100,11 @@ pub struct JoinChannelChat { pub channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CopyChannelLink { + pub channel_id: u64, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct StartMoveChannelFor { channel_id: ChannelId, @@ -157,6 +162,7 @@ impl_actions!( OpenChannelNotes, JoinChannelCall, JoinChannelChat, + CopyChannelLink, LinkChannel, StartMoveChannelFor, StartLinkChannelFor, @@ -205,6 +211,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(CollabPanel::expand_selected_channel); cx.add_action(CollabPanel::open_channel_notes); cx.add_action(CollabPanel::join_channel_chat); + cx.add_action(CollabPanel::copy_channel_link); cx.add_action( |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext| { @@ -2571,6 +2578,13 @@ impl CollabPanel { }, )); + items.push(ContextMenuItem::action( + "Copy Channel Link", + CopyChannelLink { + channel_id: path.channel_id(), + }, + )); + if self.channel_store.read(cx).is_user_admin(path.channel_id()) { let parent_id = path.parent_id(); @@ -3219,6 +3233,15 @@ impl CollabPanel { }); } } + + fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext) { + let channel_store = self.channel_store.read(cx); + let Some(channel) = channel_store.channel_for_id(action.channel_id) else { + return; + }; + let item = ClipboardItem::new(channel.link()); + cx.write_to_clipboard(item) + } } fn render_tree_branch( From 6084486dcdb98685cdc589cff385c64a5a63cb7e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:34:18 -0600 Subject: [PATCH 14/18] Code quality --- crates/client/src/client.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 22e8bc06d4e430ae922d44ca4a3dc87948ebf6cd..5767ac54b7893f7425dfd56202b7512d17314f0f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,7 @@ impl Client { } } } - status = status_rx.next().fuse() => { + _ = status_rx.next().fuse() => { return Err(anyhow!("authentication canceled")); } } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9ec05e07c81fe0b48e19cf567f4ff137e62d527c..8a8fde88eeee92985a13b37291b0b428542ebb01 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1976,21 +1976,18 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new( - channel.name.clone().to_owned() + channel_id.to_string().as_str(), - style.text.clone(), - ) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new(channel.name.clone(), style.text.clone()) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = From abfb4490d590f33ffefce2d945b540b60cad4d99 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:05:26 -0600 Subject: [PATCH 15/18] Focus the currently active project if there is one (also consider your own projects in "most_active_projects") --- crates/call/src/room.rs | 9 ++++++++- crates/workspace/src/workspace.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 5419e00f02e093419862d0c7cbaf4dab70c7b0f0..b7aeee90e20300c916db50a12ccd5dcc6d06b334 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -605,7 +605,7 @@ impl Room { } /// Returns the most 'active' projects, defined as most people in the project - pub fn most_active_project(&self) -> Option<(u64, u64)> { + pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); @@ -622,6 +622,13 @@ impl Room { } } + if let Some(user) = self.user_store.read(cx).current_user() { + for project in &self.local_participant.projects { + *projects.entry(project.id).or_insert(0) += 1; + hosts.insert(project.id, user.id); + } + } + let mut pairs: Vec<(u64, usize)> = projects.into_iter().collect(); pairs.sort_by_key(|(_, count)| *count as i32); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7773abc19cc379402cd875acd12bc7e6f79098db..e690757af71748d66d688510c5fd515c9d8a1343 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,16 +4162,37 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - let should_prompt = active_call.read_with(cx, |active_call, cx| { + let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { - return false; + return (false, None); }; - room.is_sharing_project() + let already_in_channel = room.channel_id() == Some(channel_id); + let should_prompt = room.is_sharing_project() && room.remote_participants().len() > 0 - && room.channel_id() != Some(channel_id) + && !already_in_channel; + let open_room = if already_in_channel { + active_call.room().cloned() + } else { + None + }; + (should_prompt, open_room) }); + if let Some(room) = open_room { + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project(cx) { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + } + return anyhow::Ok(true); + } + if should_prompt { if let Some(workspace) = requesting_window { if let Some(window) = workspace.update(cx, |cx| cx.window()) { @@ -4228,7 +4249,7 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; let task = room.update(cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { + if let Some((project, host)) = room.most_active_project(cx) { return Some(join_remote_project(project, host, app_state.clone(), cx)); } From 162cb19cff1efaaa127dd7619a5351facc545c1c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:59:18 -0600 Subject: [PATCH 16/18] Only allow one release channel in a call --- .../20221109000000_test_schema.sql | 1 + ...009181554_add_release_channel_to_rooms.sql | 1 + crates/collab/src/db/queries/rooms.rs | 32 +++++-- crates/collab/src/db/tables/room.rs | 1 + crates/collab/src/db/tests.rs | 2 + crates/collab/src/db/tests/channel_tests.rs | 20 +++- crates/collab/src/db/tests/db_tests.rs | 92 ++++++++++++++++++- crates/collab/src/rpc.rs | 22 ++++- 8 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 2d963ff15fa4717aa7faee092356f4b06d8a5814..a9b8d9709defb861c598239abbb3aa62b4ff71b7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,6 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, + "release_channel" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql new file mode 100644 index 0000000000000000000000000000000000000000..95d3c400fcf6fc309fe81a690cf2ed3a16f5017a --- /dev/null +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -0,0 +1 @@ +ALTER TABLE rooms ADD COLUMN release_channel TEXT; diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index b103ae1c737cfdd977418d528585c6fdd9ebb4b7..6589f23791d792d4144441d92cd25d08d088f231 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -107,10 +107,12 @@ impl Database { user_id: UserId, connection: ConnectionId, live_kit_room: &str, + release_channel: &str, ) -> Result { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), + release_channel: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -270,20 +272,31 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, + collab_release_channel: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelId { + enum QueryChannelIdAndReleaseChannel { ChannelId, + ReleaseChannel, + } + + let (channel_id, release_channel): (Option, Option) = + room::Entity::find() + .select_only() + .column(room::Column::ChannelId) + .column(room::Column::ReleaseChannel) + .filter(room::Column::Id.eq(room_id)) + .into_values::<_, QueryChannelIdAndReleaseChannel>() + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if let Some(release_channel) = release_channel { + if &release_channel != collab_release_channel { + Err(anyhow!("must join using the {} release", release_channel))?; + } } - let channel_id: Option = room::Entity::find() - .select_only() - .column(room::Column::ChannelId) - .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelId>() - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("no such room"))?; #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryParticipantIndices { @@ -300,6 +313,7 @@ impl Database { .into_values::<_, QueryParticipantIndices>() .all(&*tx) .await?; + let mut participant_index = 0; while existing_participant_indices.contains(&participant_index) { participant_index += 1; diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index f72f7000a783570d2cac4f6aebc3cf68846ab3a3..7f31edcdbd96bf453271895b63636625f7d566db 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,6 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, + pub release_channel: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 75584ff90b68cf4fea0b4151a835c77e970daae5..6a91fd6ffe145c1a31f9b5264029a04ddb1ef1de 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -12,6 +12,8 @@ use sea_orm::ConnectionTrait; use sqlx::migrate::MigrateDatabase; use std::sync::Arc; +const TEST_RELEASE_CHANNEL: &'static str = "test"; + pub struct TestDb { pub db: Option>, pub connection: Option, diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 429852d12870a232da68d165d75de68d3a7b9be0..2631e0d19184969450de53c001a9f4595e6252dc 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -5,7 +5,11 @@ use rpc::{ }; use crate::{ - db::{queries::channels::ChannelGraph, tests::graph, ChannelId, Database, NewUserParams}, + db::{ + queries::channels::ChannelGraph, + tests::{graph, TEST_RELEASE_CHANNEL}, + ChannelId, Database, NewUserParams, + }, test_both_dbs, }; use std::sync::Arc; @@ -206,7 +210,12 @@ async fn test_joining_channels(db: &Arc) { // can join a room with membership to its channel let joined_room = db - .join_room(room_1, user_1, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_1, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL, + ) .await .unwrap(); assert_eq!(joined_room.room.participants.len(), 1); @@ -214,7 +223,12 @@ async fn test_joining_channels(db: &Arc) { drop(joined_room); // cannot join a room without membership to its channel assert!(db - .join_room(room_1, user_2, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_2, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL + ) .await .is_err()); } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 9a617166fead82ca5b538b84ec268329f1f8de22..1520e081c07ead1afc376f84d2e12918fef40db2 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -479,7 +479,7 @@ async fn test_project_count(db: &Arc) { .unwrap(); let room_id = RoomId::from_proto( - db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "") + db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev") .await .unwrap() .id, @@ -493,9 +493,14 @@ async fn test_project_count(db: &Arc) { ) .await .unwrap(); - db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 }) - .await - .unwrap(); + db.join_room( + room_id, + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "dev", + ) + .await + .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0); db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[]) @@ -575,6 +580,85 @@ async fn test_fuzzy_search_users() { } } +test_both_dbs!( + test_non_matching_release_channels, + test_non_matching_release_channels_postgres, + test_non_matching_release_channels_sqlite +); + +async fn test_non_matching_release_channels(db: &Arc) { + let owner_id = db.create_server("test").await.unwrap().0 as u32; + + let user1 = db + .create_user( + &format!("admin@example.com"), + true, + NewUserParams { + github_login: "admin".into(), + github_user_id: 0, + invite_count: 0, + }, + ) + .await + .unwrap(); + let user2 = db + .create_user( + &format!("user@example.com"), + false, + NewUserParams { + github_login: "user".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap(); + + let room = db + .create_room( + user1.user_id, + ConnectionId { owner_id, id: 0 }, + "", + "stable", + ) + .await + .unwrap(); + + db.call( + RoomId::from_proto(room.id), + user1.user_id, + ConnectionId { owner_id, id: 0 }, + user2.user_id, + None, + ) + .await + .unwrap(); + + // User attempts to join from preview + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "preview", + ) + .await; + + assert!(result.is_err()); + + // User switches to stable + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "stable", + ) + .await; + + assert!(result.is_ok()) +} + fn build_background_executor() -> Arc { Deterministic::new(0).build_background() } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 6171803341b2e3bb47d890a0882f786a0b28aa6a..70052468d45e967f63ad978d7240eda0f24af777 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -63,6 +63,7 @@ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; use tracing::{info_span, instrument, Instrument}; +use util::channel::RELEASE_CHANNEL_NAME; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -957,7 +958,12 @@ async fn create_room( let room = session .db() .await - .create_room(session.user_id, session.connection_id, &live_kit_room) + .create_room( + session.user_id, + session.connection_id, + &live_kit_room, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; response.send(proto::CreateRoomResponse { @@ -979,7 +985,12 @@ async fn join_room( let room = session .db() .await - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; room_updated(&room.room, &session.peer); room.into_inner() @@ -2616,7 +2627,12 @@ async fn join_channel( let room_id = db.room_id_for_channel(channel_id).await?; let joined_room = db - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| { From 8922437fcd0ae13ab6e3ba23cdac071ff20e7f2a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 18:13:52 -0600 Subject: [PATCH 18/18] code review --- crates/call/src/room.rs | 2 +- crates/workspace/src/workspace.rs | 14 ++------------ crates/zed/Cargo.toml | 2 +- crates/zed/resources/zed.entitlements | 12 ++++++++---- crates/zed/src/main.rs | 13 ++----------- crates/zed/src/{open_url.rs => open_listener.rs} | 10 ---------- 6 files changed, 14 insertions(+), 39 deletions(-) rename crates/zed/src/{open_url.rs => open_listener.rs} (92%) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index b7aeee90e20300c916db50a12ccd5dcc6d06b334..32b8232b4f76f73eb35d5f45d7d5a902f2d50a18 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -878,7 +878,7 @@ impl Room { Ok(()) } - pub fn next_room_update(&mut self) -> impl Future { + pub fn room_update_completed(&mut self) -> impl Future { let mut done_rx = self.room_update_completed_rx.clone(); async move { while let Some(result) = done_rx.next().await { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e690757af71748d66d688510c5fd515c9d8a1343..c021384d91067fd52e649211760eb732079e3b8c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,7 +47,6 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, - fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4246,7 +4245,8 @@ async fn join_channel_internal( }) .await?; - room.update(cx, |room, _| room.next_room_update()).await; + room.update(cx, |room, _| room.room_update_completed()) + .await; let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project(cx) { @@ -4259,16 +4259,6 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - use std::io::Write; - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "no jokes" - ) - .unwrap(); anyhow::Ok(false) } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3c93462d4b9182fb362d623455d52c9212d8cc9d..7eb14559be8ab64e44eabf55eaf8837338473d06 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle] +[package.metadata.bundle-dev] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index 9f5414243de13b87e419849ab63c68eb45ab1ce0..f40a8a253ad082bc08e48ed8684b2d553d5065e8 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,8 +2,6 @@ - com.apple.developer.associated-domains - applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit @@ -12,8 +10,14 @@ com.apple.security.device.camera - com.apple.security.keychain-access-groups - MQ55VZLNZQ.dev.zed.Shared + com.apple.security.personal-information.addressbook + + com.apple.security.personal-information.calendars + + com.apple.security.personal-information.location + + com.apple.security.personal-information.photos-library + diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 004e5769d57666d1df0fb8e972aa445ddecf7980..a75caa54f6eb3a8240da736cb376e7e653da9158 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -61,20 +61,11 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; -use crate::open_url::{OpenListener, OpenRequest}; +use crate::open_listener::{OpenListener, OpenRequest}; -mod open_url; +mod open_listener; fn main() { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "HELLO" - ) - .unwrap(); let http = http::client(); init_paths(); init_logger(); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_listener.rs similarity index 92% rename from crates/zed/src/open_url.rs rename to crates/zed/src/open_listener.rs index 8cad217a8c952358d9aa613e6cdc2f934fd361de..e3c08ff2c8e6aebdcf96af4b18d7751faf0ffc44 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_listener.rs @@ -43,16 +43,6 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "{:?}", - &urls, - ) - .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://"))