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

View File

@ -12,8 +12,9 @@ type Operation struct {
buf *RuneBuffer
outchan chan []rune
history *list.List
current *list.Element
history *list.List
historyVer int64
current *list.Element
}
const (
@ -23,7 +24,7 @@ const (
CharPrev = 0x10
CharBackward = 0x2
CharForward = 0x6
CharEscape = 0x7f
CharBackspace = 0x7f
CharEnter = 0xd
CharEnter2 = 0xa
)
@ -69,14 +70,17 @@ func (l *Operation) ioloop() {
l.buf.MoveToLineEnd()
case KeyDelete:
l.buf.Delete()
case CharEscape:
l.buf.BackEscape()
case CharBackspace:
l.buf.Backspace()
case MetaBackspace:
l.buf.BackEscapeWord()
case CharEnter, CharEnter2:
l.buf.WriteRune('\n')
data := l.buf.Reset()
data = data[:len(data)-1] // trim \n
l.outchan <- data
l.NewHistory(data)
debugList(l.history)
case CharBackward:
l.buf.MoveBackward()
case CharForward:
@ -87,8 +91,8 @@ func (l *Operation) ioloop() {
l.buf.Set(buf)
}
case CharNext:
buf := l.NextHistory()
if buf != nil {
buf, ok := l.NextHistory()
if ok {
l.buf.Set(buf)
}
case KeyInterrupt:
@ -97,7 +101,7 @@ func (l *Operation) ioloop() {
default:
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)))
}
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 {
return
}
@ -153,12 +171,8 @@ func (r *RuneBuffer) RefreshSet(originLength, originIdx int) {
func (r *RuneBuffer) Output(originLength, originIdx int) []byte {
buf := bytes.NewBuffer(nil)
if r.printPrompt {
r.printPrompt = false
buf.Write(r.prompt)
}
buf.Write(bytes.Repeat([]byte{'\b'}, originIdx))
buf.Write(bytes.Repeat([]byte{'\b'}, originIdx+len(r.prompt)))
buf.Write(r.prompt)
buf.Write([]byte(string(r.buf)))
if originLength > len(r.buf) {
buf.Write(bytes.Repeat([]byte{' '}, originLength-len(r.buf)))

View File

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

View File

@ -1,8 +1,11 @@
package readline
import (
"container/list"
"fmt"
"os"
"syscall"
"unsafe"
"golang.org/x/crypto/ssh/terminal"
)
@ -33,6 +36,8 @@ func prefixKey(r rune) rune {
r = MetaNext
case 'd':
r = MetaDelete
case CharBackspace:
r = MetaBackspace
case KeyEsc:
}
@ -44,3 +49,44 @@ func Debug(o ...interface{}) {
fmt.Fprintln(f, o...)
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
}