Skip to content

Commit ae8fa10

Browse files
committed
Fix recomputeSegments: strip y flag, precompute newline offsets, fix docs code block lang
1 parent 2f4ba11 commit ae8fa10

2 files changed

Lines changed: 20 additions & 7 deletions

File tree

docs/usage/search-syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ an `A OR B OR C` query to the GitHub API so that **all branches are covered**
106106

107107
If the extracted term is very short (fewer than 3 characters), the CLI will exit with a warning and ask you to provide a manual hint:
108108

109-
```
109+
```text
110110
⚠ Regex mode — could not extract a term longer than 2 chars from /[~^]?[0-9]/
111111
Provide a manual hint with --regex-hint <term>.
112112
```

src/aggregate.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,30 @@ export function extractRef(repoFullName: string, path: string, matchIndex: numbe
3838
* that `highlightFragment` can map them to the correct terminal lines.
3939
*/
4040
function recomputeSegments(fragment: string, regex: RegExp): TextMatchSegment[] {
41-
// Force global flag so exec() advances; strip it first to avoid double-g
42-
const re = new RegExp(regex.source, regex.flags.replace("g", "") + "g");
41+
// Force global flag so exec() advances; strip g and y first to avoid double-g
42+
// and to prevent sticky mode from anchoring every match at lastIndex.
43+
const re = new RegExp(regex.source, regex.flags.replace(/[gy]/g, "") + "g");
44+
// Precompute newline positions once — O(n) — so per-match line/col lookup
45+
// is O(log n) via binary search instead of O(n) per match (O(n²) overall).
46+
const newlines: number[] = [];
47+
for (let i = 0; i < fragment.length; i++) {
48+
if (fragment[i] === "\n") newlines.push(i);
49+
}
4350
const segments: TextMatchSegment[] = [];
4451
let m: RegExpExecArray | null;
4552
while ((m = re.exec(fragment)) !== null) {
4653
const start = m.index;
4754
const end = start + m[0].length;
48-
const before = fragment.slice(0, start);
49-
const nlIdx = before.lastIndexOf("\n");
50-
const line = (before.match(/\n/g)?.length ?? 0) + 1;
51-
const col = (nlIdx === -1 ? start : start - nlIdx - 1) + 1;
55+
// Binary-search for the number of newlines before `start`.
56+
let lo = 0;
57+
let hi = newlines.length;
58+
while (lo < hi) {
59+
const mid = (lo + hi) >> 1;
60+
if (newlines[mid] < start) lo = mid + 1;
61+
else hi = mid;
62+
}
63+
const line = lo + 1; // 1-based
64+
const col = (lo === 0 ? start : start - newlines[lo - 1] - 1) + 1; // 1-based
5265
segments.push({ text: m[0], indices: [start, end], line, col });
5366
if (m[0].length === 0) re.lastIndex++; // guard against zero-width matches
5467
}

0 commit comments

Comments
 (0)