@@ -6,6 +6,7 @@ package cmd
6
6
7
7
import (
8
8
"bufio"
9
+ "bytes"
9
10
"context"
10
11
"encoding/json"
11
12
"fmt"
@@ -16,6 +17,7 @@ import (
16
17
"path/filepath"
17
18
"strings"
18
19
"time"
20
+ "unicode/utf8"
19
21
20
22
"github.com/gitpod-io/gitpod/common-go/log"
21
23
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/supervisor"
@@ -564,59 +566,85 @@ func pipeTask(ctx context.Context, task *api.TaskStatus, supervisor *supervisor.
564
566
}
565
567
}
566
568
567
- func listenTerminal (ctx context.Context , task * api.TaskStatus , supervisor * supervisor.SupervisorClient , runLog * logrus.Entry ) error {
568
- listen , err := supervisor .Terminal .Listen (ctx , & api.ListenTerminalRequest {
569
- Alias : task .Terminal ,
570
- })
571
- if err != nil {
572
- return err
573
- }
569
+ // TerminalReader is an interface for anything that can receive terminal data (this is abstracted for use in testing)
570
+ type TerminalReader interface {
571
+ Recv () ([]byte , error )
572
+ }
574
573
575
- pr , pw := io .Pipe ()
576
- defer pr .Close ()
577
- defer pw .Close ()
574
+ type LinePrinter func (string )
578
575
579
- scanner := bufio . NewScanner ( pr )
580
- const maxTokenSize = 1 * 1024 * 1024 // 1 MB
581
- buf := make ([] byte , maxTokenSize )
582
- scanner . Buffer ( buf , maxTokenSize )
576
+ // processTerminalOutput reads from a TerminalReader, processes the output, and calls the provided LinePrinter for each complete line.
577
+ // It handles UTF-8 decoding of characters split across chunks and control characters (\n \r \b).
578
+ func processTerminalOutput ( reader TerminalReader , printLine LinePrinter ) error {
579
+ var buffer , line bytes. Buffer
583
580
584
- go func () {
585
- defer pw .Close ()
586
- for {
587
- resp , err := listen .Recv ()
588
- if err != nil {
589
- _ = pw .CloseWithError (err )
590
- return
591
- }
581
+ flushLine := func () {
582
+ if line .Len () > 0 {
583
+ printLine (line .String ())
584
+ line .Reset ()
585
+ }
586
+ }
592
587
593
- title := resp .GetTitle ()
594
- if title != "" {
595
- task .Presentation .Name = title
588
+ for {
589
+ data , err := reader .Recv ()
590
+ if err != nil {
591
+ if err == io .EOF {
592
+ flushLine ()
593
+ return nil
596
594
}
595
+ return err
596
+ }
597
+
598
+ buffer .Write (data )
597
599
598
- exitCode := resp .GetExitCode ()
599
- if exitCode != 0 {
600
- runLog .Infof ("%s: exited with code %d" , task .Presentation .Name , exitCode )
600
+ for {
601
+ r , size := utf8 .DecodeRune (buffer .Bytes ())
602
+ if r == utf8 .RuneError && size == 0 {
603
+ break // incomplete character at the end
601
604
}
602
605
603
- data := resp .GetData ()
604
- if len (data ) > 0 {
605
- _ , err := pw .Write (data )
606
- if err != nil {
607
- _ = pw .CloseWithError (err )
608
- return
606
+ char := buffer .Next (size )
607
+
608
+ switch r {
609
+ case '\r' :
610
+ flushLine ()
611
+ case '\n' :
612
+ flushLine ()
613
+ case '\b' :
614
+ if line .Len () > 0 {
615
+ line .Truncate (line .Len () - 1 )
609
616
}
617
+ default :
618
+ line .Write (char )
610
619
}
611
620
}
612
- }()
621
+ }
622
+ }
613
623
614
- for scanner .Scan () {
615
- line := scanner .Text ()
624
+ func listenTerminal (ctx context.Context , task * api.TaskStatus , supervisor * supervisor.SupervisorClient , runLog * logrus.Entry ) error {
625
+ listen , err := supervisor .Terminal .Listen (ctx , & api.ListenTerminalRequest {Alias : task .Terminal })
626
+ if err != nil {
627
+ return err
628
+ }
629
+
630
+ terminalReader := & TerminalReaderAdapter {listen }
631
+ printLine := func (line string ) {
616
632
runLog .Infof ("%s: %s" , task .Presentation .Name , line )
617
633
}
618
634
619
- return scanner .Err ()
635
+ return processTerminalOutput (terminalReader , printLine )
636
+ }
637
+
638
+ type TerminalReaderAdapter struct {
639
+ client api.TerminalService_ListenClient
640
+ }
641
+
642
+ func (t * TerminalReaderAdapter ) Recv () ([]byte , error ) {
643
+ resp , err := t .client .Recv ()
644
+ if err != nil {
645
+ return nil , err
646
+ }
647
+ return resp .GetData (), nil
620
648
}
621
649
622
650
var validateOpts struct {
0 commit comments