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
9098 "anyhow",9098 "anyhow",
9099 "client",9099 "client",
9100 "gpui",9100 "gpui",
9101 "log",
9101 "release_channel",9102 "release_channel",
9102 "smol",9103 "smol",
9103 "util",9104 "util",
crates/install_cli/Cargo.toml+1−0
@@ -18,6 +18,7 @@ test-support = [] · 17 unmodified lines
18anyhow.workspace = true18anyhow.workspace = true
19client.workspace = true19client.workspace = true
20gpui.workspace = true20gpui.workspace = true
21log.workspace = true
21release_channel.workspace = true22release_channel.workspace = true
22smol.workspace = true23smol.workspace = true
23util.workspace = true24util.workspace = true
crates/install_cli/src/install_cli_binary.rs+60−17
@@ -1,10 +1,11 @@
1use super::register_zed_scheme;1use super::register_zed_scheme;
2use anyhow::{Context as _, Result};2use anyhow::Result;
3use gpui::{AppContext as _, AsyncApp, Context, PromptLevel, Window, actions};3use gpui::{AppContext as _, AsyncApp, Context, PromptLevel, Window, actions};
4use release_channel::ReleaseChannel;4use release_channel::ReleaseChannel;
5use std::ops::Deref;5use std::ops::Deref;
6use std::path::{Path, PathBuf};6use std::path::{Path, PathBuf};
7use util::ResultExt;7use util::ResultExt;
8use workspace::notifications::simple_message_notification::MessageNotification;
8use workspace::notifications::{DetachAndPromptErr, NotificationId};9use workspace::notifications::{DetachAndPromptErr, NotificationId};
9use workspace::{Toast, Workspace};10use workspace::{Toast, Workspace};
10 11
@@ -16,14 +17,20 @@ actions!( · 5 unmodified lines
16 ]17 ]
17);18);
18 19
19async fn install_script(cx: &AsyncApp) -> Result<PathBuf> {20const 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.
26async fn install_script(cx: &AsyncApp) -> Result<Option<PathBuf>> {
20 let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))?;27 let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))?;
21 let link_path = Path::new("/usr/local/bin/zed");28 let link_path = Path::new("/usr/local/bin/zed");
22 let bin_dir_path = link_path.parent().unwrap();29 let bin_dir_path = link_path.parent().unwrap();
23 30
24 // Don't re-create symlink if it points to the same CLI binary.31 // Don't re-create symlink if it points to the same CLI binary.
25 if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {32 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()));
27 }34 }
28 35
29 // If the symlink is not there or is outdated, first try replacing it36 // 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
34 .log_err()41 .log_err()
35 .is_some()42 .is_some()
36 {43 {
37 return Ok(link_path.into());44 return Ok(Some(link_path.into()));
38 }45 }
39 46
40 // The symlink could not be created, so use osascript with admin privileges47 // The symlink could not be created without escalating, so use osascript
41 // to create it.48 // with admin privileges to create it.
42 let status = smol::process::Command::new("/usr/bin/osascript")49 let output = smol::process::Command::new("/usr/bin/osascript")
43 .args([50 .args([
44 "-e",51 "-e",
45 &format!(52 &format!(
@@ -52,13 +59,24 @@ async fn install_script(cx: &AsyncApp) -> Result<PathBuf> { · 6 unmodified lines
52 link_path.to_string_lossy(),59 link_path.to_string_lossy(),
53 ),60 ),
54 ])61 ])
55 .stdout(smol::process::Stdio::inherit())
56 .stderr(smol::process::Stdio::inherit())
57 .output()62 .output()
58 .await?63 .await?;
59 .status;64
60 anyhow::ensure!(status.success(), "error running osascript");65 if output.status.success() {
61 Ok(link_path.into())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());
62}80}
63 81
64pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) {82pub 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
75 cx.background_spawn(prompt).detach();93 cx.background_spawn(prompt).detach();
76 return Ok(());94 return Ok(());
77 }95 }
78 let path = install_script(cx.deref())96 let path = match install_script(cx.deref()).await {
79 .await97 Ok(Some(path)) => path,
80 .context("error creating CLI symlink")?;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 };
81 124
82 workspace.update_in(cx, |workspace, _, cx| {125 workspace.update_in(cx, |workspace, _, cx| {
83 struct InstalledZedCli;126 struct InstalledZedCli;
@@ -97,5 +140,5 @@ pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) { · 13 unmodified lines
97 register_zed_scheme(cx).await.log_err();140 register_zed_scheme(cx).await.log_err();
98 Ok(())141 Ok(())
99 })142 })
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);
101}144}
docs/src/macos.md+26−0
@@ -104,6 +104,32 @@ If the `zed` command isn't available after installation: · 103 unmodified lines
1042. Try reinstalling the CLI via {#action cli::InstallCliBinary} in the command palette1042. Try reinstalling the CLI via {#action cli::InstallCliBinary} in the command palette
1053. Open a new terminal window to reload your PATH1053. Open a new terminal window to reload your PATH
106 106
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
111Instead, 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)
115alias zed="/Applications/Zed.app/Contents/MacOS/cli"
116
117# User install (Zed in ~/Applications)
118alias zed="$HOME/Applications/Zed.app/Contents/MacOS/cli"
119
120# Preview build (Zed Preview in ~/Applications)
121alias zed="$HOME/Applications/Zed Preview.app/Contents/MacOS/cli"
122```
123
124Add 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
126After you restart your shell, you will be able to use `zed` from your terminal:
127
128```sh
129zed . # Open current folder
130zed file.txt # Open a file
131```
132
107### GPU or rendering issues133### GPU or rendering issues
108 134
109Zed uses Metal for rendering. If you experience graphical glitches:135Zed 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