mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
terminal: scrollDown with hyperlinks
This commit is contained in:
@ -5187,6 +5187,57 @@ test "Terminal: scrollDown simple" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Terminal: scrollDown hyperlink moves" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try init(alloc, .{ .rows = 5, .cols = 5 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
|
try t.screen.startHyperlink("http://example.com", null);
|
||||||
|
try t.printString("ABC");
|
||||||
|
t.screen.endHyperlink();
|
||||||
|
t.carriageReturn();
|
||||||
|
try t.linefeed();
|
||||||
|
try t.printString("DEF");
|
||||||
|
t.carriageReturn();
|
||||||
|
try t.linefeed();
|
||||||
|
try t.printString("GHI");
|
||||||
|
t.setCursorPos(2, 2);
|
||||||
|
t.scrollDown(1);
|
||||||
|
|
||||||
|
{
|
||||||
|
const str = try t.plainString(testing.allocator);
|
||||||
|
defer testing.allocator.free(str);
|
||||||
|
try testing.expectEqualStrings("\nABC\nDEF\nGHI", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..3) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
|
const row = list_cell.row;
|
||||||
|
try testing.expect(row.hyperlink);
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell).?;
|
||||||
|
try testing.expectEqual(@as(hyperlink.Id, 1), id);
|
||||||
|
const page = &list_cell.page.data;
|
||||||
|
try testing.expectEqual(1, page.hyperlink_set.count());
|
||||||
|
}
|
||||||
|
for (0..3) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
|
const row = list_cell.row;
|
||||||
|
try testing.expect(!row.hyperlink);
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(!cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell);
|
||||||
|
try testing.expect(id == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Terminal: scrollDown outside of scroll region" {
|
test "Terminal: scrollDown outside of scroll region" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var t = try init(alloc, .{ .rows = 5, .cols = 5 });
|
var t = try init(alloc, .{ .rows = 5, .cols = 5 });
|
||||||
@ -5256,6 +5307,112 @@ test "Terminal: scrollDown left/right scroll region" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Terminal: scrollDown left/right scroll region hyperlink" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try init(alloc, .{ .cols = 10, .rows = 10 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
|
try t.screen.startHyperlink("http://example.com", null);
|
||||||
|
try t.printString("ABC123");
|
||||||
|
t.screen.endHyperlink();
|
||||||
|
t.carriageReturn();
|
||||||
|
try t.linefeed();
|
||||||
|
try t.printString("DEF456");
|
||||||
|
t.carriageReturn();
|
||||||
|
try t.linefeed();
|
||||||
|
try t.printString("GHI789");
|
||||||
|
t.scrolling_region.left = 1;
|
||||||
|
t.scrolling_region.right = 3;
|
||||||
|
t.setCursorPos(2, 2);
|
||||||
|
t.scrollDown(1);
|
||||||
|
|
||||||
|
{
|
||||||
|
const str = try t.plainString(testing.allocator);
|
||||||
|
defer testing.allocator.free(str);
|
||||||
|
try testing.expectEqualStrings("A 23\nDBC156\nGEF489\n HI7", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First row preserves hyperlink where we didn't scroll
|
||||||
|
{
|
||||||
|
for (0..1) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
|
const row = list_cell.row;
|
||||||
|
try testing.expect(row.hyperlink);
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell).?;
|
||||||
|
try testing.expectEqual(@as(hyperlink.Id, 1), id);
|
||||||
|
const page = &list_cell.page.data;
|
||||||
|
try testing.expectEqual(1, page.hyperlink_set.count());
|
||||||
|
}
|
||||||
|
for (1..4) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(!cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell);
|
||||||
|
try testing.expect(id == null);
|
||||||
|
}
|
||||||
|
for (4..6) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 0,
|
||||||
|
} }).?;
|
||||||
|
const row = list_cell.row;
|
||||||
|
try testing.expect(row.hyperlink);
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell).?;
|
||||||
|
try testing.expectEqual(@as(hyperlink.Id, 1), id);
|
||||||
|
const page = &list_cell.page.data;
|
||||||
|
try testing.expectEqual(1, page.hyperlink_set.count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second row gets some hyperlinks
|
||||||
|
{
|
||||||
|
for (0..1) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(!cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell);
|
||||||
|
try testing.expect(id == null);
|
||||||
|
}
|
||||||
|
for (1..4) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
|
const row = list_cell.row;
|
||||||
|
try testing.expect(row.hyperlink);
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell).?;
|
||||||
|
try testing.expectEqual(@as(hyperlink.Id, 1), id);
|
||||||
|
const page = &list_cell.page.data;
|
||||||
|
try testing.expectEqual(1, page.hyperlink_set.count());
|
||||||
|
}
|
||||||
|
for (4..6) |x| {
|
||||||
|
const list_cell = t.screen.pages.getCell(.{ .viewport = .{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = 1,
|
||||||
|
} }).?;
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(!cell.hyperlink);
|
||||||
|
const id = list_cell.page.data.lookupHyperlink(cell);
|
||||||
|
try testing.expect(id == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Terminal: scrollDown outside of left/right scroll region" {
|
test "Terminal: scrollDown outside of left/right scroll region" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var t = try init(alloc, .{ .cols = 10, .rows = 10 });
|
var t = try init(alloc, .{ .cols = 10, .rows = 10 });
|
||||||
|
@ -764,31 +764,28 @@ pub const Page = struct {
|
|||||||
// Clear our destination now matter what
|
// Clear our destination now matter what
|
||||||
self.clearCells(dst_row, dst_left, dst_left + len);
|
self.clearCells(dst_row, dst_left, dst_left + len);
|
||||||
|
|
||||||
// If src has no graphemes, this is very fast because we can
|
// If src has no managed memory, this is very fast.
|
||||||
// just copy the cells directly because every other attribute
|
if (!src_row.managedMemory()) {
|
||||||
// is position-independent.
|
|
||||||
const src_grapheme = src_row.grapheme or grapheme: {
|
|
||||||
for (src_cells) |c| if (c.hasGrapheme()) break :grapheme true;
|
|
||||||
break :grapheme false;
|
|
||||||
};
|
|
||||||
if (!src_grapheme) {
|
|
||||||
fastmem.copy(Cell, dst_cells, src_cells);
|
fastmem.copy(Cell, dst_cells, src_cells);
|
||||||
} else {
|
} else {
|
||||||
// Source has graphemes, meaning we have to do a slower
|
// Source has graphemes or hyperlinks...
|
||||||
// cell by cell copy.
|
|
||||||
for (src_cells, dst_cells) |*src, *dst| {
|
for (src_cells, dst_cells) |*src, *dst| {
|
||||||
dst.* = src.*;
|
dst.* = src.*;
|
||||||
if (!src.hasGrapheme()) continue;
|
if (src.hasGrapheme()) {
|
||||||
|
// Required for moveGrapheme assertions
|
||||||
// Required for moveGrapheme assertions
|
dst.content_tag = .codepoint;
|
||||||
dst.content_tag = .codepoint;
|
self.moveGrapheme(src, dst);
|
||||||
self.moveGrapheme(src, dst);
|
src.content_tag = .codepoint;
|
||||||
src.content_tag = .codepoint;
|
dst.content_tag = .codepoint_grapheme;
|
||||||
dst.content_tag = .codepoint_grapheme;
|
dst_row.grapheme = true;
|
||||||
|
}
|
||||||
|
if (src.hyperlink) {
|
||||||
|
dst.hyperlink = false;
|
||||||
|
self.moveHyperlink(src, dst);
|
||||||
|
dst.hyperlink = true;
|
||||||
|
dst_row.hyperlink = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The destination row must be marked
|
|
||||||
dst_row.grapheme = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The destination row has styles if any of the cells are styled
|
// The destination row has styles if any of the cells are styled
|
||||||
@ -805,6 +802,7 @@ pub const Page = struct {
|
|||||||
@memset(@as([]u64, @ptrCast(src_cells)), 0);
|
@memset(@as([]u64, @ptrCast(src_cells)), 0);
|
||||||
if (src_cells.len == self.size.cols) {
|
if (src_cells.len == self.size.cols) {
|
||||||
src_row.grapheme = false;
|
src_row.grapheme = false;
|
||||||
|
src_row.hyperlink = false;
|
||||||
src_row.styled = false;
|
src_row.styled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -888,7 +886,6 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (row.hyperlink) {
|
if (row.hyperlink) {
|
||||||
row.hyperlink = false;
|
|
||||||
for (cells) |*cell| {
|
for (cells) |*cell| {
|
||||||
if (cell.hyperlink) self.clearHyperlink(row, cell);
|
if (cell.hyperlink) self.clearHyperlink(row, cell);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user