@@ -2,6 +2,7 @@ import fs from "fs/promises";
22import path from "path" ;
33import os from 'os' ;
44import { randomBytes } from 'crypto' ;
5+ import { StringDecoder } from 'string_decoder' ;
56import { diffLines , createTwoFilesPatch } from 'diff' ;
67import { minimatch } from 'minimatch' ;
78import { normalizePath , expandHome } from './path-utils.js' ;
@@ -292,42 +293,28 @@ export async function tailFile(filePath: string, numLines: number): Promise<stri
292293 // Open file for reading
293294 const fileHandle = await fs . open ( filePath , 'r' ) ;
294295 try {
295- const lines : string [ ] = [ ] ;
296+ const chunks : Buffer [ ] = [ ] ;
296297 let position = fileSize ;
297- let chunk = Buffer . alloc ( CHUNK_SIZE ) ;
298- let linesFound = 0 ;
299- let remainingText = '' ;
298+ const chunk = Buffer . alloc ( CHUNK_SIZE ) ;
299+ let newlinesFound = 0 ;
300300
301301 // Read chunks from the end of the file until we have enough lines
302- while ( position > 0 && linesFound < numLines ) {
302+ while ( position > 0 && newlinesFound < numLines ) {
303303 const size = Math . min ( CHUNK_SIZE , position ) ;
304304 position -= size ;
305305
306306 const { bytesRead } = await fileHandle . read ( chunk , 0 , size , position ) ;
307307 if ( ! bytesRead ) break ;
308-
309- // Get the chunk as a string and prepend any remaining text from previous iteration
310- const readData = chunk . slice ( 0 , bytesRead ) . toString ( 'utf-8' ) ;
311- const chunkText = readData + remainingText ;
312-
313- // Split by newlines and count
314- const chunkLines = normalizeLineEndings ( chunkText ) . split ( '\n' ) ;
315-
316- // If this isn't the end of the file, the first line is likely incomplete
317- // Save it to prepend to the next chunk
318- if ( position > 0 ) {
319- remainingText = chunkLines [ 0 ] ;
320- chunkLines . shift ( ) ; // Remove the first (incomplete) line
321- }
322-
323- // Add lines to our result (up to the number we need)
324- for ( let i = chunkLines . length - 1 ; i >= 0 && linesFound < numLines ; i -- ) {
325- lines . unshift ( chunkLines [ i ] ) ;
326- linesFound ++ ;
308+
309+ const readData = Buffer . from ( chunk . subarray ( 0 , bytesRead ) ) ;
310+ chunks . unshift ( readData ) ;
311+ for ( const byte of readData ) {
312+ if ( byte === 0x0a ) newlinesFound ++ ;
327313 }
328314 }
329-
330- return lines . join ( '\n' ) ;
315+
316+ const text = normalizeLineEndings ( Buffer . concat ( chunks ) . toString ( 'utf-8' ) ) ;
317+ return text . split ( '\n' ) . slice ( - numLines ) . join ( '\n' ) ;
331318 } finally {
332319 await fileHandle . close ( ) ;
333320 }
@@ -341,13 +328,14 @@ export async function headFile(filePath: string, numLines: number): Promise<stri
341328 let buffer = '' ;
342329 let bytesRead = 0 ;
343330 const chunk = Buffer . alloc ( 1024 ) ; // 1KB buffer
331+ const decoder = new StringDecoder ( 'utf-8' ) ;
344332
345333 // Read chunks and count lines until we have enough or reach EOF
346334 while ( lines . length < numLines ) {
347335 const result = await fileHandle . read ( chunk , 0 , chunk . length , bytesRead ) ;
348336 if ( result . bytesRead === 0 ) break ; // End of file
349337 bytesRead += result . bytesRead ;
350- buffer += chunk . slice ( 0 , result . bytesRead ) . toString ( 'utf-8' ) ;
338+ buffer += decoder . write ( chunk . subarray ( 0 , result . bytesRead ) ) ;
351339
352340 const newLineIndex = buffer . lastIndexOf ( '\n' ) ;
353341 if ( newLineIndex !== - 1 ) {
@@ -359,6 +347,8 @@ export async function headFile(filePath: string, numLines: number): Promise<stri
359347 }
360348 }
361349 }
350+
351+ buffer += decoder . end ( ) ;
362352
363353 // If there is leftover content and we still need lines, add it
364354 if ( buffer . length > 0 && lines . length < numLines ) {
0 commit comments