mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
@ -81,12 +81,14 @@ pub const Shaper = struct {
|
||||
group: *GroupCache,
|
||||
row: terminal.Screen.Row,
|
||||
selection: ?terminal.Selection,
|
||||
cursor_x: ?usize,
|
||||
) font.shape.RunIterator {
|
||||
return .{
|
||||
.hooks = .{ .shaper = self },
|
||||
.group = group,
|
||||
.row = row,
|
||||
.selection = selection,
|
||||
.cursor_x = cursor_x,
|
||||
};
|
||||
}
|
||||
|
||||
@ -169,7 +171,7 @@ test "run iterator" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
try testing.expectEqual(@as(usize, 1), count);
|
||||
@ -182,7 +184,7 @@ test "run iterator" {
|
||||
try screen.testWriteString("ABCD EFG");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
try testing.expectEqual(@as(usize, 1), count);
|
||||
@ -196,7 +198,7 @@ test "run iterator" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| {
|
||||
count += 1;
|
||||
@ -230,7 +232,7 @@ test "run iterator: empty cells with background set" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -265,7 +267,7 @@ test "shape" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -288,7 +290,7 @@ test "shape inconsolata ligs" {
|
||||
try screen.testWriteString(">=");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -305,7 +307,7 @@ test "shape inconsolata ligs" {
|
||||
try screen.testWriteString("===");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -330,7 +332,7 @@ test "shape emoji width" {
|
||||
try screen.testWriteString("👍");
|
||||
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -364,7 +366,7 @@ test "shape emoji width long" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -395,7 +397,7 @@ test "shape variation selector VS15" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -426,7 +428,7 @@ test "shape variation selector VS16" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -454,7 +456,7 @@ test "shape with empty cells in between" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -486,7 +488,7 @@ test "shape Chinese characters" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -527,7 +529,7 @@ test "shape box glyphs" {
|
||||
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null);
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -561,7 +563,7 @@ test "shape selection boundary" {
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
|
||||
.start = .{ .x = 0, .y = 0 },
|
||||
.end = .{ .x = screen.cols - 1, .y = 0 },
|
||||
});
|
||||
}, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -577,7 +579,7 @@ test "shape selection boundary" {
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
|
||||
.start = .{ .x = 2, .y = 0 },
|
||||
.end = .{ .x = screen.cols - 1, .y = 0 },
|
||||
});
|
||||
}, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -593,7 +595,7 @@ test "shape selection boundary" {
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
|
||||
.start = .{ .x = 0, .y = 0 },
|
||||
.end = .{ .x = 3, .y = 0 },
|
||||
});
|
||||
}, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -609,7 +611,7 @@ test "shape selection boundary" {
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
|
||||
.start = .{ .x = 1, .y = 0 },
|
||||
.end = .{ .x = 3, .y = 0 },
|
||||
});
|
||||
}, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -625,7 +627,7 @@ test "shape selection boundary" {
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
|
||||
.start = .{ .x = 1, .y = 0 },
|
||||
.end = .{ .x = 1, .y = 0 },
|
||||
});
|
||||
}, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
@ -635,6 +637,71 @@ test "shape selection boundary" {
|
||||
}
|
||||
}
|
||||
|
||||
test "shape cursor boundary" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var testdata = try testShaper(alloc);
|
||||
defer testdata.deinit();
|
||||
|
||||
// Make a screen with some data
|
||||
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
|
||||
defer screen.deinit();
|
||||
try screen.testWriteString("a1b2c3d4e5");
|
||||
|
||||
// No cursor is full line
|
||||
{
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
_ = try shaper.shape(run);
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 1), count);
|
||||
}
|
||||
|
||||
// Cursor at index 0 is two runs
|
||||
{
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
_ = try shaper.shape(run);
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 2), count);
|
||||
}
|
||||
|
||||
// Cursor at index 1 is three runs
|
||||
{
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
_ = try shaper.shape(run);
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 3), count);
|
||||
}
|
||||
|
||||
// Cursor at last col is two runs
|
||||
{
|
||||
// Get our run iterator
|
||||
var shaper = testdata.shaper;
|
||||
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 9);
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
count += 1;
|
||||
_ = try shaper.shape(run);
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 2), count);
|
||||
}
|
||||
}
|
||||
|
||||
const TestShaper = struct {
|
||||
alloc: Allocator,
|
||||
shaper: Shaper,
|
||||
|
@ -29,6 +29,7 @@ pub const RunIterator = struct {
|
||||
group: *font.GroupCache,
|
||||
row: terminal.Screen.Row,
|
||||
selection: ?terminal.Selection = null,
|
||||
cursor_x: ?usize = null,
|
||||
i: usize = 0,
|
||||
|
||||
pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
|
||||
@ -67,6 +68,32 @@ pub const RunIterator = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// If our cursor is on this line then we break the run around the
|
||||
// cursor. This means that any row with a cursor has at least
|
||||
// three breaks: before, exactly the cursor, and after.
|
||||
if (self.cursor_x) |cursor_x| {
|
||||
// Exactly: self.i is the cursor and we iterated once. This
|
||||
// means that we started exactly at the cursor and did at
|
||||
// least one (probably exactly one) iteration. That is
|
||||
// exactly one character.
|
||||
if (self.i == cursor_x and j > self.i) {
|
||||
assert(j == self.i + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Before: up to and not including the cursor. This means
|
||||
// that we started before the cursor (self.i < cursor_x)
|
||||
// and j is now at the cursor meaning we haven't yet processed
|
||||
// the cursor.
|
||||
if (self.i < cursor_x and j == cursor_x) {
|
||||
assert(j > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
// After: after the cursor. We don't need to do anything
|
||||
// special, we just let the run complete.
|
||||
}
|
||||
|
||||
// If we're a spacer, then we ignore it
|
||||
if (cell.attrs.wide_spacer_tail) continue;
|
||||
|
||||
|
@ -60,12 +60,14 @@ pub const Shaper = struct {
|
||||
group: *font.GroupCache,
|
||||
row: terminal.Screen.Row,
|
||||
selection: ?terminal.Selection,
|
||||
cursor_x: ?usize,
|
||||
) font.shape.RunIterator {
|
||||
return .{
|
||||
.hooks = .{ .shaper = self },
|
||||
.group = group,
|
||||
.row = row,
|
||||
.selection = selection,
|
||||
.cursor_x = cursor_x,
|
||||
};
|
||||
}
|
||||
|
||||
@ -289,7 +291,7 @@ pub const Wasm = struct {
|
||||
while (rowIter.next()) |row| {
|
||||
defer y += 1;
|
||||
|
||||
var iter = self.runIterator(group, row, null);
|
||||
var iter = self.runIterator(group, row, null, null);
|
||||
while (try iter.next(alloc)) |run| {
|
||||
const cells = try self.shape(run);
|
||||
log.info("y={} run={d} shape={any} idx={}", .{
|
||||
|
@ -871,15 +871,31 @@ fn rebuildCells(
|
||||
while (rowIter.next()) |row| {
|
||||
defer y += 1;
|
||||
|
||||
// If this is the row with our cursor, then we may have to modify
|
||||
// the cell with the cursor.
|
||||
const start_i: usize = self.cells.items.len;
|
||||
defer if (draw_cursor and
|
||||
// True if this is the row with our cursor. There are a lot of conditions
|
||||
// here because the reasons we need to know this are primarily to invert.
|
||||
//
|
||||
// - If we aren't drawing the cursor (draw_cursor), then we don't need
|
||||
// to change our rendering.
|
||||
// - If the cursor is not visible, then we don't need to change rendering.
|
||||
// - If the cursor style is not a box, then we don't need to change
|
||||
// rendering because it'll never fully overlap a glyph.
|
||||
// - If the viewport is not at the bottom, then we don't need to
|
||||
// change rendering because the cursor is not visible.
|
||||
// (NOTE: this may not be fully correct, we may be scrolled
|
||||
// slightly up and the cursor may be visible)
|
||||
// - If this y doesn't match our cursor y then we don't need to
|
||||
// change rendering.
|
||||
//
|
||||
const cursor_row = draw_cursor and
|
||||
self.cursor_visible and
|
||||
self.cursor_style == .box and
|
||||
screen.viewportIsBottom() and
|
||||
y == screen.cursor.y)
|
||||
{
|
||||
y == screen.cursor.y;
|
||||
|
||||
// If this is the row with our cursor, then we may have to modify
|
||||
// the cell with the cursor.
|
||||
const start_i: usize = self.cells.items.len;
|
||||
defer if (cursor_row) {
|
||||
for (self.cells.items[start_i..]) |cell| {
|
||||
if (cell.grid_pos[0] == @as(f32, @floatFromInt(screen.cursor.x)) and
|
||||
cell.mode == .fg)
|
||||
@ -907,7 +923,12 @@ fn rebuildCells(
|
||||
};
|
||||
|
||||
// Split our row into runs and shape each one.
|
||||
var iter = self.font_shaper.runIterator(self.font_group, row, row_selection);
|
||||
var iter = self.font_shaper.runIterator(
|
||||
self.font_group,
|
||||
row,
|
||||
row_selection,
|
||||
if (cursor_row) screen.cursor.x else null,
|
||||
);
|
||||
while (try iter.next(self.alloc)) |run| {
|
||||
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
||||
if (self.updateCell(
|
||||
|
@ -903,15 +903,17 @@ pub fn rebuildCells(
|
||||
break :sel null;
|
||||
};
|
||||
|
||||
// If this is the row with our cursor, then we may have to modify
|
||||
// the cell with the cursor.
|
||||
const start_i: usize = self.cells.items.len;
|
||||
defer if (draw_cursor and
|
||||
// See Metal.zig
|
||||
const cursor_row = draw_cursor and
|
||||
self.cursor_visible and
|
||||
self.cursor_style == .box and
|
||||
screen.viewportIsBottom() and
|
||||
y == screen.cursor.y)
|
||||
{
|
||||
y == screen.cursor.y;
|
||||
|
||||
// If this is the row with our cursor, then we may have to modify
|
||||
// the cell with the cursor.
|
||||
const start_i: usize = self.cells.items.len;
|
||||
defer if (cursor_row) {
|
||||
for (self.cells.items[start_i..]) |cell| {
|
||||
if (cell.grid_col == screen.cursor.x and
|
||||
cell.mode == .fg)
|
||||
@ -942,7 +944,12 @@ pub fn rebuildCells(
|
||||
const start = self.cells.items.len;
|
||||
|
||||
// Split our row into runs and shape each one.
|
||||
var iter = self.font_shaper.runIterator(self.font_group, row, selection);
|
||||
var iter = self.font_shaper.runIterator(
|
||||
self.font_group,
|
||||
row,
|
||||
selection,
|
||||
if (cursor_row) screen.cursor.x else null,
|
||||
);
|
||||
while (try iter.next(self.alloc)) |run| {
|
||||
for (try self.font_shaper.shape(run)) |shaper_cell| {
|
||||
if (self.updateCell(
|
||||
|
Reference in New Issue
Block a user