( handle: FileHandle, needle: string, contextLines: number, )
| 58 | * Handle-accepting core of readEditContext. Caller owns open/close. |
| 59 | */ |
| 60 | export async function scanForContext( |
| 61 | handle: FileHandle, |
| 62 | needle: string, |
| 63 | contextLines: number, |
| 64 | ): Promise<EditContext> { |
| 65 | if (needle === '') return { content: '', lineOffset: 1, truncated: false } |
| 66 | const needleLF = Buffer.from(needle, 'utf8') |
| 67 | // Model sends LF; files may be CRLF. Count newlines to size the overlap for |
| 68 | // the longer CRLF form; defer encoding the CRLF buffer until LF scan misses. |
| 69 | let nlCount = 0 |
| 70 | for (let i = 0; i < needleLF.length; i++) if (needleLF[i] === NL) nlCount++ |
| 71 | let needleCRLF: Buffer | undefined |
| 72 | const overlap = needleLF.length + nlCount - 1 |
| 73 | |
| 74 | const buf = Buffer.allocUnsafe(CHUNK_SIZE + overlap) |
| 75 | let pos = 0 |
| 76 | let linesBeforePos = 0 |
| 77 | let prevTail = 0 |
| 78 | |
| 79 | while (pos < MAX_SCAN_BYTES) { |
| 80 | const { bytesRead } = await handle.read(buf, prevTail, CHUNK_SIZE, pos) |
| 81 | if (bytesRead === 0) break |
| 82 | const viewLen = prevTail + bytesRead |
| 83 | |
| 84 | let matchAt = indexOfWithin(buf, needleLF, viewLen) |
| 85 | let matchLen = needleLF.length |
| 86 | if (matchAt === -1 && nlCount > 0) { |
| 87 | needleCRLF ??= Buffer.from(needle.replaceAll('\n', '\r\n'), 'utf8') |
| 88 | matchAt = indexOfWithin(buf, needleCRLF, viewLen) |
| 89 | matchLen = needleCRLF.length |
| 90 | } |
| 91 | if (matchAt !== -1) { |
| 92 | const absMatch = pos - prevTail + matchAt |
| 93 | return await sliceContext( |
| 94 | handle, |
| 95 | buf, |
| 96 | absMatch, |
| 97 | matchLen, |
| 98 | contextLines, |
| 99 | linesBeforePos + countNewlines(buf, 0, matchAt), |
| 100 | ) |
| 101 | } |
| 102 | pos += bytesRead |
| 103 | // Shift the tail to the front for straddle. linesBeforePos tracks |
| 104 | // newlines in bytes we've DISCARDED (not in buf) — count only the |
| 105 | // non-overlap portion we're about to copyWithin over. |
| 106 | const nextTail = Math.min(overlap, viewLen) |
| 107 | linesBeforePos += countNewlines(buf, 0, viewLen - nextTail) |
| 108 | prevTail = nextTail |
| 109 | buf.copyWithin(0, viewLen - prevTail, viewLen) |
| 110 | } |
| 111 | |
| 112 | return { content: '', lineOffset: 1, truncated: pos >= MAX_SCAN_BYTES } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Reads the entire file via `handle` up to MAX_SCAN_BYTES. Returns null if the |
no test coverage detected