diff --git a/Cargo.lock b/Cargo.lock index 47cf6cfed628ececc6f2b40b185920e9c264b587..aa6eb75f2c55f4b297ab775b416b38fc975d759f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2390,7 +2390,6 @@ dependencies = [ "picker", "postage", "project", - "release_channel", "serde", "serde_json", "settings", diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index dd7751c70ae598c70c6e431cb9c48bd9c917f647..0afd7e41b94a7b26e05794e362065c3429687221 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -3,7 +3,7 @@ mod channel_index; use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage}; use anyhow::{anyhow, Result}; use channel_index::ChannelIndex; -use client::{ChannelId, Client, Subscription, User, UserId, UserStore}; +use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{ @@ -11,11 +11,11 @@ use gpui::{ Task, WeakModel, }; use language::Capability; -use release_channel::RELEASE_CHANNEL; use rpc::{ proto::{self, ChannelRole, ChannelVisibility}, TypedEnvelope, }; +use settings::Settings; use std::{mem, sync::Arc, time::Duration}; use util::{async_maybe, maybe, ResultExt}; @@ -93,16 +93,17 @@ pub struct ChannelState { } impl Channel { - pub fn link(&self) -> String { - RELEASE_CHANNEL.link_prefix().to_owned() - + "channel/" - + &Self::slug(&self.name) - + "-" - + &self.id.to_string() + pub fn link(&self, cx: &AppContext) -> String { + format!( + "{}/channel/{}-{}", + ClientSettings::get_global(cx).server_url, + Self::slug(&self.name), + self.id + ) } - pub fn notes_link(&self, heading: Option) -> String { - self.link() + pub fn notes_link(&self, heading: Option, cx: &AppContext) -> String { + self.link(cx) + "/notes" + &heading .map(|h| format!("#{}", Self::slug(&h))) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0f60f439d0b7ff213dbeede90e0cc628fd08c17e..754a47baa49537b4c484382555291e90670f9edf 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1437,21 +1437,29 @@ async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { .await } -const WORKTREE_URL_PREFIX: &str = "zed://worktrees/"; - -pub fn encode_worktree_url(id: u64, access_token: &str) -> String { - format!("{}{}/{}", WORKTREE_URL_PREFIX, id, access_token) -} - -pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> { - let path = url.trim().strip_prefix(WORKTREE_URL_PREFIX)?; - let mut parts = path.split('/'); - let id = parts.next()?.parse::().ok()?; - let access_token = parts.next()?; - if access_token.is_empty() { - return None; +/// prefix for the zed:// url scheme +pub static ZED_URL_SCHEME: &str = "zed"; + +/// Parses the given link into a Zed link. +/// +/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link. +/// Returns [`None`] otherwise. +pub fn parse_zed_link<'a>(link: &'a str, cx: &AppContext) -> Option<&'a str> { + let server_url = &ClientSettings::get_global(cx).server_url; + if let Some(stripped) = link + .strip_prefix(server_url) + .and_then(|result| result.strip_prefix('/')) + { + return Some(stripped); } - Some((id, access_token.to_string())) + if let Some(stripped) = link + .strip_prefix(ZED_URL_SCHEME) + .and_then(|result| result.strip_prefix("://")) + { + return Some(stripped); + } + + None } #[cfg(test)] @@ -1629,17 +1637,6 @@ mod tests { assert_eq!(*dropped_auth_count.lock(), 1); } - #[test] - fn test_encode_and_decode_worktree_url() { - let url = encode_worktree_url(5, "deadbeef"); - assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string()))); - assert_eq!( - decode_worktree_url(&format!("\n {}\t", url)), - Some((5, "deadbeef".to_string())) - ); - assert_eq!(decode_worktree_url("not://the-right-format"), None); - } - #[gpui::test] async fn test_subscribing_to_entity(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 1cc48591c77738e859090a1c1f02d9df28acdeb7..ac0793715fad450f0be95425920d99d2a0ebe371 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -265,7 +265,7 @@ impl ChannelView { return; }; - let link = channel.notes_link(closest_heading.map(|heading| heading.text)); + let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx); cx.write_to_clipboard(ClipboardItem::new(link)); self.workspace .update(cx, |workspace, cx| { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 1df8e7e73bde433e1618cd2a21aedd03eb84207d..500c7affcf942668ff5ea7d701aca1244268bad8 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2023,7 +2023,7 @@ impl CollabPanel { let Some(channel) = channel_store.channel_for_id(channel_id) else { return; }; - let item = ClipboardItem::new(channel.link()); + let item = ClipboardItem::new(channel.link(cx)); cx.write_to_clipboard(item) } @@ -2206,7 +2206,7 @@ impl CollabPanel { let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; - channel_link = Some(channel.link()); + channel_link = Some(channel.link(cx)); (channel_icon, channel_tooltip_text) = match channel.visibility { proto::ChannelVisibility::Public => { (Some("icons/public.svg"), Some("Copy public channel link.")) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 125e8c64f38ac0da899c418984303e304814c8ab..4f9b18198b032460b434c7fc86d4686b9552f712 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -197,7 +197,7 @@ impl Render for ChannelModal { .read(cx) .channel_for_id(channel_id) { - let item = ClipboardItem::new(channel.link()); + let item = ClipboardItem::new(channel.link(cx)); cx.write_to_clipboard(item); } })), diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 4d268545e8f28af0127d4d5c3408915230274b2c..565939e4a358c32eed611214302013ac0a6eb15b 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -18,7 +18,6 @@ gpui.workspace = true picker.workspace = true postage.workspace = true project.workspace = true -release_channel.workspace = true serde.workspace = true settings.workspace = true theme.workspace = true diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 197ad90586fd53c09a63639a9f1aeef4785d78f1..0cd7f581def160764f9408f05dca7db2cb3592ba 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use client::telemetry::Telemetry; +use client::{parse_zed_link, telemetry::Telemetry}; use collections::HashMap; use command_palette_hooks::{ CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor, @@ -17,7 +17,6 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use postage::{sink::Sink, stream::Stream}; -use release_channel::parse_zed_link; use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ModalView, Workspace}; @@ -26,6 +25,7 @@ use zed_actions::OpenZedUrl; actions!(command_palette, [Toggle]); pub fn init(cx: &mut AppContext) { + client::init_settings(cx); cx.set_global(HitCounts::default()); cx.set_global(CommandPaletteFilter::default()); cx.observe_new_views(CommandPalette::register).detach(); @@ -192,7 +192,7 @@ impl CommandPaletteDelegate { None }; - if parse_zed_link(&query).is_some() { + if parse_zed_link(&query, cx).is_some() { intercept_result = Some(CommandInterceptResult { action: OpenZedUrl { url: query.clone() }.boxed_clone(), string: query.clone(), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f30e5264f129762f84303a18b32e4b42625fbd4b..9373ad66e85a760695ed1501b1d6ececd85fa467 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -566,6 +566,14 @@ impl AppContext { self.platform.open_url(url); } + /// register_url_scheme requests that the given scheme (e.g. `zed` for `zed://` urls) + /// is opened by the current app. + /// On some platforms (e.g. macOS) you may be able to register URL schemes as part of app + /// distribution, but this method exists to let you register schemes at runtime. + pub fn register_url_scheme(&self, scheme: &str) -> Task> { + self.platform.register_url_scheme(scheme) + } + /// Returns the full pathname of the current app bundle. /// If the app is not being run from a bundle, returns an error. pub fn app_path(&self) -> Result { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 8222b88fe1ea65b9b3e807dd45a0a185f5327c9d..1e1b66fffeb33090a28b9d32dcd37db12d7381e2 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -101,6 +101,8 @@ pub(crate) trait Platform: 'static { fn open_url(&self, url: &str); fn on_open_urls(&self, callback: Box)>); + fn register_url_scheme(&self, url: &str) -> Task>; + fn prompt_for_paths( &self, options: PathPromptOptions, diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 414c1507dc95139029a446da80162d9ad48b7e76..2a7b8bfb2ece400d8399afc53068934321ff8944 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -441,6 +441,10 @@ impl Platform for LinuxPlatform { fn window_appearance(&self) -> crate::WindowAppearance { crate::WindowAppearance::Light } + + fn register_url_scheme(&self, _: &str) -> Task> { + Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) + } } #[cfg(test)] diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 0a60fd51723b074a44f0487ee557ad9326fe12a4..d293f4cf4023aa3375989305f60a6ba8e33b6231 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -525,6 +525,49 @@ impl Platform for MacPlatform { } } + fn register_url_scheme(&self, scheme: &str) -> Task> { + // API only available post Monterey + // https://developer.apple.com/documentation/appkit/nsworkspace/3753004-setdefaultapplicationaturl + let (done_tx, done_rx) = oneshot::channel(); + if self.os_version().ok() < Some(SemanticVersion::new(12, 0, 0)) { + return Task::ready(Err(anyhow!( + "macOS 12.0 or later is required to register URL schemes" + ))); + } + + let bundle_id = unsafe { + let bundle: id = msg_send![class!(NSBundle), mainBundle]; + let bundle_id: id = msg_send![bundle, bundleIdentifier]; + if bundle_id == nil { + return Task::ready(Err(anyhow!("Can only register URL scheme in bundled apps"))); + } + bundle_id + }; + + unsafe { + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + let scheme: id = ns_string(scheme); + let app: id = msg_send![workspace, URLForApplicationWithBundleIdentifier: bundle_id]; + let done_tx = Cell::new(Some(done_tx)); + let block = ConcreteBlock::new(move |error: id| { + let result = if error == nil { + Ok(()) + } else { + let msg: id = msg_send![error, localizedDescription]; + Err(anyhow!("Failed to register: {:?}", msg)) + }; + + if let Some(done_tx) = done_tx.take() { + let _ = done_tx.send(result); + } + }); + let _: () = msg_send![workspace, setDefaultApplicationAtURL: app toOpenURLsWithScheme: scheme completionHandler: block]; + } + + self.background_executor() + .spawn(async { crate::Flatten::flatten(done_rx.await.map_err(|e| anyhow!(e))) }) + } + fn on_open_urls(&self, callback: Box)>) { self.0.lock().open_urls = Some(callback); } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index d97a4fc5abe62499049b333b085c13d577f8699d..5df416f9ab080985c88229f57cf40c19549a5636 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -298,4 +298,8 @@ impl Platform for TestPlatform { fn double_click_interval(&self) -> std::time::Duration { Duration::from_millis(500) } + + fn register_url_scheme(&self, _: &str) -> Task> { + unimplemented!() + } } diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index e7751579c9d5e358a772fb8f4363877af9e950bf..be3d789813e8319029a9bcacf19619c5ac42f501 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -314,4 +314,8 @@ impl Platform for WindowsPlatform { fn delete_credentials(&self, url: &str) -> Task> { Task::Ready(Some(Err(anyhow!("not implemented yet.")))) } + + fn register_url_scheme(&self, _: &str) -> Task> { + Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) + } } diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs index 61c5aa2fb90f2198b10a4860c2a1e494e747f3f8..506de309ef892ef66cb314f7cd3401908de7d32d 100644 --- a/crates/install_cli/src/install_cli.rs +++ b/crates/install_cli/src/install_cli.rs @@ -1,18 +1,18 @@ use anyhow::{anyhow, Result}; use gpui::{actions, AsyncAppContext}; -use std::path::Path; +use std::path::{Path, PathBuf}; use util::ResultExt; -actions!(cli, [Install]); +actions!(cli, [Install, RegisterZedScheme]); -pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { +pub async fn install_cli(cx: &AsyncAppContext) -> Result { let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??; let link_path = Path::new("/usr/local/bin/zed"); let bin_dir_path = link_path.parent().unwrap(); // Don't re-create symlink if it points to the same CLI binary. if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) { - return Ok(()); + return Ok(link_path.into()); } // If the symlink is not there or is outdated, first try replacing it @@ -26,7 +26,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { .log_err() .is_some() { - return Ok(()); + return Ok(link_path.into()); } } @@ -51,7 +51,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { .await? .status; if status.success() { - Ok(()) + Ok(link_path.into()) } else { Err(anyhow!("error running osascript")) } diff --git a/crates/release_channel/src/lib.rs b/crates/release_channel/src/lib.rs index 78b17ba9977acca6e4cef4e7620363792863fccf..864df387c008d33d7a843cbdbd7a47c56a413adb 100644 --- a/crates/release_channel/src/lib.rs +++ b/crates/release_channel/src/lib.rs @@ -139,26 +139,6 @@ impl ReleaseChannel { } } - /// Returns the URL scheme for this [`ReleaseChannel`]. - pub fn url_scheme(&self) -> &'static str { - match self { - ReleaseChannel::Dev => "zed-dev://", - ReleaseChannel::Nightly => "zed-nightly://", - ReleaseChannel::Preview => "zed-preview://", - ReleaseChannel::Stable => "zed://", - } - } - - /// Returns the link prefix for this [`ReleaseChannel`]. - pub fn link_prefix(&self) -> &'static str { - match self { - ReleaseChannel::Dev => "https://zed.dev/dev/", - ReleaseChannel::Nightly => "https://zed.dev/nightly/", - ReleaseChannel::Preview => "https://zed.dev/preview/", - ReleaseChannel::Stable => "https://zed.dev/", - } - } - /// Returns the query parameter for this [`ReleaseChannel`]. pub fn release_query_param(&self) -> Option<&'static str> { match self { @@ -169,24 +149,3 @@ impl ReleaseChannel { } } } - -/// Parses the given link into a Zed link. -/// -/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link. -/// Returns [`None`] otherwise. -pub fn parse_zed_link(link: &str) -> Option<&str> { - for release in [ - ReleaseChannel::Dev, - ReleaseChannel::Nightly, - 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/zed/Cargo.toml b/crates/zed/Cargo.toml index 4ad8549e689ba16532ae627904846ca7b282343b..2ad94a8bae62b3d548b73b03e4e6a7f932a165d2 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -107,7 +107,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"] +osx_url_schemes = ["zed"] [package.metadata.bundle-nightly] icon = ["resources/app-icon-nightly@2x.png", "resources/app-icon-nightly.png"] @@ -115,7 +115,7 @@ identifier = "dev.zed.Zed-Nightly" name = "Zed Nightly" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] -osx_url_schemes = ["zed-nightly"] +osx_url_schemes = ["zed"] [package.metadata.bundle-preview] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] @@ -123,7 +123,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"] +osx_url_schemes = ["zed"] [package.metadata.bundle-stable] icon = ["resources/app-icon@2x.png", "resources/app-icon.png"] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c7356216a1bdc199db5771f1cf277ac72e9f5606..277d4dda03f9d569094994530dc5b8a6e648bec1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Context as _, Result}; use backtrace::Backtrace; use chrono::Utc; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; -use client::{Client, UserStore}; +use client::{parse_zed_link, Client, UserStore}; use collab_ui::channel_view::ChannelView; use db::kvp::KEY_VALUE_STORE; use editor::Editor; @@ -23,7 +23,7 @@ use assets::Assets; use mimalloc::MiMalloc; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; -use release_channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}; +use release_channel::{AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}; use serde::{Deserialize, Serialize}; use settings::{ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, @@ -106,7 +106,7 @@ fn main() { let (listener, mut open_rx) = OpenListener::new(); let listener = Arc::new(listener); let open_listener = listener.clone(); - app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); + app.on_open_urls(move |urls, cx| open_listener.open_urls(&urls, cx)); app.on_reopen(move |cx| { if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade()) { @@ -271,9 +271,9 @@ fn main() { #[cfg(not(target_os = "linux"))] upload_panics_and_crashes(http.clone(), cx); cx.activate(true); - let urls = collect_url_args(); + let urls = collect_url_args(cx); if !urls.is_empty() { - listener.open_urls(&urls) + listener.open_urls(&urls, cx) } } else { upload_panics_and_crashes(http.clone(), cx); @@ -282,7 +282,7 @@ fn main() { if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() && !listener.triggered.load(Ordering::Acquire) { - listener.open_urls(&collect_url_args()) + listener.open_urls(&collect_url_args(cx), cx) } } @@ -921,13 +921,13 @@ 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_url_args() -> Vec { +fn collect_url_args(cx: &AppContext) -> Vec { env::args() .skip(1) .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) { Ok(path) => Some(format!("file://{}", path.to_string_lossy())), Err(error) => { - if let Some(_) = parse_zed_link(&arg) { + if let Some(_) = parse_zed_link(&arg, cx) { Some(arg) } else { log::error!("error parsing path argument: {}", error); diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index 4d3630a846f3cdbb2f14b3c21aea0a1c9d9a4047..41dfe7e4321ea7785f8604d00f60f5ee49155ae1 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use client::parse_zed_link; use collections::HashMap; use editor::scroll::Autoscroll; use editor::Editor; @@ -10,7 +11,6 @@ use futures::{FutureExt, SinkExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Global}; use itertools::Itertools; use language::{Bias, Point}; -use release_channel::parse_zed_link; use std::path::Path; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -66,13 +66,13 @@ impl OpenListener { ) } - pub fn open_urls(&self, urls: &[String]) { + pub fn open_urls(&self, urls: &[String], cx: &AppContext) { self.triggered.store(true, Ordering::Release); 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| parse_zed_link(url)) { + } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url, cx)) { self.handle_zed_url_scheme(request_path) } else { self.handle_file_urls(urls) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 7c47a6b6b5c7f5b973edbd4e4c90997eee4c1bfb..ca282674727669281440f415e887f606425ebd72 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -5,11 +5,12 @@ mod open_listener; pub use app_menus::*; use assistant::AssistantPanel; use breadcrumbs::Breadcrumbs; +use client::ZED_URL_SCHEME; use collections::VecDeque; use editor::{Editor, MultiBuffer}; use gpui::{ - actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View, - ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions, + actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, PromptLevel, + TitlebarOptions, View, ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -38,11 +39,11 @@ use util::{ use uuid::Uuid; use vim::VimModeSetting; use welcome::BaseKeymap; -use workspace::Pane; use workspace::{ create_and_open_local_file, notifications::simple_message_notification::MessageNotification, - open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings, + open_new, AppState, NewFile, NewWindow, Toast, Workspace, WorkspaceSettings, }; +use workspace::{notifications::DetachAndPromptErr, Pane}; use zed_actions::{OpenBrowser, OpenSettings, OpenZedUrl, Quit}; actions!( @@ -232,7 +233,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.toggle_full_screen(); }) .register_action(|_, action: &OpenZedUrl, cx| { - OpenListener::global(cx).open_urls(&[action.url.clone()]) + OpenListener::global(cx).open_urls(&[action.url.clone()], cx) }) .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) .register_action(move |_, _: &IncreaseBufferFontSize, cx| { @@ -243,12 +244,50 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }) .register_action(move |_, _: &ResetBufferFontSize, cx| theme::reset_font_size(cx)) .register_action(|_, _: &install_cli::Install, cx| { - cx.spawn(|_, cx| async move { - install_cli::install_cli(cx.deref()) + cx.spawn(|workspace, mut cx| async move { + let path = install_cli::install_cli(cx.deref()) .await - .context("error creating CLI symlink") + .context("error creating CLI symlink")?; + workspace.update(&mut cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + 0, + format!( + "Installed `zed` to {}. You can launch {} from your terminal.", + path.to_string_lossy(), + ReleaseChannel::global(cx).display_name() + ), + ), + cx, + ) + })?; + register_zed_scheme(&cx).await.log_err(); + Ok(()) + }) + .detach_and_prompt_err("Error installing zed cli", cx, |_, _| None); + }) + .register_action(|_, _: &install_cli::RegisterZedScheme, cx| { + cx.spawn(|workspace, mut cx| async move { + register_zed_scheme(&cx).await?; + workspace.update(&mut cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + 0, + format!( + "zed:// links will now open in {}.", + ReleaseChannel::global(cx).display_name() + ), + ), + cx, + ) + })?; + Ok(()) }) - .detach_and_log_err(cx); + .detach_and_prompt_err( + "Error registering zed:// scheme", + cx, + |_, _| None, + ); }) .register_action(|workspace, _: &OpenLog, cx| { open_log_file(workspace, cx); @@ -2881,3 +2920,8 @@ mod tests { } } } + +async fn register_zed_scheme(cx: &AsyncAppContext) -> anyhow::Result<()> { + cx.update(|cx| cx.register_url_scheme(ZED_URL_SCHEME))? + .await +} diff --git a/script/bundle b/script/bundle index a6414febcb8c55cae553b0ac1858b14f4b2d69bf..d90ed2a014bd445c5f8f40c725112c2bfacc8f0f 100755 --- a/script/bundle +++ b/script/bundle @@ -173,6 +173,7 @@ 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/" + cp -R target/${target_dir}/cli "${app_path}/Contents/MacOS/" fi # Note: The app identifier for our development builds is the same as the app identifier for nightly.