#if os(macOS) import AppKit import SwiftUI struct DocsCloseGuard: NSViewRepresentable { let hasUnsaved: @MainActor () -> Bool let save: @MainActor () async -> Bool let discard: @MainActor () async -> Void func makeNSView(context: Context) -> DocsCloseGuardView { let view = DocsCloseGuardView() view.hasUnsaved = hasUnsaved; view.save = save; view.discard = discard return view } func updateNSView(_ view: DocsCloseGuardView, context: Context) { view.hasUnsaved = hasUnsaved; view.save = save; view.discard = discard } } @MainActor final class DocsCloseGuardView: NSView, NSWindowDelegate { var hasUnsaved: @MainActor () -> Bool = { false } var save: @MainActor () async -> Bool = { true } var discard: @MainActor () async -> Void = {} var presenting = false nonisolated(unsafe) weak var previousDelegate: (any NSWindowDelegate)? override func viewDidMoveToWindow() { super.viewDidMoveToWindow() guard let window, window.delegate !== self else { return } DocsCloseRegistry.views.removeAll { $0.value == nil } if !DocsCloseRegistry.views.contains(where: { $0.value === self }) { DocsCloseRegistry.views.append(WeakDocsCloseGuard(value: self)) } previousDelegate = window.delegate window.delegate = self } override func responds(to selector: Selector!) -> Bool { super.responds(to: selector) || previousDelegate?.responds(to: selector) == true } override func forwardingTarget(for selector: Selector!) -> Any? { previousDelegate } func windowShouldClose(_ sender: NSWindow) -> Bool { guard hasUnsaved() else { return previousDelegate?.windowShouldClose?(sender) ?? true } guard !presenting else { return false } presenting = true let alert = Self.alert() alert.beginSheetModal(for: sender) { [weak self] response in guard let self else { return } Task { @MainActor in defer { self.presenting = false } if response == .alertFirstButtonReturn, await self.save() { sender.performClose(nil) } if response == .alertThirdButtonReturn { await self.discard(); sender.performClose(nil) } } } return false } static func alert() -> NSAlert { let alert = NSAlert() alert.messageText = String(localized: "native.close.pending.title", bundle: DocsResources.bundle) alert.informativeText = String(localized: "native.close.pending.detail", bundle: DocsResources.bundle) alert.addButton(withTitle: String(localized: "native.close.pending.save", bundle: DocsResources.bundle)) alert.addButton(withTitle: String(localized: "native.close.pending.cancel", bundle: DocsResources.bundle)) alert.addButton(withTitle: String(localized: "native.discard.action", bundle: DocsResources.bundle)) return alert } } @MainActor private struct WeakDocsCloseGuard { weak var value: DocsCloseGuardView? } @MainActor private enum DocsCloseRegistry { static var views: [WeakDocsCloseGuard] = [] } @MainActor public final class DocsApplicationDelegate: NSObject, NSApplicationDelegate { public func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { let views = DocsCloseRegistry.views.compactMap(\.value).filter { $0.window != nil } if views.contains(where: \.presenting) { return .terminateCancel } let pending = views.filter { $0.hasUnsaved() } guard !pending.isEmpty else { return .terminateNow } let response = DocsCloseGuardView.alert().runModal() guard response == .alertFirstButtonReturn || response == .alertThirdButtonReturn else { return .terminateCancel } Task { @MainActor in var saved = true for view in pending { if response == .alertThirdButtonReturn { await view.discard() } else if !(await view.save()) { saved = false } } sender.reply(toApplicationShouldTerminate: saved && !views.contains { $0.hasUnsaved() }) } return .terminateLater } } #endif