github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/message/spinner.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package message provides a rich set of functions for displaying messages to the user.
     5  package message
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/pterm/pterm"
    14  )
    15  
    16  var activeSpinner *Spinner
    17  
    18  var sequence = []string{`  ⠋ `, `  ⠙ `, `  ⠹ `, `  ⠸ `, `  ⠼ `, `  ⠴ `, `  ⠦ `, `  ⠧ `, `  ⠇ `, `  ⠏ `}
    19  
    20  // Spinner is a wrapper around pterm.SpinnerPrinter.
    21  type Spinner struct {
    22  	spinner        *pterm.SpinnerPrinter
    23  	startText      string
    24  	termWidth      int
    25  	preserveWrites bool
    26  }
    27  
    28  // NewProgressSpinner creates a new progress spinner.
    29  func NewProgressSpinner(format string, a ...any) *Spinner {
    30  	if activeSpinner != nil {
    31  		activeSpinner.Updatef(format, a...)
    32  		debugPrinter(2, "Active spinner already exists")
    33  		return activeSpinner
    34  	}
    35  
    36  	var spinner *pterm.SpinnerPrinter
    37  	text := pterm.Sprintf(format, a...)
    38  	if NoProgress {
    39  		Info(text)
    40  	} else {
    41  		spinner, _ = pterm.DefaultSpinner.
    42  			WithRemoveWhenDone(false).
    43  			// Src: https://github.com/gernest/wow/blob/master/spin/spinners.go#L335
    44  			WithSequence(sequence...).
    45  			Start(text)
    46  	}
    47  
    48  	activeSpinner = &Spinner{
    49  		spinner:   spinner,
    50  		startText: text,
    51  		termWidth: pterm.GetTerminalWidth(),
    52  	}
    53  
    54  	return activeSpinner
    55  }
    56  
    57  // EnablePreserveWrites enables preserving writes to the terminal.
    58  func (p *Spinner) EnablePreserveWrites() {
    59  	p.preserveWrites = true
    60  }
    61  
    62  // DisablePreserveWrites disables preserving writes to the terminal.
    63  func (p *Spinner) DisablePreserveWrites() {
    64  	p.preserveWrites = false
    65  }
    66  
    67  // Write the given text to the spinner.
    68  func (p *Spinner) Write(raw []byte) (int, error) {
    69  	size := len(raw)
    70  	if NoProgress {
    71  		if p.preserveWrites {
    72  			pterm.Printfln("     %s", string(raw))
    73  		}
    74  
    75  		return size, nil
    76  	}
    77  
    78  	// Split the text into lines and update the spinner for each line.
    79  	scanner := bufio.NewScanner(bytes.NewReader(raw))
    80  	scanner.Split(bufio.ScanLines)
    81  	for scanner.Scan() {
    82  		// Only be fancy if preserve writes is enabled.
    83  		if p.preserveWrites {
    84  			text := pterm.Sprintf("     %s", scanner.Text())
    85  			pterm.Fprinto(p.spinner.Writer, strings.Repeat(" ", pterm.GetTerminalWidth()))
    86  			pterm.Fprintln(p.spinner.Writer, text)
    87  		} else {
    88  			// Otherwise just update the spinner text.
    89  			p.spinner.UpdateText(scanner.Text())
    90  		}
    91  	}
    92  
    93  	return size, nil
    94  }
    95  
    96  // Updatef updates the spinner text.
    97  func (p *Spinner) Updatef(format string, a ...any) {
    98  	if NoProgress {
    99  		debugPrinter(2, fmt.Sprintf(format, a...))
   100  		return
   101  	}
   102  
   103  	pterm.Fprinto(p.spinner.Writer, strings.Repeat(" ", pterm.GetTerminalWidth()))
   104  	text := pterm.Sprintf(format, a...)
   105  	p.spinner.UpdateText(text)
   106  }
   107  
   108  // Stop the spinner.
   109  func (p *Spinner) Stop() {
   110  	if p.spinner != nil && p.spinner.IsActive {
   111  		_ = p.spinner.Stop()
   112  	}
   113  	activeSpinner = nil
   114  }
   115  
   116  // Success prints a success message and stops the spinner.
   117  func (p *Spinner) Success() {
   118  	p.Successf(p.startText)
   119  }
   120  
   121  // Successf prints a success message with the spinner and stops it.
   122  func (p *Spinner) Successf(format string, a ...any) {
   123  	text := pterm.Sprintf(format, a...)
   124  	if p.spinner != nil {
   125  		p.spinner.Success(text)
   126  	} else {
   127  		Info(text)
   128  	}
   129  	p.Stop()
   130  }
   131  
   132  // Errorf prints an error message with the spinner.
   133  func (p *Spinner) Errorf(err error, format string, a ...any) {
   134  	Warnf(format, a...)
   135  	debugPrinter(2, err)
   136  }
   137  
   138  // Fatal calls message.Fatalf with the given error.
   139  func (p *Spinner) Fatal(err error) {
   140  	p.Fatalf(err, p.startText)
   141  }
   142  
   143  // Fatalf calls message.Fatalf with the given error and format.
   144  func (p *Spinner) Fatalf(err error, format string, a ...any) {
   145  	if p.spinner != nil {
   146  		p.spinner.RemoveWhenDone = true
   147  		_ = p.spinner.Stop()
   148  		activeSpinner = nil
   149  	}
   150  	Fatalf(err, format, a...)
   151  }