macOS: check abnormal-command-exit-runtime when process exits (#12393)

<img width="849" height="434" alt="image"
src="https://github.com/user-attachments/assets/81c89d8d-6f0a-4bb9-b942-6734ff616bf9"
/>
This commit is contained in:
Mitchell Hashimoto
2026-04-23 06:34:08 -07:00
committed by GitHub
4 changed files with 37 additions and 5 deletions

View File

@@ -1649,7 +1649,8 @@ extension Ghostty {
// We handle this when the window is visible and timetime_ms is greater than 0,
// which will rule out exit codes on launch
guard surfaceView.window != nil, v.timetime_ms > 0 else { return false }
surfaceView.setChildExitedMessage(.init(v))
guard let config = (NSApplication.shared.delegate as? AppDelegate)?.ghostty.config else { return false }
surfaceView.setChildExitedMessage(.init(v, threshold: config.abnormalCommandExitRuntime))
return true
default:
return false

View File

@@ -10,14 +10,32 @@ extension Ghostty {
let text: String
let level: Level
init(_ message: ghostty_surface_message_childexited_s) {
init(_ message: ghostty_surface_message_childexited_s, threshold abnormalCommandExitRuntime: Duration) {
var level: Level
switch Int(message.exit_code) {
case Int(EXIT_SUCCESS):
level = .success
default:
level = .error
}
text = "Process exited. Press any key to close the terminal."
// See: Surface.zig/childExited
// If our runtime was below some threshold then we assume that this
// was an abnormal exit and we show an error message.
if abnormalCommandExitRuntime >= .milliseconds(message.timetime_ms) {
level = .error
let measure = Measurement.init(value: Double(message.timetime_ms), unit: UnitDuration.milliseconds)
let formatter = MeasurementFormatter()
if message.timetime_ms > 1000 {
formatter.unitOptions = .naturalScale
} else {
formatter.unitOptions = .providedUnit
}
formatter.locale = .init(identifier: "en_US")
text = "Process exited after **`\(formatter.string(from: measure))`**. Press any key to close the terminal."
} else {
text = "Process exited. Press any key to close the terminal."
}
self.level = level
}
}
}

View File

@@ -709,6 +709,16 @@ extension Ghostty {
return MacShortcuts(rawValue: str) ?? defaultValue
}
var abnormalCommandExitRuntime: Duration {
let defaultValue: Duration = .milliseconds(250)
guard let config = self.config else { return defaultValue }
var v: UInt32?
let key = "abnormal-command-exit-runtime"
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
guard let v else { return defaultValue }
return .milliseconds(v)
}
var scrollbar: Scrollbar {
let defaultValue = Scrollbar.system
guard let config = self.config else { return defaultValue }

View File

@@ -6,8 +6,7 @@ struct ChildExitedMessageBar: View {
var body: some View {
HStack(spacing: 6) {
Text(msg.text)
.fontWeight(.medium)
Text(message)
.lineLimit(1)
.truncationMode(.tail)
}
@@ -28,6 +27,10 @@ struct ChildExitedMessageBar: View {
}
}
}
private var message: AttributedString {
(try? AttributedString(markdown: msg.text)) ?? AttributedString(msg.text)
}
}
private extension Ghostty.ChildExitedMessage.Level {