mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
stream: add bell effect callback support
Add an Effects struct to the readonly stream Handler that allows callers to provide optional callbacks for side effects like bell. Previously, the bell action was silently ignored along with other query/response actions. Now it is handled separately and dispatched through the effects callback if one is provided. Add a test that verifies bell with a null callback (default readonly behavior) does not crash, and that a provided callback is invoked the correct number of times.
This commit is contained in:
@@ -31,6 +31,24 @@ pub const Handler = struct {
|
||||
/// The terminal state to modify.
|
||||
terminal: *Terminal,
|
||||
|
||||
/// Callbacks for certain effects that handlers may have. These
|
||||
/// may or may not fully replace internal handling of certain effects,
|
||||
/// but they allow for the handler to trigger or query external
|
||||
/// effects.
|
||||
effects: Effects = .readonly,
|
||||
|
||||
pub const Effects = struct {
|
||||
/// Called when the bell is rung (BEL).
|
||||
bell: ?*const fn (*Handler) void,
|
||||
|
||||
/// No effects means that the stream effectively becomes readonly
|
||||
/// that only affects pure terminal state and ignores all side
|
||||
/// effects beyond that.
|
||||
pub const readonly: Effects = .{
|
||||
.bell = null,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn init(terminal: *Terminal) Handler {
|
||||
return .{
|
||||
.terminal = terminal,
|
||||
@@ -170,6 +188,9 @@ pub const Handler = struct {
|
||||
.color_operation => try self.colorOperation(value.op, &value.requests),
|
||||
.kitty_color_report => try self.kittyColorOperation(value),
|
||||
|
||||
// Effect-based handlers
|
||||
.bell => self.bell(),
|
||||
|
||||
// No supported DCS commands have any terminal-modifying effects,
|
||||
// but they may in the future. For now we just ignore it.
|
||||
.dcs_hook,
|
||||
@@ -185,7 +206,6 @@ pub const Handler = struct {
|
||||
=> {},
|
||||
|
||||
// Have no terminal-modifying effect
|
||||
.bell,
|
||||
.enquiry,
|
||||
.request_mode,
|
||||
.request_mode_unknown,
|
||||
@@ -205,6 +225,11 @@ pub const Handler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
inline fn bell(self: *Handler) void {
|
||||
const func = self.effects.bell orelse return;
|
||||
func(self);
|
||||
}
|
||||
|
||||
inline fn horizontalTab(self: *Handler, count: u16) void {
|
||||
for (0..count) |_| {
|
||||
const x = self.terminal.screens.active.cursor.x;
|
||||
@@ -1001,6 +1026,50 @@ test "semantic prompt end_prompt_start_input_terminate_eol clears on linefeed" {
|
||||
try testing.expectEqual(.output, t.screens.active.cursor.semantic_content);
|
||||
}
|
||||
|
||||
test "bell effect callback" {
|
||||
var t: Terminal = try .init(testing.allocator, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Test bell with null callback (default readonly effects) doesn't crash
|
||||
{
|
||||
var s: Stream = .initAlloc(testing.allocator, .init(&t));
|
||||
defer s.deinit();
|
||||
|
||||
s.nextSlice("\x07");
|
||||
|
||||
// Terminal should still be functional after bell
|
||||
s.nextSlice("AfterBell");
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("AfterBell", str);
|
||||
}
|
||||
|
||||
t.fullReset();
|
||||
|
||||
// Test bell with a callback
|
||||
{
|
||||
const S = struct {
|
||||
var bell_count: usize = 0;
|
||||
fn bell(_: *Handler) void {
|
||||
bell_count += 1;
|
||||
}
|
||||
};
|
||||
S.bell_count = 0;
|
||||
|
||||
var handler: Handler = .init(&t);
|
||||
handler.effects.bell = &S.bell;
|
||||
|
||||
var s: Stream = .initAlloc(testing.allocator, handler);
|
||||
defer s.deinit();
|
||||
|
||||
s.nextSlice("\x07");
|
||||
try testing.expectEqual(@as(usize, 1), S.bell_count);
|
||||
|
||||
s.nextSlice("\x07\x07");
|
||||
try testing.expectEqual(@as(usize, 3), S.bell_count);
|
||||
}
|
||||
}
|
||||
|
||||
test "stream: CSI W with intermediate but no params" {
|
||||
// Regression test from AFL++ crash. CSI ? W without
|
||||
// parameters caused an out-of-bounds access on input.params[0].
|
||||
|
||||
Reference in New Issue
Block a user