Vectrum
Sign In

Merge close confirmation

JjohannSocommitted 2 parents d78d175 cb860d1commit5db34af

Showing 5 changed files with 70 additions and 7 deletions.

Merge commit — showing changes against the first parent.

+2−1README.mdView File
@@ -200,7 +200,8 @@ Gruvbox, Rosé Pine, Solarized and Dracula.
200200 menu, the rich text composer, collapsing on scroll, the send shortcut (Enter, Ctrl+Enter
201201 for multiline prompts, or Ctrl+Enter always), follow-up behavior, update checks,
202202 continuing after restarts, background activity); Projects & threads; Confirmations
203- (unpin, archive, delete, and the quit shortcut: Direct, Hold or Double press); the text
203+ (unpin, archive, delete, and the quit shortcut: Direct, Hold or Double press, and a close
204+ confirmation that Ctrl+Q, Alt+F4 and the window's close button go through); the text
204205 generation model; the version; Diagnostics (CPU, memory and every process the app
205206 started, with interrupt and kill buttons) and the open source licenses; and the folded
206207 Legacy features (plan mode, the context window ring, the per-project sidebar).
+56−6src/main.rsView File
@@ -23,11 +23,14 @@ mod worktree;
2323
2424 use std::{
2525 path::PathBuf,
26- sync::Mutex,
26+ sync::{
27+ Mutex,
28+ atomic::{AtomicBool, Ordering},
29+ },
2730 time::{Duration, Instant},
2831 };
2932
30- use gpui_kit::component::{WindowExt as _, notification::Notification};
33+ use gpui_kit::component::{WindowExt as _, button::ButtonVariant, notification::Notification};
3134 use gpui_kit::*;
3235
3336 use crate::ui::{icons::AppAssets, metal, workspace::Workspace};
@@ -195,6 +198,8 @@ fn main() {
195198 };
196199 gpui_kit::open_window(options, cx, |window, cx| {
197200 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));
198203 cx.new(|cx| Workspace::new(project, window, cx))
199204 })
200205 .expect("failed to open the main window");
@@ -202,17 +207,17 @@ fn main() {
202207 });
203208 }
204209
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.
207212 fn quit(_: &Quit, cx: &mut App) {
208213 static FIRST_PRESS: Mutex<Option<Instant>> = Mutex::new(None);
209214 if settings::get(|settings| settings.quit_shortcut) == settings::QuitShortcut::Press {
210- cx.quit();
215+ quit_or_ask(cx);
211216 return;
212217 }
213218 let mut first = FIRST_PRESS.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
214219 if first.is_some_and(|at| at.elapsed() < QUIT_WINDOW) {
215- cx.quit();
220+ quit_or_ask(cx);
216221 return;
217222 }
218223 *first = Some(Instant::now());
@@ -226,3 +231,48 @@ fn quit(_: &Quit, cx: &mut App) {
226231 });
227232 }
228233 }
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+ }
+4−0src/settings.rsView File
@@ -486,6 +486,8 @@ pub struct Settings {
486486 pub confirm_settle: bool,
487487 pub confirm_delete: bool,
488488 pub quit_shortcut: QuitShortcut,
489+ /// Ctrl+Q, Alt+F4 and the window's close button ask before closing.
490+ pub confirm_close: bool,
489491
490492 // ---- notifications and background work ----
491493 /// A desktop notification when a thread finishes or needs you while the
@@ -602,6 +604,7 @@ impl Default for Settings {
602604 confirm_settle: false,
603605 confirm_delete: true,
604606 quit_shortcut: QuitShortcut::Hold,
607+ confirm_close: true,
605608 notify: false,
606609 notify_sound: false,
607610 in_app_notifications: false,
@@ -701,6 +704,7 @@ impl Settings {
701704 ("Archive confirmation", self.confirm_settle != d.confirm_settle),
702705 ("Delete confirmation", self.confirm_delete != d.confirm_delete),
703706 ("Quit shortcut", self.quit_shortcut != d.quit_shortcut),
707+ ("Close confirmation", self.confirm_close != d.confirm_close),
704708 ("Text generation model", (&self.text_model, &self.text_reasoning) != (&d.text_model, &d.text_reasoning)),
705709 (
706710 "Worktree branch naming",
+7−0src/ui/settings/general.rsView File
@@ -502,6 +502,13 @@ impl SettingsView {
502502 settings.quit_shortcut = shortcut
503503 })),
504504 )
505+ .row(
506+ row("Close confirmation")
507+ .id("close-confirmation")
508+ .description("Ask before Ctrl+Q, Alt+F4 or the window's close button closes the app.")
509+ .reset(reset(&view, cx, |s| s.confirm_close, |s, on| s.confirm_close = on))
510+ .control(switch("confirm-close", saved.confirm_close, &view, p, |settings, on| settings.confirm_close = on)),
511+ )
505512 .render(cx);
506513
507514 // ---- Text generation ----
+1−0src/ui/workspace/palette.rsView File
@@ -243,6 +243,7 @@ const SETTINGS: &[(&str, &str)] = &[
243243 ("Settle confirmation", "General"),
244244 ("Delete confirmation", "General"),
245245 ("Quit shortcut", "General"),
246+ ("Close confirmation", "General"),
246247 ("Thread notifications", "Behavior"),
247248 ("In-app notifications", "Behavior"),
248249 ("Time format", "Behavior"),