mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00

Fixes #1159 The cursor position is an "active" coordinate (defined at the top of Screen.zig), but our resize was treating it as a "viewport" coordinate.
255 lines
8.0 KiB
Zig
255 lines
8.0 KiB
Zig
const std = @import("std");
|
|
const terminal = @import("main.zig");
|
|
const Screen = terminal.Screen;
|
|
|
|
// This file contains various types to represent x/y coordinates. We
|
|
// use different types so that we can lean on type-safety to get the
|
|
// exact expected type of point.
|
|
|
|
/// Active is a point within the active part of the screen.
|
|
pub const Active = struct {
|
|
x: usize = 0,
|
|
y: usize = 0,
|
|
|
|
pub fn toScreen(self: Active, screen: *const Screen) ScreenPoint {
|
|
return .{
|
|
.x = self.x,
|
|
.y = screen.history + self.y,
|
|
};
|
|
}
|
|
|
|
test "toScreen with scrollback" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 3, 5, 3);
|
|
defer s.deinit();
|
|
const str = "1\n2\n3\n4\n5\n6\n7\n8";
|
|
try s.testWriteString(str);
|
|
|
|
try testing.expectEqual(ScreenPoint{
|
|
.x = 1,
|
|
.y = 5,
|
|
}, (Active{ .x = 1, .y = 2 }).toScreen(&s));
|
|
}
|
|
};
|
|
|
|
/// Viewport is a point within the viewport of the screen.
|
|
pub const Viewport = struct {
|
|
x: usize = 0,
|
|
y: usize = 0,
|
|
|
|
pub fn toScreen(self: Viewport, screen: *const Screen) ScreenPoint {
|
|
// x is unchanged, y we have to add the visible offset to
|
|
// get the full offset from the top.
|
|
return .{
|
|
.x = self.x,
|
|
.y = screen.viewport + self.y,
|
|
};
|
|
}
|
|
|
|
pub fn eql(self: Viewport, other: Viewport) bool {
|
|
return self.x == other.x and self.y == other.y;
|
|
}
|
|
|
|
test "toScreen with no scrollback" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 3, 5, 0);
|
|
defer s.deinit();
|
|
|
|
try testing.expectEqual(ScreenPoint{
|
|
.x = 1,
|
|
.y = 1,
|
|
}, (Viewport{ .x = 1, .y = 1 }).toScreen(&s));
|
|
}
|
|
|
|
test "toScreen with scrollback" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 3, 5, 3);
|
|
defer s.deinit();
|
|
|
|
// At the bottom
|
|
try s.scroll(.{ .screen = 6 });
|
|
try testing.expectEqual(ScreenPoint{
|
|
.x = 0,
|
|
.y = 3,
|
|
}, (Viewport{ .x = 0, .y = 0 }).toScreen(&s));
|
|
|
|
// Move the viewport a bit up
|
|
try s.scroll(.{ .screen = -1 });
|
|
try testing.expectEqual(ScreenPoint{
|
|
.x = 0,
|
|
.y = 2,
|
|
}, (Viewport{ .x = 0, .y = 0 }).toScreen(&s));
|
|
|
|
// Move the viewport to top
|
|
try s.scroll(.{ .top = {} });
|
|
try testing.expectEqual(ScreenPoint{
|
|
.x = 0,
|
|
.y = 0,
|
|
}, (Viewport{ .x = 0, .y = 0 }).toScreen(&s));
|
|
}
|
|
};
|
|
|
|
/// A screen point. This is offset from the top of the scrollback
|
|
/// buffer. If the screen is scrolled or resized, this will have to
|
|
/// be recomputed.
|
|
pub const ScreenPoint = struct {
|
|
x: usize = 0,
|
|
y: usize = 0,
|
|
|
|
/// Returns if this point is before another point.
|
|
pub fn before(self: ScreenPoint, other: ScreenPoint) bool {
|
|
return self.y < other.y or
|
|
(self.y == other.y and self.x < other.x);
|
|
}
|
|
|
|
/// Returns if two points are equal.
|
|
pub fn eql(self: ScreenPoint, other: ScreenPoint) bool {
|
|
return self.x == other.x and self.y == other.y;
|
|
}
|
|
|
|
/// Returns true if this screen point is currently in the active viewport.
|
|
pub fn inViewport(self: ScreenPoint, screen: *const Screen) bool {
|
|
return self.y >= screen.viewport and
|
|
self.y < screen.viewport + screen.rows;
|
|
}
|
|
|
|
/// Converts this to a viewport point. If the point is above the
|
|
/// viewport this will move the point to (0, 0) and if it is below
|
|
/// the viewport it'll move it to (cols - 1, rows - 1).
|
|
pub fn toViewport(self: ScreenPoint, screen: *const Screen) Viewport {
|
|
// TODO: test
|
|
|
|
// Before viewport
|
|
if (self.y < screen.viewport) return .{ .x = 0, .y = 0 };
|
|
|
|
// After viewport
|
|
if (self.y > screen.viewport + screen.rows) return .{
|
|
.x = screen.cols - 1,
|
|
.y = screen.rows - 1,
|
|
};
|
|
|
|
return .{ .x = self.x, .y = self.y - screen.viewport };
|
|
}
|
|
|
|
/// Returns a screen point iterator. This will iterate over all of
|
|
/// of the points in a screen in a given direction one by one.
|
|
///
|
|
/// The iterator is only valid as long as the screen is not resized.
|
|
pub fn iterator(
|
|
self: ScreenPoint,
|
|
screen: *const Screen,
|
|
dir: Direction,
|
|
) Iterator {
|
|
return .{ .screen = screen, .current = self, .direction = dir };
|
|
}
|
|
|
|
pub const Iterator = struct {
|
|
screen: *const Screen,
|
|
current: ?ScreenPoint,
|
|
direction: Direction,
|
|
|
|
pub fn next(self: *Iterator) ?ScreenPoint {
|
|
const current = self.current orelse return null;
|
|
self.current = switch (self.direction) {
|
|
.left_up => left_up: {
|
|
if (current.x == 0) {
|
|
if (current.y == 0) break :left_up null;
|
|
break :left_up .{
|
|
.x = self.screen.cols - 1,
|
|
.y = current.y - 1,
|
|
};
|
|
}
|
|
|
|
break :left_up .{
|
|
.x = current.x - 1,
|
|
.y = current.y,
|
|
};
|
|
},
|
|
|
|
.right_down => right_down: {
|
|
if (current.x == self.screen.cols - 1) {
|
|
const max = self.screen.rows + self.screen.max_scrollback;
|
|
if (current.y == max - 1) break :right_down null;
|
|
break :right_down .{
|
|
.x = 0,
|
|
.y = current.y + 1,
|
|
};
|
|
}
|
|
|
|
break :right_down .{
|
|
.x = current.x + 1,
|
|
.y = current.y,
|
|
};
|
|
},
|
|
};
|
|
|
|
return current;
|
|
}
|
|
};
|
|
|
|
test "before" {
|
|
const testing = std.testing;
|
|
|
|
const p: ScreenPoint = .{ .x = 5, .y = 2 };
|
|
try testing.expect(p.before(.{ .x = 6, .y = 2 }));
|
|
try testing.expect(p.before(.{ .x = 3, .y = 3 }));
|
|
}
|
|
|
|
test "iterator" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 5, 5, 0);
|
|
defer s.deinit();
|
|
|
|
// Back from the first line
|
|
{
|
|
var pt: ScreenPoint = .{ .x = 1, .y = 0 };
|
|
var it = pt.iterator(&s, .left_up);
|
|
try testing.expectEqual(ScreenPoint{ .x = 1, .y = 0 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 0, .y = 0 }, it.next().?);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Back from second line
|
|
{
|
|
var pt: ScreenPoint = .{ .x = 1, .y = 1 };
|
|
var it = pt.iterator(&s, .left_up);
|
|
try testing.expectEqual(ScreenPoint{ .x = 1, .y = 1 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 0, .y = 1 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 4, .y = 0 }, it.next().?);
|
|
}
|
|
|
|
// Forward last line
|
|
{
|
|
var pt: ScreenPoint = .{ .x = 3, .y = 4 };
|
|
var it = pt.iterator(&s, .right_down);
|
|
try testing.expectEqual(ScreenPoint{ .x = 3, .y = 4 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 4, .y = 4 }, it.next().?);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Forward not last line
|
|
{
|
|
var pt: ScreenPoint = .{ .x = 3, .y = 3 };
|
|
var it = pt.iterator(&s, .right_down);
|
|
try testing.expectEqual(ScreenPoint{ .x = 3, .y = 3 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 4, .y = 3 }, it.next().?);
|
|
try testing.expectEqual(ScreenPoint{ .x = 0, .y = 4 }, it.next().?);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Direction that points can go.
|
|
pub const Direction = enum { left_up, right_down };
|
|
|
|
test {
|
|
std.testing.refAllDecls(@This());
|
|
}
|