cleanup by just scrolling in the renderer

This commit is contained in:
Mitchell Hashimoto
2026-02-19 14:06:52 -08:00
parent 263469755c
commit eb335fb8dd
7 changed files with 33 additions and 39 deletions

View File

@@ -1070,12 +1070,6 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.scrollbar => |scrollbar| self.updateScrollbar(scrollbar),
.scroll_to_bottom => {
self.queueIo(.{
.scroll_viewport = .{ .bottom = {} },
}, .unlocked);
},
.present_surface => try self.presentSurface(),
.password_input => |v| try self.passwordInput(v),
@@ -1180,7 +1174,7 @@ fn selectionScrollTick(self: *Surface) !void {
}
// Scroll the viewport as required
try t.scrollViewport(.{ .delta = delta });
t.scrollViewport(.{ .delta = delta });
// Next, trigger our drag behavior
const pin = t.screens.active.pages.pin(.{
@@ -2785,7 +2779,7 @@ pub fn keyCallback(
try self.setSelection(null);
}
if (self.config.scroll_to_bottom.keystroke) try self.io.terminal.scrollViewport(.bottom);
if (self.config.scroll_to_bottom.keystroke) self.io.terminal.scrollViewport(.bottom);
try self.queueRender();
}
@@ -3538,7 +3532,7 @@ pub fn scrollCallback(
// Modify our viewport, this requires a lock since it affects
// rendering. We have to switch signs here because our delta
// is negative down but our viewport is positive down.
try self.io.terminal.scrollViewport(.{ .delta = y.delta * -1 });
self.io.terminal.scrollViewport(.{ .delta = y.delta * -1 });
}
}
@@ -5069,7 +5063,7 @@ pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordin
///
/// Precondition: the render_state mutex must be held.
fn scrollToBottom(self: *Surface) !void {
try self.io.terminal.scrollViewport(.{ .bottom = {} });
self.io.terminal.scrollViewport(.{ .bottom = {} });
try self.queueRender();
}

View File

@@ -108,10 +108,6 @@ pub const Message = union(enum) {
/// Selected search index change
search_selected: ?usize,
/// Scroll the viewport to the bottom. This is triggered by the renderer
/// when new output is detected and scroll-to-bottom on output is enabled.
scroll_to_bottom,
pub const ReportTitleStyle = enum {
csi_21_t,

View File

@@ -128,7 +128,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// Tracks the last bottom-right pin of the screen to detect new output.
/// When the final line changes (node or y differs), new content was added.
/// Used for scroll-to-bottom on output feature.
last_bottom_node: ?*terminal.PageList.List.Node,
last_bottom_node: ?usize,
last_bottom_y: terminal.size.CellCountInt,
/// The most recent viewport matches so that we can render search
@@ -1176,6 +1176,26 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
return;
}
// If scroll-to-bottom on output is enabled, check if the final line
// changed by comparing the bottom-right pin. If the node pointer or
// y offset changed, new content was added to the screen.
// Update this BEFORE we update our render state so we can
// draw the new scrolled data immediately.
if (self.config.scroll_to_bottom_on_output) scroll: {
const br = state.terminal.screens.active.pages.getBottomRight(.screen) orelse break :scroll;
// If the pin hasn't changed, then don't scroll.
if (self.last_bottom_node == @intFromPtr(br.node) and
self.last_bottom_y == br.y) break :scroll;
// Update tracked pin state for next frame
self.last_bottom_node = @intFromPtr(br.node);
self.last_bottom_y = br.y;
// Scroll
state.terminal.scrollViewport(.bottom);
}
// Update our terminal state
try self.terminal_state.update(self.alloc, state.terminal);
@@ -1192,25 +1212,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// cross-thread mailbox message within the IO path.
const scrollbar = state.terminal.screens.active.pages.scrollbar();
// If scroll-to-bottom on output is enabled, check if the final line
// changed by comparing the bottom-right pin. If the node pointer or
// y offset changed, new content was added to the screen.
if (self.config.scroll_to_bottom_on_output) {
const bottom_right = state.terminal.screens.active.pages.getBottomRight(.screen);
if (bottom_right) |br| {
const pin_changed = (self.last_bottom_node != br.node) or
(self.last_bottom_y != br.y);
if (pin_changed and !state.terminal.screens.active.viewportIsBottom()) {
_ = self.surface_mailbox.push(.scroll_to_bottom, .instant);
}
// Update tracked pin state for next frame
self.last_bottom_node = br.node;
self.last_bottom_y = br.y;
}
}
// Get our preedit state
const preedit: ?renderer.State.Preedit = preedit: {
const p = state.preedit orelse break :preedit null;

View File

@@ -1625,7 +1625,7 @@ pub const ScrollViewport = union(enum) {
};
/// Scroll the viewport of the terminal grid.
pub fn scrollViewport(self: *Terminal, behavior: ScrollViewport) !void {
pub fn scrollViewport(self: *Terminal, behavior: ScrollViewport) void {
self.screens.active.scroll(switch (behavior) {
.top => .{ .top = {} },
.bottom => .{ .active = {} },

View File

@@ -641,10 +641,13 @@ pub fn clearScreen(self: *Termio, td: *ThreadData, history: bool) !void {
}
/// Scroll the viewport
pub fn scrollViewport(self: *Termio, scroll: terminalpkg.Terminal.ScrollViewport) !void {
pub fn scrollViewport(
self: *Termio,
scroll: terminalpkg.Terminal.ScrollViewport,
) void {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
try self.terminal.scrollViewport(scroll);
self.terminal.scrollViewport(scroll);
}
/// Jump the viewport to the prompt.

View File

@@ -321,7 +321,7 @@ fn drainMailbox(
.resize => |v| self.handleResize(cb, v),
.size_report => |v| try io.sizeReport(data, v),
.clear_screen => |v| try io.clearScreen(data, v.history),
.scroll_viewport => |v| try io.scrollViewport(v),
.scroll_viewport => |v| io.scrollViewport(v),
.selection_scroll => |v| {
if (v) {
self.startScrollTimer(cb);

View File

@@ -232,7 +232,7 @@ pub const StreamHandler = struct {
.erase_display_below => self.terminal.eraseDisplay(.below, value),
.erase_display_above => self.terminal.eraseDisplay(.above, value),
.erase_display_complete => {
try self.terminal.scrollViewport(.{ .bottom = {} });
self.terminal.scrollViewport(.{ .bottom = {} });
self.terminal.eraseDisplay(.complete, value);
},
.erase_display_scrollback => self.terminal.eraseDisplay(.scrollback, value),