fix(gpui): confirm busy processes before window close

This commit is contained in:
kuddev committed 2026-08-16 11:36:34 +08:00
1 parent 300c98221a
commit 09f224aa5f
1 file changed
+70
+70
View File
@@ -566,6 +566,8 @@ pub struct NebulaWorkspace {
spinner_last: std::time::Instant,
/// 最近一次写盘的会话快照(1 Hz 自动保存的去重 + 退出收尾的素材)。
last_saved_session: Option<crate::session::Session>,
/// 系统关闭按钮可能连续送来多次 should-close;确认框在场时只保留一份。
window_close_confirm_open: bool,
}
impl NebulaWorkspace {
@@ -672,6 +674,7 @@ impl NebulaWorkspace {
spinner_phase: 0.0,
spinner_last: std::time::Instant::now(),
last_saved_session: None,
window_close_confirm_open: false,
};
// 配置装载错误的驻留横幅(消息栏层:用户要去修文件,必须看见)。
// 只在开窗时呈现一次;设置页 persist 的重载不重复弹。
@@ -695,6 +698,12 @@ impl NebulaWorkspace {
Self::start_ai_hook_pump(ai_events, cx);
this.start_session_autosave(cx);
this.apply_custom_keybinds(cx);
let workspace = cx.entity().downgrade();
window.on_window_should_close(cx, move |window, cx| {
workspace
.update(cx, |workspace, cx| workspace.should_close_window(window, cx))
.unwrap_or(true)
});
this
}
@@ -1096,6 +1105,67 @@ impl NebulaWorkspace {
.find_map(|pane| pane.view.read(cx).busy_process())
}
/// 系统标题栏关闭的是整个窗口,必须把所有 Tab/Pane 都纳入同一份旧壳
/// `busy_child(shell_pid)` 判据;只检查当前 Tab 会漏掉后台仍在编译的任务。
fn busy_process_in_window(&self, cx: &App) -> Option<String> {
(0..self.tabs.len()).find_map(|tab_ix| self.busy_process_in_tab(tab_ix, None, cx))
}
fn save_clean_window_session(&mut self, cx: &App) {
let mut snapshot = self.snapshot_session(cx);
crate::session::save_final(&mut snapshot);
// Drop 不再用最多滞后一秒的自动保存副本覆盖刚写下的准确快照。
self.last_saved_session = None;
}
/// GPUI 的 should-close 回调必须同步返回:无繁忙进程时直接允许系统关闭;
/// 有繁忙进程时先返回 false,再由对话框确认回调显式移除窗口。
fn should_close_window(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
let Some(process) = self.busy_process_in_window(cx) else {
self.save_clean_window_session(cx);
return true;
};
if self.window_close_confirm_open {
return false;
}
self.window_close_confirm_open = true;
let body: SharedString = format!("{process} 仍在运行,关闭窗口会中止它。").into();
let confirm_workspace = cx.entity().downgrade();
let close_workspace = confirm_workspace.clone();
window.open_dialog(cx, move |dialog, _window, _cx| {
let confirm_workspace = confirm_workspace.clone();
let close_workspace = close_workspace.clone();
dialog
.title("关闭窗口?")
.confirm()
.button_props(
DialogButtonProps::default()
.ok_text("关闭")
.ok_variant(gpui_component::button::ButtonVariant::Danger)
.cancel_text("取消"),
)
.child(body.clone())
.on_ok(move |_, window, cx| {
let _ = confirm_workspace.update(cx, |workspace, cx| {
workspace.save_clean_window_session(cx);
workspace.window_close_confirm_open = false;
// `remove_window` 是确认后的最终动作,不会重新触发
// should-close,从而避免再次弹出同一确认框。
window.remove_window();
});
true
})
.on_close(move |_, _, cx| {
let _ = close_workspace.update(cx, |workspace, cx| {
workspace.window_close_confirm_open = false;
cx.notify();
});
})
});
false
}
fn request_close_pane(
&mut self,
tab_ix: usize,