Fix link opening (#44910)
- **Fix editor::OpenUrl on zed links** - **Fix cmd-clicking links too** Closes #44293 Closes #43833 Release Notes: - The `editor::OpenUrl` action now works for links to https://zed.dev - Clicking on a link to a Zed channel or channel-note within the editor no-longer redirects you via the web. --------- Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1730,23 +1730,59 @@ impl ProtoClient for Client {
|
||||
/// prefix for the zed:// url scheme
|
||||
pub const ZED_URL_SCHEME: &str = "zed";
|
||||
|
||||
/// A parsed Zed link that can be handled internally by the application.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ZedLink {
|
||||
/// Join a channel: `zed.dev/channel/channel-name-123` or `zed://channel/channel-name-123`
|
||||
Channel { channel_id: u64 },
|
||||
/// Open channel notes: `zed.dev/channel/channel-name-123/notes` or with heading `notes#heading`
|
||||
ChannelNotes {
|
||||
channel_id: u64,
|
||||
heading: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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: &App) -> Option<&'a str> {
|
||||
/// Returns a [`Some`] containing the parsed link if the link is a recognized Zed link
|
||||
/// that should be handled internally by the application.
|
||||
/// Returns [`None`] for links that should be opened in the browser.
|
||||
pub fn parse_zed_link(link: &str, cx: &App) -> Option<ZedLink> {
|
||||
let server_url = &ClientSettings::get_global(cx).server_url;
|
||||
if let Some(stripped) = link
|
||||
let path = link
|
||||
.strip_prefix(server_url)
|
||||
.and_then(|result| result.strip_prefix('/'))
|
||||
{
|
||||
return Some(stripped);
|
||||
.or_else(|| {
|
||||
link.strip_prefix(ZED_URL_SCHEME)
|
||||
.and_then(|result| result.strip_prefix("://"))
|
||||
})?;
|
||||
|
||||
let mut parts = path.split('/');
|
||||
|
||||
if parts.next() != Some("channel") {
|
||||
return None;
|
||||
}
|
||||
if let Some(stripped) = link
|
||||
.strip_prefix(ZED_URL_SCHEME)
|
||||
.and_then(|result| result.strip_prefix("://"))
|
||||
{
|
||||
return Some(stripped);
|
||||
|
||||
let slug = parts.next()?;
|
||||
let id_str = slug.split('-').next_back()?;
|
||||
let channel_id = id_str.parse::<u64>().ok()?;
|
||||
|
||||
let Some(next) = parts.next() else {
|
||||
return Some(ZedLink::Channel { channel_id });
|
||||
};
|
||||
|
||||
if let Some(heading) = next.strip_prefix("notes#") {
|
||||
return Some(ZedLink::ChannelNotes {
|
||||
channel_id,
|
||||
heading: Some(heading.to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
if next == "notes" {
|
||||
return Some(ZedLink::ChannelNotes {
|
||||
channel_id,
|
||||
heading: None,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -17467,7 +17467,14 @@ impl Editor {
|
||||
// If there is one url or file, open it directly
|
||||
match first_url_or_file {
|
||||
Some(Either::Left(url)) => {
|
||||
cx.update(|_, cx| cx.open_url(&url))?;
|
||||
cx.update(|window, cx| {
|
||||
if parse_zed_link(&url, cx).is_some() {
|
||||
window
|
||||
.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
|
||||
} else {
|
||||
cx.open_url(&url);
|
||||
}
|
||||
})?;
|
||||
Ok(Navigated::Yes)
|
||||
}
|
||||
Some(Either::Right(path)) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::restorable_workspace_locations;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use cli::{CliRequest, CliResponse, ipc::IpcSender};
|
||||
use cli::{IpcHandshake, ipc};
|
||||
use client::parse_zed_link;
|
||||
use client::{ZedLink, parse_zed_link};
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
@@ -112,8 +112,18 @@ impl OpenRequest {
|
||||
});
|
||||
} else if url.starts_with("ssh://") {
|
||||
this.parse_ssh_file_path(&url, cx)?
|
||||
} else if let Some(request_path) = parse_zed_link(&url, cx) {
|
||||
this.parse_request_path(request_path).log_err();
|
||||
} else if let Some(zed_link) = parse_zed_link(&url, cx) {
|
||||
match zed_link {
|
||||
ZedLink::Channel { channel_id } => {
|
||||
this.join_channel = Some(channel_id);
|
||||
}
|
||||
ZedLink::ChannelNotes {
|
||||
channel_id,
|
||||
heading,
|
||||
} => {
|
||||
this.open_channel_notes.push((channel_id, heading));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("unhandled url: {}", url);
|
||||
}
|
||||
@@ -157,31 +167,6 @@ impl OpenRequest {
|
||||
self.parse_file_path(url.path());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_request_path(&mut self, request_path: &str) -> Result<()> {
|
||||
let mut parts = request_path.split('/');
|
||||
if parts.next() == Some("channel")
|
||||
&& let Some(slug) = parts.next()
|
||||
&& let Some(id_str) = slug.split('-').next_back()
|
||||
&& let Ok(channel_id) = id_str.parse::<u64>()
|
||||
{
|
||||
let Some(next) = parts.next() else {
|
||||
self.join_channel = Some(channel_id);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(heading) = next.strip_prefix("notes#") {
|
||||
self.open_channel_notes
|
||||
.push((channel_id, Some(heading.to_string())));
|
||||
return Ok(());
|
||||
}
|
||||
if next == "notes" {
|
||||
self.open_channel_notes.push((channel_id, None));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
anyhow::bail!("invalid zed url: {request_path}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
Reference in New Issue
Block a user