From 112b5c16b74b27ff9efee98416402932c80847c5 Mon Sep 17 00:00:00 2001 From: John Tur Date: Mon, 10 Nov 2025 16:45:43 -0500 Subject: [PATCH] Add QuitMode policy to GPUI (#42391) Applications can select a policy for when the app quits using the new function `Application::with_quit_mode`: - Only on explicit calls to `App::quit` - When the last window is closed - Platform default (former on macOS, latter everywhere else) Release Notes: - N/A --- crates/gpui/src/app.rs | 37 ++++++++++++++++++++++++++++++ crates/zed/src/main.rs | 52 ++++++++++++++++++++++-------------------- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d4bd7798187a5b7a358106965d9e41fd85efeffe..864968b9e7a9ad862d9b67a19cc8897524dffb9e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -169,6 +169,13 @@ impl Application { self } + /// Configures when the application should automatically quit. + /// By default, [`QuitMode::Default`] is used. + pub fn with_quit_mode(self, mode: QuitMode) -> Self { + self.0.borrow_mut().quit_mode = mode; + self + } + /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run(self, on_finish_launching: F) @@ -238,6 +245,18 @@ type WindowClosedHandler = Box; type ReleaseListener = Box; type NewEntityListener = Box, &mut App) + 'static>; +/// Defines when the application should automatically quit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum QuitMode { + /// Use [`QuitMode::Explicit`] on macOS and [`QuitMode::LastWindowClosed`] on other platforms. + #[default] + Default, + /// Quit automatically when the last window is closed. + LastWindowClosed, + /// Quit only when requested via [`App::quit`]. + Explicit, +} + #[doc(hidden)] #[derive(Clone, PartialEq, Eq)] pub struct SystemWindowTab { @@ -588,6 +607,7 @@ pub struct App { pub(crate) inspector_element_registry: InspectorElementRegistry, #[cfg(any(test, feature = "test-support", debug_assertions))] pub(crate) name: Option<&'static str>, + quit_mode: QuitMode, quitting: bool, } @@ -659,6 +679,7 @@ impl App { inspector_renderer: None, #[cfg(any(feature = "inspector", debug_assertions))] inspector_element_registry: InspectorElementRegistry::default(), + quit_mode: QuitMode::default(), quitting: false, #[cfg(any(test, feature = "test-support", debug_assertions))] @@ -1172,6 +1193,12 @@ impl App { self.http_client = new_client; } + /// Configures when the application should automatically quit. + /// By default, [`QuitMode::Default`] is used. + pub fn set_quit_mode(&mut self, mode: QuitMode) { + self.quit_mode = mode; + } + /// Returns the SVG renderer used by the application. pub fn svg_renderer(&self) -> SvgRenderer { self.svg_renderer.clone() @@ -1379,6 +1406,16 @@ impl App { callback(cx); true }); + + let quit_on_empty = match cx.quit_mode { + QuitMode::Explicit => false, + QuitMode::LastWindowClosed => true, + QuitMode::Default => !cfg!(macos), + }; + + if quit_on_empty && cx.windows.is_empty() { + cx.quit(); + } } else { cx.windows.get_mut(id)?.replace(window); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 14308450e2adda064de4ded30a7649441b4d2d25..180e0f1f04d1c7b1eddb0156659f697f423967ea 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -15,7 +15,7 @@ use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::{StreamExt, channel::oneshot, future}; use git::GitHostingProviderRegistry; -use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _}; +use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, QuitMode, UpdateGlobal as _}; use gpui_tokio::Tokio; use language::LanguageRegistry; @@ -87,31 +87,33 @@ fn files_not_created_on_launch(errors: HashMap>) { .collect::>().join("\n\n"); eprintln!("{message}: {error_details}"); - Application::new().run(move |cx| { - if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| { - cx.new(|_| gpui::Empty) - }) { - window - .update(cx, |_, window, cx| { - let response = window.prompt( - gpui::PromptLevel::Critical, - message, - Some(&error_details), - &["Exit"], - cx, - ); - - cx.spawn_in(window, async move |_, cx| { - response.await?; - cx.update(|_, cx| cx.quit()) + Application::new() + .with_quit_mode(QuitMode::Explicit) + .run(move |cx| { + if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| { + cx.new(|_| gpui::Empty) + }) { + window + .update(cx, |_, window, cx| { + let response = window.prompt( + gpui::PromptLevel::Critical, + message, + Some(&error_details), + &["Exit"], + cx, + ); + + cx.spawn_in(window, async move |_, cx| { + response.await?; + cx.update(|_, cx| cx.quit()) + }) + .detach_and_log_err(cx); }) - .detach_and_log_err(cx); - }) - .log_err(); - } else { - fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx) - } - }) + .log_err(); + } else { + fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx) + } + }) } fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {