@@ -182,14 +182,6 @@ async function readAllLogLines(logPath: string): Promise<string[]> {
182182 return lines
183183}
184184
185- function splitTailChunk ( text : string ) : string [ ] {
186- const lines = text . split ( / \r ? \n / )
187- if ( lines . length > 0 && lines [ lines . length - 1 ] === '' ) {
188- lines . pop ( )
189- }
190- return lines
191- }
192-
193185// Streaming fallback for compatibility with older beforeLine requests. This still
194186// scans the whole file, so byte cursors are used for large-log pagination below.
195187async function streamLogLineWindow (
@@ -256,7 +248,7 @@ async function readLogLineWindowFromEnd(
256248
257249 const handle = await fs . open ( logPath , 'r' )
258250 const chunkSize = 64 * 1024
259- const chunks : string [ ] = [ ]
251+ const chunks : Buffer [ ] = [ ]
260252 let cursor = endByte
261253 let newlineCount = 0
262254 let sawNonEmptyContent = false
@@ -268,20 +260,42 @@ async function readLogLineWindowFromEnd(
268260 const buffer = Buffer . allocUnsafe ( readSize )
269261 const { bytesRead } = await handle . read ( buffer , 0 , readSize , start )
270262 if ( bytesRead <= 0 ) break
271- const chunk = buffer . subarray ( 0 , bytesRead ) . toString ( 'utf8' )
263+ const chunk = buffer . subarray ( 0 , bytesRead )
272264 chunks . unshift ( chunk )
273- if ( chunk . trimEnd ( ) . length > 0 ) sawNonEmptyContent = true
274- newlineCount += ( chunk . match ( / \n / g) ?? [ ] ) . length
265+ for ( const byte of chunk ) {
266+ if ( byte === 0x0a ) newlineCount += 1
267+ if ( byte > 0x20 ) sawNonEmptyContent = true
268+ }
275269 cursor = start
276270 }
277271 } finally {
278272 await handle . close ( )
279273 }
280274
281- const text = chunks . join ( '' )
282- const lines = splitTailChunk ( text )
283- const selectedLines = lines . slice ( - limit )
284- if ( selectedLines . length === 0 && ! sawNonEmptyContent ) {
275+ const buffer = Buffer . concat ( chunks )
276+ let contentEnd = buffer . length
277+ if ( contentEnd > 0 && buffer [ contentEnd - 1 ] === 0x0a ) {
278+ contentEnd -= 1
279+ }
280+
281+ const lineStarts = [ 0 ]
282+ for ( let index = 0 ; index < contentEnd ; index += 1 ) {
283+ if ( buffer [ index ] === 0x0a ) {
284+ lineStarts . push ( index + 1 )
285+ }
286+ }
287+
288+ const selectedStarts = lineStarts . slice ( - limit )
289+ const selectedLines = selectedStarts . map ( ( startOffset , index ) => {
290+ const nextStart = selectedStarts [ index + 1 ]
291+ let endOffset = nextStart === undefined ? contentEnd : nextStart - 1
292+ if ( endOffset > startOffset && buffer [ endOffset - 1 ] === 0x0d ) {
293+ endOffset -= 1
294+ }
295+ return buffer . subarray ( startOffset , endOffset ) . toString ( 'utf8' )
296+ } )
297+
298+ if ( contentEnd === 0 && ! sawNonEmptyContent ) {
285299 return {
286300 totalLines : null ,
287301 startLine : 0 ,
@@ -293,17 +307,9 @@ async function readLogLineWindowFromEnd(
293307 }
294308 }
295309
296- const selectedText = selectedLines . join ( '\n' )
297- const encodedLength = Buffer . byteLength ( selectedText , 'utf8' )
298- const hasTrailingNewline = text . endsWith ( '\n' )
299- const trailingBytes = hasTrailingNewline ? 1 : 0
300- const startByte = Math . max ( 0 , endByte - trailingBytes - encodedLength )
301- let lineStartByte = startByte
302- const lineKeys = selectedLines . map ( ( line ) => {
303- const key = `b:${ lineStartByte } `
304- lineStartByte += Buffer . byteLength ( line , 'utf8' ) + 1
305- return key
306- } )
310+ const selectedStartByte = selectedStarts [ 0 ] ?? contentEnd
311+ const startByte = Math . max ( 0 , cursor + selectedStartByte )
312+ const lineKeys = selectedStarts . map ( ( startOffset ) => `b:${ cursor + startOffset } ` )
307313
308314 return {
309315 totalLines : null ,
0 commit comments