diff --git a/VimR/VimR/AppDelegate.swift b/VimR/VimR/AppDelegate.swift index 68dec66cc..5f63bae04 100644 --- a/VimR/VimR/AppDelegate.swift +++ b/VimR/VimR/AppDelegate.swift @@ -276,6 +276,7 @@ extension AppDelegate { alert.alertStyle = .informational alert.runModal() + NSApplication.shared.reply(toApplicationShouldTerminate: false) return } @@ -297,6 +298,7 @@ extension AppDelegate { return } + NSApplication.shared.reply(toApplicationShouldTerminate: false) return } diff --git a/VimR/VimR/MainWindow+Actions.swift b/VimR/VimR/MainWindow+Actions.swift index 243091fbd..69dc77f66 100644 --- a/VimR/VimR/MainWindow+Actions.swift +++ b/VimR/VimR/MainWindow+Actions.swift @@ -156,7 +156,6 @@ extension MainWindow { } @IBAction func closeWindow(_: Any?) { - self.closeWindow = true self.window.performClose(nil) } diff --git a/VimR/VimR/MainWindow+Delegates.swift b/VimR/VimR/MainWindow+Delegates.swift index db5478f54..138b656c5 100644 --- a/VimR/VimR/MainWindow+Delegates.swift +++ b/VimR/VimR/MainWindow+Delegates.swift @@ -103,7 +103,9 @@ extension MainWindow { + reason alert.alertStyle = .critical alert.beginSheetModal(for: self.window) { _ in - self.windowController.close() + // Route through neoVimStopped so isClosing is set, the CLI pipe is closed, + // and the Redux .close action is emitted — same as the normal close path. + self.neoVimStopped() } } @@ -200,9 +202,6 @@ extension MainWindow { } func windowShouldClose(_: NSWindow) -> Bool { - defer { self.closeWindow = false } - let closeWindow = self.closeWindow - Task { if await self.neoVimView.isBlocked() { let alert = NSAlert() @@ -212,22 +211,6 @@ extension MainWindow { return } - if closeWindow { - if await self.neoVimView.hasDirtyBuffers() { - self.discardCloseActionAlert().beginSheetModal(for: self.window) { response in - if response == .alertSecondButtonReturn { - Task { - await self.neoVimView.quitNeoVimWithoutSaving() - } - } - } - } else { - await self.neoVimView.quitNeoVimWithoutSaving() - } - - return - } - guard await self.neoVimView.isCurrentBufferDirty() else { await self.neoVimView.closeCurrentTab() return diff --git a/VimR/VimR/MainWindow.swift b/VimR/VimR/MainWindow.swift index 17e46570c..a410048b9 100644 --- a/VimR/VimR/MainWindow.swift +++ b/VimR/VimR/MainWindow.swift @@ -56,7 +56,6 @@ final class MainWindow: NSObject, var repIcon: NSButton? var titleView: NSTextField? - var closeWindow = false var isClosing = false let cliPipePath: String? diff --git a/VimR/VimR/UiRoot.swift b/VimR/VimR/UiRoot.swift index a13d0f32f..91d8b546b 100644 --- a/VimR/VimR/UiRoot.swift +++ b/VimR/VimR/UiRoot.swift @@ -28,6 +28,7 @@ final class UiRoot: UiComponent { context.subscribe(uuid: self.uuid) { state in let uuidsInState = Set(state.mainWindows.keys) + // Open any new windows that appeared in state. uuidsInState .subtracting(self.mainWindows.keys) .compactMap { state.mainWindows[$0] } @@ -36,17 +37,9 @@ final class UiRoot: UiComponent { mainWindow.show() } - if self.mainWindows.isEmpty { - // We exit here if there are no main windows open. - // Otherwise, when hide/quit after last main window is active, - // you have to be really quick to open a new window - // when re-activating VimR w/o automatic new main window. - return - } - - self.mainWindows.keys - .filter { !uuidsInState.contains($0) } - .forEach(self.removeMainWindow) + // Remove windows that are no longer in state. + let uuidsToRemove = self.mainWindows.keys.filter { !uuidsInState.contains($0) } + uuidsToRemove.forEach(self.removeMainWindow) if self.activateAsciiImInInsertMode != state.activateAsciiImInNormalMode { self.activateAsciiImInInsertMode = state.activateAsciiImInNormalMode @@ -54,7 +47,12 @@ final class UiRoot: UiComponent { .forEach { $0.activateAsciiImInInsertMode = self.activateAsciiImInInsertMode } } - guard self.mainWindows.isEmpty else { return } + // Only trigger the after-last-window action if we actually removed something + // in this update and the result is that no windows remain. Guarding on + // `!uuidsToRemove.isEmpty` prevents a rapid second state update (where + // self.mainWindows is already empty at entry) from erroneously skipping or + // double-firing the quit/hide action. + guard !uuidsToRemove.isEmpty, self.mainWindows.isEmpty else { return } switch state.afterLastWindowAction { case .doNothing: return