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