History

install_cli: Show notification instead of failing CLI install (#59575)

Closes #10065 Closes #42293 Release Notes: - Improved error handling and documentation when Zed cannot install the CLI on macOS

Conrad Irwin committed 4 months ago

Commit 39bba3c1 parent 8f438e64 files · +88 −17Browse files

Unified · Split

4 files
  1. Cargo.lock +1 −0
  2. crates/install_cli/Cargo.toml +1 −0
  3. crates/install_cli/src/install_cli_binary.rs +60 −17
  4. docs/src/macos.md +26 −0
Cargo.lock+1−0
@@ -9098,6 +9098,7 @@ dependencies = [ · 9097 unmodified lines
90989098 "anyhow",
90999099 "client",
91009100 "gpui",
9101+ "log",
91019102 "release_channel",
91029103 "smol",
91039104 "util",
crates/install_cli/Cargo.toml+1−0
@@ -18,6 +18,7 @@ test-support = [] · 17 unmodified lines
1818 anyhow.workspace = true
1919 client.workspace = true
2020 gpui.workspace = true
21+log.workspace = true
2122 release_channel.workspace = true
2223 smol.workspace = true
2324 util.workspace = true
crates/install_cli/src/install_cli_binary.rs+60−17
@@ -1,10 +1,11 @@
11 use super::register_zed_scheme;
2−use anyhow::{Context as _, Result};
2+use anyhow::Result;
33 use gpui::{AppContext as _, AsyncApp, Context, PromptLevel, Window, actions};
44 use release_channel::ReleaseChannel;
55 use std::ops::Deref;
66 use std::path::{Path, PathBuf};
77 use util::ResultExt;
8+use workspace::notifications::simple_message_notification::MessageNotification;
89 use workspace::notifications::{DetachAndPromptErr, NotificationId};
910 use workspace::{Toast, Workspace};
1011
@@ -16,14 +17,20 @@ actions!( · 5 unmodified lines
1617 ]
1718 );
1819
19−async fn install_script(cx: &AsyncApp) -> Result<PathBuf> {
20+const CANT_INSTALL_DOCS_URL: &str = "https://zed.dev/docs/macos#cant-install-cli";
21+
22+/// Attempts to install the CLI symlink. Returns the installed path on success,
23+/// or `None` if the user dismissed the macOS administrator authentication
24+/// prompt. Returns an error if the install could not be completed, most
25+/// commonly because the user is not an admin.
26+async fn install_script(cx: &AsyncApp) -> Result<Option<PathBuf>> {
2027 let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))?;
2128 let link_path = Path::new("/usr/local/bin/zed");
2229 let bin_dir_path = link_path.parent().unwrap();
2330
2431 // Don't re-create symlink if it points to the same CLI binary.
2532 if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {
26− return Ok(link_path.into());
33+ return Ok(Some(link_path.into()));
2734 }
2835
2936 // If the symlink is not there or is outdated, first try replacing it
@@ -34,12 +41,12 @@ async fn install_script(cx: &AsyncApp) -> Result<PathBuf> { · 4 unmodified lines
3441 .log_err()
3542 .is_some()
3643 {
37− return Ok(link_path.into());
44+ return Ok(Some(link_path.into()));
3845 }
3946
40− // The symlink could not be created, so use osascript with admin privileges
41− // to create it.
42− let status = smol::process::Command::new("/usr/bin/osascript")
47+ // The symlink could not be created without escalating, so use osascript
48+ // with admin privileges to create it.
49+ let output = smol::process::Command::new("/usr/bin/osascript")
4350 .args([
4451 "-e",
4552 &format!(
@@ -52,13 +59,24 @@ async fn install_script(cx: &AsyncApp) -> Result<PathBuf> { · 6 unmodified lines
5259 link_path.to_string_lossy(),
5360 ),
5461 ])
55− .stdout(smol::process::Stdio::inherit())
56− .stderr(smol::process::Stdio::inherit())
5762 .output()
58− .await?
59− .status;
60− anyhow::ensure!(status.success(), "error running osascript");
61− Ok(link_path.into())
63+ .await?;
64+
65+ if output.status.success() {
66+ return Ok(Some(link_path.into()));
67+ }
68+
69+ // osascript reports "User canceled." (error -128) when the administrator
70+ // prompt is dismissed. Treat that as a cancellation rather than a failure
71+ // so we don't show an error the user already chose to avoid.
72+ let stderr = String::from_utf8_lossy(&output.stderr);
73+ if stderr.contains("User canceled") || stderr.contains("-128") {
74+ return Ok(None);
75+ }
76+
77+ // The privileged write failed, most commonly because the user is not an
78+ // admin.
79+ anyhow::bail!("error running osascript: {}", stderr.trim());
6280 }
6381
6482 pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) {
@@ -75,9 +93,34 @@ pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) { · 10 unmodified lines
7593 cx.background_spawn(prompt).detach();
7694 return Ok(());
7795 }
78− let path = install_script(cx.deref())
79− .await
80− .context("error creating CLI symlink")?;
96+ let path = match install_script(cx.deref()).await {
97+ Ok(Some(path)) => path,
98+ // The user dismissed the administrator prompt; nothing to do.
99+ Ok(None) => return Ok(()),
100+ Err(error) => {
101+ log::error!("failed to install zed CLI: {error:#}");
102+ workspace.update(cx, |workspace, cx| {
103+ struct CliInstallFailed;
104+
105+ workspace.show_notification(
106+ NotificationId::unique::<CliInstallFailed>(),
107+ cx,
108+ |cx| {
109+ cx.new(|cx| {
110+ MessageNotification::new(
111+ "You can add `zed` to your PATH manually.",
112+ cx,
113+ )
114+ .with_title("Couldn't install the Zed CLI")
115+ .more_info_message("Show me how")
116+ .more_info_url(CANT_INSTALL_DOCS_URL)
117+ })
118+ },
119+ );
120+ })?;
121+ return Ok(());
122+ }
123+ };
81124
82125 workspace.update_in(cx, |workspace, _, cx| {
83126 struct InstalledZedCli;
@@ -97,5 +140,5 @@ pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) { · 13 unmodified lines
97140 register_zed_scheme(cx).await.log_err();
98141 Ok(())
99142 })
100− .detach_and_prompt_err("Error installing zed cli", window, cx, |_, _, _| None);
143+ .detach_and_prompt_err("Cannot install the Zed CLI", window, cx, |_, _, _| None);
101144 }
docs/src/macos.md+26−0
@@ -104,6 +104,32 @@ If the `zed` command isn't available after installation: · 103 unmodified lines
104104 2. Try reinstalling the CLI via {#action cli::InstallCliBinary} in the command palette
105105 3. Open a new terminal window to reload your PATH
106106
107+### Can't install CLI {#cant-install-cli}
108+
109+{#action cli::InstallCliBinary} writes a `zed` symlink to `/usr/local/bin`, which requires administrator privileges. If your macOS account isn't in the `admin` group, Zed can't create that symlink and will report that it can't install the CLI automatically.
110+
111+Instead, you can add an alias pointing to the `cli` binary bundled inside the app. The path depends on where Zed is installed:
112+
113+```sh
114+# Default install (Zed in /Applications)
115+alias zed="/Applications/Zed.app/Contents/MacOS/cli"
116+
117+# User install (Zed in ~/Applications)
118+alias zed="$HOME/Applications/Zed.app/Contents/MacOS/cli"
119+
120+# Preview build (Zed Preview in ~/Applications)
121+alias zed="$HOME/Applications/Zed Preview.app/Contents/MacOS/cli"
122+```
123+
124+Add the line that matches your install to your shell configuration file. Use `~/.zshrc` for Zsh (the default on modern macOS) or `~/.bashrc` for Bash.
125+
126+After you restart your shell, you will be able to use `zed` from your terminal:
127+
128+```sh
129+zed . # Open current folder
130+zed file.txt # Open a file
131+```
132+
107133 ### GPU or rendering issues
108134
109135 Zed uses Metal for rendering. If you experience graphical glitches:

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t