github.com/elves/elvish@v0.15.0/pkg/cli/tty.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/signal"
     7  	"sync"
     8  
     9  	"github.com/elves/elvish/pkg/cli/term"
    10  	"github.com/elves/elvish/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  
    33  	// StopInput causes input delivery to be stopped. When this function
    34  	// returns, the channel previously returned by StartInput will no longer
    35  	// deliver input events.
    36  	StopInput()
    37  
    38  	// NotifySignals start relaying signals and returns a channel on which
    39  	// signals are delivered.
    40  	NotifySignals() <-chan os.Signal
    41  	// StopSignals stops the relaying of signals. After this function returns,
    42  	// the channel returned by NotifySignals will no longer deliver signals.
    43  	StopSignals()
    44  
    45  	// Size returns the height and width of the terminal.
    46  	Size() (h, w int)
    47  
    48  	// Buffer returns the current buffer. The initial value of the current
    49  	// buffer is nil.
    50  	Buffer() *term.Buffer
    51  	// ResetBuffer resets the current buffer to nil without actuating any redraw.
    52  	ResetBuffer()
    53  	// UpdateBuffer updates the current buffer and draw it to the terminal.
    54  	UpdateBuffer(bufNotes, bufMain *term.Buffer, full bool) error
    55  }
    56  
    57  // StdTTY is the terminal connected to inputs from stdin and output to stderr.
    58  var StdTTY = NewTTY(os.Stdin, os.Stderr)
    59  
    60  type aTTY struct {
    61  	in, out *os.File
    62  	r       term.Reader
    63  	w       term.Writer
    64  	sigCh   chan os.Signal
    65  
    66  	rawMutex sync.Mutex
    67  	raw      int
    68  }
    69  
    70  // NewTTY returns a new TTY from input and output terminal files.
    71  func NewTTY(in, out *os.File) TTY {
    72  	return &aTTY{in: in, out: out, w: term.NewWriter(out)}
    73  }
    74  
    75  func (t *aTTY) Setup() (func(), error) {
    76  	restore, err := term.Setup(t.in, t.out)
    77  	return func() {
    78  		err := restore()
    79  		if err != nil {
    80  			fmt.Println(t.out, "failed to restore terminal properties:", err)
    81  		}
    82  	}, err
    83  }
    84  
    85  func (t *aTTY) Size() (h, w int) {
    86  	return sys.GetWinsize(t.out)
    87  }
    88  
    89  func (t *aTTY) ReadEvent() (term.Event, error) {
    90  	if t.r == nil {
    91  		t.r = term.NewReader(t.in)
    92  	}
    93  	if t.consumeRaw() {
    94  		return t.r.ReadRawEvent()
    95  	}
    96  	return t.r.ReadEvent()
    97  }
    98  
    99  func (t *aTTY) consumeRaw() bool {
   100  	t.rawMutex.Lock()
   101  	defer t.rawMutex.Unlock()
   102  	if t.raw <= 0 {
   103  		return false
   104  	}
   105  	t.raw--
   106  	return true
   107  }
   108  
   109  func (t *aTTY) SetRawInput(n int) {
   110  	t.rawMutex.Lock()
   111  	defer t.rawMutex.Unlock()
   112  	t.raw = n
   113  }
   114  
   115  func (t *aTTY) StopInput() {
   116  	if t.r != nil {
   117  		t.r.Close()
   118  	}
   119  	t.r = nil
   120  }
   121  
   122  func (t *aTTY) Buffer() *term.Buffer {
   123  	return t.w.CurrentBuffer()
   124  }
   125  
   126  func (t *aTTY) ResetBuffer() {
   127  	t.w.ResetCurrentBuffer()
   128  }
   129  
   130  func (t *aTTY) UpdateBuffer(bufNotes, bufMain *term.Buffer, full bool) error {
   131  	return t.w.CommitBuffer(bufNotes, bufMain, full)
   132  }
   133  
   134  func (t *aTTY) NotifySignals() <-chan os.Signal {
   135  	t.sigCh = sys.NotifySignals()
   136  	return t.sigCh
   137  }
   138  
   139  func (t *aTTY) StopSignals() {
   140  	signal.Stop(t.sigCh)
   141  	close(t.sigCh)
   142  	t.sigCh = nil
   143  }