mirror of https://github.com/chzyer/readline.git
support save history item
This commit is contained in:
parent
c8f8ec4b96
commit
3f23122fec
|
@ -17,7 +17,10 @@ bye: quit
|
|||
}
|
||||
|
||||
func main() {
|
||||
l, err := readline.New("home -> ")
|
||||
l, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "home -> ",
|
||||
HistoryFile: "/tmp/readline.tmp",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
62
history.go
62
history.go
|
@ -1,5 +1,12 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HisItem struct {
|
||||
Source []rune
|
||||
Version int64
|
||||
|
@ -11,7 +18,47 @@ func (h *HisItem) Clean() {
|
|||
h.Tmp = nil
|
||||
}
|
||||
|
||||
func (o *Operation) showItem(obj interface{}) []rune {
|
||||
type opHistory struct {
|
||||
path string
|
||||
history *list.List
|
||||
historyVer int64
|
||||
current *list.Element
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
func newOpHistory(path string) (o *opHistory) {
|
||||
o = &opHistory{
|
||||
path: path,
|
||||
history: list.New(),
|
||||
}
|
||||
if o.path == "" {
|
||||
return
|
||||
}
|
||||
f, err := os.OpenFile(o.path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
o.fd = f
|
||||
r := bufio.NewReader(o.fd)
|
||||
for {
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
o.PushHistory([]rune(strings.TrimSpace(string(line))))
|
||||
}
|
||||
o.historyVer++
|
||||
o.PushHistory(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Close() {
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) showItem(obj interface{}) []rune {
|
||||
item := obj.(*HisItem)
|
||||
if item.Version == o.historyVer {
|
||||
return item.Tmp
|
||||
|
@ -19,7 +66,7 @@ func (o *Operation) showItem(obj interface{}) []rune {
|
|||
return item.Source
|
||||
}
|
||||
|
||||
func (o *Operation) PrevHistory() []rune {
|
||||
func (o *opHistory) PrevHistory() []rune {
|
||||
if o.current == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -31,7 +78,7 @@ func (o *Operation) PrevHistory() []rune {
|
|||
return o.showItem(current.Value)
|
||||
}
|
||||
|
||||
func (o *Operation) NextHistory() ([]rune, bool) {
|
||||
func (o *opHistory) NextHistory() ([]rune, bool) {
|
||||
if o.current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -44,7 +91,7 @@ func (o *Operation) NextHistory() ([]rune, bool) {
|
|||
return o.showItem(current.Value), true
|
||||
}
|
||||
|
||||
func (o *Operation) NewHistory(current []rune) {
|
||||
func (o *opHistory) NewHistory(current []rune) {
|
||||
// if just use last command without modify
|
||||
// just clean lastest history
|
||||
if back := o.history.Back(); back != nil {
|
||||
|
@ -82,7 +129,7 @@ func (o *Operation) NewHistory(current []rune) {
|
|||
o.PushHistory(nil)
|
||||
}
|
||||
|
||||
func (o *Operation) UpdateHistory(s []rune, commit bool) {
|
||||
func (o *opHistory) UpdateHistory(s []rune, commit bool) {
|
||||
if o.current == nil {
|
||||
o.PushHistory(s)
|
||||
return
|
||||
|
@ -92,13 +139,16 @@ func (o *Operation) UpdateHistory(s []rune, commit bool) {
|
|||
if commit {
|
||||
r.Source = make([]rune, len(s))
|
||||
copy(r.Source, s)
|
||||
if o.fd != nil {
|
||||
o.fd.Write([]byte(string(r.Source) + "\n"))
|
||||
}
|
||||
} else {
|
||||
r.Tmp = append(r.Tmp[:0], s...)
|
||||
}
|
||||
o.current.Value = r
|
||||
}
|
||||
|
||||
func (o *Operation) PushHistory(s []rune) {
|
||||
func (o *opHistory) PushHistory(s []rune) {
|
||||
// copy
|
||||
newCopy := make([]rune, len(s))
|
||||
copy(newCopy, s)
|
||||
|
|
20
operation.go
20
operation.go
|
@ -1,20 +1,16 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
r *os.File
|
||||
cfg *Config
|
||||
t *Terminal
|
||||
buf *RuneBuffer
|
||||
outchan chan []rune
|
||||
|
||||
history *list.List
|
||||
historyVer int64
|
||||
current *list.Element
|
||||
*opHistory
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -43,13 +39,13 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
func NewOperation(r *os.File, t *Terminal, prompt string) *Operation {
|
||||
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||
op := &Operation{
|
||||
r: r,
|
||||
cfg: cfg,
|
||||
t: t,
|
||||
buf: NewRuneBuffer(t, prompt),
|
||||
buf: NewRuneBuffer(t, cfg.Prompt),
|
||||
outchan: make(chan []rune),
|
||||
history: list.New(),
|
||||
opHistory: newOpHistory(cfg.HistoryFile),
|
||||
}
|
||||
go op.ioloop()
|
||||
return op
|
||||
|
@ -136,3 +132,7 @@ func (l *Operation) Slice() ([]byte, error) {
|
|||
}
|
||||
return []byte(string(r)), nil
|
||||
}
|
||||
|
||||
func (l *Operation) Close() {
|
||||
l.opHistory.Close()
|
||||
}
|
||||
|
|
16
readline.go
16
readline.go
|
@ -7,18 +7,27 @@ type Instance struct {
|
|||
o *Operation
|
||||
}
|
||||
|
||||
func New(prompt string) (*Instance, error) {
|
||||
t, err := NewTerminal()
|
||||
type Config struct {
|
||||
Prompt string
|
||||
HistoryFile string
|
||||
}
|
||||
|
||||
func NewEx(cfg *Config) (*Instance, error) {
|
||||
t, err := NewTerminal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rl := t.Readline(prompt)
|
||||
rl := t.Readline()
|
||||
return &Instance{
|
||||
t: t,
|
||||
o: rl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func New(prompt string) (*Instance, error) {
|
||||
return NewEx(&Config{Prompt: prompt})
|
||||
}
|
||||
|
||||
func (i *Instance) Stderr() io.Writer {
|
||||
return i.o.Stderr()
|
||||
}
|
||||
|
@ -32,5 +41,6 @@ func (i *Instance) ReadSlice() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (i *Instance) Close() error {
|
||||
i.o.Close()
|
||||
return i.t.Close()
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@ type RuneBuffer struct {
|
|||
idx int
|
||||
prompt []byte
|
||||
w io.Writer
|
||||
hasPrompt bool
|
||||
lastWritten int
|
||||
}
|
||||
|
||||
func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
|
||||
|
@ -187,7 +185,9 @@ func (r *RuneBuffer) Output() []byte {
|
|||
|
||||
func (r *RuneBuffer) CleanOutput() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write([]byte("\033[J"))
|
||||
buf.Write([]byte("\033[J")) // just like ^k :)
|
||||
|
||||
// TODO: calculate how many line before cursor.
|
||||
for i := 0; i <= 100; i++ {
|
||||
buf.WriteString("\033[2K\r\b")
|
||||
}
|
||||
|
@ -202,7 +202,6 @@ func (r *RuneBuffer) Reset() []rune {
|
|||
ret := r.buf
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
r.hasPrompt = false
|
||||
return ret
|
||||
}
|
||||
|
||||
|
|
|
@ -27,17 +27,19 @@ const (
|
|||
)
|
||||
|
||||
type Terminal struct {
|
||||
cfg *Config
|
||||
state *terminal.State
|
||||
outchan chan rune
|
||||
closed int64
|
||||
}
|
||||
|
||||
func NewTerminal() (*Terminal, error) {
|
||||
func NewTerminal(cfg *Config) (*Terminal, error) {
|
||||
state, err := MakeRaw(syscall.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &Terminal{
|
||||
cfg: cfg,
|
||||
state: state,
|
||||
outchan: make(chan rune),
|
||||
}
|
||||
|
@ -58,8 +60,8 @@ func (t *Terminal) PrintRune(r rune) {
|
|||
fmt.Fprintf(os.Stdout, "%c", r)
|
||||
}
|
||||
|
||||
func (t *Terminal) Readline(prompt string) *Operation {
|
||||
return NewOperation(os.Stdin, t, prompt)
|
||||
func (t *Terminal) Readline() *Operation {
|
||||
return NewOperation(t, t.cfg)
|
||||
}
|
||||
|
||||
func (t *Terminal) ReadRune() rune {
|
||||
|
|
Loading…
Reference in New Issue