diff --git a/src/Surface.zig b/src/Surface.zig index 025b96573..0c60556c2 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3673,7 +3673,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .quit => try self.app.setQuit(), - .crash => @panic("crash binding action, crashing intentionally"), + .crash => |location| switch (location) { + .main => @panic("crash binding action, crashing intentionally"), + + .render => { + _ = self.renderer_thread.mailbox.push(.{ .crash = {} }, .{ .forever = {} }); + self.queueRender() catch |err| { + // Not a big deal if this fails. + log.warn("failed to notify renderer of crash message err={}", .{err}); + }; + }, + + .io => self.io.queueMessage(.{ .crash = {} }, .unlocked), + }, .adjust_selection => |direction| { self.renderer_state.mutex.lock(); diff --git a/src/input/Binding.zig b/src/input/Binding.zig index d628b5dbe..37b18f581 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -300,12 +300,27 @@ pub const Action = union(enum) { /// Quit ghostty. quit: void, - /// Crash ghostty. This is funny, but is used for testing crash handling. - /// It is shipped in production builds because sometimes we need to - /// test crash handling works for a user who may have some specific - /// configuration. This is not bound by default and I recommend not - /// binding it. - crash: void, + /// Crash ghostty in the desired thread for the focused surface. + /// + /// WARNING: This is a hard crash (panic) and data can be lost. + /// + /// The purpose of this action is to test crash handling. For some + /// users, it may be useful to test crash reporting functionality in + /// order to determine if it all works as expected. + /// + /// The value determines the crash location: + /// + /// - "main" - crash on the main (GUI) thread. + /// - "io" - crash on the IO thread for the focused surface. + /// - "render" - crash on the render thread for the focused surface. + /// + crash: CrashThread, + + pub const CrashThread = enum { + main, + io, + render, + }; pub const CursorKey = struct { normal: []const u8, diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 1b730693b..11a546d3b 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -270,6 +270,8 @@ fn drainMailbox(self: *Thread) !void { while (self.mailbox.pop()) |message| { log.debug("mailbox message={}", .{message}); switch (message) { + .crash => @panic("crash request, crashing intentionally"), + .visible => |v| { // Set our visible state self.flags.visible = v; diff --git a/src/renderer/message.zig b/src/renderer/message.zig index c2444b3c9..18b4d916f 100644 --- a/src/renderer/message.zig +++ b/src/renderer/message.zig @@ -8,6 +8,10 @@ const terminal = @import("../terminal/main.zig"); /// The messages that can be sent to a renderer thread. pub const Message = union(enum) { + /// Purposely crash the renderer. This is used for testing and debugging. + /// See the "crash" binding action. + crash: void, + /// A change in state in the window focus that this renderer is /// rendering within. This is only sent when a change is detected so /// the renderer is expected to handle all of these. diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index d38c6a1ce..d72a10449 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -268,6 +268,7 @@ fn drainMailbox( log.debug("mailbox message={}", .{message}); switch (message) { + .crash => @panic("crash request, crashing intentionally"), .change_config => |config| { defer config.alloc.destroy(config.ptr); try io.changeConfig(data, config.ptr); diff --git a/src/termio/message.zig b/src/termio/message.zig index 20d47fd32..6bdb7a2da 100644 --- a/src/termio/message.zig +++ b/src/termio/message.zig @@ -29,6 +29,10 @@ pub const Message = union(enum) { padding: renderer.Padding, }; + /// Purposely crash the renderer. This is used for testing and debugging. + /// See the "crash" binding action. + crash: void, + /// The derived configuration to update the implementation with. This /// is allocated via the allocator and is expected to be freed when done. change_config: struct {