forked from mirror/readline
improve history
This commit is contained in:
parent
c16e43d258
commit
772978399e
72
history.go
72
history.go
|
@ -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
|
||||
}
|
||||
|
|
20
operation.go
20
operation.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
28
runebuf.go
28
runebuf.go
|
@ -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)))
|
||||
|
|
|
@ -14,6 +14,7 @@ const (
|
|||
MetaPrev = -iota - 1
|
||||
MetaNext
|
||||
MetaDelete
|
||||
MetaBackspace
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
46
utils.go
46
utils.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue