mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 02:17:35 +03:00
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:
parent
64a796d436
commit
0c6105992c
@ -11,6 +11,7 @@ pub struct IpcHandshake {
|
|||||||
pub enum CliRequest {
|
pub enum CliRequest {
|
||||||
Open {
|
Open {
|
||||||
paths: Vec<String>,
|
paths: Vec<String>,
|
||||||
|
urls: Vec<String>,
|
||||||
wait: bool,
|
wait: bool,
|
||||||
open_new_workspace: Option<bool>,
|
open_new_workspace: Option<bool>,
|
||||||
dev_server_token: Option<String>,
|
dev_server_token: Option<String>,
|
||||||
|
@ -5,6 +5,7 @@ use clap::Parser;
|
|||||||
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
env, fs, io,
|
env, fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
@ -37,8 +38,7 @@ struct Args {
|
|||||||
///
|
///
|
||||||
/// Use `path:line:row` syntax to open a file at a specific location.
|
/// Use `path:line:row` syntax to open a file at a specific location.
|
||||||
/// Non-existing paths and directories will ignore `:line:row` suffix.
|
/// Non-existing paths and directories will ignore `:line:row` suffix.
|
||||||
#[arg(value_parser = parse_path_with_position)]
|
paths_with_position: Vec<String>,
|
||||||
paths_with_position: Vec<PathLikeWithPosition<PathBuf>>,
|
|
||||||
/// Print Zed's version and the app path.
|
/// Print Zed's version and the app path.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
version: bool,
|
version: bool,
|
||||||
@ -53,12 +53,30 @@ struct Args {
|
|||||||
dev_server_token: Option<String>,
|
dev_server_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path_with_position(
|
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
|
||||||
argument_str: &str,
|
let path_like = PathLikeWithPosition::parse_str::<Infallible>(argument_str, |_, path_str| {
|
||||||
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
|
|
||||||
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
|
|
||||||
Ok(Path::new(path_str).to_path_buf())
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
@ -91,28 +109,6 @@ fn main() -> Result<()> {
|
|||||||
return Ok(());
|
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) =
|
let (server, server_name) =
|
||||||
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
||||||
let url = format!("zed-cli://{server_name}");
|
let url = format!("zed-cli://{server_name}");
|
||||||
@ -126,6 +122,19 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let exit_status = Arc::new(Mutex::new(None));
|
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 sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
||||||
let exit_status = exit_status.clone();
|
let exit_status = exit_status.clone();
|
||||||
@ -134,6 +143,7 @@ fn main() -> Result<()> {
|
|||||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||||
tx.send(CliRequest::Open {
|
tx.send(CliRequest::Open {
|
||||||
paths,
|
paths,
|
||||||
|
urls,
|
||||||
wait: args.wait,
|
wait: args.wait,
|
||||||
open_new_workspace,
|
open_new_workspace,
|
||||||
dev_server_token: args.dev_server_token,
|
dev_server_token: args.dev_server_token,
|
||||||
|
@ -4,13 +4,13 @@ Type=Application
|
|||||||
Name=$APP_NAME
|
Name=$APP_NAME
|
||||||
GenericName=Text Editor
|
GenericName=Text Editor
|
||||||
Comment=A high-performance, multiplayer code editor.
|
Comment=A high-performance, multiplayer code editor.
|
||||||
TryExec=$APP_CLI
|
TryExec=$APP
|
||||||
StartupNotify=$DO_STARTUP_NOTIFY
|
StartupNotify=$DO_STARTUP_NOTIFY
|
||||||
Exec=$APP_CLI $APP_ARGS
|
Exec=$APP_CLI $APP_ARGS
|
||||||
Icon=$APP_ICON
|
Icon=$APP_ICON
|
||||||
Categories=Utility;TextEditor;Development;IDE;
|
Categories=Utility;TextEditor;Development;IDE;
|
||||||
Keywords=zed;
|
Keywords=zed;
|
||||||
MimeType=text/plain;inode/directory;
|
MimeType=text/plain;inode/directory;x-scheme-handler/zed;
|
||||||
Actions=NewWorkspace;
|
Actions=NewWorkspace;
|
||||||
|
|
||||||
[Desktop Action NewWorkspace]
|
[Desktop Action NewWorkspace]
|
||||||
|
@ -22,7 +22,7 @@ use welcome::{show_welcome_view, FIRST_OPEN};
|
|||||||
use workspace::item::ItemHandle;
|
use workspace::item::ItemHandle;
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
use crate::{init_headless, init_ui};
|
use crate::{handle_open_request, init_headless, init_ui};
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct OpenRequest {
|
pub struct OpenRequest {
|
||||||
@ -223,6 +223,7 @@ pub async fn handle_cli_connection(
|
|||||||
if let Some(request) = requests.next().await {
|
if let Some(request) = requests.next().await {
|
||||||
match request {
|
match request {
|
||||||
CliRequest::Open {
|
CliRequest::Open {
|
||||||
|
urls,
|
||||||
paths,
|
paths,
|
||||||
wait,
|
wait,
|
||||||
open_new_workspace,
|
open_new_workspace,
|
||||||
@ -257,6 +258,27 @@ pub async fn handle_cli_connection(
|
|||||||
return;
|
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
|
if let Err(e) = cx
|
||||||
.update(|cx| init_ui(app_state.clone(), cx))
|
.update(|cx| init_ui(app_state.clone(), cx))
|
||||||
.and_then(|r| r)
|
.and_then(|r| r)
|
||||||
|
Loading…
Reference in New Issue
Block a user