Open URIs from the CLI, support for the zed:// URI scheme on Linux (#14104)

Allows Zed to open custom `zed://` links (redirects from
https://zed.dev/channels) on Linux used XDG MIME types.

This PR also allows the CLI to be able to open Zed (`zed://`) URIs
directly instead of executing the main executable in
`/usr/libexec/zed-editor`.


Release Notes:

- Linux: Allow `zed.dev/channel` (`zed://`) URIs to open on Linux
- CLI: Ability to open URIs from the command line

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Cappy Ishihara 2024-07-17 03:49:15 +07:00 committed by GitHub
parent 64a796d436
commit 0c6105992c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 64 additions and 31 deletions

View File

@ -11,6 +11,7 @@ pub struct IpcHandshake {
pub enum CliRequest {
Open {
paths: Vec<String>,
urls: Vec<String>,
wait: bool,
open_new_workspace: Option<bool>,
dev_server_token: Option<String>,

View File

@ -5,6 +5,7 @@ use clap::Parser;
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
use parking_lot::Mutex;
use std::{
convert::Infallible,
env, fs, io,
path::{Path, PathBuf},
process::ExitStatus,
@ -37,8 +38,7 @@ struct Args {
///
/// Use `path:line:row` syntax to open a file at a specific location.
/// Non-existing paths and directories will ignore `:line:row` suffix.
#[arg(value_parser = parse_path_with_position)]
paths_with_position: Vec<PathLikeWithPosition<PathBuf>>,
paths_with_position: Vec<String>,
/// Print Zed's version and the app path.
#[arg(short, long)]
version: bool,
@ -53,12 +53,30 @@ struct Args {
dev_server_token: Option<String>,
}
fn parse_path_with_position(
argument_str: &str,
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
let path_like = PathLikeWithPosition::parse_str::<Infallible>(argument_str, |_, path_str| {
Ok(Path::new(path_str).to_path_buf())
})
.unwrap();
let curdir = env::current_dir()?;
let canonicalized = path_like.map_path_like(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
if let Some(mut parent) = path.parent() {
if parent == Path::new("") {
parent = &curdir
}
match fs::canonicalize(parent) {
Ok(parent) => Ok(parent.join(path.file_name().unwrap())),
Err(_) => Err(e),
}
} else {
Err(e)
}
}
})?;
Ok(canonicalized.to_string(|path| path.display().to_string()))
}
fn main() -> Result<()> {
@ -91,28 +109,6 @@ fn main() -> Result<()> {
return Ok(());
}
let curdir = env::current_dir()?;
let mut paths = vec![];
for path in args.paths_with_position {
let canonicalized = path.map_path_like(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
if let Some(mut parent) = path.parent() {
if parent == Path::new("") {
parent = &curdir;
}
match fs::canonicalize(parent) {
Ok(parent) => Ok(parent.join(path.file_name().unwrap())),
Err(_) => Err(e),
}
} else {
Err(e)
}
}
})?;
paths.push(canonicalized.to_string(|path| path.display().to_string()))
}
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
let url = format!("zed-cli://{server_name}");
@ -126,6 +122,19 @@ fn main() -> Result<()> {
};
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
for path in args.paths_with_position.iter() {
if path.starts_with("zed://")
|| path.starts_with("http://")
|| path.starts_with("https://")
|| path.starts_with("file://")
{
urls.push(path.to_string());
} else {
paths.push(parse_path_with_position(path)?)
}
}
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
let exit_status = exit_status.clone();
@ -134,6 +143,7 @@ fn main() -> Result<()> {
let (tx, rx) = (handshake.requests, handshake.responses);
tx.send(CliRequest::Open {
paths,
urls,
wait: args.wait,
open_new_workspace,
dev_server_token: args.dev_server_token,

View File

@ -4,13 +4,13 @@ Type=Application
Name=$APP_NAME
GenericName=Text Editor
Comment=A high-performance, multiplayer code editor.
TryExec=$APP_CLI
TryExec=$APP
StartupNotify=$DO_STARTUP_NOTIFY
Exec=$APP_CLI $APP_ARGS
Icon=$APP_ICON
Categories=Utility;TextEditor;Development;IDE;
Keywords=zed;
MimeType=text/plain;inode/directory;
MimeType=text/plain;inode/directory;x-scheme-handler/zed;
Actions=NewWorkspace;
[Desktop Action NewWorkspace]

View File

@ -22,7 +22,7 @@ use welcome::{show_welcome_view, FIRST_OPEN};
use workspace::item::ItemHandle;
use workspace::{AppState, Workspace};
use crate::{init_headless, init_ui};
use crate::{handle_open_request, init_headless, init_ui};
#[derive(Default, Debug)]
pub struct OpenRequest {
@ -223,6 +223,7 @@ pub async fn handle_cli_connection(
if let Some(request) = requests.next().await {
match request {
CliRequest::Open {
urls,
paths,
wait,
open_new_workspace,
@ -257,6 +258,27 @@ pub async fn handle_cli_connection(
return;
}
if !urls.is_empty() {
cx.update(|cx| {
match OpenRequest::parse(urls, cx) {
Ok(open_request) => {
handle_open_request(open_request, app_state.clone(), cx);
responses.send(CliResponse::Exit { status: 0 }).log_err();
}
Err(e) => {
responses
.send(CliResponse::Stderr {
message: format!("{e}"),
})
.log_err();
responses.send(CliResponse::Exit { status: 1 }).log_err();
}
};
})
.log_err();
return;
}
if let Err(e) = cx
.update(|cx| init_ui(app_state.clone(), cx))
.and_then(|r| r)