Detailed changes
@@ -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"]
@@ -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
}
@@ -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<WorkspaceLocation> {
DB.last_workspace().await.log_err().flatten()
}
-pub fn join_channel(
+async fn join_channel_internal(
channel_id: u64,
app_state: Arc<AppState>,
requesting_window: Option<WindowHandle<Workspace>>,
- cx: &mut AppContext,
-) -> Task<Result<()>> {
- 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<ActiveCall>,
+ cx: &mut AsyncAppContext,
+) -> Result<bool> {
+ 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<AppState>,
+ requesting_window: Option<WindowHandle<Workspace>>,
+ cx: &mut AppContext,
+) -> Task<Result<()>> {
+ 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(|_| ());
})
}
@@ -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<PathBuf> {
+fn collect_url_args() -> Vec<String> {
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<String> {
- env::args().skip(1).collect()
-}
-
fn load_embedded_fonts(app: &App) {
let font_paths = Assets.list("fonts");
let embedded_fonts = Mutex::new(Vec::new());
@@ -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)