1+ import { highlight } from 'cli-highlight'
12import MarkdownIt from 'markdown-it'
23// @ts -ignore: Type definitions not available for markdown-it-terminal
34import terminal from 'markdown-it-terminal'
4- import { highlight } from 'cli-highlight'
5- import { gray } from 'picocolors'
65import wrapAnsi from 'wrap-ansi'
76
7+ import { Spinner } from '../utils/spinner'
8+
89export type MarkdownStreamRendererOptions = {
910 width ?: number
1011 isTTY ?: boolean
@@ -39,7 +40,6 @@ export class MarkdownStreamRenderer {
3940 private width : number
4041 private isTTY : boolean
4142 private syntaxHighlight : boolean
42- private maxBufferBytes : number
4343 private streamingMode : 'smart' | 'conservative'
4444 private md : MarkdownIt
4545
@@ -48,33 +48,14 @@ export class MarkdownStreamRenderer {
4848 private lookaheadBuffer = ''
4949 private consumedIndex = 0
5050 private sourceBuffer = ''
51- private lastFlushTime = Date . now ( )
5251
5352 // Loading indicator state
54- private loadingIndicatorTimer : NodeJS . Timeout | null = null
55- private isShowingIndicator = false
56- private indicatorStartTime = 0
57- private indicatorFrame = 0
5853 private resizeHandler ?: ( ) => void
59- // Three dots expanding and contracting animation
60- private readonly indicatorFrames = [
61- '···' ,
62- '•··' ,
63- '●•·' ,
64- '●●•' ,
65- '●●●' ,
66- '●●•' ,
67- '●•·' ,
68- '•··' ,
69- ]
70- private readonly indicatorThresholdMs = 50
71- private readonly indicatorUpdateMs = 150
7254
7355 constructor ( opts : MarkdownStreamRendererOptions = { } ) {
7456 this . width = opts . width ?? ( process . stdout . columns || 80 )
7557 this . isTTY = opts . isTTY ?? process . stdout . isTTY
7658 this . syntaxHighlight = opts . syntaxHighlight ?? true
77- this . maxBufferBytes = ( opts . maxBufferKB ?? 64 ) * 1024
7859 this . streamingMode = opts . streamingMode ?? 'smart'
7960
8061 // Initialize markdown-it with terminal renderer
@@ -170,8 +151,7 @@ export class MarkdownStreamRenderer {
170151 // Check if we should force flush due to age or size
171152 this . checkForceFlush ( outs )
172153
173- // Update loading indicator
174- this . updateLoadingIndicator ( )
154+ Spinner . get ( ) . start ( null , true )
175155 }
176156
177157 private processLine (
@@ -341,7 +321,7 @@ export class MarkdownStreamRenderer {
341321 if ( ! this . currentBlock ) return
342322
343323 // Hide loading indicator if showing
344- this . hideLoadingIndicator ( )
324+ Spinner . get ( ) . stop ( )
345325
346326 // Render the block
347327 const rendered = this . render ( this . currentBlock . buffer )
@@ -354,7 +334,6 @@ export class MarkdownStreamRenderer {
354334
355335 // Reset block state
356336 this . currentBlock = null
357- this . lastFlushTime = Date . now ( )
358337 }
359338
360339 private checkForceFlush ( outs : string [ ] ) {
@@ -373,6 +352,9 @@ export class MarkdownStreamRenderer {
373352 ( age > 1000 && this . currentBlock . type === 'list' ) // Lists get extra time to accumulate
374353
375354 if ( shouldForceFlush ) {
355+ // Hide indicator since we flushed something
356+ Spinner . get ( ) . stop ( )
357+
376358 // Try to find a soft boundary for force flush
377359 const buffer = this . currentBlock . buffer
378360 const sentenceEnd = buffer . lastIndexOf ( '. ' )
@@ -387,71 +369,10 @@ export class MarkdownStreamRenderer {
387369 const rendered = this . render ( toFlush )
388370 outs . push ( rendered )
389371 this . consumedIndex += toFlush . length
390- this . lastFlushTime = now
391-
392- // Hide indicator since we flushed something
393- this . hideLoadingIndicator ( )
394372 }
395373 }
396374 }
397375
398- private updateLoadingIndicator ( ) {
399- if ( ! this . currentBlock || ! this . isTTY ) return
400-
401- const now = Date . now ( )
402- const age = now - this . currentBlock . startTime
403-
404- // Show indicator if buffering for too long
405- if ( age > this . indicatorThresholdMs && ! this . isShowingIndicator ) {
406- this . showLoadingIndicator ( )
407- }
408- }
409-
410- private showLoadingIndicator ( ) {
411- if ( this . isShowingIndicator || ! this . isTTY ) return
412-
413- this . isShowingIndicator = true
414- this . indicatorStartTime = Date . now ( )
415- this . indicatorFrame = 0
416-
417- // Write initial indicator
418- this . writeIndicator ( )
419-
420- // Start update timer
421- this . loadingIndicatorTimer = setInterval ( ( ) => {
422- this . indicatorFrame =
423- ( this . indicatorFrame + 1 ) % this . indicatorFrames . length
424- this . writeIndicator ( )
425- } , this . indicatorUpdateMs )
426- }
427-
428- private writeIndicator ( ) {
429- if ( ! this . isShowingIndicator ) return
430-
431- const dot = this . indicatorFrames [ this . indicatorFrame ]
432- const message = gray ( ` ${ dot } ` )
433-
434- // Write to stderr to avoid interfering with stdout
435- process . stderr . write ( `\r${ message } ` )
436- }
437-
438- private hideLoadingIndicator ( ) {
439- if ( ! this . isShowingIndicator ) return
440-
441- this . isShowingIndicator = false
442-
443- // Clear timer first to prevent race conditions
444- if ( this . loadingIndicatorTimer ) {
445- clearInterval ( this . loadingIndicatorTimer )
446- this . loadingIndicatorTimer = null
447- }
448-
449- // Clear the indicator line completely and move cursor to beginning of line
450- // Use enough spaces to clear the longest possible indicator + padding
451- // Format is ' ●●● ' so we need at least 5 chars + some buffer
452- process . stderr . write ( '\r' + ' ' . repeat ( 20 ) + '\r' )
453- }
454-
455376 private processConservativeMode ( text : string , outs : string [ ] ) {
456377 // This is a simplified version of the original behavior
457378 // Just accumulate and flush on double newlines
@@ -472,7 +393,7 @@ export class MarkdownStreamRenderer {
472393
473394 end ( ) : string | null {
474395 // Hide any loading indicator
475- this . hideLoadingIndicator ( )
396+ Spinner . get ( ) . stop ( )
476397
477398 const outputs : string [ ] = [ ]
478399
@@ -617,7 +538,7 @@ export class MarkdownStreamRenderer {
617538 } )
618539 }
619540
620- const bufferLine = `${ bgGray } ${ padLeft } ${ ' ' . repeat ( wrapWidth ) } ${ padRight } ${ reset } `
541+ const bufferLine = `${ bgGray } \`\`\` ${ padLeft } ${ ' ' . repeat ( wrapWidth - 3 ) } ${ padRight } ${ reset } `
621542 const backgroundCode = [
622543 bufferLine ,
623544 ...wrappedLines ,
0 commit comments