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 }