diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 4c16ed3a2..e6c7f6ba0 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -64,11 +64,13 @@ pub const Set = struct { self: *const Set, alloc: Allocator, screen: *Screen, - mouse_vp_pt: point.Viewport, + mouse_vp_pt: point.Coordinate, mouse_mods: inputpkg.Mods, ) !MatchSet { // Convert the viewport point to a screen point. - const mouse_pt = mouse_vp_pt.toScreen(screen); + const mouse_pin = screen.pages.pin(.{ + .viewport = mouse_vp_pt, + }) orelse return .{}; // This contains our list of matches. The matches are stored // as selections which contain the start and end points of @@ -78,14 +80,25 @@ pub const Set = struct { defer matches.deinit(); // Iterate over all the visible lines. - var lineIter = screen.lineIterator(.viewport); - while (lineIter.next()) |line| { - const strmap = line.stringMap(alloc) catch |err| { - log.warn( - "failed to build string map for link checking err={}", - .{err}, - ); - continue; + var lineIter = screen.lineIterator(screen.pages.pin(.{ + .viewport = .{}, + }) orelse return .{}); + while (lineIter.next()) |line_sel| { + const strmap: terminal.StringMap = strmap: { + var strmap: terminal.StringMap = undefined; + const str = screen.selectionString(alloc, .{ + .sel = line_sel, + .trim = false, + .map = &strmap, + }) catch |err| { + log.warn( + "failed to build string map for link checking err={}", + .{err}, + ); + continue; + }; + alloc.free(str); + break :strmap strmap; }; defer strmap.deinit(alloc); @@ -98,7 +111,7 @@ pub const Set = struct { .always => {}, .always_mods => |v| if (!mouse_mods.equal(v)) continue, inline .hover, .hover_mods => |v, tag| { - if (!line.selection().contains(mouse_pt)) continue; + if (!line_sel.contains(screen, mouse_pin)) continue; if (comptime tag == .hover_mods) { if (!mouse_mods.equal(v)) continue; } @@ -121,7 +134,7 @@ pub const Set = struct { .always, .always_mods => {}, .hover, .hover_mods, - => if (!sel.contains(mouse_pt)) continue, + => if (!sel.contains(screen, mouse_pin)) continue, } try matches.append(sel); @@ -153,156 +166,228 @@ pub const MatchSet = struct { /// results. pub fn orderedContains( self: *MatchSet, - pt: point.ScreenPoint, + screen: *const Screen, + pin: terminal.Pin, ) bool { // If we're beyond the end of our possible matches, we're done. if (self.i >= self.matches.len) return false; // If our selection ends before the point, then no point will ever // again match this selection so we move on to the next one. - while (self.matches[self.i].end.before(pt)) { + while (self.matches[self.i].end().before(pin)) { self.i += 1; if (self.i >= self.matches.len) return false; } - return self.matches[self.i].contains(pt); + return self.matches[self.i].contains(screen, pin); } }; -// TODO(paged-terminal) -// test "matchset" { -// const testing = std.testing; -// const alloc = testing.allocator; -// -// // Initialize our screen -// var s = try Screen.init(alloc, 5, 5, 0); -// defer s.deinit(); -// const str = "1ABCD2EFGH\n3IJKL"; -// try s.testWriteString(str); -// -// // Get a set -// var set = try Set.fromConfig(alloc, &.{ -// .{ -// .regex = "AB", -// .action = .{ .open = {} }, -// .highlight = .{ .always = {} }, -// }, -// -// .{ -// .regex = "EF", -// .action = .{ .open = {} }, -// .highlight = .{ .always = {} }, -// }, -// }); -// defer set.deinit(alloc); -// -// // Get our matches -// var match = try set.matchSet(alloc, &s, .{}, .{}); -// defer match.deinit(alloc); -// try testing.expectEqual(@as(usize, 2), match.matches.len); -// -// // Test our matches -// try testing.expect(!match.orderedContains(.{ .x = 0, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 2, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 3, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 1 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 2 })); -// } -// -// test "matchset hover links" { -// const testing = std.testing; -// const alloc = testing.allocator; -// -// // Initialize our screen -// var s = try Screen.init(alloc, 5, 5, 0); -// defer s.deinit(); -// const str = "1ABCD2EFGH\n3IJKL"; -// try s.testWriteString(str); -// -// // Get a set -// var set = try Set.fromConfig(alloc, &.{ -// .{ -// .regex = "AB", -// .action = .{ .open = {} }, -// .highlight = .{ .hover = {} }, -// }, -// -// .{ -// .regex = "EF", -// .action = .{ .open = {} }, -// .highlight = .{ .always = {} }, -// }, -// }); -// defer set.deinit(alloc); -// -// // Not hovering over the first link -// { -// var match = try set.matchSet(alloc, &s, .{}, .{}); -// defer match.deinit(alloc); -// try testing.expectEqual(@as(usize, 1), match.matches.len); -// -// // Test our matches -// try testing.expect(!match.orderedContains(.{ .x = 0, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 2, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 3, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 1 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 2 })); -// } -// -// // Hovering over the first link -// { -// var match = try set.matchSet(alloc, &s, .{ .x = 1, .y = 0 }, .{}); -// defer match.deinit(alloc); -// try testing.expectEqual(@as(usize, 2), match.matches.len); -// -// // Test our matches -// try testing.expect(!match.orderedContains(.{ .x = 0, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 2, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 3, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 1 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 2 })); -// } -// } -// -// test "matchset mods no match" { -// const testing = std.testing; -// const alloc = testing.allocator; -// -// // Initialize our screen -// var s = try Screen.init(alloc, 5, 5, 0); -// defer s.deinit(); -// const str = "1ABCD2EFGH\n3IJKL"; -// try s.testWriteString(str); -// -// // Get a set -// var set = try Set.fromConfig(alloc, &.{ -// .{ -// .regex = "AB", -// .action = .{ .open = {} }, -// .highlight = .{ .always = {} }, -// }, -// -// .{ -// .regex = "EF", -// .action = .{ .open = {} }, -// .highlight = .{ .always_mods = .{ .ctrl = true } }, -// }, -// }); -// defer set.deinit(alloc); -// -// // Get our matches -// var match = try set.matchSet(alloc, &s, .{}, .{}); -// defer match.deinit(alloc); -// try testing.expectEqual(@as(usize, 1), match.matches.len); -// -// // Test our matches -// try testing.expect(!match.orderedContains(.{ .x = 0, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 1, .y = 0 })); -// try testing.expect(match.orderedContains(.{ .x = 2, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 3, .y = 0 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 1 })); -// try testing.expect(!match.orderedContains(.{ .x = 1, .y = 2 })); -// } +test "matchset" { + const testing = std.testing; + const alloc = testing.allocator; + + // Initialize our screen + var s = try Screen.init(alloc, 5, 5, 0); + defer s.deinit(); + const str = "1ABCD2EFGH\n3IJKL"; + try s.testWriteString(str); + + // Get a set + var set = try Set.fromConfig(alloc, &.{ + .{ + .regex = "AB", + .action = .{ .open = {} }, + .highlight = .{ .always = {} }, + }, + + .{ + .regex = "EF", + .action = .{ .open = {} }, + .highlight = .{ .always = {} }, + }, + }); + defer set.deinit(alloc); + + // Get our matches + var match = try set.matchSet(alloc, &s, .{}, .{}); + defer match.deinit(alloc); + try testing.expectEqual(@as(usize, 2), match.matches.len); + + // Test our matches + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 0, + .y = 0, + } }).?)); + try testing.expect(match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 0, + } }).?)); + 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 = 1, + .y = 1, + } }).?)); + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 2, + } }).?)); +} + +test "matchset hover links" { + const testing = std.testing; + const alloc = testing.allocator; + + // Initialize our screen + var s = try Screen.init(alloc, 5, 5, 0); + defer s.deinit(); + const str = "1ABCD2EFGH\n3IJKL"; + try s.testWriteString(str); + + // Get a set + var set = try Set.fromConfig(alloc, &.{ + .{ + .regex = "AB", + .action = .{ .open = {} }, + .highlight = .{ .hover = {} }, + }, + + .{ + .regex = "EF", + .action = .{ .open = {} }, + .highlight = .{ .always = {} }, + }, + }); + defer set.deinit(alloc); + + // Not hovering over the first link + { + var match = try set.matchSet(alloc, &s, .{}, .{}); + 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 = 0, + .y = 0, + } }).?)); + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 0, + } }).?)); + 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 = 1, + .y = 1, + } }).?)); + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 2, + } }).?)); + } + + // Hovering over the first link + { + var match = try set.matchSet(alloc, &s, .{ .x = 1, .y = 0 }, .{}); + defer match.deinit(alloc); + try testing.expectEqual(@as(usize, 2), match.matches.len); + + // Test our matches + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 0, + .y = 0, + } }).?)); + try testing.expect(match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 0, + } }).?)); + 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 = 1, + .y = 1, + } }).?)); + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 2, + } }).?)); + } +} + +test "matchset mods no match" { + const testing = std.testing; + const alloc = testing.allocator; + + // Initialize our screen + var s = try Screen.init(alloc, 5, 5, 0); + defer s.deinit(); + const str = "1ABCD2EFGH\n3IJKL"; + try s.testWriteString(str); + + // Get a set + var set = try Set.fromConfig(alloc, &.{ + .{ + .regex = "AB", + .action = .{ .open = {} }, + .highlight = .{ .always = {} }, + }, + + .{ + .regex = "EF", + .action = .{ .open = {} }, + .highlight = .{ .always_mods = .{ .ctrl = true } }, + }, + }); + defer set.deinit(alloc); + + // Get our matches + var match = try set.matchSet(alloc, &s, .{}, .{}); + 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 = 0, + .y = 0, + } }).?)); + try testing.expect(match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 0, + } }).?)); + 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 = 1, + .y = 1, + } }).?)); + try testing.expect(!match.orderedContains(&s, s.pages.pin(.{ .screen = .{ + .x = 1, + .y = 2, + } }).?)); +}