Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
12 kB · 383 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384// ---------------------------------------------------------------------------// readFileInRange — line-oriented file reader with two code paths// ---------------------------------------------------------------------------//// Returns lines [offset, offset + maxLines) from a file.//// Fast path (regular files < 10 MB):// Opens the file, stats the fd, reads the whole file with readFile(),// then splits lines in memory. This avoids the per-chunk async overhead// of createReadStream and is ~2x faster for typical source files.//// Streaming path (large files, pipes, devices, etc.):// Uses createReadStream with manual indexOf('\n') scanning. Content is// only accumulated for lines inside the requested range — lines outside// the range are counted (for totalLines) but discarded, so reading line// 1 of a 100 GB file won't balloon RSS.//// All event handlers (streamOnOpen/Data/End) are module-level named// functions with zero closures. State lives in a StreamState object;// handlers access it via `this`, bound at registration time.//// Lifecycle: `open`, `end`, and `error` use .once() (auto-remove).// `data` fires until the stream ends or is destroyed — either way the// stream and state become unreachable together and are GC'd.//// On error (including maxBytes exceeded), stream.destroy(err) emits// 'error' → reject (passed directly to .once('error')).//// Both paths strip UTF-8 BOM and \r (CRLF → LF).//// mtime comes from fstat/stat on the already-open fd — no extra open().//// maxBytes behavior depends on options.truncateOnByteLimit:// false (default): legacy semantics — throws FileTooLargeError if the FILE// size (fast path) or total streamed bytes (streaming) exceed maxBytes.// true: caps SELECTED OUTPUT at maxBytes. Stops at the last complete line// that fits; sets truncatedByBytes in the result. Never throws.// ---------------------------------------------------------------------------
import { createReadStream, fstat } from 'fs'import { stat as fsStat, readFile } from 'fs/promises'import { formatFileSize } from './format.js'
const FAST_PATH_MAX_SIZE = 10 * 1024 * 1024 // 10 MB
export type ReadFileRangeResult = { content: string lineCount: number totalLines: number totalBytes: number readBytes: number mtimeMs: number /** true when output was clipped to maxBytes under truncate mode */ truncatedByBytes?: boolean}
export class FileTooLargeError extends Error { constructor( public sizeInBytes: number, public maxSizeBytes: number, ) { super( `File content (${formatFileSize(sizeInBytes)}) exceeds maximum allowed size (${formatFileSize(maxSizeBytes)}). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.`, ) this.name = 'FileTooLargeError' }}
// ---------------------------------------------------------------------------// Public entry point// ---------------------------------------------------------------------------
export async function readFileInRange( filePath: string, offset = 0, maxLines?: number, maxBytes?: number, signal?: AbortSignal, options?: { truncateOnByteLimit?: boolean },): Promise<ReadFileRangeResult> { signal?.throwIfAborted() const truncateOnByteLimit = options?.truncateOnByteLimit ?? false
// stat to decide the code path and guard against OOM. // For regular files under 10 MB: readFile + in-memory split (fast). // Everything else (large files, FIFOs, devices): streaming. const stats = await fsStat(filePath)
if (stats.isDirectory()) { throw new Error( `EISDIR: illegal operation on a directory, read '${filePath}'`, ) }
if (stats.isFile() && stats.size < FAST_PATH_MAX_SIZE) { if ( !truncateOnByteLimit && maxBytes !== undefined && stats.size > maxBytes ) { throw new FileTooLargeError(stats.size, maxBytes) }
const text = await readFile(filePath, { encoding: 'utf8', signal }) return readFileInRangeFast( text, stats.mtimeMs, offset, maxLines, truncateOnByteLimit ? maxBytes : undefined, ) }
return readFileInRangeStreaming( filePath, offset, maxLines, maxBytes, truncateOnByteLimit, signal, )}
// ---------------------------------------------------------------------------// Fast path — readFile + in-memory split// ---------------------------------------------------------------------------
function readFileInRangeFast( raw: string, mtimeMs: number, offset: number, maxLines: number | undefined, truncateAtBytes: number | undefined,): ReadFileRangeResult { const endLine = maxLines !== undefined ? offset + maxLines : Infinity
// Strip BOM. const text = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw
// Split lines, strip \r, select range. const selectedLines: string[] = [] let lineIndex = 0 let startPos = 0 let newlinePos: number let selectedBytes = 0 let truncatedByBytes = false
function tryPush(line: string): boolean { if (truncateAtBytes !== undefined) { const sep = selectedLines.length > 0 ? 1 : 0 const nextBytes = selectedBytes + sep + Buffer.byteLength(line) if (nextBytes > truncateAtBytes) { truncatedByBytes = true return false } selectedBytes = nextBytes } selectedLines.push(line) return true }
while ((newlinePos = text.indexOf('\n', startPos)) !== -1) { if (lineIndex >= offset && lineIndex < endLine && !truncatedByBytes) { let line = text.slice(startPos, newlinePos) if (line.endsWith('\r')) { line = line.slice(0, -1) } tryPush(line) } lineIndex++ startPos = newlinePos + 1 }
// Final fragment (no trailing newline). if (lineIndex >= offset && lineIndex < endLine && !truncatedByBytes) { let line = text.slice(startPos) if (line.endsWith('\r')) { line = line.slice(0, -1) } tryPush(line) } lineIndex++
const content = selectedLines.join('\n') return { content, lineCount: selectedLines.length, totalLines: lineIndex, totalBytes: Buffer.byteLength(text, 'utf8'), readBytes: Buffer.byteLength(content, 'utf8'), mtimeMs, ...(truncatedByBytes ? { truncatedByBytes: true } : {}), }}
// ---------------------------------------------------------------------------// Streaming path — createReadStream + event handlers// ---------------------------------------------------------------------------
type StreamState = { stream: ReturnType<typeof createReadStream> offset: number endLine: number maxBytes: number | undefined truncateOnByteLimit: boolean resolve: (value: ReadFileRangeResult) => void totalBytesRead: number selectedBytes: number truncatedByBytes: boolean currentLineIndex: number selectedLines: string[] partial: string isFirstChunk: boolean resolveMtime: (ms: number) => void mtimeReady: Promise<number>}
function streamOnOpen(this: StreamState, fd: number): void { fstat(fd, (err, stats) => { this.resolveMtime(err ? 0 : stats.mtimeMs) })}
function streamOnData(this: StreamState, chunk: string): void { if (this.isFirstChunk) { this.isFirstChunk = false if (chunk.charCodeAt(0) === 0xfeff) { chunk = chunk.slice(1) } }
this.totalBytesRead += Buffer.byteLength(chunk) if ( !this.truncateOnByteLimit && this.maxBytes !== undefined && this.totalBytesRead > this.maxBytes ) { this.stream.destroy( new FileTooLargeError(this.totalBytesRead, this.maxBytes), ) return }
const data = this.partial.length > 0 ? this.partial + chunk : chunk this.partial = ''
let startPos = 0 let newlinePos: number while ((newlinePos = data.indexOf('\n', startPos)) !== -1) { if ( this.currentLineIndex >= this.offset && this.currentLineIndex < this.endLine ) { let line = data.slice(startPos, newlinePos) if (line.endsWith('\r')) { line = line.slice(0, -1) } if (this.truncateOnByteLimit && this.maxBytes !== undefined) { const sep = this.selectedLines.length > 0 ? 1 : 0 const nextBytes = this.selectedBytes + sep + Buffer.byteLength(line) if (nextBytes > this.maxBytes) { // Cap hit — collapse the selection range so nothing more is // accumulated. Stream continues (to count totalLines). this.truncatedByBytes = true this.endLine = this.currentLineIndex } else { this.selectedBytes = nextBytes this.selectedLines.push(line) } } else { this.selectedLines.push(line) } } this.currentLineIndex++ startPos = newlinePos + 1 }
// Only keep the trailing fragment when inside the selected range. // Outside the range we just count newlines — discarding prevents // unbounded memory growth on huge single-line files. if (startPos < data.length) { if ( this.currentLineIndex >= this.offset && this.currentLineIndex < this.endLine ) { const fragment = data.slice(startPos) // In truncate mode, `partial` can grow unboundedly if the selected // range contains a huge single line (no newline across many chunks). // Once the fragment alone would overflow the remaining budget, we know // the completed line can never fit — set truncated, collapse the // selection range, and discard the fragment to stop accumulation. if (this.truncateOnByteLimit && this.maxBytes !== undefined) { const sep = this.selectedLines.length > 0 ? 1 : 0 const fragBytes = this.selectedBytes + sep + Buffer.byteLength(fragment) if (fragBytes > this.maxBytes) { this.truncatedByBytes = true this.endLine = this.currentLineIndex return } } this.partial = fragment } }}
function streamOnEnd(this: StreamState): void { let line = this.partial if (line.endsWith('\r')) { line = line.slice(0, -1) } if ( this.currentLineIndex >= this.offset && this.currentLineIndex < this.endLine ) { if (this.truncateOnByteLimit && this.maxBytes !== undefined) { const sep = this.selectedLines.length > 0 ? 1 : 0 const nextBytes = this.selectedBytes + sep + Buffer.byteLength(line) if (nextBytes > this.maxBytes) { this.truncatedByBytes = true } else { this.selectedLines.push(line) } } else { this.selectedLines.push(line) } } this.currentLineIndex++
const content = this.selectedLines.join('\n') const truncated = this.truncatedByBytes this.mtimeReady.then(mtimeMs => { this.resolve({ content, lineCount: this.selectedLines.length, totalLines: this.currentLineIndex, totalBytes: this.totalBytesRead, readBytes: Buffer.byteLength(content, 'utf8'), mtimeMs, ...(truncated ? { truncatedByBytes: true } : {}), }) })}
function readFileInRangeStreaming( filePath: string, offset: number, maxLines: number | undefined, maxBytes: number | undefined, truncateOnByteLimit: boolean, signal?: AbortSignal,): Promise<ReadFileRangeResult> { return new Promise((resolve, reject) => { const state: StreamState = { stream: createReadStream(filePath, { encoding: 'utf8', highWaterMark: 512 * 1024, ...(signal ? { signal } : undefined), }), offset, endLine: maxLines !== undefined ? offset + maxLines : Infinity, maxBytes, truncateOnByteLimit, resolve, totalBytesRead: 0, selectedBytes: 0, truncatedByBytes: false, currentLineIndex: 0, selectedLines: [], partial: '', isFirstChunk: true, resolveMtime: () => {}, mtimeReady: null as unknown as Promise<number>, } state.mtimeReady = new Promise<number>(r => { state.resolveMtime = r })
state.stream.once('open', streamOnOpen.bind(state)) state.stream.on('data', streamOnData.bind(state)) state.stream.once('end', streamOnEnd.bind(state)) state.stream.once('error', reject) })}