windows: Fix auto update failure when launching from the cli (#34303)

张小白 and Max Brunsfeld created

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/activity_indicator/src/activity_indicator.rs |  12 -
crates/auto_update/src/auto_update.rs               | 112 +++++++++-----
crates/auto_update_helper/src/auto_update_helper.rs |  86 +++++++++++
crates/auto_update_helper/src/dialog.rs             |   4 
crates/auto_update_helper/src/updater.rs            |  14 +
crates/editor/src/editor_tests.rs                   |   2 
crates/gpui/src/app.rs                              |  31 +++
crates/gpui/src/app/context.rs                      |  35 ++-
crates/gpui/src/platform/windows/platform.rs        |   4 
crates/title_bar/src/title_bar.rs                   |   2 
crates/workspace/src/workspace.rs                   |  22 --
crates/zed/src/main.rs                              |  51 ++----
crates/zed/src/zed/windows_only_instance.rs         |   6 
13 files changed, 250 insertions(+), 131 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -716,18 +716,10 @@ impl ActivityIndicator {
                     })),
                     tooltip_message: Some(Self::version_tooltip_message(&version)),
                 }),
-                AutoUpdateStatus::Updated {
-                    binary_path,
-                    version,
-                } => Some(Content {
+                AutoUpdateStatus::Updated { version } => Some(Content {
                     icon: None,
                     message: "Click to restart and update Zed".to_string(),
-                    on_click: Some(Arc::new({
-                        let reload = workspace::Reload {
-                            binary_path: Some(binary_path.clone()),
-                        };
-                        move |_, _, cx| workspace::reload(&reload, cx)
-                    })),
+                    on_click: Some(Arc::new(move |_, _, cx| workspace::reload(cx))),
                     tooltip_message: Some(Self::version_tooltip_message(&version)),
                 }),
                 AutoUpdateStatus::Errored => Some(Content {

crates/auto_update/src/auto_update.rs 🔗

@@ -59,16 +59,9 @@ pub enum VersionCheckType {
 pub enum AutoUpdateStatus {
     Idle,
     Checking,
-    Downloading {
-        version: VersionCheckType,
-    },
-    Installing {
-        version: VersionCheckType,
-    },
-    Updated {
-        binary_path: PathBuf,
-        version: VersionCheckType,
-    },
+    Downloading { version: VersionCheckType },
+    Installing { version: VersionCheckType },
+    Updated { version: VersionCheckType },
     Errored,
 }
 
@@ -83,6 +76,7 @@ pub struct AutoUpdater {
     current_version: SemanticVersion,
     http_client: Arc<HttpClientWithUrl>,
     pending_poll: Option<Task<Option<()>>>,
+    quit_subscription: Option<gpui::Subscription>,
 }
 
 #[derive(Deserialize, Clone, Debug)]
@@ -164,7 +158,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
     AutoUpdateSetting::register(cx);
 
     cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
-        workspace.register_action(|_, action: &Check, window, cx| check(action, window, cx));
+        workspace.register_action(|_, action, window, cx| check(action, window, cx));
 
         workspace.register_action(|_, action, _, cx| {
             view_release_notes(action, cx);
@@ -174,7 +168,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
 
     let version = release_channel::AppVersion::global(cx);
     let auto_updater = cx.new(|cx| {
-        let updater = AutoUpdater::new(version, http_client);
+        let updater = AutoUpdater::new(version, http_client, cx);
 
         let poll_for_updates = ReleaseChannel::try_global(cx)
             .map(|channel| channel.poll_for_updates())
@@ -321,12 +315,34 @@ impl AutoUpdater {
         cx.default_global::<GlobalAutoUpdate>().0.clone()
     }
 
-    fn new(current_version: SemanticVersion, http_client: Arc<HttpClientWithUrl>) -> Self {
+    fn new(
+        current_version: SemanticVersion,
+        http_client: Arc<HttpClientWithUrl>,
+        cx: &mut Context<Self>,
+    ) -> Self {
+        // On windows, executable files cannot be overwritten while they are
+        // running, so we must wait to overwrite the application until quitting
+        // or restarting. When quitting the app, we spawn the auto update helper
+        // to finish the auto update process after Zed exits. When restarting
+        // the app after an update, we use `set_restart_path` to run the auto
+        // update helper instead of the app, so that it can overwrite the app
+        // and then spawn the new binary.
+        let quit_subscription = Some(cx.on_app_quit(|_, _| async move {
+            #[cfg(target_os = "windows")]
+            finalize_auto_update_on_quit();
+        }));
+
+        cx.on_app_restart(|this, _| {
+            this.quit_subscription.take();
+        })
+        .detach();
+
         Self {
             status: AutoUpdateStatus::Idle,
             current_version,
             http_client,
             pending_poll: None,
+            quit_subscription,
         }
     }
 
@@ -536,6 +552,8 @@ impl AutoUpdater {
                 )
             })?;
 
+        Self::check_dependencies()?;
+
         this.update(&mut cx, |this, cx| {
             this.status = AutoUpdateStatus::Checking;
             cx.notify();
@@ -582,13 +600,15 @@ impl AutoUpdater {
             cx.notify();
         })?;
 
-        let binary_path = Self::binary_path(installer_dir, target_path, &cx).await?;
+        let new_binary_path = Self::install_release(installer_dir, target_path, &cx).await?;
+        if let Some(new_binary_path) = new_binary_path {
+            cx.update(|cx| cx.set_restart_path(new_binary_path))?;
+        }
 
         this.update(&mut cx, |this, cx| {
             this.set_should_show_update_notification(true, cx)
                 .detach_and_log_err(cx);
             this.status = AutoUpdateStatus::Updated {
-                binary_path,
                 version: newer_version,
             };
             cx.notify();
@@ -639,6 +659,15 @@ impl AutoUpdater {
         }
     }
 
+    fn check_dependencies() -> Result<()> {
+        #[cfg(not(target_os = "windows"))]
+        anyhow::ensure!(
+            which::which("rsync").is_ok(),
+            "Aborting. Could not find rsync which is required for auto-updates."
+        );
+        Ok(())
+    }
+
     async fn target_path(installer_dir: &InstallerDir) -> Result<PathBuf> {
         let filename = match OS {
             "macos" => anyhow::Ok("Zed.dmg"),
@@ -647,20 +676,14 @@ impl AutoUpdater {
             unsupported_os => anyhow::bail!("not supported: {unsupported_os}"),
         }?;
 
-        #[cfg(not(target_os = "windows"))]
-        anyhow::ensure!(
-            which::which("rsync").is_ok(),
-            "Aborting. Could not find rsync which is required for auto-updates."
-        );
-
         Ok(installer_dir.path().join(filename))
     }
 
-    async fn binary_path(
+    async fn install_release(
         installer_dir: InstallerDir,
         target_path: PathBuf,
         cx: &AsyncApp,
-    ) -> Result<PathBuf> {
+    ) -> Result<Option<PathBuf>> {
         match OS {
             "macos" => install_release_macos(&installer_dir, target_path, cx).await,
             "linux" => install_release_linux(&installer_dir, target_path, cx).await,
@@ -801,7 +824,7 @@ async fn install_release_linux(
     temp_dir: &InstallerDir,
     downloaded_tar_gz: PathBuf,
     cx: &AsyncApp,
-) -> Result<PathBuf> {
+) -> Result<Option<PathBuf>> {
     let channel = cx.update(|cx| ReleaseChannel::global(cx).dev_name())?;
     let home_dir = PathBuf::from(env::var("HOME").context("no HOME env var set")?);
     let running_app_path = cx.update(|cx| cx.app_path())??;
@@ -861,14 +884,14 @@ async fn install_release_linux(
         String::from_utf8_lossy(&output.stderr)
     );
 
-    Ok(to.join(expected_suffix))
+    Ok(Some(to.join(expected_suffix)))
 }
 
 async fn install_release_macos(
     temp_dir: &InstallerDir,
     downloaded_dmg: PathBuf,
     cx: &AsyncApp,
-) -> Result<PathBuf> {
+) -> Result<Option<PathBuf>> {
     let running_app_path = cx.update(|cx| cx.app_path())??;
     let running_app_filename = running_app_path
         .file_name()
@@ -910,10 +933,10 @@ async fn install_release_macos(
         String::from_utf8_lossy(&output.stderr)
     );
 
-    Ok(running_app_path)
+    Ok(None)
 }
 
-async fn install_release_windows(downloaded_installer: PathBuf) -> Result<PathBuf> {
+async fn install_release_windows(downloaded_installer: PathBuf) -> Result<Option<PathBuf>> {
     let output = Command::new(downloaded_installer)
         .arg("/verysilent")
         .arg("/update=true")
@@ -926,29 +949,36 @@ async fn install_release_windows(downloaded_installer: PathBuf) -> Result<PathBu
         "failed to start installer: {:?}",
         String::from_utf8_lossy(&output.stderr)
     );
-    Ok(std::env::current_exe()?)
+    // We return the path to the update helper program, because it will
+    // perform the final steps of the update process, copying the new binary,
+    // deleting the old one, and launching the new binary.
+    let helper_path = std::env::current_exe()?
+        .parent()
+        .context("No parent dir for Zed.exe")?
+        .join("tools\\auto_update_helper.exe");
+    Ok(Some(helper_path))
 }
 
-pub fn check_pending_installation() -> bool {
+pub fn finalize_auto_update_on_quit() {
     let Some(installer_path) = std::env::current_exe()
         .ok()
         .and_then(|p| p.parent().map(|p| p.join("updates")))
     else {
-        return false;
+        return;
     };
 
     // The installer will create a flag file after it finishes updating
     let flag_file = installer_path.join("versions.txt");
-    if flag_file.exists() {
-        if let Some(helper) = installer_path
+    if flag_file.exists()
+        && let Some(helper) = installer_path
             .parent()
             .map(|p| p.join("tools\\auto_update_helper.exe"))
-        {
-            let _ = std::process::Command::new(helper).spawn();
-            return true;
-        }
+    {
+        let mut command = std::process::Command::new(helper);
+        command.arg("--launch");
+        command.arg("false");
+        let _ = command.spawn();
     }
-    false
 }
 
 #[cfg(test)]
@@ -1002,7 +1032,6 @@ mod tests {
         let app_commit_sha = Ok(Some("a".to_string()));
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Semantic(SemanticVersion::new(1, 0, 1)),
         };
         let fetched_version = SemanticVersion::new(1, 0, 1);
@@ -1024,7 +1053,6 @@ mod tests {
         let app_commit_sha = Ok(Some("a".to_string()));
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Semantic(SemanticVersion::new(1, 0, 1)),
         };
         let fetched_version = SemanticVersion::new(1, 0, 2);
@@ -1090,7 +1118,6 @@ mod tests {
         let app_commit_sha = Ok(Some("a".to_string()));
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
         let fetched_sha = "b".to_string();
@@ -1112,7 +1139,6 @@ mod tests {
         let app_commit_sha = Ok(Some("a".to_string()));
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
         let fetched_sha = "c".to_string();
@@ -1160,7 +1186,6 @@ mod tests {
         let app_commit_sha = Ok(None);
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
         let fetched_sha = "b".to_string();
@@ -1183,7 +1208,6 @@ mod tests {
         let app_commit_sha = Ok(None);
         let installed_version = SemanticVersion::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            binary_path: PathBuf::new(),
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
         let fetched_sha = "c".to_string();

crates/auto_update_helper/src/auto_update_helper.rs 🔗

@@ -37,6 +37,11 @@ mod windows_impl {
     pub(crate) const WM_JOB_UPDATED: u32 = WM_USER + 1;
     pub(crate) const WM_TERMINATE: u32 = WM_USER + 2;
 
+    #[derive(Debug)]
+    struct Args {
+        launch: Option<bool>,
+    }
+
     pub(crate) fn run() -> Result<()> {
         let helper_dir = std::env::current_exe()?
             .parent()
@@ -51,8 +56,9 @@ mod windows_impl {
         log::info!("======= Starting Zed update =======");
         let (tx, rx) = std::sync::mpsc::channel();
         let hwnd = create_dialog_window(rx)?.0 as isize;
+        let args = parse_args();
         std::thread::spawn(move || {
-            let result = perform_update(app_dir.as_path(), Some(hwnd));
+            let result = perform_update(app_dir.as_path(), Some(hwnd), args.launch.unwrap_or(true));
             tx.send(result).ok();
             unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_TERMINATE, WPARAM(0), LPARAM(0)) }.ok();
         });
@@ -77,6 +83,41 @@ mod windows_impl {
         Ok(())
     }
 
+    fn parse_args() -> Args {
+        let mut result = Args { launch: None };
+        if let Some(candidate) = std::env::args().nth(1) {
+            parse_single_arg(&candidate, &mut result);
+        }
+
+        result
+    }
+
+    fn parse_single_arg(arg: &str, result: &mut Args) {
+        let Some((key, value)) = arg.strip_prefix("--").and_then(|arg| arg.split_once('=')) else {
+            log::error!(
+                "Invalid argument format: '{}'. Expected format: --key=value",
+                arg
+            );
+            return;
+        };
+
+        match key {
+            "launch" => parse_launch_arg(value, &mut result.launch),
+            _ => log::error!("Unknown argument: --{}", key),
+        }
+    }
+
+    fn parse_launch_arg(value: &str, arg: &mut Option<bool>) {
+        match value {
+            "true" => *arg = Some(true),
+            "false" => *arg = Some(false),
+            _ => log::error!(
+                "Invalid value for --launch: '{}'. Expected 'true' or 'false'",
+                value
+            ),
+        }
+    }
+
     pub(crate) fn show_error(mut content: String) {
         if content.len() > 600 {
             content.truncate(600);
@@ -91,4 +132,47 @@ mod windows_impl {
             )
         };
     }
+
+    #[cfg(test)]
+    mod tests {
+        use crate::windows_impl::{Args, parse_launch_arg, parse_single_arg};
+
+        #[test]
+        fn test_parse_launch_arg() {
+            let mut arg = None;
+            parse_launch_arg("true", &mut arg);
+            assert_eq!(arg, Some(true));
+
+            let mut arg = None;
+            parse_launch_arg("false", &mut arg);
+            assert_eq!(arg, Some(false));
+
+            let mut arg = None;
+            parse_launch_arg("invalid", &mut arg);
+            assert_eq!(arg, None);
+        }
+
+        #[test]
+        fn test_parse_single_arg() {
+            let mut args = Args { launch: None };
+            parse_single_arg("--launch=true", &mut args);
+            assert_eq!(args.launch, Some(true));
+
+            let mut args = Args { launch: None };
+            parse_single_arg("--launch=false", &mut args);
+            assert_eq!(args.launch, Some(false));
+
+            let mut args = Args { launch: None };
+            parse_single_arg("--launch=invalid", &mut args);
+            assert_eq!(args.launch, None);
+
+            let mut args = Args { launch: None };
+            parse_single_arg("--launch", &mut args);
+            assert_eq!(args.launch, None);
+
+            let mut args = Args { launch: None };
+            parse_single_arg("--unknown", &mut args);
+            assert_eq!(args.launch, None);
+        }
+    }
 }

crates/auto_update_helper/src/dialog.rs 🔗

@@ -72,7 +72,7 @@ pub(crate) fn create_dialog_window(receiver: Receiver<Result<()>>) -> Result<HWN
         let hwnd = CreateWindowExW(
             WS_EX_TOPMOST,
             class_name,
-            windows::core::w!("Zed Editor"),
+            windows::core::w!("Zed"),
             WS_VISIBLE | WS_POPUP | WS_CAPTION,
             rect.right / 2 - width / 2,
             rect.bottom / 2 - height / 2,
@@ -171,7 +171,7 @@ unsafe extern "system" fn wnd_proc(
                 &HSTRING::from(font_name),
             );
             let temp = SelectObject(hdc, font.into());
-            let string = HSTRING::from("Zed Editor is updating...");
+            let string = HSTRING::from("Updating Zed...");
             return_if_failed!(TextOutW(hdc, 20, 15, &string).ok());
             return_if_failed!(DeleteObject(temp).ok());
 

crates/auto_update_helper/src/updater.rs 🔗

@@ -118,7 +118,7 @@ pub(crate) const JOBS: [Job; 2] = [
     },
 ];
 
-pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>) -> Result<()> {
+pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool) -> Result<()> {
     let hwnd = hwnd.map(|ptr| HWND(ptr as _));
 
     for job in JOBS.iter() {
@@ -145,9 +145,11 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>) -> Result<()>
             }
         }
     }
-    let _ = std::process::Command::new(app_dir.join("Zed.exe"))
-        .creation_flags(CREATE_NEW_PROCESS_GROUP.0)
-        .spawn();
+    if launch {
+        let _ = std::process::Command::new(app_dir.join("Zed.exe"))
+            .creation_flags(CREATE_NEW_PROCESS_GROUP.0)
+            .spawn();
+    }
     log::info!("Update completed successfully");
     Ok(())
 }
@@ -159,11 +161,11 @@ mod test {
     #[test]
     fn test_perform_update() {
         let app_dir = std::path::Path::new("C:/");
-        assert!(perform_update(app_dir, None).is_ok());
+        assert!(perform_update(app_dir, None, false).is_ok());
 
         // Simulate a timeout
         unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err") };
-        let ret = perform_update(app_dir, None);
+        let ret = perform_update(app_dir, None, false);
         assert!(ret.is_err_and(|e| e.to_string().as_str() == "Timed out"));
     }
 }

crates/editor/src/editor_tests.rs 🔗

@@ -22456,7 +22456,7 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
     );
 
     cx.update(|_, cx| {
-        workspace::reload(&workspace::Reload::default(), cx);
+        workspace::reload(cx);
     });
     assert_language_servers_count(
         1,

crates/gpui/src/app.rs 🔗

@@ -277,6 +277,8 @@ pub struct App {
     pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
     pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
     pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
+    pub(crate) restart_observers: SubscriberSet<(), Handler>,
+    pub(crate) restart_path: Option<PathBuf>,
     pub(crate) window_closed_observers: SubscriberSet<(), WindowClosedHandler>,
     pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
     pub(crate) propagate_event: bool,
@@ -349,6 +351,8 @@ impl App {
                 keyboard_layout_observers: SubscriberSet::new(),
                 global_observers: SubscriberSet::new(),
                 quit_observers: SubscriberSet::new(),
+                restart_observers: SubscriberSet::new(),
+                restart_path: None,
                 window_closed_observers: SubscriberSet::new(),
                 layout_id_buffer: Default::default(),
                 propagate_event: true,
@@ -832,8 +836,16 @@ impl App {
     }
 
     /// Restarts the application.
-    pub fn restart(&self, binary_path: Option<PathBuf>) {
-        self.platform.restart(binary_path)
+    pub fn restart(&mut self) {
+        self.restart_observers
+            .clone()
+            .retain(&(), |observer| observer(self));
+        self.platform.restart(self.restart_path.take())
+    }
+
+    /// Sets the path to use when restarting the application.
+    pub fn set_restart_path(&mut self, path: PathBuf) {
+        self.restart_path = Some(path);
     }
 
     /// Returns the HTTP client for the application.
@@ -1466,6 +1478,21 @@ impl App {
         subscription
     }
 
+    /// Register a callback to be invoked when the application is about to restart.
+    ///
+    /// These callbacks are called before any `on_app_quit` callbacks.
+    pub fn on_app_restart(&self, mut on_restart: impl 'static + FnMut(&mut App)) -> Subscription {
+        let (subscription, activate) = self.restart_observers.insert(
+            (),
+            Box::new(move |cx| {
+                on_restart(cx);
+                true
+            }),
+        );
+        activate();
+        subscription
+    }
+
     /// Register a callback to be invoked when a window is closed
     /// The window is no longer accessible at the point this callback is invoked.
     pub fn on_window_closed(&self, mut on_closed: impl FnMut(&mut App) + 'static) -> Subscription {

crates/gpui/src/app/context.rs 🔗

@@ -164,6 +164,20 @@ impl<'a, T: 'static> Context<'a, T> {
         subscription
     }
 
+    /// Register a callback to be invoked when the application is about to restart.
+    pub fn on_app_restart(
+        &self,
+        mut on_restart: impl FnMut(&mut T, &mut App) + 'static,
+    ) -> Subscription
+    where
+        T: 'static,
+    {
+        let handle = self.weak_entity();
+        self.app.on_app_restart(move |cx| {
+            handle.update(cx, |entity, cx| on_restart(entity, cx)).ok();
+        })
+    }
+
     /// Arrange for the given function to be invoked whenever the application is quit.
     /// The future returned from this callback will be polled for up to [crate::SHUTDOWN_TIMEOUT] until the app fully quits.
     pub fn on_app_quit<Fut>(
@@ -175,20 +189,15 @@ impl<'a, T: 'static> Context<'a, T> {
         T: 'static,
     {
         let handle = self.weak_entity();
-        let (subscription, activate) = self.app.quit_observers.insert(
-            (),
-            Box::new(move |cx| {
-                let future = handle.update(cx, |entity, cx| on_quit(entity, cx)).ok();
-                async move {
-                    if let Some(future) = future {
-                        future.await;
-                    }
+        self.app.on_app_quit(move |cx| {
+            let future = handle.update(cx, |entity, cx| on_quit(entity, cx)).ok();
+            async move {
+                if let Some(future) = future {
+                    future.await;
                 }
-                .boxed_local()
-            }),
-        );
-        activate();
-        subscription
+            }
+            .boxed_local()
+        })
     }
 
     /// Tell GPUI that this entity has changed and observers of it should be notified.

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -370,9 +370,9 @@ impl Platform for WindowsPlatform {
             .detach();
     }
 
-    fn restart(&self, _: Option<PathBuf>) {
+    fn restart(&self, binary_path: Option<PathBuf>) {
         let pid = std::process::id();
-        let Some(app_path) = self.app_path().log_err() else {
+        let Some(app_path) = binary_path.or(self.app_path().log_err()) else {
             return;
         };
         let script = format!(

crates/title_bar/src/title_bar.rs 🔗

@@ -595,7 +595,7 @@ impl TitleBar {
                         .on_click(|_, window, cx| {
                             if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
                                 if auto_updater.read(cx).status().is_updated() {
-                                    workspace::reload(&Default::default(), cx);
+                                    workspace::reload(cx);
                                     return;
                                 }
                             }

crates/workspace/src/workspace.rs 🔗

@@ -224,6 +224,8 @@ actions!(
         ResetActiveDockSize,
         /// Resets all open docks to their default sizes.
         ResetOpenDocksSize,
+        /// Reloads the application
+        Reload,
         /// Saves the current file with a new name.
         SaveAs,
         /// Saves without formatting.
@@ -340,14 +342,6 @@ pub struct CloseInactiveTabsAndPanes {
 #[action(namespace = workspace)]
 pub struct SendKeystrokes(pub String);
 
-/// Reloads the active item or workspace.
-#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema, Action)]
-#[action(namespace = workspace)]
-#[serde(deny_unknown_fields)]
-pub struct Reload {
-    pub binary_path: Option<PathBuf>,
-}
-
 actions!(
     project_symbols,
     [
@@ -555,8 +549,8 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     toast_layer::init(cx);
     history_manager::init(cx);
 
-    cx.on_action(Workspace::close_global);
-    cx.on_action(reload);
+    cx.on_action(|_: &CloseWindow, cx| Workspace::close_global(cx));
+    cx.on_action(|_: &Reload, cx| reload(cx));
 
     cx.on_action({
         let app_state = Arc::downgrade(&app_state);
@@ -2184,7 +2178,7 @@ impl Workspace {
         }
     }
 
-    pub fn close_global(_: &CloseWindow, cx: &mut App) {
+    pub fn close_global(cx: &mut App) {
         cx.defer(|cx| {
             cx.windows().iter().find(|window| {
                 window
@@ -7642,7 +7636,7 @@ pub fn join_in_room_project(
     })
 }
 
-pub fn reload(reload: &Reload, cx: &mut App) {
+pub fn reload(cx: &mut App) {
     let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
     let mut workspace_windows = cx
         .windows()
@@ -7669,7 +7663,6 @@ pub fn reload(reload: &Reload, cx: &mut App) {
             .ok();
     }
 
-    let binary_path = reload.binary_path.clone();
     cx.spawn(async move |cx| {
         if let Some(prompt) = prompt {
             let answer = prompt.await?;
@@ -7688,8 +7681,7 @@ pub fn reload(reload: &Reload, cx: &mut App) {
                 }
             }
         }
-
-        cx.update(|cx| cx.restart(binary_path))
+        cx.update(|cx| cx.restart())
     })
     .detach_and_log_err(cx);
 }

crates/zed/src/main.rs 🔗

@@ -201,16 +201,6 @@ pub fn main() {
         return;
     }
 
-    // Check if there is a pending installer
-    // If there is, run the installer and exit
-    // And we don't want to run the installer if we are not the first instance
-    #[cfg(target_os = "windows")]
-    let is_first_instance = crate::zed::windows_only_instance::is_first_instance();
-    #[cfg(target_os = "windows")]
-    if is_first_instance && auto_update::check_pending_installation() {
-        return;
-    }
-
     if args.dump_all_actions {
         dump_all_gpui_actions();
         return;
@@ -283,30 +273,27 @@ pub fn main() {
 
     let (open_listener, mut open_rx) = OpenListener::new();
 
-    let failed_single_instance_check =
-        if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
-            false
-        } else {
-            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
-            {
-                crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
-            }
+    let failed_single_instance_check = if *db::ZED_STATELESS
+        || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
+    {
+        false
+    } else {
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
+        {
+            crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
+        }
 
-            #[cfg(target_os = "windows")]
-            {
-                !crate::zed::windows_only_instance::handle_single_instance(
-                    open_listener.clone(),
-                    &args,
-                    is_first_instance,
-                )
-            }
+        #[cfg(target_os = "windows")]
+        {
+            !crate::zed::windows_only_instance::handle_single_instance(open_listener.clone(), &args)
+        }
 
-            #[cfg(target_os = "macos")]
-            {
-                use zed::mac_only_instance::*;
-                ensure_only_instance() != IsOnlyInstance::Yes
-            }
-        };
+        #[cfg(target_os = "macos")]
+        {
+            use zed::mac_only_instance::*;
+            ensure_only_instance() != IsOnlyInstance::Yes
+        }
+    };
     if failed_single_instance_check {
         println!("zed is already running");
         return;

crates/zed/src/zed/windows_only_instance.rs 🔗

@@ -25,7 +25,8 @@ use windows::{
 
 use crate::{Args, OpenListener, RawOpenRequest};
 
-pub fn is_first_instance() -> bool {
+#[inline]
+fn is_first_instance() -> bool {
     unsafe {
         CreateMutexW(
             None,
@@ -37,7 +38,8 @@ pub fn is_first_instance() -> bool {
     unsafe { GetLastError() != ERROR_ALREADY_EXISTS }
 }
 
-pub fn handle_single_instance(opener: OpenListener, args: &Args, is_first_instance: bool) -> bool {
+pub fn handle_single_instance(opener: OpenListener, args: &Args) -> bool {
+    let is_first_instance = is_first_instance();
     if is_first_instance {
         // We are the first instance, listen for messages sent from other instances
         std::thread::spawn(move || {