mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
renderer: matchSet matches OSC8
This commit is contained in:
@ -6,6 +6,7 @@ const inputpkg = @import("../input.zig");
|
|||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
const point = terminal.point;
|
const point = terminal.point;
|
||||||
const Screen = terminal.Screen;
|
const Screen = terminal.Screen;
|
||||||
|
const Terminal = terminal.Terminal;
|
||||||
|
|
||||||
const log = std.log.scoped(.renderer_link);
|
const log = std.log.scoped(.renderer_link);
|
||||||
|
|
||||||
@ -79,10 +80,125 @@ pub const Set = struct {
|
|||||||
var matches = std.ArrayList(terminal.Selection).init(alloc);
|
var matches = std.ArrayList(terminal.Selection).init(alloc);
|
||||||
defer matches.deinit();
|
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.
|
// Iterate over all the visible lines.
|
||||||
var lineIter = screen.lineIterator(screen.pages.pin(.{
|
var lineIter = screen.lineIterator(screen.pages.pin(.{
|
||||||
.viewport = .{},
|
.viewport = .{},
|
||||||
}) orelse return .{});
|
}) orelse return);
|
||||||
while (lineIter.next()) |line_sel| {
|
while (lineIter.next()) |line_sel| {
|
||||||
const strmap: terminal.StringMap = strmap: {
|
const strmap: terminal.StringMap = strmap: {
|
||||||
var strmap: terminal.StringMap = undefined;
|
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,
|
.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,
|
||||||
|
} }).?));
|
||||||
|
}
|
||||||
|
@ -916,7 +916,7 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the hyperlink ID for the given cell.
|
/// 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 cell_offset = getOffset(Cell, self.memory, cell);
|
||||||
const map = self.hyperlink_map.map(self.memory);
|
const map = self.hyperlink_map.map(self.memory);
|
||||||
return map.get(cell_offset);
|
return map.get(cell_offset);
|
||||||
|
Reference in New Issue
Block a user