diff --git a/Package.resolved b/Package.resolved index 18ea62a..abcd348 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "e9329a960500fada10c24c10c67e7bb3459bf7f2fc828d8961e553c8fe9129c1", + "originHash" : "bb132ce859e2804ba5e6a969344342c19b2486c090f541521573787ad5504e0e", "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, { "identity" : "swift-docc-plugin", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index f7c7001..78a522a 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,8 @@ let package = Package( .executable(name: "swift-cli-example", targets: ["SwiftCLIExample"]) ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0") + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0") ], targets: [ // MARK: - Foundation Layer @@ -38,7 +39,10 @@ let package = Package( /// Low-level terminal operations: I/O, raw mode, capabilities .target( name: "TerminalCore", - dependencies: ["ANSI"] + dependencies: [ + "ANSI", + .product(name: "Atomics", package: "swift-atomics") + ] ), // MARK: - Feature Packages diff --git a/Sources/SwiftCLIExample/SwiftCLIExample.swift b/Sources/SwiftCLIExample/SwiftCLIExample.swift index 2154950..d64d198 100644 --- a/Sources/SwiftCLIExample/SwiftCLIExample.swift +++ b/Sources/SwiftCLIExample/SwiftCLIExample.swift @@ -614,7 +614,7 @@ func demoTerminalInfo(_ terminal: Terminal) async throws { await terminal.writeLine("") let caps = await terminal.capabilities - let size = await terminal.size + let size = await terminal.refreshSize() await terminal.render(Box( """ diff --git a/Sources/TerminalCore/Terminal.swift b/Sources/TerminalCore/Terminal.swift index 7f59636..5ece407 100644 --- a/Sources/TerminalCore/Terminal.swift +++ b/Sources/TerminalCore/Terminal.swift @@ -1,4 +1,5 @@ import ANSI +import Atomics import Dispatch #if canImport(Darwin) @@ -7,6 +8,9 @@ import Darwin import Glibc #endif +/// Thread-safe flag for SIGWINCH signal (accessible from signal handler without actor isolation) +private let _resizeSignalReceived = ManagedAtomic(false) + /// The central actor for terminal operations. /// /// `Terminal` provides thread-safe access to terminal capabilities including @@ -61,9 +65,6 @@ public actor Terminal { /// Signal source for SIGWINCH (terminal resize) private var resizeSignalSource: DispatchSourceSignal? - /// Whether a resize event has occurred - public private(set) var resizeOccurred: Bool = false - // MARK: - Initialization /// Create a new terminal instance with default configuration. @@ -418,12 +419,9 @@ public actor Terminal { // Create dispatch source for SIGWINCH let source = DispatchSource.makeSignalSource(signal: SIGWINCH, queue: .main) source.setEventHandler { - // Set a flag that can be checked in the event loop - // We can't safely call actor methods from here, so we just - // trigger the flag synchronously. The event loop will check it. - Task { @MainActor in - await Terminal.shared.handleResize() - } + // Set atomic flag directly - no Task needed + // This allows the flag to be set even when poll(2) is blocking + _resizeSignalReceived.store(true, ordering: .relaxed) } source.resume() resizeSignalSource = source @@ -436,17 +434,15 @@ public actor Terminal { resizeSignalSource = nil } - /// Handle a resize event. - private func handleResize() { - size = TerminalSize.detect(fd: outputFD) - resizeOccurred = true - } - /// Check and clear the resize flag. /// Returns true if a resize occurred since last check. public func checkResize() -> Bool { - let didResize = resizeOccurred - resizeOccurred = false - return didResize + // Atomically check and clear the flag + if _resizeSignalReceived.exchange(false, ordering: .relaxed) { + // Signal was received - refresh the terminal size + size = TerminalSize.detect(fd: outputFD) + return true + } + return false } }