From eeab85b9679920e4f23459171db8405157a589d7 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Tue, 18 Aug 2026 23:36:13 +0200 Subject: [PATCH 1/3] terminal: clear progress bar on full reset Emit a progress_report remove effect from the full_reset arm, matching kitty and WezTerm which both clear progress on reset. Previously, only the termio StreamHandler removed the progress bar on RIS (ghostty#10178); the terminal stream handler used by libghostty-vt did not, so an embedder's progress bar would outlive the reset. Signed-off-by: Fredrik Fornwall --- src/terminal/stream_terminal.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index 6af1c6f8a..5b6f08b2b 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -345,7 +345,12 @@ pub const Handler = struct { }, .active_status_display => self.terminal.status_display = value, .decaln => try self.terminal.decaln(), - .full_reset => self.terminal.fullReset(), + .full_reset => { + self.terminal.fullReset(); + + // Clear the progress bar + self.progressReport(.{ .state = .remove }); + }, .start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id), .end_hyperlink => self.terminal.screens.active.endHyperlink(), .semantic_prompt => try self.terminal.semanticPrompt(value), @@ -2504,6 +2509,15 @@ test "progress_report effect callback" { try testing.expectEqual(case.state, S.last_state); try testing.expectEqual(case.progress, S.last_progress); } + + // A full reset (RIS) removes any active progress bar. + s.nextSlice("\x1B]9;4;1;50\x1B\\"); + try testing.expectEqual(@as(usize, cases.len + 1), S.count); + try testing.expectEqual(osc.Command.ProgressReport.State.set, S.last_state); + s.nextSlice("\x1Bc"); + try testing.expectEqual(@as(usize, cases.len + 2), S.count); + try testing.expectEqual(osc.Command.ProgressReport.State.remove, S.last_state); + try testing.expectEqual(@as(?u8, null), S.last_progress); } test "clipboard_write effect callback" { From b56c6d88f81bd68d36a9dcb84fa43c1455df53b0 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 19 Aug 2026 00:04:41 +0200 Subject: [PATCH 2/3] terminal: only clear the progress bar on full reset if there is one --- src/terminal/stream_terminal.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index 5b6f08b2b..d7c02414e 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -71,6 +71,9 @@ pub const Handler = struct { /// The DCS command handler maintains state for DCS queries. dcs_handler: dcs.Handler = .{}, + /// Whether a OSC 9;4 progress bar is active. + progress_active: bool = false, + /// Called for sequence identifiers not supported by this library. /// Currently, only APC is reported. Content is borrowed and only valid /// for the duration of the callback. Set `apc_handler.unknown_max_bytes` @@ -348,8 +351,8 @@ pub const Handler = struct { .full_reset => { self.terminal.fullReset(); - // Clear the progress bar - self.progressReport(.{ .state = .remove }); + // Clear the progress bar if one is active. + if (self.progress_active) self.progressReport(.{ .state = .remove }); }, .start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id), .end_hyperlink => self.terminal.screens.active.endHyperlink(), @@ -501,6 +504,7 @@ pub const Handler = struct { } fn progressReport(self: *Handler, report: osc.Command.ProgressReport) void { + self.progress_active = report.state != .remove; const func = self.effects.progress_report orelse return; func(self, report); } @@ -2518,6 +2522,10 @@ test "progress_report effect callback" { try testing.expectEqual(@as(usize, cases.len + 2), S.count); try testing.expectEqual(osc.Command.ProgressReport.State.remove, S.last_state); try testing.expectEqual(@as(?u8, null), S.last_progress); + + // A full reset with no active progress bar reports nothing. + s.nextSlice("\x1Bc"); + try testing.expectEqual(@as(usize, cases.len + 2), S.count); } test "clipboard_write effect callback" { From b7ee3ab6b321470e0638d02aabb5043efdbe797b Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Thu, 20 Aug 2026 11:29:56 +0200 Subject: [PATCH 3/3] Revert "terminal: only clear the progress bar on full reset if there is one" This reverts commit b56c6d88f81bd68d36a9dcb84fa43c1455df53b0. --- src/terminal/stream_terminal.zig | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index d7c02414e..5b6f08b2b 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -71,9 +71,6 @@ pub const Handler = struct { /// The DCS command handler maintains state for DCS queries. dcs_handler: dcs.Handler = .{}, - /// Whether a OSC 9;4 progress bar is active. - progress_active: bool = false, - /// Called for sequence identifiers not supported by this library. /// Currently, only APC is reported. Content is borrowed and only valid /// for the duration of the callback. Set `apc_handler.unknown_max_bytes` @@ -351,8 +348,8 @@ pub const Handler = struct { .full_reset => { self.terminal.fullReset(); - // Clear the progress bar if one is active. - if (self.progress_active) self.progressReport(.{ .state = .remove }); + // Clear the progress bar + self.progressReport(.{ .state = .remove }); }, .start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id), .end_hyperlink => self.terminal.screens.active.endHyperlink(), @@ -504,7 +501,6 @@ pub const Handler = struct { } fn progressReport(self: *Handler, report: osc.Command.ProgressReport) void { - self.progress_active = report.state != .remove; const func = self.effects.progress_report orelse return; func(self, report); } @@ -2522,10 +2518,6 @@ test "progress_report effect callback" { try testing.expectEqual(@as(usize, cases.len + 2), S.count); try testing.expectEqual(osc.Command.ProgressReport.State.remove, S.last_state); try testing.expectEqual(@as(?u8, null), S.last_progress); - - // A full reset with no active progress bar reports nothing. - s.nextSlice("\x1Bc"); - try testing.expectEqual(@as(usize, cases.len + 2), S.count); } test "clipboard_write effect callback" {