123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- package readline
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "syscall"
- )
- type Prompt struct {
- Prompt string
- AltPrompt string
- Placeholder string
- AltPlaceholder string
- UseAlt bool
- }
- type Terminal struct {
- outchan chan rune
- }
- type Instance struct {
- Prompt *Prompt
- Terminal *Terminal
- History *History
- Pasting bool
- }
- func New(prompt Prompt) (*Instance, error) {
- term, err := NewTerminal()
- if err != nil {
- return nil, err
- }
- history, err := NewHistory()
- if err != nil {
- return nil, err
- }
- return &Instance{
- Prompt: &prompt,
- Terminal: term,
- History: history,
- }, nil
- }
- func (i *Instance) Readline() (string, error) {
- prompt := i.Prompt.Prompt
- if i.Prompt.UseAlt || i.Pasting {
- prompt = i.Prompt.AltPrompt
- }
- fmt.Print(prompt)
- fd := int(syscall.Stdin)
- termios, err := SetRawMode(fd)
- if err != nil {
- return "", err
- }
- defer UnsetRawMode(fd, termios)
- buf, _ := NewBuffer(i.Prompt)
- var esc bool
- var escex bool
- var metaDel bool
- var currentLineBuf []rune
- for {
- // don't show placeholder when pasting unless we're in multiline mode
- showPlaceholder := !i.Pasting || i.Prompt.UseAlt
- if buf.IsEmpty() && showPlaceholder {
- ph := i.Prompt.Placeholder
- if i.Prompt.UseAlt {
- ph = i.Prompt.AltPlaceholder
- }
- fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)
- }
- r, err := i.Terminal.Read()
- if buf.IsEmpty() {
- fmt.Print(ClearToEOL)
- }
- if err != nil {
- return "", io.EOF
- }
- if escex {
- escex = false
- switch r {
- case KeyUp:
- if i.History.Pos > 0 {
- if i.History.Pos == i.History.Size() {
- currentLineBuf = []rune(buf.String())
- }
- buf.Replace(i.History.Prev())
- }
- case KeyDown:
- if i.History.Pos < i.History.Size() {
- buf.Replace(i.History.Next())
- if i.History.Pos == i.History.Size() {
- buf.Replace(currentLineBuf)
- }
- }
- case KeyLeft:
- buf.MoveLeft()
- case KeyRight:
- buf.MoveRight()
- case CharBracketedPaste:
- var code string
- for cnt := 0; cnt < 3; cnt++ {
- r, err = i.Terminal.Read()
- if err != nil {
- return "", io.EOF
- }
- code += string(r)
- }
- if code == CharBracketedPasteStart {
- i.Pasting = true
- } else if code == CharBracketedPasteEnd {
- i.Pasting = false
- }
- case KeyDel:
- if buf.Size() > 0 {
- buf.Delete()
- }
- metaDel = true
- case MetaStart:
- buf.MoveToStart()
- case MetaEnd:
- buf.MoveToEnd()
- default:
- // skip any keys we don't know about
- continue
- }
- continue
- } else if esc {
- esc = false
- switch r {
- case 'b':
- buf.MoveLeftWord()
- case 'f':
- buf.MoveRightWord()
- case CharBackspace:
- buf.DeleteWord()
- case CharEscapeEx:
- escex = true
- }
- continue
- }
- switch r {
- case CharNull:
- continue
- case CharEsc:
- esc = true
- case CharInterrupt:
- return "", ErrInterrupt
- case CharLineStart:
- buf.MoveToStart()
- case CharLineEnd:
- buf.MoveToEnd()
- case CharBackward:
- buf.MoveLeft()
- case CharForward:
- buf.MoveRight()
- case CharBackspace, CharCtrlH:
- buf.Remove()
- case CharTab:
- // todo: convert back to real tabs
- for cnt := 0; cnt < 8; cnt++ {
- buf.Add(' ')
- }
- case CharDelete:
- if buf.Size() > 0 {
- buf.Delete()
- } else {
- return "", io.EOF
- }
- case CharKill:
- buf.DeleteRemaining()
- case CharCtrlU:
- buf.DeleteBefore()
- case CharCtrlL:
- buf.ClearScreen()
- case CharCtrlW:
- buf.DeleteWord()
- case CharEnter:
- output := buf.String()
- if output != "" {
- i.History.Add([]rune(output))
- }
- buf.MoveToEnd()
- fmt.Println()
- return output, nil
- default:
- if metaDel {
- metaDel = false
- continue
- }
- if r >= CharSpace || r == CharEnter {
- buf.Add(r)
- }
- }
- }
- }
- func (i *Instance) HistoryEnable() {
- i.History.Enabled = true
- }
- func (i *Instance) HistoryDisable() {
- i.History.Enabled = false
- }
- func NewTerminal() (*Terminal, error) {
- t := &Terminal{
- outchan: make(chan rune),
- }
- go t.ioloop()
- return t, nil
- }
- func (t *Terminal) ioloop() {
- buf := bufio.NewReader(os.Stdin)
- for {
- r, _, err := buf.ReadRune()
- if err != nil {
- close(t.outchan)
- break
- }
- t.outchan <- r
- }
- }
- func (t *Terminal) Read() (rune, error) {
- r, ok := <-t.outchan
- if !ok {
- return 0, io.EOF
- }
- return r, nil
- }
|