readline/readline.go

289 lines
6.0 KiB
Go
Raw Normal View History

2016-09-03 06:26:39 +03:00
// 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)
// }
//
2015-09-20 18:14:29 +03:00
package readline
2016-03-05 10:27:02 +03:00
import "io"
2015-09-20 18:14:29 +03:00
2015-09-21 08:30:10 +03:00
type Instance struct {
2015-10-04 10:23:00 +03:00
Config *Config
Terminal *Terminal
Operation *Operation
2015-09-20 18:14:29 +03:00
}
2015-09-22 13:16:24 +03:00
type Config struct {
2015-10-04 10:23:00 +03:00
// 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
2015-11-20 15:56:42 +03:00
// 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
2015-10-04 10:23:00 +03:00
// AutoCompleter will called once user press TAB
2015-09-25 07:59:36 +03:00
AutoComplete AutoCompleter
2015-10-04 10:23:00 +03:00
2015-11-20 15:56:42 +03:00
// Any key press will pass to Listener
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
Listener Listener
2015-10-04 10:23:00 +03:00
// If VimMode is true, readline will in vim.insert mode by default
VimMode bool
InterruptPrompt string
EOFPrompt string
2016-03-05 05:46:11 +03:00
FuncGetWidth func() int
Stdin io.Reader
2015-10-04 10:23:00 +03:00
Stdout io.Writer
Stderr io.Writer
2016-03-05 05:46:11 +03:00
EnableMask bool
MaskRune rune
2015-11-20 15:56:42 +03:00
2016-03-05 05:46:11 +03:00
// erase the editing line after user submited it
2016-03-05 10:45:34 +03:00
// it use in IM usually.
2016-02-15 05:03:41 +03:00
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
2016-03-13 13:32:48 +03:00
FuncIsTerminal func() bool
FuncMakeRaw func() error
FuncExitRaw func() error
FuncOnWidthChanged func(func())
ForceUseInteractive bool
2015-11-20 15:56:42 +03:00
// private fields
inited bool
opHistory *opHistory
opSearch *opSearch
}
func (c *Config) useInteractive() bool {
if c.ForceUseInteractive {
return true
}
2016-03-13 13:32:48 +03:00
return c.FuncIsTerminal()
}
func (c *Config) Init() error {
if c.inited {
return nil
}
c.inited = true
2015-12-23 08:39:23 +03:00
if c.Stdin == nil {
2016-09-04 16:02:24 +03:00
c.Stdin = NewCancelableStdin(Stdin)
2015-12-23 08:39:23 +03:00
}
if c.Stdout == nil {
2015-09-28 19:26:49 +03:00
c.Stdout = Stdout
}
if c.Stderr == nil {
2015-09-28 19:26:49 +03:00
c.Stderr = Stderr
}
2015-11-20 15:56:42 +03:00
if c.HistoryLimit == 0 {
2015-10-04 10:23:00 +03:00
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 = ""
}
2016-09-02 15:10:31 +03:00
if c.AutoComplete == nil {
c.AutoComplete = &TabCompleter{}
}
2016-03-05 05:46:11 +03:00
if c.FuncGetWidth == nil {
2016-03-13 13:32:48 +03:00
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
2016-03-05 05:46:11 +03:00
}
return nil
2015-09-22 13:16:24 +03:00
}
func (c Config) Clone() *Config {
c.opHistory = nil
c.opSearch = nil
return &c
}
2015-11-20 15:56:42 +03:00
func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
c.Listener = FuncListener(f)
}
2015-09-22 13:16:24 +03:00
func NewEx(cfg *Config) (*Instance, error) {
t, err := NewTerminal(cfg)
2015-09-21 08:30:10 +03:00
if err != nil {
return nil, err
2015-09-20 18:14:29 +03:00
}
2015-09-22 13:16:24 +03:00
rl := t.Readline()
2015-09-21 08:30:10 +03:00
return &Instance{
2015-10-04 10:23:00 +03:00
Config: cfg,
Terminal: t,
Operation: rl,
2015-09-21 08:30:10 +03:00
}, nil
2015-09-20 18:14:29 +03:00
}
2015-09-22 13:16:24 +03:00
func New(prompt string) (*Instance, error) {
return NewEx(&Config{Prompt: prompt})
}
2016-03-31 05:55:53 +03:00
func (i *Instance) ResetHistory() {
i.Operation.ResetHistory()
}
2015-09-27 13:54:26 +03:00
func (i *Instance) SetPrompt(s string) {
i.Operation.SetPrompt(s)
2015-09-27 13:54:26 +03:00
}
2015-11-20 15:56:42 +03:00
func (i *Instance) SetMaskRune(r rune) {
i.Operation.SetMaskRune(r)
}
2016-04-21 05:06:19 +03:00
// change history persistence in runtime
2015-10-04 10:23:00 +03:00
func (i *Instance) SetHistoryPath(p string) {
i.Operation.SetHistoryPath(p)
}
// readline will refresh automatic when write through Stdout()
2015-09-24 19:16:49 +03:00
func (i *Instance) Stdout() io.Writer {
return i.Operation.Stdout()
2015-09-24 19:16:49 +03:00
}
2015-10-04 10:23:00 +03:00
// readline will refresh automatic when write through Stdout()
2015-09-21 08:30:10 +03:00
func (i *Instance) Stderr() io.Writer {
return i.Operation.Stderr()
2015-09-20 18:14:29 +03:00
}
2015-10-04 10:23:00 +03:00
// switch VimMode in runtime
2015-10-01 17:44:43 +03:00
func (i *Instance) SetVimMode(on bool) {
i.Operation.SetVimMode(on)
2015-10-01 17:44:43 +03:00
}
2015-10-02 05:37:21 +03:00
func (i *Instance) IsVimMode() bool {
return i.Operation.IsEnableVimMode()
2015-10-02 05:37:21 +03:00
}
2015-11-20 15:56:42 +03:00
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)
}
2015-09-30 09:16:46 +03:00
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
return i.Operation.Password(prompt)
2015-09-30 09:16:46 +03:00
}
2016-03-31 05:55:53 +03:00
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)
2015-09-21 08:30:10 +03:00
func (i *Instance) Readline() (string, error) {
return i.Operation.String()
2015-09-20 18:14:29 +03:00
}
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)
}
2015-10-04 10:23:00 +03:00
// same as readline
2015-09-21 08:30:10 +03:00
func (i *Instance) ReadSlice() ([]byte, error) {
return i.Operation.Slice()
2015-09-20 18:14:29 +03:00
}
2015-10-04 10:23:00 +03:00
// we must make sure that call Close() before process exit.
2015-09-21 08:30:10 +03:00
func (i *Instance) Close() error {
if err := i.Terminal.Close(); err != nil {
return err
}
i.Operation.Close()
return nil
2015-09-20 18:14:29 +03:00
}
2016-03-31 05:55:53 +03:00
func (i *Instance) Clean() {
i.Operation.Clean()
}
2015-11-20 15:56:42 +03:00
2016-03-13 18:15:38 +03:00
func (i *Instance) Write(b []byte) (int, error) {
return i.Stdout().Write(b)
}
2015-11-20 15:56:42 +03:00
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()
}