install_cli.rs

  1use anyhow::{Context as _, Result};
  2use client::ZED_URL_SCHEME;
  3use gpui::{AppContext as _, AsyncApp, Context, PromptLevel, Window, actions};
  4use release_channel::ReleaseChannel;
  5use std::ops::Deref;
  6use std::path::{Path, PathBuf};
  7use util::ResultExt;
  8use workspace::notifications::{DetachAndPromptErr, NotificationId};
  9use workspace::{Toast, Workspace};
 10
 11actions!(
 12    cli,
 13    [
 14        /// Installs the Zed CLI tool to the system PATH.
 15        Install,
 16        /// Registers the zed:// URL scheme handler.
 17        RegisterZedScheme
 18    ]
 19);
 20
 21async fn install_script(cx: &AsyncApp) -> Result<PathBuf> {
 22    let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
 23    let link_path = Path::new("/usr/local/bin/zed");
 24    let bin_dir_path = link_path.parent().unwrap();
 25
 26    // Don't re-create symlink if it points to the same CLI binary.
 27    if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {
 28        return Ok(link_path.into());
 29    }
 30
 31    // If the symlink is not there or is outdated, first try replacing it
 32    // without escalating.
 33    smol::fs::remove_file(link_path).await.log_err();
 34    // todo("windows")
 35    #[cfg(not(windows))]
 36    {
 37        if smol::fs::unix::symlink(&cli_path, link_path)
 38            .await
 39            .log_err()
 40            .is_some()
 41        {
 42            return Ok(link_path.into());
 43        }
 44    }
 45
 46    // The symlink could not be created, so use osascript with admin privileges
 47    // to create it.
 48    let status = smol::process::Command::new("/usr/bin/osascript")
 49        .args([
 50            "-e",
 51            &format!(
 52                "do shell script \" \
 53                    mkdir -p \'{}\' && \
 54                    ln -sf \'{}\' \'{}\' \
 55                \" with administrator privileges",
 56                bin_dir_path.to_string_lossy(),
 57                cli_path.to_string_lossy(),
 58                link_path.to_string_lossy(),
 59            ),
 60        ])
 61        .stdout(smol::process::Stdio::inherit())
 62        .stderr(smol::process::Stdio::inherit())
 63        .output()
 64        .await?
 65        .status;
 66    anyhow::ensure!(status.success(), "error running osascript");
 67    Ok(link_path.into())
 68}
 69
 70pub async fn register_zed_scheme(cx: &AsyncApp) -> anyhow::Result<()> {
 71    cx.update(|cx| cx.register_url_scheme(ZED_URL_SCHEME))?
 72        .await
 73}
 74
 75pub fn install_cli(window: &mut Window, cx: &mut Context<Workspace>) {
 76    const LINUX_PROMPT_DETAIL: &str = "If you installed Zed from our official release add ~/.local/bin to your PATH.\n\nIf you installed Zed from a different source like your package manager, then you may need to create an alias/symlink manually.\n\nDepending on your package manager, the CLI might be named zeditor, zedit, zed-editor or something else.";
 77
 78    cx.spawn_in(window, async move |workspace, cx| {
 79        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 80            let prompt = cx.prompt(
 81                PromptLevel::Warning,
 82                "CLI should already be installed",
 83                Some(LINUX_PROMPT_DETAIL),
 84                &["Ok"],
 85            );
 86            cx.background_spawn(prompt).detach();
 87            return Ok(());
 88        }
 89        let path = install_script(cx.deref())
 90            .await
 91            .context("error creating CLI symlink")?;
 92
 93        workspace.update_in(cx, |workspace, _, cx| {
 94            struct InstalledZedCli;
 95
 96            workspace.show_toast(
 97                Toast::new(
 98                    NotificationId::unique::<InstalledZedCli>(),
 99                    format!(
100                        "Installed `zed` to {}. You can launch {} from your terminal.",
101                        path.to_string_lossy(),
102                        ReleaseChannel::global(cx).display_name()
103                    ),
104                ),
105                cx,
106            )
107        })?;
108        register_zed_scheme(cx).await.log_err();
109        Ok(())
110    })
111    .detach_and_prompt_err("Error installing zed cli", window, cx, |_, _, _| None);
112}