Skip to content

Commit 1c9fc33

Browse files
committed
window close / quit safety
1 parent ae6b2b4 commit 1c9fc33

5 files changed

Lines changed: 15 additions & 34 deletions

File tree

VimR/VimR/AppDelegate.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ extension AppDelegate {
276276
alert.alertStyle = .informational
277277
alert.runModal()
278278

279+
NSApplication.shared.reply(toApplicationShouldTerminate: false)
279280
return
280281
}
281282

@@ -297,6 +298,7 @@ extension AppDelegate {
297298
return
298299
}
299300

301+
NSApplication.shared.reply(toApplicationShouldTerminate: false)
300302
return
301303
}
302304

VimR/VimR/MainWindow+Actions.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ extension MainWindow {
156156
}
157157

158158
@IBAction func closeWindow(_: Any?) {
159-
self.closeWindow = true
160159
self.window.performClose(nil)
161160
}
162161

VimR/VimR/MainWindow+Delegates.swift

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ extension MainWindow {
103103
+ reason
104104
alert.alertStyle = .critical
105105
alert.beginSheetModal(for: self.window) { _ in
106-
self.windowController.close()
106+
// Route through neoVimStopped so isClosing is set, the CLI pipe is closed,
107+
// and the Redux .close action is emitted — same as the normal close path.
108+
self.neoVimStopped()
107109
}
108110
}
109111

@@ -200,9 +202,6 @@ extension MainWindow {
200202
}
201203

202204
func windowShouldClose(_: NSWindow) -> Bool {
203-
defer { self.closeWindow = false }
204-
let closeWindow = self.closeWindow
205-
206205
Task {
207206
if await self.neoVimView.isBlocked() {
208207
let alert = NSAlert()
@@ -212,22 +211,6 @@ extension MainWindow {
212211
return
213212
}
214213

215-
if closeWindow {
216-
if await self.neoVimView.hasDirtyBuffers() {
217-
self.discardCloseActionAlert().beginSheetModal(for: self.window) { response in
218-
if response == .alertSecondButtonReturn {
219-
Task {
220-
await self.neoVimView.quitNeoVimWithoutSaving()
221-
}
222-
}
223-
}
224-
} else {
225-
await self.neoVimView.quitNeoVimWithoutSaving()
226-
}
227-
228-
return
229-
}
230-
231214
guard await self.neoVimView.isCurrentBufferDirty() else {
232215
await self.neoVimView.closeCurrentTab()
233216
return

VimR/VimR/MainWindow.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ final class MainWindow: NSObject,
5656
var repIcon: NSButton?
5757
var titleView: NSTextField?
5858

59-
var closeWindow = false
6059
var isClosing = false
6160
let cliPipePath: String?
6261

VimR/VimR/UiRoot.swift

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ final class UiRoot: UiComponent {
2828
context.subscribe(uuid: self.uuid) { state in
2929
let uuidsInState = Set(state.mainWindows.keys)
3030

31+
// Open any new windows that appeared in state.
3132
uuidsInState
3233
.subtracting(self.mainWindows.keys)
3334
.compactMap { state.mainWindows[$0] }
@@ -36,25 +37,22 @@ final class UiRoot: UiComponent {
3637
mainWindow.show()
3738
}
3839

39-
if self.mainWindows.isEmpty {
40-
// We exit here if there are no main windows open.
41-
// Otherwise, when hide/quit after last main window is active,
42-
// you have to be really quick to open a new window
43-
// when re-activating VimR w/o automatic new main window.
44-
return
45-
}
46-
47-
self.mainWindows.keys
48-
.filter { !uuidsInState.contains($0) }
49-
.forEach(self.removeMainWindow)
40+
// Remove windows that are no longer in state.
41+
let uuidsToRemove = self.mainWindows.keys.filter { !uuidsInState.contains($0) }
42+
uuidsToRemove.forEach(self.removeMainWindow)
5043

5144
if self.activateAsciiImInInsertMode != state.activateAsciiImInNormalMode {
5245
self.activateAsciiImInInsertMode = state.activateAsciiImInNormalMode
5346
self.mainWindows.values
5447
.forEach { $0.activateAsciiImInInsertMode = self.activateAsciiImInInsertMode }
5548
}
5649

57-
guard self.mainWindows.isEmpty else { return }
50+
// Only trigger the after-last-window action if we actually removed something
51+
// in this update and the result is that no windows remain. Guarding on
52+
// `!uuidsToRemove.isEmpty` prevents a rapid second state update (where
53+
// self.mainWindows is already empty at entry) from erroneously skipping or
54+
// double-firing the quit/hide action.
55+
guard !uuidsToRemove.isEmpty, self.mainWindows.isEmpty else { return }
5856

5957
switch state.afterLastWindowAction {
6058
case .doNothing: return

0 commit comments

Comments
 (0)