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 }