Detailed changes
@@ -3080,6 +3080,7 @@ name = "cli"
version = "0.1.0"
dependencies = [
"anyhow",
+ "askpass",
"clap",
"collections",
"core-foundation 0.10.0",
@@ -20215,7 +20216,6 @@ dependencies = [
"agent_ui",
"anyhow",
"ashpd 0.11.0",
- "askpass",
"assets",
"assistant_tools",
"audio",
@@ -85,11 +85,8 @@ impl AskPassSession {
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
- #[cfg(not(target_os = "windows"))]
- let zed_path = util::get_shell_safe_zed_path()?;
- #[cfg(target_os = "windows")]
- let zed_path = std::env::current_exe()
- .context("finding current executable path for use in askpass")?;
+ let zed_cli_path =
+ util::get_shell_safe_zed_cli_path().context("getting zed-cli path for askpass")?;
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
let mut kill_tx = Some(askpass_kill_master_tx);
@@ -137,7 +134,7 @@ impl AskPassSession {
});
// Create an askpass script that communicates back to this process.
- let askpass_script = generate_askpass_script(&zed_path, &askpass_socket);
+ let askpass_script = generate_askpass_script(&zed_cli_path, &askpass_socket);
fs::write(&askpass_script_path, askpass_script)
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
@@ -254,10 +251,10 @@ pub fn main(socket: &str) {
#[inline]
#[cfg(not(target_os = "windows"))]
-fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) -> String {
+fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
format!(
- "{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n",
- zed_exe = zed_path,
+ "{shebang}\n{print_args} | {zed_cli} --askpass={askpass_socket} 2> /dev/null \n",
+ zed_cli = zed_cli_path,
askpass_socket = askpass_socket.display(),
print_args = "printf '%s\\0' \"$@\"",
shebang = "#!/bin/sh",
@@ -266,13 +263,13 @@ fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) ->
#[inline]
#[cfg(target_os = "windows")]
-fn generate_askpass_script(zed_path: &std::path::Path, askpass_socket: &std::path::Path) -> String {
+fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
format!(
r#"
$ErrorActionPreference = 'Stop';
- ($args -join [char]0) | & "{zed_exe}" --askpass={askpass_socket} 2> $null
+ ($args -join [char]0) | & "{zed_cli}" --askpass={askpass_socket} 2> $null
"#,
- zed_exe = zed_path.display(),
+ zed_cli = zed_cli_path,
askpass_socket = askpass_socket.display(),
)
}
@@ -67,7 +67,7 @@ impl TryFrom<&str> for EncryptedPassword {
unsafe {
CryptProtectMemory(
value.as_mut_ptr() as _,
- len,
+ padded_length,
CRYPTPROTECTMEMORY_SAME_PROCESS,
)?;
}
@@ -97,7 +97,7 @@ pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result<String> {
unsafe {
CryptUnprotectMemory(
password.0.as_mut_ptr() as _,
- password.1,
+ password.0.len().try_into()?,
CRYPTPROTECTMEMORY_SAME_PROCESS,
)
.context("while decrypting a SSH password")?
@@ -22,6 +22,7 @@ default = []
[dependencies]
anyhow.workspace = true
+askpass.workspace = true
clap.workspace = true
collections.workspace = true
ipc-channel = "0.19"
@@ -116,6 +116,11 @@ struct Args {
))]
#[arg(long)]
uninstall: bool,
+
+ /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
+ /// by having Zed act like netcat communicating over a Unix socket.
+ #[arg(long, hide = true)]
+ askpass: Option<String>,
}
fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
@@ -203,6 +208,12 @@ fn main() -> Result<()> {
}
let args = Args::parse();
+ // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
+ if let Some(socket) = &args.askpass {
+ askpass::main(socket);
+ return Ok(());
+ }
+
// Set custom data directory before any path operations
let user_data_dir = args.user_data_dir.clone();
if let Some(dir) = &user_data_dir {
@@ -14,7 +14,7 @@ pub mod size;
pub mod test;
pub mod time;
-use anyhow::Result;
+use anyhow::{Context as _, Result};
use futures::Future;
use itertools::Either;
use regex::Regex;
@@ -290,8 +290,6 @@ fn load_shell_from_passwd() -> Result<()> {
#[cfg(unix)]
/// Returns a shell escaped path for the current zed executable
pub fn get_shell_safe_zed_path() -> anyhow::Result<String> {
- use anyhow::Context;
-
let zed_path = std::env::current_exe()
.context("Failed to determine current zed executable path.")?
.to_string_lossy()
@@ -307,6 +305,59 @@ pub fn get_shell_safe_zed_path() -> anyhow::Result<String> {
Ok(zed_path_escaped.to_string())
}
+/// Returns a shell escaped path for the zed cli executable, this function
+/// should be called from the zed executable, not zed-cli.
+pub fn get_shell_safe_zed_cli_path() -> Result<String> {
+ let zed_path =
+ std::env::current_exe().context("Failed to determine current zed executable path.")?;
+ let parent = zed_path
+ .parent()
+ .context("Failed to determine parent directory of zed executable path.")?;
+
+ let possible_locations: &[&str] = if cfg!(target_os = "macos") {
+ // On macOS, the zed executable and zed-cli are inside the app bundle,
+ // so here ./cli is for both installed and development builds.
+ &["./cli"]
+ } else if cfg!(target_os = "windows") {
+ // bin/zed.exe is for installed builds, ./cli.exe is for development builds.
+ &["bin/zed.exe", "./cli.exe"]
+ } else if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") {
+ // bin is the standard, ./cli is for the target directory in development builds.
+ &["../bin/zed", "./cli"]
+ } else {
+ anyhow::bail!("unsupported platform for determining zed-cli path");
+ };
+
+ let zed_cli_path = possible_locations
+ .iter()
+ .find_map(|p| {
+ parent
+ .join(p)
+ .canonicalize()
+ .ok()
+ .filter(|p| p != &zed_path)
+ })
+ .with_context(|| {
+ format!(
+ "could not find zed-cli from any of: {}",
+ possible_locations.join(", ")
+ )
+ })?
+ .to_string_lossy()
+ .to_string();
+
+ #[cfg(target_os = "windows")]
+ {
+ Ok(zed_cli_path)
+ }
+ #[cfg(not(target_os = "windows"))]
+ {
+ Ok(shlex::try_quote(&zed_cli_path)
+ .context("Failed to shell-escape Zed executable path.")?
+ .to_string())
+ }
+}
+
#[cfg(unix)]
pub async fn load_login_shell_environment() -> Result<()> {
load_shell_from_passwd().log_err();
@@ -25,7 +25,6 @@ agent.workspace = true
agent_settings.workspace = true
agent_ui.workspace = true
anyhow.workspace = true
-askpass.workspace = true
assets.workspace = true
assistant_tools.workspace = true
audio.workspace = true
@@ -176,12 +176,6 @@ pub fn main() {
return;
}
- // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
- if let Some(socket) = &args.askpass {
- askpass::main(socket);
- return;
- }
-
// `zed --nc` Makes zed operate in nc/netcat mode for use with MCP
if let Some(socket) = &args.nc {
match nc::main(socket) {
@@ -1249,11 +1243,6 @@ struct Args {
#[arg(long)]
system_specs: bool,
- /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
- /// by having Zed act like netcat communicating over a Unix socket.
- #[arg(long, hide = true)]
- askpass: Option<String>,
-
/// Used for the MCP Server, to remove the need for netcat as a dependency,
/// by having Zed act like netcat communicating over a Unix socket.
#[arg(long, hide = true)]