github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/terminal/glint_step_group.go (about)

     1  package terminal
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/mitchellh/go-glint"
    11  )
    12  
    13  // glintStepGroup implements StepGroup with live updating and a display
    14  // "window" for live terminal output (when using TermOutput).
    15  type glintStepGroup struct {
    16  	mu     sync.Mutex
    17  	ctx    context.Context
    18  	cancel context.CancelFunc
    19  	wg     sync.WaitGroup
    20  	steps  []*glintStep
    21  	closed bool
    22  }
    23  
    24  // Start a step in the output
    25  func (f *glintStepGroup) Add(str string, args ...interface{}) Step {
    26  	f.mu.Lock()
    27  	defer f.mu.Unlock()
    28  
    29  	// Build our step
    30  	step := &glintStep{ctx: f.ctx, status: newGlintStatus()}
    31  
    32  	// Setup initial status
    33  	step.Update(str, args...)
    34  
    35  	// If we're closed we don't add this step to our waitgroup or document.
    36  	// We still create a step and return a non-nil step so downstreams don't
    37  	// crash.
    38  	if !f.closed {
    39  		// Add since we have a step
    40  		step.wg = &f.wg
    41  		f.wg.Add(1)
    42  
    43  		// Add it to our list
    44  		f.steps = append(f.steps, step)
    45  	}
    46  
    47  	return step
    48  }
    49  
    50  func (f *glintStepGroup) Wait() {
    51  	f.mu.Lock()
    52  	f.closed = true
    53  	f.cancel()
    54  	wg := &f.wg
    55  	f.mu.Unlock()
    56  
    57  	wg.Wait()
    58  }
    59  
    60  func (f *glintStepGroup) Body(context.Context) glint.Component {
    61  	f.mu.Lock()
    62  	defer f.mu.Unlock()
    63  
    64  	var cs []glint.Component
    65  	for _, s := range f.steps {
    66  		cs = append(cs, s)
    67  	}
    68  
    69  	return glint.Fragment(cs...)
    70  }
    71  
    72  type glintStep struct {
    73  	mu        sync.Mutex
    74  	ctx       context.Context
    75  	wg        *sync.WaitGroup
    76  	done      bool
    77  	msg       string
    78  	statusVal string
    79  	status    *glintStatus
    80  	term      *glintTerm
    81  }
    82  
    83  func (f *glintStep) TermOutput() io.Writer {
    84  	f.mu.Lock()
    85  	defer f.mu.Unlock()
    86  
    87  	if f.term == nil {
    88  		t, err := newGlintTerm(f.ctx, 10, 80)
    89  		if err != nil {
    90  			panic(err)
    91  		}
    92  
    93  		f.term = t
    94  	}
    95  
    96  	return f.term
    97  }
    98  
    99  func (f *glintStep) Update(str string, args ...interface{}) {
   100  	f.mu.Lock()
   101  	defer f.mu.Unlock()
   102  	f.msg = fmt.Sprintf(str, args...)
   103  	f.status.reset()
   104  
   105  	if f.statusVal != "" {
   106  		f.status.Step(f.statusVal, f.msg)
   107  	} else {
   108  		f.status.Update(f.msg)
   109  	}
   110  }
   111  
   112  func (f *glintStep) Status(status string) {
   113  	f.mu.Lock()
   114  	defer f.mu.Unlock()
   115  	f.statusVal = status
   116  	f.status.reset()
   117  	f.status.Step(status, f.msg)
   118  }
   119  
   120  func (f *glintStep) Done() {
   121  	f.mu.Lock()
   122  	defer f.mu.Unlock()
   123  
   124  	if f.done {
   125  		return
   126  	}
   127  
   128  	// Set done
   129  	f.done = true
   130  
   131  	// Set status
   132  	if f.statusVal == "" {
   133  		f.status.reset()
   134  		f.status.Step(StatusOK, f.msg)
   135  	}
   136  
   137  	// Unset the waitgroup
   138  	f.wg.Done()
   139  	time.Sleep(f.minimumLag())
   140  }
   141  
   142  func (f *glintStep) Abort() {
   143  	f.mu.Lock()
   144  	defer f.mu.Unlock()
   145  
   146  	if f.done {
   147  		return
   148  	}
   149  
   150  	f.done = true
   151  
   152  	// This will cause the term to render the full scrollback from now on
   153  	if f.term != nil {
   154  		f.term.showFull()
   155  	}
   156  
   157  	f.status.Step(StatusError, f.msg)
   158  	f.wg.Done()
   159  	time.Sleep(f.minimumLag())
   160  }
   161  
   162  func (f *glintStep) Body(context.Context) glint.Component {
   163  	f.mu.Lock()
   164  	defer f.mu.Unlock()
   165  
   166  	var cs []glint.Component
   167  	cs = append(cs, f.status)
   168  
   169  	// If we have a terminal, output that too.
   170  	if f.term != nil {
   171  		cs = append(cs, f.term)
   172  	}
   173  
   174  	return glint.Fragment(cs...)
   175  }
   176  
   177  func (s *glintStep) minimumLag() time.Duration {
   178  	return time.Millisecond * 50
   179  }