renderer: matchSet matches OSC8

This commit is contained in:
Mitchell Hashimoto
2024-07-04 18:58:21 -07:00
parent f777e42af2
commit 041c779512
2 changed files with 181 additions and 4 deletions

View File

@ -6,6 +6,7 @@ const inputpkg = @import("../input.zig");
const terminal = @import("../terminal/main.zig");
const point = terminal.point;
const Screen = terminal.Screen;
const Terminal = terminal.Terminal;
const log = std.log.scoped(.renderer_link);
@ -79,10 +80,125 @@ pub const Set = struct {
var matches = std.ArrayList(terminal.Selection).init(alloc);
defer matches.deinit();
// If our mouse is over an OSC8 link, then we can skip the regex
// matches below since OSC8 takes priority.
try self.matchSetFromOSC8(
alloc,
&matches,
screen,
mouse_pin,
mouse_mods,
);
// If we have no matches then we can try the regex matches.
if (matches.items.len == 0) {
try self.matchSetFromLinks(
alloc,
&matches,
screen,
mouse_pin,
mouse_mods,
);
}
return .{ .matches = try matches.toOwnedSlice() };
}
fn matchSetFromOSC8(
self: *const Set,
alloc: Allocator,
matches: *std.ArrayList(terminal.Selection),
screen: *Screen,
mouse_pin: terminal.Pin,
mouse_mods: inputpkg.Mods,
) !void {
_ = alloc;
_ = self;
// If the right mods aren't pressed, then we can't match.
if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return;
// Check if the cell the mouse is over is an OSC8 hyperlink
const mouse_cell = mouse_pin.rowAndCell().cell;
if (!mouse_cell.hyperlink) return;
// Get our hyperlink entry
const page = &mouse_pin.page.data;
const link_id = page.lookupHyperlink(mouse_cell) orelse {
log.warn("failed to find hyperlink for cell", .{});
return;
};
// Go through every row and find matching hyperlinks for the given ID.
// Note the link ID is not the same as the OSC8 ID parameter. But
// we hash hyperlinks by their contents which should achieve the same
// thing so we can use the ID as a key.
var current: ?terminal.Selection = null;
var row_it = screen.pages.getTopLeft(.viewport).rowIterator(.right_down, null);
while (row_it.next()) |row_pin| {
const row = row_pin.rowAndCell().row;
// If the row doesn't have any hyperlinks then we're done
// building our matching selection.
if (!row.hyperlink) {
if (current) |sel| {
try matches.append(sel);
current = null;
}
continue;
}
// We have hyperlinks, look for our own matching hyperlink.
for (row_pin.cells(.right), 0..) |*cell, x| {
const match = match: {
if (cell.hyperlink) {
if (row_pin.page.data.lookupHyperlink(cell)) |cell_link_id| {
break :match cell_link_id == link_id;
}
}
break :match false;
};
// If we have a match, extend our selection or start a new
// selection.
if (match) {
const cell_pin = row_pin.right(x);
if (current) |*sel| {
sel.endPtr().* = cell_pin;
} else {
current = terminal.Selection.init(
cell_pin,
cell_pin,
false,
);
}
continue;
}
// No match, if we have a current selection then complete it.
if (current) |sel| {
try matches.append(sel);
current = null;
}
}
}
}
/// Fills matches with the matches from regex link matches.
fn matchSetFromLinks(
self: *const Set,
alloc: Allocator,
matches: *std.ArrayList(terminal.Selection),
screen: *Screen,
mouse_pin: terminal.Pin,
mouse_mods: inputpkg.Mods,
) !void {
// Iterate over all the visible lines.
var lineIter = screen.lineIterator(screen.pages.pin(.{
.viewport = .{},
}) orelse return .{});
}) orelse return);
while (lineIter.next()) |line_sel| {
const strmap: terminal.StringMap = strmap: {
var strmap: terminal.StringMap = undefined;
@ -141,8 +257,6 @@ pub const Set = struct {
}
}
}
return .{ .matches = try matches.toOwnedSlice() };
}
};
@ -391,3 +505,66 @@ test "matchset mods no match" {
.y = 2,
} }).?));
}
test "matchset osc8" {
const testing = std.testing;
const alloc = testing.allocator;
// Initialize our terminal
var t = try Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer t.deinit(alloc);
const s = &t.screen;
try t.printString("ABC");
try t.screen.startHyperlink("http://example.com", null);
try t.printString("123");
t.screen.endHyperlink();
// Get a set
var set = try Set.fromConfig(alloc, &.{});
defer set.deinit(alloc);
// No matches over the non-link
{
var match = try set.matchSet(
alloc,
&t.screen,
.{ .x = 2, .y = 0 },
inputpkg.ctrlOrSuper(.{}),
);
defer match.deinit(alloc);
try testing.expectEqual(@as(usize, 0), match.matches.len);
}
// Match over link
var match = try set.matchSet(
alloc,
&t.screen,
.{ .x = 3, .y = 0 },
inputpkg.ctrlOrSuper(.{}),
);
defer match.deinit(alloc);
try testing.expectEqual(@as(usize, 1), match.matches.len);
// Test our matches
try testing.expect(!match.orderedContains(s, s.pages.pin(.{ .screen = .{
.x = 2,
.y = 0,
} }).?));
try testing.expect(match.orderedContains(s, s.pages.pin(.{ .screen = .{
.x = 3,
.y = 0,
} }).?));
try testing.expect(match.orderedContains(s, s.pages.pin(.{ .screen = .{
.x = 4,
.y = 0,
} }).?));
try testing.expect(match.orderedContains(s, s.pages.pin(.{ .screen = .{
.x = 5,
.y = 0,
} }).?));
try testing.expect(!match.orderedContains(s, s.pages.pin(.{ .screen = .{
.x = 6,
.y = 0,
} }).?));
}

View File

@ -916,7 +916,7 @@ pub const Page = struct {
}
/// Returns the hyperlink ID for the given cell.
pub fn lookupHyperlink(self: *const Page, cell: *Cell) ?hyperlink.Id {
pub fn lookupHyperlink(self: *const Page, cell: *const Cell) ?hyperlink.Id {
const cell_offset = getOffset(Cell, self.memory, cell);
const map = self.hyperlink_map.map(self.memory);
return map.get(cell_offset);