mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
when cols grow, move cursor if it unwraps the line it is on
This commit is contained in:
@ -29,7 +29,6 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const color = @import("color.zig");
|
const color = @import("color.zig");
|
||||||
const point = @import("point.zig");
|
const point = @import("point.zig");
|
||||||
const Point = point.Point;
|
|
||||||
const Selection = @import("Selection.zig");
|
const Selection = @import("Selection.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.screen);
|
const log = std.log.scoped(.screen);
|
||||||
@ -485,13 +484,20 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
);
|
);
|
||||||
std.mem.set(Cell, storage, .{ .char = 0 });
|
std.mem.set(Cell, storage, .{ .char = 0 });
|
||||||
|
|
||||||
|
// Convert our cursor coordinates to screen coordinates because
|
||||||
|
// we may have to reflow the cursor if the line it is on is unwrapped.
|
||||||
|
const cursor_pos = (point.Viewport{
|
||||||
|
.x = self.cursor.x,
|
||||||
|
.y = self.cursor.y,
|
||||||
|
}).toScreen(self);
|
||||||
|
|
||||||
// Nothing can fail from this point forward (no "try" expressions)
|
// Nothing can fail from this point forward (no "try" expressions)
|
||||||
// so replace our storage. We defer freeing the "old" value because
|
// so replace our storage. We defer freeing the "old" value because
|
||||||
// we need to access the old screen to copy.
|
// we need to access the old screen to copy.
|
||||||
var old = self.*;
|
var old = self.*;
|
||||||
defer {
|
defer {
|
||||||
assert(old.storage.ptr != self.storage.ptr);
|
assert(old.storage.ptr != self.storage.ptr);
|
||||||
old.deinit(alloc);
|
alloc.free(old.storage);
|
||||||
}
|
}
|
||||||
self.storage = storage;
|
self.storage = storage;
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
@ -514,9 +520,15 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
// The goal is to keep the messiness of reflow down here and
|
// The goal is to keep the messiness of reflow down here and
|
||||||
// only reloop when we're back to clean non-wrapped lines.
|
// only reloop when we're back to clean non-wrapped lines.
|
||||||
|
|
||||||
|
// Whether we need to move the cursor or not
|
||||||
|
var new_cursor: ?point.ScreenPoint = null;
|
||||||
|
|
||||||
// Mark the last element as not wrapped
|
// Mark the last element as not wrapped
|
||||||
new_row[row.len - 1].attrs.wrap = 0;
|
new_row[row.len - 1].attrs.wrap = 0;
|
||||||
new_row = new_row[row.len..];
|
|
||||||
|
// We maintain an x coord so that we can set cursors properly
|
||||||
|
var x: usize = row.len;
|
||||||
|
new_row = new_row[x..];
|
||||||
wrapping: while (iter.next()) |wrapped_row| {
|
wrapping: while (iter.next()) |wrapped_row| {
|
||||||
var wrapped_rem = wrapped_row;
|
var wrapped_rem = wrapped_row;
|
||||||
while (wrapped_rem.len > 0) {
|
while (wrapped_rem.len > 0) {
|
||||||
@ -525,6 +537,13 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
// Copy the row
|
// Copy the row
|
||||||
std.mem.copy(Cell, new_row, wrapped_rem);
|
std.mem.copy(Cell, new_row, wrapped_rem);
|
||||||
|
|
||||||
|
// If our cursor is in this line, then we have to move it
|
||||||
|
// onto the new line because it got unwrapped.
|
||||||
|
if (cursor_pos.y == iter.value - 1) {
|
||||||
|
assert(new_cursor == null); // should only happen once
|
||||||
|
new_cursor = .{ .y = y, .x = cursor_pos.x + x };
|
||||||
|
}
|
||||||
|
|
||||||
// If this row isn't also wrapped, we're done!
|
// If this row isn't also wrapped, we're done!
|
||||||
if (wrapped_rem[wrapped_rem.len - 1].attrs.wrap == 0) {
|
if (wrapped_rem[wrapped_rem.len - 1].attrs.wrap == 0) {
|
||||||
y += 1;
|
y += 1;
|
||||||
@ -534,6 +553,7 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
// Wrapped again!
|
// Wrapped again!
|
||||||
new_row[wrapped_rem.len - 1].attrs.wrap = 0;
|
new_row[wrapped_rem.len - 1].attrs.wrap = 0;
|
||||||
new_row = new_row[wrapped_rem.len..];
|
new_row = new_row[wrapped_rem.len..];
|
||||||
|
x += wrapped_rem.len;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,11 +565,30 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
// We still need to copy the remainder
|
// We still need to copy the remainder
|
||||||
wrapped_rem = wrapped_rem[new_row.len..];
|
wrapped_rem = wrapped_rem[new_row.len..];
|
||||||
|
|
||||||
|
// We need to check if our cursor was on this line
|
||||||
|
// and in the part that WAS copied. If so, we need to move it.
|
||||||
|
if (cursor_pos.y == iter.value - 1 and
|
||||||
|
cursor_pos.x < new_row.len)
|
||||||
|
{
|
||||||
|
if (true) @panic("IN FLOW"); // TODO: to test
|
||||||
|
assert(new_cursor == null); // should only happen once
|
||||||
|
new_cursor = .{ .y = y, .x = x + cursor_pos.x };
|
||||||
|
}
|
||||||
|
|
||||||
// Move to a new line in our new screen
|
// Move to a new line in our new screen
|
||||||
y += 1;
|
y += 1;
|
||||||
|
x = 0;
|
||||||
new_row = self.getRow(.{ .screen = y });
|
new_row = self.getRow(.{ .screen = y });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a new cursor, we need to convert that to a viewport
|
||||||
|
// point and set it up.
|
||||||
|
if (new_cursor) |pos| {
|
||||||
|
const viewport_pos = pos.toViewport(self);
|
||||||
|
self.cursor.x = viewport_pos.x;
|
||||||
|
self.cursor.y = viewport_pos.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,7 +650,7 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
var old = self.*;
|
var old = self.*;
|
||||||
defer {
|
defer {
|
||||||
assert(old.storage.ptr != self.storage.ptr);
|
assert(old.storage.ptr != self.storage.ptr);
|
||||||
old.deinit(alloc);
|
alloc.free(old.storage);
|
||||||
}
|
}
|
||||||
self.storage = storage;
|
self.storage = storage;
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
@ -647,6 +686,7 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
if (y >= self.rows) {
|
if (y >= self.rows) {
|
||||||
self.scroll(.{ .delta = 1 });
|
self.scroll(.{ .delta = 1 });
|
||||||
y -= 1;
|
y -= 1;
|
||||||
|
x = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the old cell, unset the old wrap state
|
// Copy the old cell, unset the old wrap state
|
||||||
@ -1180,8 +1220,12 @@ test "Screen: resize more rows no scrollback" {
|
|||||||
defer s.deinit(alloc);
|
defer s.deinit(alloc);
|
||||||
const str = "1ABCD\n2EFGH\n3IJKL";
|
const str = "1ABCD\n2EFGH\n3IJKL";
|
||||||
s.testWriteString(str);
|
s.testWriteString(str);
|
||||||
|
const cursor = s.cursor;
|
||||||
try s.resize(alloc, 10, 5);
|
try s.resize(alloc, 10, 5);
|
||||||
|
|
||||||
|
// Cursor should not move
|
||||||
|
try testing.expectEqual(cursor, s.cursor);
|
||||||
|
|
||||||
{
|
{
|
||||||
var contents = try s.testString(alloc, .viewport);
|
var contents = try s.testString(alloc, .viewport);
|
||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
@ -1202,9 +1246,13 @@ test "Screen: resize more rows with empty scrollback" {
|
|||||||
defer s.deinit(alloc);
|
defer s.deinit(alloc);
|
||||||
const str = "1ABCD\n2EFGH\n3IJKL";
|
const str = "1ABCD\n2EFGH\n3IJKL";
|
||||||
s.testWriteString(str);
|
s.testWriteString(str);
|
||||||
|
const cursor = s.cursor;
|
||||||
try s.resize(alloc, 10, 5);
|
try s.resize(alloc, 10, 5);
|
||||||
try testing.expectEqual(@as(usize, 20), s.totalRows());
|
try testing.expectEqual(@as(usize, 20), s.totalRows());
|
||||||
|
|
||||||
|
// Cursor should not move
|
||||||
|
try testing.expectEqual(cursor, s.cursor);
|
||||||
|
|
||||||
{
|
{
|
||||||
var contents = try s.testString(alloc, .viewport);
|
var contents = try s.testString(alloc, .viewport);
|
||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
@ -1233,9 +1281,13 @@ test "Screen: resize more rows with populated scrollback" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
|
const cursor = s.cursor;
|
||||||
try s.resize(alloc, 10, 5);
|
try s.resize(alloc, 10, 5);
|
||||||
try testing.expectEqual(@as(usize, 15), s.totalRows());
|
try testing.expectEqual(@as(usize, 15), s.totalRows());
|
||||||
|
|
||||||
|
// Cursor should not move
|
||||||
|
try testing.expectEqual(cursor, s.cursor);
|
||||||
|
|
||||||
{
|
{
|
||||||
var contents = try s.testString(alloc, .viewport);
|
var contents = try s.testString(alloc, .viewport);
|
||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
@ -1251,8 +1303,12 @@ test "Screen: resize more cols no reflow" {
|
|||||||
defer s.deinit(alloc);
|
defer s.deinit(alloc);
|
||||||
const str = "1ABCD\n2EFGH\n3IJKL";
|
const str = "1ABCD\n2EFGH\n3IJKL";
|
||||||
s.testWriteString(str);
|
s.testWriteString(str);
|
||||||
|
const cursor = s.cursor;
|
||||||
try s.resize(alloc, 3, 10);
|
try s.resize(alloc, 3, 10);
|
||||||
|
|
||||||
|
// Cursor should not move
|
||||||
|
try testing.expectEqual(cursor, s.cursor);
|
||||||
|
|
||||||
{
|
{
|
||||||
var contents = try s.testString(alloc, .viewport);
|
var contents = try s.testString(alloc, .viewport);
|
||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
@ -1282,6 +1338,11 @@ test "Screen: resize more cols with reflow that fits full width" {
|
|||||||
try testing.expectEqualStrings(expected, contents);
|
try testing.expectEqualStrings(expected, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let's put our cursor on row 2, where the soft wrap is
|
||||||
|
s.cursor.x = 0;
|
||||||
|
s.cursor.y = 1;
|
||||||
|
try testing.expectEqual(@as(u32, '2'), s.getCell(s.cursor.y, s.cursor.x).char);
|
||||||
|
|
||||||
// Resize and verify we undid the soft wrap because we have space now
|
// Resize and verify we undid the soft wrap because we have space now
|
||||||
try s.resize(alloc, 3, 10);
|
try s.resize(alloc, 3, 10);
|
||||||
{
|
{
|
||||||
@ -1289,6 +1350,10 @@ test "Screen: resize more cols with reflow that fits full width" {
|
|||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
try testing.expectEqualStrings(str, contents);
|
try testing.expectEqualStrings(str, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Our cursor should've moved
|
||||||
|
try testing.expectEqual(@as(usize, 5), s.cursor.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.cursor.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Screen: resize more cols with reflow that forces more wrapping" {
|
test "Screen: resize more cols with reflow that forces more wrapping" {
|
||||||
|
@ -76,6 +76,24 @@ pub const ScreenPoint = struct {
|
|||||||
(self.y == other.y and self.x < other.x);
|
(self.y == other.y and self.x < other.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.visible_offset) return .{ .x = 0, .y = 0 };
|
||||||
|
|
||||||
|
// After viewport
|
||||||
|
if (self.y > screen.visible_offset + screen.rows) return .{
|
||||||
|
.x = screen.cols - 1,
|
||||||
|
.y = screen.rows - 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{ .x = self.x, .y = self.y - screen.visible_offset };
|
||||||
|
}
|
||||||
|
|
||||||
test "before" {
|
test "before" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user