1package readline 2 3import ( 4 "bufio" 5 "fmt" 6 "io" 7 "strings" 8 "sync" 9 "sync/atomic" 10) 11 12type Terminal struct { 13 m sync.Mutex 14 cfg *Config 15 outchan chan rune 16 closed int32 17 stopChan chan struct{} 18 kickChan chan struct{} 19 wg sync.WaitGroup 20 isReading int32 21 sleeping int32 22 23 sizeChan chan string 24} 25 26func NewTerminal(cfg *Config) (*Terminal, error) { 27 if err := cfg.Init(); err != nil { 28 return nil, err 29 } 30 t := &Terminal{ 31 cfg: cfg, 32 kickChan: make(chan struct{}, 1), 33 outchan: make(chan rune), 34 stopChan: make(chan struct{}, 1), 35 sizeChan: make(chan string, 1), 36 } 37 38 go t.ioloop() 39 return t, nil 40} 41 42// SleepToResume will sleep myself, and return only if I'm resumed. 43func (t *Terminal) SleepToResume() { 44 if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { 45 return 46 } 47 defer atomic.StoreInt32(&t.sleeping, 0) 48 49 t.ExitRawMode() 50 ch := WaitForResume() 51 SuspendMe() 52 <-ch 53 t.EnterRawMode() 54} 55 56func (t *Terminal) EnterRawMode() (err error) { 57 return t.cfg.FuncMakeRaw() 58} 59 60func (t *Terminal) ExitRawMode() (err error) { 61 return t.cfg.FuncExitRaw() 62} 63 64func (t *Terminal) Write(b []byte) (int, error) { 65 return t.cfg.Stdout.Write(b) 66} 67 68// WriteStdin prefill the next Stdin fetch 69// Next time you call ReadLine() this value will be writen before the user input 70func (t *Terminal) WriteStdin(b []byte) (int, error) { 71 return t.cfg.StdinWriter.Write(b) 72} 73 74type termSize struct { 75 left int 76 top int 77} 78 79func (t *Terminal) GetOffset(f func(offset string)) { 80 go func() { 81 f(<-t.sizeChan) 82 }() 83 t.Write([]byte("\033[6n")) 84} 85 86func (t *Terminal) Print(s string) { 87 fmt.Fprintf(t.cfg.Stdout, "%s", s) 88} 89 90func (t *Terminal) PrintRune(r rune) { 91 fmt.Fprintf(t.cfg.Stdout, "%c", r) 92} 93 94func (t *Terminal) Readline() *Operation { 95 return NewOperation(t, t.cfg) 96} 97 98// return rune(0) if meet EOF 99func (t *Terminal) ReadRune() rune { 100 ch, ok := <-t.outchan 101 if !ok { 102 return rune(0) 103 } 104 return ch 105} 106 107func (t *Terminal) IsReading() bool { 108 return atomic.LoadInt32(&t.isReading) == 1 109} 110 111func (t *Terminal) KickRead() { 112 select { 113 case t.kickChan <- struct{}{}: 114 default: 115 } 116} 117 118func (t *Terminal) ioloop() { 119 t.wg.Add(1) 120 defer func() { 121 t.wg.Done() 122 close(t.outchan) 123 }() 124 125 var ( 126 isEscape bool 127 isEscapeEx bool 128 expectNextChar bool 129 ) 130 131 buf := bufio.NewReader(t.getStdin()) 132 for { 133 if !expectNextChar { 134 atomic.StoreInt32(&t.isReading, 0) 135 select { 136 case <-t.kickChan: 137 atomic.StoreInt32(&t.isReading, 1) 138 case <-t.stopChan: 139 return 140 } 141 } 142 expectNextChar = false 143 r, _, err := buf.ReadRune() 144 if err != nil { 145 if strings.Contains(err.Error(), "interrupted system call") { 146 expectNextChar = true 147 continue 148 } 149 break 150 } 151 152 if isEscape { 153 isEscape = false 154 if r == CharEscapeEx { 155 expectNextChar = true 156 isEscapeEx = true 157 continue 158 } 159 r = escapeKey(r, buf) 160 } else if isEscapeEx { 161 isEscapeEx = false 162 if key := readEscKey(r, buf); key != nil { 163 r = escapeExKey(key) 164 // offset 165 if key.typ == 'R' { 166 if _, _, ok := key.Get2(); ok { 167 select { 168 case t.sizeChan <- key.attr: 169 default: 170 } 171 } 172 expectNextChar = true 173 continue 174 } 175 } 176 if r == 0 { 177 expectNextChar = true 178 continue 179 } 180 } 181 182 expectNextChar = true 183 switch r { 184 case CharEsc: 185 if t.cfg.VimMode { 186 t.outchan <- r 187 break 188 } 189 isEscape = true 190 case CharInterrupt, CharEnter, CharCtrlJ, CharEOT: 191 expectNextChar = false 192 fallthrough 193 default: 194 t.outchan <- r 195 } 196 } 197 198} 199 200func (t *Terminal) Bell() { 201 fmt.Fprintf(t, "%c", CharBell) 202} 203 204func (t *Terminal) Close() error { 205 if atomic.SwapInt32(&t.closed, 1) != 0 { 206 return nil 207 } 208 if closer, ok := t.cfg.Stdin.(io.Closer); ok { 209 closer.Close() 210 } 211 close(t.stopChan) 212 t.wg.Wait() 213 return t.ExitRawMode() 214} 215 216func (t *Terminal) GetConfig() *Config { 217 t.m.Lock() 218 cfg := *t.cfg 219 t.m.Unlock() 220 return &cfg 221} 222 223func (t *Terminal) getStdin() io.Reader { 224 t.m.Lock() 225 r := t.cfg.Stdin 226 t.m.Unlock() 227 return r 228} 229 230func (t *Terminal) SetConfig(c *Config) error { 231 if err := c.Init(); err != nil { 232 return err 233 } 234 t.m.Lock() 235 t.cfg = c 236 t.m.Unlock() 237 return nil 238} 239