// Readline is a pure go implementation for GNU-Readline kind library. // // example: // rl, err := readline.New(readline.StaticPrompt("> ")) // if err != nil { // panic(err) // } // defer rl.Close() // // for { // line, err := rl.Readline() // if err != nil { // io.EOF // break // } // println(line) // } // package readline import ( "fmt" "io" ) type Instance struct { Config *Config Terminal *Terminal Operation *Operation } type Config struct { // prompt supports ANSI escape sequence, so we can color some characters even in windows Prompt fmt.Stringer // readline will persist historys to file where HistoryFile specified HistoryFile string // specify the max length of historys, it's 500 by default, set it to -1 to disable history HistoryLimit int DisableAutoSaveHistory bool // enable case-insensitive history searching HistorySearchFold bool // AutoCompleter will called once user press TAB AutoComplete AutoCompleter // Any key press will pass to Listener // NOTE: Listener will be triggered by (nil, 0, 0) immediately Listener Listener Painter Painter // If VimMode is true, readline will in vim.insert mode by default VimMode bool InterruptPrompt string EOFPrompt string FuncGetWidth func() int Stdin io.ReadCloser StdinWriter io.Writer Stdout io.Writer Stderr io.Writer EnableMask bool MaskRune rune // erase the editing line after user submited it // it use in IM usually. UniqueEditLine bool // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) // -> output = new (translated) rune and true/false if continue with processing this one FuncFilterInputRune func(rune) (rune, bool) // force use interactive even stdout is not a tty FuncIsTerminal func() bool FuncMakeRaw func() error FuncExitRaw func() error FuncOnWidthChanged func(func()) ForceUseInteractive bool // private fields inited bool opHistory *opHistory opSearch *opSearch } func (c *Config) useInteractive() bool { if c.ForceUseInteractive { return true } return c.FuncIsTerminal() } func (c *Config) Init() error { if c.inited { return nil } c.inited = true if c.Stdin == nil { c.Stdin = NewCancelableStdin(Stdin) } c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) if c.Stdout == nil { c.Stdout = Stdout } if c.Stderr == nil { c.Stderr = Stderr } if c.HistoryLimit == 0 { c.HistoryLimit = 500 } if c.InterruptPrompt == "" { c.InterruptPrompt = "^C" } else if c.InterruptPrompt == "\n" { c.InterruptPrompt = "" } if c.EOFPrompt == "" { c.EOFPrompt = "^D" } else if c.EOFPrompt == "\n" { c.EOFPrompt = "" } if c.AutoComplete == nil { c.AutoComplete = &TabCompleter{} } if c.FuncGetWidth == nil { c.FuncGetWidth = GetScreenWidth } if c.FuncIsTerminal == nil { c.FuncIsTerminal = DefaultIsTerminal } rm := new(RawMode) if c.FuncMakeRaw == nil { c.FuncMakeRaw = rm.Enter } if c.FuncExitRaw == nil { c.FuncExitRaw = rm.Exit } if c.FuncOnWidthChanged == nil { c.FuncOnWidthChanged = DefaultOnWidthChanged } return nil } func (c Config) Clone() *Config { c.opHistory = nil c.opSearch = nil return &c } func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { c.Listener = FuncListener(f) } func (c *Config) SetPainter(p Painter) { c.Painter = p } func NewEx(cfg *Config) (*Instance, error) { t, err := NewTerminal(cfg) if err != nil { return nil, err } rl := t.Readline() if cfg.Painter == nil { cfg.Painter = &defaultPainter{} } return &Instance{ Config: cfg, Terminal: t, Operation: rl, }, nil } type Prompter interface { String() string } func New(prompt fmt.Stringer) (*Instance, error) { return NewEx(&Config{Prompt: prompt}) } func (i *Instance) ResetHistory() { i.Operation.ResetHistory() } func (i *Instance) SetPrompt(s fmt.Stringer) { i.Operation.SetPrompt(s) } func (i *Instance) SetMaskRune(r rune) { i.Operation.SetMaskRune(r) } // change history persistence in runtime func (i *Instance) SetHistoryPath(p string) { i.Operation.SetHistoryPath(p) } // readline will refresh automatic when write through Stdout() func (i *Instance) Stdout() io.Writer { return i.Operation.Stdout() } // readline will refresh automatic when write through Stdout() func (i *Instance) Stderr() io.Writer { return i.Operation.Stderr() } // switch VimMode in runtime func (i *Instance) SetVimMode(on bool) { i.Operation.SetVimMode(on) } func (i *Instance) IsVimMode() bool { return i.Operation.IsEnableVimMode() } func (i *Instance) GenPasswordConfig() *Config { return i.Operation.GenPasswordConfig() } // we can generate a config by `i.GenPasswordConfig()` func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { return i.Operation.PasswordWithConfig(cfg) } func (i *Instance) ReadPasswordEx(prompt fmt.Stringer, l Listener) ([]byte, error) { return i.Operation.PasswordEx(prompt, l) } func (i *Instance) ReadPassword(prompt fmt.Stringer) ([]byte, error) { return i.Operation.Password(prompt) } type Result struct { Line string Error error } func (l *Result) CanContinue() bool { return len(l.Line) != 0 && l.Error == ErrInterrupt } func (l *Result) CanBreak() bool { return !l.CanContinue() && l.Error != nil } func (i *Instance) Line() *Result { ret, err := i.Readline() return &Result{ret, err} } // err is one of (nil, io.EOF, readline.ErrInterrupt) func (i *Instance) Readline() (string, error) { return i.Operation.String() } func (i *Instance) ReadlineWithDefault(what string) (string, error) { i.Operation.SetBuffer(what) return i.Operation.String() } func (i *Instance) SaveHistory(content string) error { return i.Operation.SaveHistory(content) } // same as readline func (i *Instance) ReadSlice() ([]byte, error) { return i.Operation.Slice() } // we must make sure that call Close() before process exit. func (i *Instance) Close() error { if err := i.Terminal.Close(); err != nil { return err } i.Config.Stdin.Close() i.Operation.Close() return nil } func (i *Instance) Clean() { i.Operation.Clean() } func (i *Instance) Write(b []byte) (int, error) { return i.Stdout().Write(b) } // WriteStdin prefill the next Stdin fetch // Next time you call ReadLine() this value will be writen before the user input // ie : // i := readline.New() // i.WriteStdin([]byte("test")) // _, _= i.Readline() // // gives // // > test[cursor] func (i *Instance) WriteStdin(val []byte) (int, error) { return i.Terminal.WriteStdin(val) } func (i *Instance) SetConfig(cfg *Config) *Config { if i.Config == cfg { return cfg } old := i.Config i.Config = cfg i.Operation.SetConfig(cfg) i.Terminal.SetConfig(cfg) return old } func (i *Instance) Refresh() { i.Operation.Refresh() } // HistoryDisable the save of the commands into the history func (i *Instance) HistoryDisable() { i.Operation.history.Disable() } // HistoryEnable the save of the commands into the history (default on) func (i *Instance) HistoryEnable() { i.Operation.history.Enable() }