surface: respect semantic prompt boundaries for links

Link detection currently expands the clicked location to a full line
before running the configured regexes. When semantic prompt markers
are present, this can cause prompt text and neighboring content to be
matched together even though they are distinct semantic regions.

Use semantic prompt boundaries when selecting the text to inspect for
link matching. This keeps prompt text separate from the content beside
it and avoids folding prompt text into double-click link/path
selection.

Add a regression test that models a prompt and command on the same
line and verifies the prompt region and input region remain separate.
This commit is contained in:
Vasyl Zuziak
2026-04-25 15:46:25 +02:00
parent 57b5e1e250
commit 85dc4b1842

View File

@@ -4295,7 +4295,9 @@ fn linkAtPin(
const line = screen.selectLine(.{
.pin = mouse_pin,
.whitespace = null,
.semantic_prompt_boundary = false,
// Respect semantic prompt boundaries so link/path matching doesn't
// merge shell prompt content with the text beside it.
.semantic_prompt_boundary = true,
}) orelse return null;
var strmap: terminal.StringMap = undefined;
@@ -6658,3 +6660,60 @@ test "Surface: rectangle selection logic" {
true, //rectangle selection
);
}
test "Surface: link selection line respects prompt boundary" {
const testing = std.testing;
const alloc = testing.allocator;
var screen = try terminal.Screen.init(alloc, .{
.cols = 20,
.rows = 5,
.max_scrollback = 0,
});
defer screen.deinit();
screen.cursorSetSemanticContent(.{ .prompt = .initial });
try screen.testWriteString("/tmp $ ");
screen.cursorSetSemanticContent(.{ .input = .clear_explicit });
try screen.testWriteString("locale");
{
var sel = screen.selectLine(.{
.pin = screen.pages.pin(.{ .active = .{
.x = 1,
.y = 0,
} }).?,
.whitespace = null,
.semantic_prompt_boundary = true,
}).?;
defer sel.deinit(&screen);
const contents = try screen.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(contents);
try testing.expectEqualStrings("/tmp $ ", contents);
}
{
var sel = screen.selectLine(.{
.pin = screen.pages.pin(.{ .active = .{
.x = 8,
.y = 0,
} }).?,
.whitespace = null,
.semantic_prompt_boundary = true,
}).?;
defer sel.deinit(&screen);
const contents = try screen.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(contents);
try testing.expectEqualStrings("locale", contents);
}
}