src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/tty.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/signal"
     7  	"sync"
     8  
     9  	"src.elv.sh/pkg/cli/term"
    10  	"src.elv.sh/pkg/sys"
    11  )
    12  
    13  // TTY is the type the terminal dependency of the editor needs to satisfy.
    14  type TTY interface {
    15  	// Setup sets up the terminal for the CLI app.
    16  	//
    17  	// This method returns a restore function that undoes the setup, and any
    18  	// error during setup. It only returns fatal errors that make the terminal
    19  	// unsuitable for later operations; non-fatal errors may be reported by
    20  	// showing a warning message, but not returned.
    21  	//
    22  	// This method should be called before any other method is called.
    23  	Setup() (restore func(), err error)
    24  
    25  	// ReadEvent reads a terminal event.
    26  	ReadEvent() (term.Event, error)
    27  	// SetRawInput requests the next n ReadEvent calls to read raw events. It
    28  	// is applicable to environments where events are represented as a special
    29  	// sequences, such as VT100. It is a no-op if events are delivered as whole
    30  	// units by the terminal, such as Windows consoles.
    31  	SetRawInput(n int)
    32  	// CloseReader releases resources allocated for reading terminal events.
    33  	CloseReader()
    34  
    35  	term.Writer
    36  
    37  	// NotifySignals start relaying signals and returns a channel on which
    38  	// signals are delivered.
    39  	NotifySignals() <-chan os.Signal
    40  	// StopSignals stops the relaying of signals. After this function returns,
    41  	// the channel returned by NotifySignals will no longer deliver signals.
    42  	StopSignals()
    43  
    44  	// Size returns the height and width of the terminal.
    45  	Size() (h, w int)
    46  }
    47  
    48  type aTTY struct {
    49  	in, out *os.File
    50  	r       term.Reader
    51  	term.Writer
    52  	sigCh chan os.Signal
    53  
    54  	rawMutex sync.Mutex
    55  	raw      int
    56  }
    57  
    58  // NewTTY returns a new TTY from input and output terminal files.
    59  func NewTTY(in, out *os.File) TTY {
    60  	return &aTTY{in: in, out: out, Writer: term.NewWriter(out)}
    61  }
    62  
    63  func (t *aTTY) Setup() (func(), error) {
    64  	restore, err := term.Setup(t.in, t.out)
    65  	return func() {
    66  		err := restore()
    67  		if err != nil {
    68  			fmt.Println(t.out, "failed to restore terminal properties:", err)
    69  		}
    70  	}, err
    71  }
    72  
    73  func (t *aTTY) Size() (h, w int) {
    74  	return sys.WinSize(t.out)
    75  }
    76  
    77  func (t *aTTY) ReadEvent() (term.Event, error) {
    78  	if t.r == nil {
    79  		t.r = term.NewReader(t.in)
    80  	}
    81  	if t.consumeRaw() {
    82  		return t.r.ReadRawEvent()
    83  	}
    84  	return t.r.ReadEvent()
    85  }
    86  
    87  func (t *aTTY) consumeRaw() bool {
    88  	t.rawMutex.Lock()
    89  	defer t.rawMutex.Unlock()
    90  	if t.raw <= 0 {
    91  		return false
    92  	}
    93  	t.raw--
    94  	return true
    95  }
    96  
    97  func (t *aTTY) SetRawInput(n int) {
    98  	t.rawMutex.Lock()
    99  	defer t.rawMutex.Unlock()
   100  	t.raw = n
   101  }
   102  
   103  func (t *aTTY) CloseReader() {
   104  	if t.r != nil {
   105  		t.r.Close()
   106  	}
   107  	t.r = nil
   108  }
   109  
   110  func (t *aTTY) NotifySignals() <-chan os.Signal {
   111  	t.sigCh = sys.NotifySignals()
   112  	return t.sigCh
   113  }
   114  
   115  func (t *aTTY) StopSignals() {
   116  	signal.Stop(t.sigCh)
   117  	close(t.sigCh)
   118  	t.sigCh = nil
   119  }