mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
@@ -1174,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(.{
|
||||
@@ -2779,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();
|
||||
}
|
||||
@@ -3532,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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5063,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -906,7 +906,7 @@ palette: Palette = .{},
|
||||
/// anything but modifiers or keybinds that are processed by Ghostty).
|
||||
///
|
||||
/// - `output` If set, scroll the surface to the bottom if there is new data
|
||||
/// to display. (Currently unimplemented.)
|
||||
/// to display (e.g., when new lines are printed to the terminal).
|
||||
///
|
||||
/// The default is `keystroke, no-output`.
|
||||
@"scroll-to-bottom": ScrollToBottom = .default,
|
||||
|
||||
@@ -143,7 +143,7 @@ test "cursor: always block with preedit" {
|
||||
|
||||
// If we're scrolled though, then we don't show the cursor.
|
||||
for (0..100) |_| try term.index();
|
||||
try term.scrollViewport(.{ .top = {} });
|
||||
term.scrollViewport(.{ .top = {} });
|
||||
try state.update(alloc, &term);
|
||||
|
||||
// In any bool state
|
||||
|
||||
@@ -125,6 +125,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
scrollbar: terminal.Scrollbar,
|
||||
scrollbar_dirty: bool,
|
||||
|
||||
/// 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: ?usize,
|
||||
last_bottom_y: terminal.size.CellCountInt,
|
||||
|
||||
/// The most recent viewport matches so that we can render search
|
||||
/// matches in the visible frame. This is provided asynchronously
|
||||
/// from the search thread so we have the dirty flag to also note
|
||||
@@ -563,6 +569,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
colorspace: configpkg.Config.WindowColorspace,
|
||||
blending: configpkg.Config.AlphaBlending,
|
||||
background_blur: configpkg.Config.BackgroundBlur,
|
||||
scroll_to_bottom_on_output: bool,
|
||||
|
||||
pub fn init(
|
||||
alloc_gpa: Allocator,
|
||||
@@ -636,6 +643,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.colorspace = config.@"window-colorspace",
|
||||
.blending = config.@"alpha-blending",
|
||||
.background_blur = config.@"background-blur",
|
||||
.scroll_to_bottom_on_output = config.@"scroll-to-bottom".output,
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
@@ -699,6 +707,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.focused = true,
|
||||
.scrollbar = .zero,
|
||||
.scrollbar_dirty = false,
|
||||
.last_bottom_node = null,
|
||||
.last_bottom_y = 0,
|
||||
.search_matches = null,
|
||||
.search_selected_match = null,
|
||||
.search_matches_dirty = false,
|
||||
@@ -1166,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);
|
||||
|
||||
|
||||
@@ -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 = {} },
|
||||
|
||||
@@ -1092,7 +1092,7 @@ test "cursor state out of viewport" {
|
||||
try testing.expectEqual(1, state.cursor.viewport.?.y);
|
||||
|
||||
// Scroll the viewport
|
||||
try t.scrollViewport(.top);
|
||||
t.scrollViewport(.top);
|
||||
try state.update(alloc, &t);
|
||||
|
||||
// Set a style on the cursor
|
||||
|
||||
@@ -358,7 +358,7 @@ test "history search, no active area" {
|
||||
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
|
||||
try s.nextSlice("Buzz\r\nFizz");
|
||||
|
||||
try t.scrollViewport(.top);
|
||||
t.scrollViewport(.top);
|
||||
|
||||
var search: ViewportSearch = try .init(alloc, "Fizz");
|
||||
defer search.deinit();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user