diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 5dd53b5a09..18f49a5691 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -16,6 +16,10 @@ doctest = false name = "cli" path = "src/main.rs" +[features] +no-bundled-uninstall = [] +default = [] + [dependencies] anyhow.workspace = true clap.workspace = true diff --git a/crates/cli/build.rs b/crates/cli/build.rs new file mode 100644 index 0000000000..399755fa28 --- /dev/null +++ b/crates/cli/build.rs @@ -0,0 +1,5 @@ +fn main() { + if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() { + println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#); + } +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 002b0c0173..c8e1c8d3ed 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -59,6 +59,13 @@ struct Args { /// Run zed in dev-server mode #[arg(long)] dev_server_token: Option, + /// Uninstall Zed from user system + #[cfg(all( + any(target_os = "linux", target_os = "macos"), + not(feature = "no-bundled-uninstall") + ))] + #[arg(long)] + uninstall: bool, } fn parse_path_with_position(argument_str: &str) -> anyhow::Result { @@ -119,6 +126,29 @@ fn main() -> Result<()> { return Ok(()); } + #[cfg(all( + any(target_os = "linux", target_os = "macos"), + not(feature = "no-bundled-uninstall") + ))] + if args.uninstall { + static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh"); + + let tmp_dir = tempfile::tempdir()?; + let script_path = tmp_dir.path().join("uninstall.sh"); + fs::write(&script_path, UNINSTALL_SCRIPT)?; + + use std::os::unix::fs::PermissionsExt as _; + fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?; + + let status = std::process::Command::new("sh") + .arg(&script_path) + .env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME) + .status() + .context("Failed to execute uninstall script")?; + + std::process::exit(status.code().unwrap_or(1)); + } + let (server, server_name) = IpcOneShotServer::::new().context("Handshake before Zed spawn")?; let url = format!("zed-cli://{server_name}"); diff --git a/script/uninstall.sh b/script/uninstall.sh new file mode 100644 index 0000000000..3e460b8186 --- /dev/null +++ b/script/uninstall.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env sh +set -eu + +# Uninstalls Zed that was installed using the install.sh script + +check_remaining_installations() { + platform="$(uname -s)" + if [ "$platform" = "Darwin" ]; then + # Check for any Zed variants in /Applications + remaining=$(ls -d /Applications/Zed*.app 2>/dev/null | wc -l) + [ "$remaining" -eq 0 ] + else + # Check for any Zed variants in ~/.local + remaining=$(ls -d "$HOME/.local/zed"*.app 2>/dev/null | wc -l) + [ "$remaining" -eq 0 ] + fi +} + +prompt_remove_preferences() { + printf "Do you want to keep your Zed preferences? [Y/n] " + read -r response + case "$response" in + [nN]|[nN][oO]) + rm -rf "$HOME/.config/zed" + echo "Preferences removed." + ;; + *) + echo "Preferences kept." + ;; + esac +} + +main() { + platform="$(uname -s)" + channel="${ZED_CHANNEL:-stable}" + + if [ "$platform" = "Darwin" ]; then + platform="macos" + elif [ "$platform" = "Linux" ]; then + platform="linux" + else + echo "Unsupported platform $platform" + exit 1 + fi + + "$platform" + + echo "Zed has been uninstalled" +} + +linux() { + suffix="" + if [ "$channel" != "stable" ]; then + suffix="-$channel" + fi + + appid="" + db_suffix="stable" + case "$channel" in + stable) + appid="dev.zed.Zed" + db_suffix="stable" + ;; + nightly) + appid="dev.zed.Zed-Nightly" + db_suffix="nightly" + ;; + preview) + appid="dev.zed.Zed-Preview" + db_suffix="preview" + ;; + dev) + appid="dev.zed.Zed-Dev" + db_suffix="dev" + ;; + *) + echo "Unknown release channel: ${channel}. Using stable app ID." + appid="dev.zed.Zed" + db_suffix="stable" + ;; + esac + + # Remove the app directory + rm -rf "$HOME/.local/zed$suffix.app" + + # Remove the binary symlink + rm -f "$HOME/.local/bin/zed" + + # Remove the .desktop file + rm -f "$HOME/.local/share/applications/${appid}.desktop" + + # Remove the database directory for this channel + rm -rf "$HOME/.local/share/zed/db/0-$db_suffix" + + # Remove socket file + rm -f "$HOME/.local/share/zed/zed-$db_suffix.sock" + + # Remove the entire Zed directory if no installations remain + if check_remaining_installations; then + rm -rf "$HOME/.local/share/zed" + prompt_remove_preferences + fi + + rm -rf $HOME/.zed_server +} + +macos() { + app="Zed.app" + db_suffix="stable" + app_id="dev.zed.Zed" + case "$channel" in + nightly) + app="Zed Nightly.app" + db_suffix="nightly" + app_id="dev.zed.Zed-Nightly" + ;; + preview) + app="Zed Preview.app" + db_suffix="preview" + app_id="dev.zed.Zed-Preview" + ;; + dev) + app="Zed Dev.app" + db_suffix="dev" + app_id="dev.zed.Zed-Dev" + ;; + esac + + # Remove the app bundle + if [ -d "/Applications/$app" ]; then + rm -rf "/Applications/$app" + fi + + # Remove the binary symlink + rm -f "$HOME/.local/bin/zed" + + # Remove the database directory for this channel + rm -rf "$HOME/Library/Application Support/Zed/db/0-$db_suffix" + + # Remove app-specific files and directories + rm -rf "$HOME/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/$app_id.sfl"* + rm -rf "$HOME/Library/Caches/$app_id" + rm -rf "$HOME/Library/HTTPStorages/$app_id" + rm -rf "$HOME/Library/Preferences/$app_id.plist" + rm -rf "$HOME/Library/Saved Application State/$app_id.savedState" + + # Remove the entire Zed directory if no installations remain + if check_remaining_installations; then + rm -rf "$HOME/Library/Application Support/Zed" + rm -rf "$HOME/Library/Logs/Zed" + + prompt_remove_preferences + fi + + rm -rf $HOME/.zed_server +} + +main "$@"