@@ -242,18 +242,26 @@ and provides a fast and easy alternative to a package manager.`,
242242 out = os .Stderr
243243 }
244244
245- // Hide cursor during live rendering.
245+ // Use alternate screen buffer for TTY progress so that
246+ // intermediate frames don't pollute scrollback. The final
247+ // completed state is printed as static text after leaving
248+ // the alternate screen.
246249 if tty && renderProgress {
247- fmt .Fprint (out , "\033 [?25l" )
248- defer fmt .Fprint (out , "\033 [?25h \n " )
250+ fmt .Fprint (out , "\033 [?1049h" ) // enter alternate screen
251+ fmt .Fprint (out , "\033 [?25l" ) // hide cursor
249252 }
250253
251- // Restore cursor on signal.
252- go func () {
253- <- signalChan
254+ leaveAltScreen := func () {
254255 if tty && renderProgress {
255- fmt .Fprint (out , "\033 [?25h\n " )
256+ fmt .Fprint (out , "\033 [?25h" ) // restore cursor
257+ fmt .Fprint (out , "\033 [?1049l" ) // leave alternate screen
256258 }
259+ }
260+
261+ // Restore terminal on signal.
262+ go func () {
263+ <- signalChan
264+ leaveAltScreen ()
257265 os .Exit (2 )
258266 }()
259267
@@ -315,9 +323,14 @@ and provides a fast and easy alternative to a package manager.`,
315323 if renderProgress {
316324 if tty {
317325 renderTTY (out , progress , parallel )
326+ leaveAltScreen ()
327+ // Print a static final frame into the main scrollback.
328+ renderTTYFinal (out , progress )
318329 } else {
319330 renderPlain (out , progress )
320331 }
332+ } else if tty {
333+ leaveAltScreen ()
321334 }
322335
323336 if firstErr != nil {
@@ -558,6 +571,79 @@ func renderTTY(out io.Writer, progress []toolProgress, parallel int) {
558571 fmt .Fprint (out , b .String ())
559572}
560573
574+ // ── Static final frame (printed after leaving alternate screen) ────
575+ //
576+ // Shows the completed state of each tool as static text in the main
577+ // scrollback. No cursor movement or screen clearing.
578+
579+ func renderTTYFinal (out io.Writer , progress []toolProgress ) {
580+ var b strings.Builder
581+
582+ b .WriteString ("\033 [1m Arkade by Alex Ellis - https://github.com/sponsors/alexellis\033 [0m\n \n " )
583+
584+ nameW := 10
585+ for i := range progress {
586+ l := len (progress [i ].name )
587+ if progress [i ].version != "" {
588+ l += len (progress [i ].version ) + 3
589+ }
590+ if l > nameW {
591+ nameW = l
592+ }
593+ }
594+ if nameW > 40 {
595+ nameW = 40
596+ }
597+
598+ barW := 20
599+
600+ for i := range progress {
601+ p := & progress [i ]
602+ read := atomic .LoadInt64 (& p .bytesRead )
603+ total := atomic .LoadInt64 (& p .totalBytes )
604+
605+ displayName := p .name
606+ if p .version != "" {
607+ displayName = fmt .Sprintf ("%s (%s)" , p .name , p .version )
608+ }
609+
610+ switch p .status {
611+ case stDone :
612+ pct := 100
613+ if total > 0 {
614+ pct = int (math .Round (float64 (read ) / float64 (total ) * 100 ))
615+ if pct > 100 {
616+ pct = 100
617+ }
618+ }
619+ bar := renderBar (int64 (pct ), barW )
620+ sizeStr := units .HumanSize (float64 (read ))
621+ speed := float64 (0 )
622+ if p .elapsed > 0 {
623+ speed = float64 (read ) / p .elapsed .Seconds ()
624+ }
625+ elapsed := fmtDuration (p .elapsed )
626+ b .WriteString (fmt .Sprintf (
627+ " \033 [32m✔\033 [0m %-*s %3d%% %s %8s %9s/s %s\n " ,
628+ nameW , displayName , pct , bar , sizeStr , units .HumanSize (speed ), elapsed ))
629+
630+ case stFailed :
631+ errMsg := ""
632+ if p .err != nil {
633+ errMsg = p .err .Error ()
634+ if len (errMsg ) > 40 {
635+ errMsg = errMsg [:40 ] + "…"
636+ }
637+ }
638+ b .WriteString (fmt .Sprintf (
639+ " \033 [31m✘\033 [0m %-*s \033 [31mfailed: %s\033 [0m\n " ,
640+ nameW , p .name , errMsg ))
641+ }
642+ }
643+
644+ fmt .Fprint (out , b .String ())
645+ }
646+
561647// ── Non-TTY / plain renderer ───────────────────────────────────────
562648//
563649// Prints each state change on its own line, no ANSI, no overwrites.
@@ -580,7 +666,11 @@ func renderPlain(out io.Writer, progress []toolProgress) {
580666 case stResolving :
581667 fmt .Fprintf (out , "[resolving] %s\n " , p .name )
582668 case stDownloading :
583- fmt .Fprintf (out , "[downloading] %s\n " , p .name )
669+ displayName := p .name
670+ if p .version != "" {
671+ displayName = fmt .Sprintf ("%s (%s)" , p .name , p .version )
672+ }
673+ fmt .Fprintf (out , "[downloading] %s\n " , displayName )
584674 case stDone :
585675 read := atomic .LoadInt64 (& p .bytesRead )
586676 sizeStr := units .HumanSize (float64 (read ))
0 commit comments