Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/web/src/terminal-links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ describe("extractTerminalLinks", () => {
]);
});

it.each([
"connected to redis://127.0.0.1:6379/0",
"connected to postgres://localhost:5432/app",
"uploaded s3://bucket/reports/today.csv",
" at main (file:///Users/jane/app/src/index.mjs:3:7)",
])("does not turn part of a non-web URL into an editor link: %s", (line) => {
expect(extractTerminalLinks(line)).toEqual([]);
});

it("keeps file links beside a non-web URL", () => {
const line = "src/db.ts:4 redis://localhost:6379/0 C:/app/src/db.ts:9";
expect(extractTerminalLinks(line)).toEqual([
{ kind: "path", text: "src/db.ts:4", start: 0, end: 11 },
{ kind: "path", text: "C:/app/src/db.ts:9", start: 37, end: 55 },
]);
});

it("keeps a question mark inside a filesystem path", () => {
const path = "/tmp/report?draft";
expect(extractTerminalLinks(`wrote ${path}`)).toEqual([
Expand Down
13 changes: 8 additions & 5 deletions apps/web/src/terminal-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const TRAILING_PATH_PUNCTUATION_PATTERN = /[.,;:!?]+$/;
// A question mark is legal in filenames; only exclude query-bearing request targets.
const REQUEST_ROUTE_PATTERN =
/(?:^|[\s"'`])(?:GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS|CONNECT|TRACE|proxying)\s+["'`]?(?:\/|[A-Za-z0-9_-]+\/)[^\s?]*\?\S*/g;
// The tail of `redis://host/0` or `file:///a.mjs` is not a path. One-letter schemes stay drives,
// and the lookbehind keeps runs of letters from being rescanned at every suffix.
const SCHEME_URL_PATTERN = /(?<![A-Za-z\d+.-])[A-Za-z][A-Za-z\d+.-]+:\/\/[^\s"'`<>]+/gu;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

function trimClosingDelimiters(value: string, kind: TerminalLinkKind): string {
let output = value.replace(
Expand Down Expand Up @@ -110,10 +113,10 @@ export function extractTerminalLinks(line: string): TerminalLinkMatch[] {
const promptMatches: TerminalLinkMatch[] = promptPath
? [{ kind: "path", text: promptPath, start: 3, end: 3 + promptPath.length }]
: [];
const requestRoutes = Array.from(line.matchAll(REQUEST_ROUTE_PATTERN), (match) => ({
start: match.index,
end: match.index + match[0].length,
}));
const nonPathRanges = Array.from(
[...line.matchAll(REQUEST_ROUTE_PATTERN), ...line.matchAll(SCHEME_URL_PATTERN)],
(match) => ({ start: match.index, end: match.index + match[0].length }),
);
const urlMatches = collectMatches(line, "url", URL_PATTERN, promptMatches);
const quotedMatches = collectMatches(line, "path", QUOTED_FILE_PATH_PATTERN, [
...promptMatches,
Expand All @@ -126,7 +129,7 @@ export function extractTerminalLinks(line: string): TerminalLinkMatch[] {
]);
return [...promptMatches, ...urlMatches, ...quotedMatches, ...pathMatches]
.filter(
(match) => match.kind === "url" || !requestRoutes.some((route) => overlaps(match, route)),
(match) => match.kind === "url" || !nonPathRanges.some((range) => overlaps(match, range)),
)
.toSorted((a, b) => a.start - b.start);
}
Expand Down
Loading