diff --git a/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.test.ts b/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.test.ts new file mode 100644 index 00000000000..2aac516928c --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest' +import { extractTerminalHttpLinks } from './terminal-http-url-extraction' + +const urlsIn = (line: string): string[] => extractTerminalHttpLinks(line).map((link) => link.url) + +describe('extractTerminalHttpLinks', () => { + it('stops at a full-width parenthesis instead of swallowing the annotation', () => { + expect( + urlsIn('- PR: https://github.com/stablyai/orca/pull/12345(作成済み・マージ待ち)') + ).toEqual(['https://github.com/stablyai/orca/pull/12345']) + }) + + it('stops at a full-width closing quote', () => { + expect(urlsIn('詳細は「https://example.com/docs」を参照')).toEqual(['https://example.com/docs']) + }) + + it('stops at an ideographic space', () => { + expect(urlsIn('PR: https://example.com/a (全角スペース区切り)')).toEqual([ + 'https://example.com/a' + ]) + }) + + it('keeps unencoded CJK letters that are genuinely part of the path', () => { + // Why: wrapped URLs with Chinese path segments are supported; only punctuation ends a url. + expect(urlsIn('https://example.com/文档/路径')).toEqual([ + new URL('https://example.com/文档/路径').toString() + ]) + }) + + it('stops at a full-width comma and full stop between a url and prose', () => { + expect(urlsIn('https://example.com/a、次へ')).toEqual(['https://example.com/a']) + expect(urlsIn('https://example.com/b。')).toEqual(['https://example.com/b']) + }) + + it('still extracts a plain ascii url and keeps its own punctuation', () => { + expect(urlsIn('see https://example.com/a_b-c?d=1&e=2#f end')).toEqual([ + 'https://example.com/a_b-c?d=1&e=2#f' + ]) + }) + + it('still trims ascii trailing punctuation', () => { + expect(urlsIn('see https://example.com/a.')).toEqual(['https://example.com/a']) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.ts b/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.ts index 192c4db15e1..eeaa463d818 100644 --- a/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.ts +++ b/src/renderer/src/components/terminal-pane/terminal-http-url-extraction.ts @@ -98,6 +98,7 @@ function trimHttpUrlTrailingPunctuation( function isHttpUrlBodyTerminator(code: number): boolean { return ( + isNonAsciiUrlTerminator(code) || isAsciiWhitespace(code) || code === 0x22 || code === 0x27 || @@ -142,6 +143,14 @@ function isHttpUrlTrailingPunctuation(code: number): boolean { ) } +// Why not all non-ascii: a url path may legitimately carry unencoded CJK (see the wrapped-url +// tests), so only punctuation and spaces end it - full-width brackets, an ideographic space, `\u30fb`. +const NON_ASCII_URL_TERMINATOR = /[\p{P}\p{S}\p{Z}]/u + +function isNonAsciiUrlTerminator(code: number): boolean { + return code > 0x7e && NON_ASCII_URL_TERMINATOR.test(String.fromCharCode(code)) +} + function isAsciiWhitespace(code: number): boolean { return code === 9 || code === 10 || code === 11 || code === 12 || code === 13 || code === 32 }