1// Readline is a pure go implementation for GNU-Readline kind library. 2// 3// example: 4// rl, err := readline.New("> ") 5// if err != nil { 6// panic(err) 7// } 8// defer rl.Close() 9// 10// for { 11// line, err := rl.Readline() 12// if err != nil { // io.EOF 13// break 14// } 15// println(line) 16// } 17// 18package readline 19 20import "io" 21 22type Instance struct { 23 Config *Config 24 Terminal *Terminal 25 Operation *Operation 26} 27 28type Config struct { 29 // prompt supports ANSI escape sequence, so we can color some characters even in windows 30 Prompt string 31 32 // readline will persist historys to file where HistoryFile specified 33 HistoryFile string 34 // specify the max length of historys, it's 500 by default, set it to -1 to disable history 35 HistoryLimit int 36 DisableAutoSaveHistory bool 37 // enable case-insensitive history searching 38 HistorySearchFold bool 39 40 // AutoCompleter will called once user press TAB 41 AutoComplete AutoCompleter 42 43 // Any key press will pass to Listener 44 // NOTE: Listener will be triggered by (nil, 0, 0) immediately 45 Listener Listener 46 47 Painter Painter 48 49 // If VimMode is true, readline will in vim.insert mode by default 50 VimMode bool 51 52 InterruptPrompt string 53 EOFPrompt string 54 55 FuncGetWidth func() int 56 57 Stdin io.ReadCloser 58 StdinWriter io.Writer 59 Stdout io.Writer 60 Stderr io.Writer 61 62 EnableMask bool 63 MaskRune rune 64 65 // erase the editing line after user submited it 66 // it use in IM usually. 67 UniqueEditLine bool 68 69 // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) 70 // -> output = new (translated) rune and true/false if continue with processing this one 71 FuncFilterInputRune func(rune) (rune, bool) 72 73 // force use interactive even stdout is not a tty 74 FuncIsTerminal func() bool 75 FuncMakeRaw func() error 76 FuncExitRaw func() error 77 FuncOnWidthChanged func(func()) 78 ForceUseInteractive bool 79 80 // private fields 81 inited bool 82 opHistory *opHistory 83 opSearch *opSearch 84} 85 86func (c *Config) useInteractive() bool { 87 if c.ForceUseInteractive { 88 return true 89 } 90 return c.FuncIsTerminal() 91} 92 93func (c *Config) Init() error { 94 if c.inited { 95 return nil 96 } 97 c.inited = true 98 if c.Stdin == nil { 99 c.Stdin = NewCancelableStdin(Stdin) 100 } 101 102 c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) 103 104 if c.Stdout == nil { 105 c.Stdout = Stdout 106 } 107 if c.Stderr == nil { 108 c.Stderr = Stderr 109 } 110 if c.HistoryLimit == 0 { 111 c.HistoryLimit = 500 112 } 113 114 if c.InterruptPrompt == "" { 115 c.InterruptPrompt = "^C" 116 } else if c.InterruptPrompt == "\n" { 117 c.InterruptPrompt = "" 118 } 119 if c.EOFPrompt == "" { 120 c.EOFPrompt = "^D" 121 } else if c.EOFPrompt == "\n" { 122 c.EOFPrompt = "" 123 } 124 125 if c.AutoComplete == nil { 126 c.AutoComplete = &TabCompleter{} 127 } 128 if c.FuncGetWidth == nil { 129 c.FuncGetWidth = GetScreenWidth 130 } 131 if c.FuncIsTerminal == nil { 132 c.FuncIsTerminal = DefaultIsTerminal 133 } 134 rm := new(RawMode) 135 if c.FuncMakeRaw == nil { 136 c.FuncMakeRaw = rm.Enter 137 } 138 if c.FuncExitRaw == nil { 139 c.FuncExitRaw = rm.Exit 140 } 141 if c.FuncOnWidthChanged == nil { 142 c.FuncOnWidthChanged = DefaultOnWidthChanged 143 } 144 145 return nil 146} 147 148func (c Config) Clone() *Config { 149 c.opHistory = nil 150 c.opSearch = nil 151 return &c 152} 153 154func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { 155 c.Listener = FuncListener(f) 156} 157 158func (c *Config) SetPainter(p Painter) { 159 c.Painter = p 160} 161 162func NewEx(cfg *Config) (*Instance, error) { 163 t, err := NewTerminal(cfg) 164 if err != nil { 165 return nil, err 166 } 167 rl := t.Readline() 168 if cfg.Painter == nil { 169 cfg.Painter = &defaultPainter{} 170 } 171 return &Instance{ 172 Config: cfg, 173 Terminal: t, 174 Operation: rl, 175 }, nil 176} 177 178func New(prompt string) (*Instance, error) { 179 return NewEx(&Config{Prompt: prompt}) 180} 181 182func (i *Instance) ResetHistory() { 183 i.Operation.ResetHistory() 184} 185 186func (i *Instance) SetPrompt(s string) { 187 i.Operation.SetPrompt(s) 188} 189 190func (i *Instance) SetMaskRune(r rune) { 191 i.Operation.SetMaskRune(r) 192} 193 194// change history persistence in runtime 195func (i *Instance) SetHistoryPath(p string) { 196 i.Operation.SetHistoryPath(p) 197} 198 199// readline will refresh automatic when write through Stdout() 200func (i *Instance) Stdout() io.Writer { 201 return i.Operation.Stdout() 202} 203 204// readline will refresh automatic when write through Stdout() 205func (i *Instance) Stderr() io.Writer { 206 return i.Operation.Stderr() 207} 208 209// switch VimMode in runtime 210func (i *Instance) SetVimMode(on bool) { 211 i.Operation.SetVimMode(on) 212} 213 214func (i *Instance) IsVimMode() bool { 215 return i.Operation.IsEnableVimMode() 216} 217 218func (i *Instance) GenPasswordConfig() *Config { 219 return i.Operation.GenPasswordConfig() 220} 221 222// we can generate a config by `i.GenPasswordConfig()` 223func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { 224 return i.Operation.PasswordWithConfig(cfg) 225} 226 227func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { 228 return i.Operation.PasswordEx(prompt, l) 229} 230 231func (i *Instance) ReadPassword(prompt string) ([]byte, error) { 232 return i.Operation.Password(prompt) 233} 234 235type Result struct { 236 Line string 237 Error error 238} 239 240func (l *Result) CanContinue() bool { 241 return len(l.Line) != 0 && l.Error == ErrInterrupt 242} 243 244func (l *Result) CanBreak() bool { 245 return !l.CanContinue() && l.Error != nil 246} 247 248func (i *Instance) Line() *Result { 249 ret, err := i.Readline() 250 return &Result{ret, err} 251} 252 253// err is one of (nil, io.EOF, readline.ErrInterrupt) 254func (i *Instance) Readline() (string, error) { 255 return i.Operation.String() 256} 257 258func (i *Instance) ReadlineWithDefault(what string) (string, error) { 259 i.Operation.SetBuffer(what) 260 return i.Operation.String() 261} 262 263func (i *Instance) SaveHistory(content string) error { 264 return i.Operation.SaveHistory(content) 265} 266 267// same as readline 268func (i *Instance) ReadSlice() ([]byte, error) { 269 return i.Operation.Slice() 270} 271 272// we must make sure that call Close() before process exit. 273func (i *Instance) Close() error { 274 if err := i.Terminal.Close(); err != nil { 275 return err 276 } 277 i.Config.Stdin.Close() 278 i.Operation.Close() 279 return nil 280} 281func (i *Instance) Clean() { 282 i.Operation.Clean() 283} 284 285func (i *Instance) Write(b []byte) (int, error) { 286 return i.Stdout().Write(b) 287} 288 289// WriteStdin prefill the next Stdin fetch 290// Next time you call ReadLine() this value will be writen before the user input 291// ie : 292// i := readline.New() 293// i.WriteStdin([]byte("test")) 294// _, _= i.Readline() 295// 296// gives 297// 298// > test[cursor] 299func (i *Instance) WriteStdin(val []byte) (int, error) { 300 return i.Terminal.WriteStdin(val) 301} 302 303func (i *Instance) SetConfig(cfg *Config) *Config { 304 if i.Config == cfg { 305 return cfg 306 } 307 old := i.Config 308 i.Config = cfg 309 i.Operation.SetConfig(cfg) 310 i.Terminal.SetConfig(cfg) 311 return old 312} 313 314func (i *Instance) Refresh() { 315 i.Operation.Refresh() 316} 317 318// HistoryDisable the save of the commands into the history 319func (i *Instance) HistoryDisable() { 320 i.Operation.history.Disable() 321} 322 323// HistoryEnable the save of the commands into the history (default on) 324func (i *Instance) HistoryEnable() { 325 i.Operation.history.Enable() 326} 327