TEMP

Conrad Irwin created

Change summary

.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(-)

Detailed changes

.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"]

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
 }

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<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(|_| ());
     })
 }
 

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<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());

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)