| // Readline is a pure go implementation for GNU-Readline kind library. |
| // |
| // example: |
| // rl, err := readline.New("> ") |
| // if err != nil { |
| // panic(err) |
| // } |
| // defer rl.Close() |
| // |
| // for { |
| // line, err := rl.Readline() |
| // if err != nil { // io.EOF |
| // break |
| // } |
| // println(line) |
| // } |
| // |
| package readline |
| |
| import "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 string |
| |
| // 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 |
| } |
| |
| func New(prompt string) (*Instance, error) { |
| return NewEx(&Config{Prompt: prompt}) |
| } |
| |
| func (i *Instance) ResetHistory() { |
| i.Operation.ResetHistory() |
| } |
| |
| func (i *Instance) SetPrompt(s string) { |
| 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 string, l Listener) ([]byte, error) { |
| return i.Operation.PasswordEx(prompt, l) |
| } |
| |
| func (i *Instance) ReadPassword(prompt string) ([]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() |
| } |