( screen: Screen, col: number, row: number, )
| 270 | * cell has no OSC 8 hyperlink. |
| 271 | */ |
| 272 | export function findPlainTextUrlAt( |
| 273 | screen: Screen, |
| 274 | col: number, |
| 275 | row: number, |
| 276 | ): string | undefined { |
| 277 | if (row < 0 || row >= screen.height) return undefined |
| 278 | const width = screen.width |
| 279 | const noSelect = screen.noSelect |
| 280 | const rowOff = row * width |
| 281 | |
| 282 | let c = col |
| 283 | if (c > 0) { |
| 284 | const cell = cellAt(screen, c, row) |
| 285 | if (cell && cell.width === CellWidth.SpacerTail) c -= 1 |
| 286 | } |
| 287 | if (c < 0 || c >= width || noSelect[rowOff + c] === 1) return undefined |
| 288 | |
| 289 | const startCell = cellAt(screen, c, row) |
| 290 | if (!startCell || !isUrlChar(startCell.char)) return undefined |
| 291 | |
| 292 | // Expand left/right to the bounds of the URL-char run. URLs are ASCII |
| 293 | // (CellWidth.Narrow, 1 codeunit), so hitting a non-ASCII/wide/spacer |
| 294 | // cell is a boundary — no need to step over spacers like wordBoundsAt. |
| 295 | let lo = c |
| 296 | while (lo > 0) { |
| 297 | const prev = lo - 1 |
| 298 | if (noSelect[rowOff + prev] === 1) break |
| 299 | const pc = cellAt(screen, prev, row) |
| 300 | if (!pc || pc.width !== CellWidth.Narrow || !isUrlChar(pc.char)) break |
| 301 | lo = prev |
| 302 | } |
| 303 | let hi = c |
| 304 | while (hi < width - 1) { |
| 305 | const next = hi + 1 |
| 306 | if (noSelect[rowOff + next] === 1) break |
| 307 | const nc = cellAt(screen, next, row) |
| 308 | if (!nc || nc.width !== CellWidth.Narrow || !isUrlChar(nc.char)) break |
| 309 | hi = next |
| 310 | } |
| 311 | |
| 312 | let token = '' |
| 313 | for (let i = lo; i <= hi; i++) token += cellAt(screen, i, row)!.char |
| 314 | |
| 315 | // 1 cell = 1 char across [lo, hi] (ASCII-only run), so string index = |
| 316 | // column offset. Find the last scheme anchor at or before the click — |
| 317 | // a run like `https://a.com,https://b.com` has two, and clicking the |
| 318 | // second should return the second URL, not the greedy match of both. |
| 319 | const clickIdx = c - lo |
| 320 | const schemeRe = /(?:https?|file):\/\//g |
| 321 | let urlStart = -1 |
| 322 | let urlEnd = token.length |
| 323 | for (let m; (m = schemeRe.exec(token)); ) { |
| 324 | if (m.index > clickIdx) { |
| 325 | urlEnd = m.index |
| 326 | break |
| 327 | } |
| 328 | urlStart = m.index |
| 329 | } |
no test coverage detected