| @@ -23,11 +23,14 @@ mod worktree; |
| 23 | 23 | |
| 24 | 24 | use std::{ |
| 25 | 25 | path::PathBuf, |
| 26 | | - sync::Mutex, |
| 26 | + sync::{ |
| 27 | + Mutex, |
| 28 | + atomic::{AtomicBool, Ordering}, |
| 29 | + }, |
| 27 | 30 | time::{Duration, Instant}, |
| 28 | 31 | }; |
| 29 | 32 | |
| 30 | | - use gpui_kit::component::{WindowExt as _, notification::Notification}; |
| 33 | + use gpui_kit::component::{WindowExt as _, button::ButtonVariant, notification::Notification}; |
| 31 | 34 | use gpui_kit::*; |
| 32 | 35 | |
| 33 | 36 | use crate::ui::{icons::AppAssets, metal, workspace::Workspace}; |
| @@ -195,6 +198,8 @@ fn main() { |
| 195 | 198 | }; |
| 196 | 199 | gpui_kit::open_window(options, cx, |window, cx| { |
| 197 | 200 | metal::sync_appearance(window, cx); |
| 201 | + // Alt+F4 and the window manager's close button. |
| 202 | + window.on_window_should_close(cx, |window, cx| !ask_to_close(window, cx)); |
| 198 | 203 | cx.new(|cx| Workspace::new(project, window, cx)) |
| 199 | 204 | }) |
| 200 | 205 | .expect("failed to open the main window"); |
| @@ -202,17 +207,17 @@ fn main() { |
| 202 | 207 | }); |
| 203 | 208 | } |
| 204 | 209 | |
| 205 | | - /// Quits, or with the Hold shortcut, asks for the keys to be held or pressed |
| 206 | | - /// again. |
| 210 | + /// Quits (through the close confirmation), or with the Hold shortcut, asks |
| 211 | + /// for the keys to be held or pressed again. |
| 207 | 212 | fn quit(_: &Quit, cx: &mut App) { |
| 208 | 213 | static FIRST_PRESS: Mutex<Option<Instant>> = Mutex::new(None); |
| 209 | 214 | if settings::get(|settings| settings.quit_shortcut) == settings::QuitShortcut::Press { |
| 210 | | - cx.quit(); |
| 215 | + quit_or_ask(cx); |
| 211 | 216 | return; |
| 212 | 217 | } |
| 213 | 218 | let mut first = FIRST_PRESS.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); |
| 214 | 219 | if first.is_some_and(|at| at.elapsed() < QUIT_WINDOW) { |
| 215 | | - cx.quit(); |
| 220 | + quit_or_ask(cx); |
| 216 | 221 | return; |
| 217 | 222 | } |
| 218 | 223 | *first = Some(Instant::now()); |
| @@ -226,3 +231,48 @@ fn quit(_: &Quit, cx: &mut App) { |
| 226 | 231 | }); |
| 227 | 232 | } |
| 228 | 233 | } |
| 234 | + |
| 235 | + /// Quits, or asks first when closing needs a confirmation. Deferred, since a |
| 236 | + /// key's action runs while its window is mid-update. |
| 237 | + fn quit_or_ask(cx: &mut App) { |
| 238 | + cx.defer(|cx| { |
| 239 | + let Some(window) = cx.active_window().or_else(|| cx.windows().first().copied()) else { |
| 240 | + cx.quit(); |
| 241 | + return; |
| 242 | + }; |
| 243 | + if let Ok(false) = window.update(cx, |_, window, cx| ask_to_close(window, cx)) { |
| 244 | + cx.quit(); |
| 245 | + } |
| 246 | + }); |
| 247 | + } |
| 248 | + |
| 249 | + /// With Close confirmation on, opens the "Quit Vectrum Code?" dialog (Enter |
| 250 | + /// or a click on Quit quits, Escape or Cancel stays) and returns true; the |
| 251 | + /// caller then must not close. Returns false when closing needs no asking. |
| 252 | + fn ask_to_close(window: &mut Window, cx: &mut App) -> bool { |
| 253 | + /// The dialog is up: a held Ctrl+Q's key repeat or a second Alt+F4 |
| 254 | + /// leaves it be rather than stacking another. |
| 255 | + static ASKING: AtomicBool = AtomicBool::new(false); |
| 256 | + if !settings::get(|settings| settings.confirm_close) { |
| 257 | + return false; |
| 258 | + } |
| 259 | + if ASKING.load(Ordering::Relaxed) && window.has_active_dialog(cx) { |
| 260 | + return true; |
| 261 | + } |
| 262 | + ASKING.store(true, Ordering::Relaxed); |
| 263 | + window.open_alert_dialog(cx, |alert, _, _| { |
| 264 | + alert |
| 265 | + .title("Quit Vectrum Code?") |
| 266 | + .description("Running agents and terminals stop. Press Enter to quit, or Escape to stay.") |
| 267 | + .ok_text("Quit") |
| 268 | + .ok_variant(ButtonVariant::Danger) |
| 269 | + .cancel_text("Cancel") |
| 270 | + .show_cancel(true) |
| 271 | + .on_ok(|_, _, cx| { |
| 272 | + cx.quit(); |
| 273 | + true |
| 274 | + }) |
| 275 | + .on_close(|_, _, _| ASKING.store(false, Ordering::Relaxed)) |
| 276 | + }); |
| 277 | + true |
| 278 | + } |