Skip to content

Commit

Permalink
support save history item
Browse files Browse the repository at this point in the history
  • Loading branch information
chzyer committed Sep 22, 2015
1 parent c8f8ec4 commit 3f23122
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 33 deletions.
5 changes: 4 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
62 changes: 56 additions & 6 deletions history.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package readline

import (
"bufio"
"container/list"
"os"
"strings"
)

type HisItem struct {
Source []rune
Version int64
Expand All @@ -11,15 +18,55 @@ 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
}
return item.Source
}

func (o *Operation) PrevHistory() []rune {
func (o *opHistory) PrevHistory() []rune {
if o.current == nil {
return nil
}
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
24 changes: 12 additions & 12 deletions operation.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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,
t: t,
buf: NewRuneBuffer(t, prompt),
outchan: make(chan []rune),
history: list.New(),
cfg: cfg,
t: t,
buf: NewRuneBuffer(t, cfg.Prompt),
outchan: make(chan []rune),
opHistory: newOpHistory(cfg.HistoryFile),
}
go op.ioloop()
return op
Expand Down Expand Up @@ -136,3 +132,7 @@ func (l *Operation) Slice() ([]byte, error) {
}
return []byte(string(r)), nil
}

func (l *Operation) Close() {
l.opHistory.Close()
}
16 changes: 13 additions & 3 deletions readline.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -32,5 +41,6 @@ func (i *Instance) ReadSlice() ([]byte, error) {
}

func (i *Instance) Close() error {
i.o.Close()
return i.t.Close()
}
15 changes: 7 additions & 8 deletions runebuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
)

type RuneBuffer struct {
buf []rune
idx int
prompt []byte
w io.Writer
hasPrompt bool
lastWritten int
buf []rune
idx int
prompt []byte
w io.Writer
}

func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
}

Expand Down
8 changes: 5 additions & 3 deletions terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand All @@ -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 {
Expand Down

0 comments on commit 3f23122

Please sign in to comment.