1package readline 2 3import ( 4 "errors" 5 "io" 6 "sync" 7) 8 9var ( 10 ErrInterrupt = errors.New("Interrupt") 11) 12 13type InterruptError struct { 14 Line []rune 15} 16 17func (*InterruptError) Error() string { 18 return "Interrupted" 19} 20 21type Operation struct { 22 m sync.Mutex 23 cfg *Config 24 t *Terminal 25 buf *RuneBuffer 26 outchan chan []rune 27 errchan chan error 28 w io.Writer 29 30 history *opHistory 31 *opSearch 32 *opCompleter 33 *opPassword 34 *opVim 35} 36 37func (o *Operation) SetBuffer(what string) { 38 o.buf.Set([]rune(what)) 39} 40 41type wrapWriter struct { 42 r *Operation 43 t *Terminal 44 target io.Writer 45} 46 47func (w *wrapWriter) Write(b []byte) (int, error) { 48 if !w.t.IsReading() { 49 return w.target.Write(b) 50 } 51 52 var ( 53 n int 54 err error 55 ) 56 w.r.buf.Refresh(func() { 57 n, err = w.target.Write(b) 58 }) 59 60 if w.r.IsSearchMode() { 61 w.r.SearchRefresh(-1) 62 } 63 if w.r.IsInCompleteMode() { 64 w.r.CompleteRefresh() 65 } 66 return n, err 67} 68 69func NewOperation(t *Terminal, cfg *Config) *Operation { 70 width := cfg.FuncGetWidth() 71 op := &Operation{ 72 t: t, 73 buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), 74 outchan: make(chan []rune), 75 errchan: make(chan error, 1), 76 } 77 op.w = op.buf.w 78 op.SetConfig(cfg) 79 op.opVim = newVimMode(op) 80 op.opCompleter = newOpCompleter(op.buf.w, op, width) 81 op.opPassword = newOpPassword(op) 82 op.cfg.FuncOnWidthChanged(func() { 83 newWidth := cfg.FuncGetWidth() 84 op.opCompleter.OnWidthChange(newWidth) 85 op.opSearch.OnWidthChange(newWidth) 86 op.buf.OnWidthChange(newWidth) 87 }) 88 go op.ioloop() 89 return op 90} 91 92func (o *Operation) SetPrompt(s string) { 93 o.buf.SetPrompt(s) 94} 95 96func (o *Operation) SetMaskRune(r rune) { 97 o.buf.SetMask(r) 98} 99 100func (o *Operation) GetConfig() *Config { 101 o.m.Lock() 102 cfg := *o.cfg 103 o.m.Unlock() 104 return &cfg 105} 106 107func (o *Operation) ioloop() { 108 for { 109 keepInSearchMode := false 110 keepInCompleteMode := false 111 r := o.t.ReadRune() 112 if o.GetConfig().FuncFilterInputRune != nil { 113 var process bool 114 r, process = o.GetConfig().FuncFilterInputRune(r) 115 if !process { 116 o.buf.Refresh(nil) // to refresh the line 117 continue // ignore this rune 118 } 119 } 120 121 if r == 0 { // io.EOF 122 if o.buf.Len() == 0 { 123 o.buf.Clean() 124 select { 125 case o.errchan <- io.EOF: 126 } 127 break 128 } else { 129 // if stdin got io.EOF and there is something left in buffer, 130 // let's flush them by sending CharEnter. 131 // And we will got io.EOF int next loop. 132 r = CharEnter 133 } 134 } 135 isUpdateHistory := true 136 137 if o.IsInCompleteSelectMode() { 138 keepInCompleteMode = o.HandleCompleteSelect(r) 139 if keepInCompleteMode { 140 continue 141 } 142 143 o.buf.Refresh(nil) 144 switch r { 145 case CharEnter, CharCtrlJ: 146 o.history.Update(o.buf.Runes(), false) 147 fallthrough 148 case CharInterrupt: 149 o.t.KickRead() 150 fallthrough 151 case CharBell: 152 continue 153 } 154 } 155 156 if o.IsEnableVimMode() { 157 r = o.HandleVim(r, o.t.ReadRune) 158 if r == 0 { 159 continue 160 } 161 } 162 163 switch r { 164 case CharBell: 165 if o.IsSearchMode() { 166 o.ExitSearchMode(true) 167 o.buf.Refresh(nil) 168 } 169 if o.IsInCompleteMode() { 170 o.ExitCompleteMode(true) 171 o.buf.Refresh(nil) 172 } 173 case CharTab: 174 if o.GetConfig().AutoComplete == nil { 175 o.t.Bell() 176 break 177 } 178 if o.OnComplete() { 179 keepInCompleteMode = true 180 } else { 181 o.t.Bell() 182 break 183 } 184 185 case CharBckSearch: 186 if !o.SearchMode(S_DIR_BCK) { 187 o.t.Bell() 188 break 189 } 190 keepInSearchMode = true 191 case CharCtrlU: 192 o.buf.KillFront() 193 case CharFwdSearch: 194 if !o.SearchMode(S_DIR_FWD) { 195 o.t.Bell() 196 break 197 } 198 keepInSearchMode = true 199 case CharKill: 200 o.buf.Kill() 201 keepInCompleteMode = true 202 case MetaForward: 203 o.buf.MoveToNextWord() 204 case CharTranspose: 205 o.buf.Transpose() 206 case MetaBackward: 207 o.buf.MoveToPrevWord() 208 case MetaDelete: 209 o.buf.DeleteWord() 210 case CharLineStart: 211 o.buf.MoveToLineStart() 212 case CharLineEnd: 213 o.buf.MoveToLineEnd() 214 case CharBackspace, CharCtrlH: 215 if o.IsSearchMode() { 216 o.SearchBackspace() 217 keepInSearchMode = true 218 break 219 } 220 221 if o.buf.Len() == 0 { 222 o.t.Bell() 223 break 224 } 225 o.buf.Backspace() 226 if o.IsInCompleteMode() { 227 o.OnComplete() 228 } 229 case CharCtrlZ: 230 o.buf.Clean() 231 o.t.SleepToResume() 232 o.Refresh() 233 case CharCtrlL: 234 ClearScreen(o.w) 235 o.Refresh() 236 case MetaBackspace, CharCtrlW: 237 o.buf.BackEscapeWord() 238 case CharCtrlY: 239 o.buf.Yank() 240 case CharEnter, CharCtrlJ: 241 if o.IsSearchMode() { 242 o.ExitSearchMode(false) 243 } 244 o.buf.MoveToLineEnd() 245 var data []rune 246 if !o.GetConfig().UniqueEditLine { 247 o.buf.WriteRune('\n') 248 data = o.buf.Reset() 249 data = data[:len(data)-1] // trim \n 250 } else { 251 o.buf.Clean() 252 data = o.buf.Reset() 253 } 254 o.outchan <- data 255 if !o.GetConfig().DisableAutoSaveHistory { 256 // ignore IO error 257 _ = o.history.New(data) 258 } else { 259 isUpdateHistory = false 260 } 261 case CharBackward: 262 o.buf.MoveBackward() 263 case CharForward: 264 o.buf.MoveForward() 265 case CharPrev: 266 buf := o.history.Prev() 267 if buf != nil { 268 o.buf.Set(buf) 269 } else { 270 o.t.Bell() 271 } 272 case CharNext: 273 buf, ok := o.history.Next() 274 if ok { 275 o.buf.Set(buf) 276 } else { 277 o.t.Bell() 278 } 279 case CharDelete: 280 if o.buf.Len() > 0 || !o.IsNormalMode() { 281 o.t.KickRead() 282 if !o.buf.Delete() { 283 o.t.Bell() 284 } 285 break 286 } 287 288 // treat as EOF 289 if !o.GetConfig().UniqueEditLine { 290 o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") 291 } 292 o.buf.Reset() 293 isUpdateHistory = false 294 o.history.Revert() 295 o.errchan <- io.EOF 296 if o.GetConfig().UniqueEditLine { 297 o.buf.Clean() 298 } 299 case CharInterrupt: 300 if o.IsSearchMode() { 301 o.t.KickRead() 302 o.ExitSearchMode(true) 303 break 304 } 305 if o.IsInCompleteMode() { 306 o.t.KickRead() 307 o.ExitCompleteMode(true) 308 o.buf.Refresh(nil) 309 break 310 } 311 o.buf.MoveToLineEnd() 312 o.buf.Refresh(nil) 313 hint := o.GetConfig().InterruptPrompt + "\n" 314 if !o.GetConfig().UniqueEditLine { 315 o.buf.WriteString(hint) 316 } 317 remain := o.buf.Reset() 318 if !o.GetConfig().UniqueEditLine { 319 remain = remain[:len(remain)-len([]rune(hint))] 320 } 321 isUpdateHistory = false 322 o.history.Revert() 323 o.errchan <- &InterruptError{remain} 324 default: 325 if o.IsSearchMode() { 326 o.SearchChar(r) 327 keepInSearchMode = true 328 break 329 } 330 o.buf.WriteRune(r) 331 if o.IsInCompleteMode() { 332 o.OnComplete() 333 keepInCompleteMode = true 334 } 335 } 336 337 listener := o.GetConfig().Listener 338 if listener != nil { 339 newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) 340 if ok { 341 o.buf.SetWithIdx(newPos, newLine) 342 } 343 } 344 345 o.m.Lock() 346 if !keepInSearchMode && o.IsSearchMode() { 347 o.ExitSearchMode(false) 348 o.buf.Refresh(nil) 349 } else if o.IsInCompleteMode() { 350 if !keepInCompleteMode { 351 o.ExitCompleteMode(false) 352 o.Refresh() 353 } else { 354 o.buf.Refresh(nil) 355 o.CompleteRefresh() 356 } 357 } 358 if isUpdateHistory && !o.IsSearchMode() { 359 // it will cause null history 360 o.history.Update(o.buf.Runes(), false) 361 } 362 o.m.Unlock() 363 } 364} 365 366func (o *Operation) Stderr() io.Writer { 367 return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} 368} 369 370func (o *Operation) Stdout() io.Writer { 371 return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} 372} 373 374func (o *Operation) String() (string, error) { 375 r, err := o.Runes() 376 return string(r), err 377} 378 379func (o *Operation) Runes() ([]rune, error) { 380 o.t.EnterRawMode() 381 defer o.t.ExitRawMode() 382 383 listener := o.GetConfig().Listener 384 if listener != nil { 385 listener.OnChange(nil, 0, 0) 386 } 387 388 o.buf.Refresh(nil) // print prompt 389 o.t.KickRead() 390 select { 391 case r := <-o.outchan: 392 return r, nil 393 case err := <-o.errchan: 394 if e, ok := err.(*InterruptError); ok { 395 return e.Line, ErrInterrupt 396 } 397 return nil, err 398 } 399} 400 401func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { 402 cfg := o.GenPasswordConfig() 403 cfg.Prompt = prompt 404 cfg.Listener = l 405 return o.PasswordWithConfig(cfg) 406} 407 408func (o *Operation) GenPasswordConfig() *Config { 409 return o.opPassword.PasswordConfig() 410} 411 412func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { 413 if err := o.opPassword.EnterPasswordMode(cfg); err != nil { 414 return nil, err 415 } 416 defer o.opPassword.ExitPasswordMode() 417 return o.Slice() 418} 419 420func (o *Operation) Password(prompt string) ([]byte, error) { 421 return o.PasswordEx(prompt, nil) 422} 423 424func (o *Operation) SetTitle(t string) { 425 o.w.Write([]byte("\033[2;" + t + "\007")) 426} 427 428func (o *Operation) Slice() ([]byte, error) { 429 r, err := o.Runes() 430 if err != nil { 431 return nil, err 432 } 433 return []byte(string(r)), nil 434} 435 436func (o *Operation) Close() { 437 o.history.Close() 438} 439 440func (o *Operation) SetHistoryPath(path string) { 441 if o.history != nil { 442 o.history.Close() 443 } 444 o.cfg.HistoryFile = path 445 o.history = newOpHistory(o.cfg) 446} 447 448func (o *Operation) IsNormalMode() bool { 449 return !o.IsInCompleteMode() && !o.IsSearchMode() 450} 451 452func (op *Operation) SetConfig(cfg *Config) (*Config, error) { 453 op.m.Lock() 454 defer op.m.Unlock() 455 if op.cfg == cfg { 456 return op.cfg, nil 457 } 458 if err := cfg.Init(); err != nil { 459 return op.cfg, err 460 } 461 old := op.cfg 462 op.cfg = cfg 463 op.SetPrompt(cfg.Prompt) 464 op.SetMaskRune(cfg.MaskRune) 465 op.buf.SetConfig(cfg) 466 width := op.cfg.FuncGetWidth() 467 468 if cfg.opHistory == nil { 469 op.SetHistoryPath(cfg.HistoryFile) 470 cfg.opHistory = op.history 471 cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) 472 } 473 op.history = cfg.opHistory 474 475 // SetHistoryPath will close opHistory which already exists 476 // so if we use it next time, we need to reopen it by `InitHistory()` 477 op.history.Init() 478 479 if op.cfg.AutoComplete != nil { 480 op.opCompleter = newOpCompleter(op.buf.w, op, width) 481 } 482 483 op.opSearch = cfg.opSearch 484 return old, nil 485} 486 487func (o *Operation) ResetHistory() { 488 o.history.Reset() 489} 490 491// if err is not nil, it just mean it fail to write to file 492// other things goes fine. 493func (o *Operation) SaveHistory(content string) error { 494 return o.history.New([]rune(content)) 495} 496 497func (o *Operation) Refresh() { 498 if o.t.IsReading() { 499 o.buf.Refresh(nil) 500 } 501} 502 503func (o *Operation) Clean() { 504 o.buf.Clean() 505} 506 507func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { 508 return &DumpListener{f: f} 509} 510 511type DumpListener struct { 512 f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) 513} 514 515func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 516 return d.f(line, pos, key) 517} 518 519type Listener interface { 520 OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) 521} 522 523type Painter interface { 524 Paint(line []rune, pos int) []rune 525} 526 527type defaultPainter struct{} 528 529func (p *defaultPainter) Paint(line []rune, _ int) []rune { 530 return line 531} 532