improve history

This commit is contained in:
Cheney 2015-09-21 21:00:48 +08:00
parent c16e43d258
commit 772978399e
5 changed files with 136 additions and 31 deletions

View File

@ -1,5 +1,24 @@
package readline package readline
type HisItem struct {
Source []rune
Version int64
Tmp []rune
}
func (h *HisItem) Clean() {
h.Source = nil
h.Tmp = nil
}
func (o *Operation) 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 *Operation) PrevHistory() []rune {
if o.current == nil { if o.current == nil {
return nil return nil
@ -9,48 +28,69 @@ func (o *Operation) PrevHistory() []rune {
return nil return nil
} }
o.current = current o.current = current
return current.Value.([]rune) return o.showItem(current.Value)
} }
func (o *Operation) NextHistory() []rune { func (o *Operation) NextHistory() ([]rune, bool) {
if o.current == nil { if o.current == nil {
return nil return nil, false
} }
current := o.current.Next() current := o.current.Next()
if current == nil { if current == nil {
return nil return nil, false
} }
o.current = current o.current = current
return current.Value.([]rune) return o.showItem(current.Value), true
} }
func (o *Operation) NewHistory(current []rune) { func (o *Operation) NewHistory(current []rune) {
o.UpdateHistory(current) // if just use last command without modify
if o.current != o.history.Back() { // just clean lastest history
// move history item to current command if o.current == o.history.Back().Prev() {
o.history.Remove(o.current) use := o.current.Value.(*HisItem)
use := o.current.Value.([]rune) if equalRunes(use.Tmp, use.Source) {
o.current = o.history.Back() o.current = o.history.Back()
o.UpdateHistory(use) o.current.Value.(*HisItem).Clean()
o.historyVer++
return
}
} }
if o.current != o.history.Back() {
// move history item to current command
use := o.current.Value.(*HisItem)
o.current = o.history.Back()
current = use.Tmp
}
o.UpdateHistory(current, true)
// push a new one to commit current command // push a new one to commit current command
o.historyVer++
o.PushHistory(nil) o.PushHistory(nil)
} }
func (o *Operation) UpdateHistory(s []rune) { func (o *Operation) UpdateHistory(s []rune, commit bool) {
if o.current == nil { if o.current == nil {
o.PushHistory(s) o.PushHistory(s)
return return
} }
r := o.current.Value.([]rune) r := o.current.Value.(*HisItem)
o.current.Value = append(r[:0], s...) r.Version = o.historyVer
if commit {
r.Source = make([]rune, len(s))
copy(r.Source, s)
} else {
r.Tmp = append(r.Tmp[:0], s...)
}
o.current.Value = r
} }
func (o *Operation) PushHistory(s []rune) { func (o *Operation) PushHistory(s []rune) {
// copy // copy
newCopy := make([]rune, len(s)) newCopy := make([]rune, len(s))
copy(newCopy, s) copy(newCopy, s)
elem := o.history.PushBack(newCopy) elem := o.history.PushBack(&HisItem{Source: newCopy})
o.current = elem o.current = elem
} }

View File

@ -12,8 +12,9 @@ type Operation struct {
buf *RuneBuffer buf *RuneBuffer
outchan chan []rune outchan chan []rune
history *list.List history *list.List
current *list.Element historyVer int64
current *list.Element
} }
const ( const (
@ -23,7 +24,7 @@ const (
CharPrev = 0x10 CharPrev = 0x10
CharBackward = 0x2 CharBackward = 0x2
CharForward = 0x6 CharForward = 0x6
CharEscape = 0x7f CharBackspace = 0x7f
CharEnter = 0xd CharEnter = 0xd
CharEnter2 = 0xa CharEnter2 = 0xa
) )
@ -69,14 +70,17 @@ func (l *Operation) ioloop() {
l.buf.MoveToLineEnd() l.buf.MoveToLineEnd()
case KeyDelete: case KeyDelete:
l.buf.Delete() l.buf.Delete()
case CharEscape: case CharBackspace:
l.buf.BackEscape() l.buf.Backspace()
case MetaBackspace:
l.buf.BackEscapeWord()
case CharEnter, CharEnter2: case CharEnter, CharEnter2:
l.buf.WriteRune('\n') l.buf.WriteRune('\n')
data := l.buf.Reset() data := l.buf.Reset()
data = data[:len(data)-1] // trim \n data = data[:len(data)-1] // trim \n
l.outchan <- data l.outchan <- data
l.NewHistory(data) l.NewHistory(data)
debugList(l.history)
case CharBackward: case CharBackward:
l.buf.MoveBackward() l.buf.MoveBackward()
case CharForward: case CharForward:
@ -87,8 +91,8 @@ func (l *Operation) ioloop() {
l.buf.Set(buf) l.buf.Set(buf)
} }
case CharNext: case CharNext:
buf := l.NextHistory() buf, ok := l.NextHistory()
if buf != nil { if ok {
l.buf.Set(buf) l.buf.Set(buf)
} }
case KeyInterrupt: case KeyInterrupt:
@ -97,7 +101,7 @@ func (l *Operation) ioloop() {
default: default:
l.buf.WriteRune(r) l.buf.WriteRune(r)
} }
l.UpdateHistory(l.buf.Runes()) l.UpdateHistory(l.buf.Runes(), false)
} }
} }

View File

@ -126,7 +126,25 @@ func (r *RuneBuffer) MoveToNextWord() {
r.Refresh(0, r.SetIdx(len(r.buf))) r.Refresh(0, r.SetIdx(len(r.buf)))
} }
func (r *RuneBuffer) BackEscape() { func (r *RuneBuffer) BackEscapeWord() {
if r.idx == 0 {
return
}
for i := r.idx - 1; i > 0; i-- {
if r.buf[i] != ' ' && r.buf[i-1] == ' ' {
change := i - r.idx
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
r.Refresh(change, r.SetIdx(i))
return
}
}
length := len(r.buf)
r.buf = r.buf[:0]
r.Refresh(-length, r.SetIdx(0))
}
func (r *RuneBuffer) Backspace() {
if r.idx == 0 { if r.idx == 0 {
return return
} }
@ -153,12 +171,8 @@ func (r *RuneBuffer) RefreshSet(originLength, originIdx int) {
func (r *RuneBuffer) Output(originLength, originIdx int) []byte { func (r *RuneBuffer) Output(originLength, originIdx int) []byte {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
if r.printPrompt { buf.Write(bytes.Repeat([]byte{'\b'}, originIdx+len(r.prompt)))
r.printPrompt = false buf.Write(r.prompt)
buf.Write(r.prompt)
}
buf.Write(bytes.Repeat([]byte{'\b'}, originIdx))
buf.Write([]byte(string(r.buf))) buf.Write([]byte(string(r.buf)))
if originLength > len(r.buf) { if originLength > len(r.buf) {
buf.Write(bytes.Repeat([]byte{' '}, originLength-len(r.buf))) buf.Write(bytes.Repeat([]byte{' '}, originLength-len(r.buf)))

View File

@ -14,6 +14,7 @@ const (
MetaPrev = -iota - 1 MetaPrev = -iota - 1
MetaNext MetaNext
MetaDelete MetaDelete
MetaBackspace
) )
const ( const (

View File

@ -1,8 +1,11 @@
package readline package readline
import ( import (
"container/list"
"fmt" "fmt"
"os" "os"
"syscall"
"unsafe"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -33,6 +36,8 @@ func prefixKey(r rune) rune {
r = MetaNext r = MetaNext
case 'd': case 'd':
r = MetaDelete r = MetaDelete
case CharBackspace:
r = MetaBackspace
case KeyEsc: case KeyEsc:
} }
@ -44,3 +49,44 @@ func Debug(o ...interface{}) {
fmt.Fprintln(f, o...) fmt.Fprintln(f, o...)
f.Close() f.Close()
} }
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
func getWidth() int {
ws := &winsize{}
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdin),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
Debug(ws)
if int(retCode) == -1 {
panic(errno)
}
return int(ws.Col)
}
func debugList(l *list.List) {
idx := 0
for e := l.Front(); e != nil; e = e.Next() {
Debug(idx, fmt.Sprintf("%+v", e.Value))
idx++
}
}
func equalRunes(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}