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
|
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
|
||||||
}
|
}
|
||||||
|
|
20
operation.go
20
operation.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
runebuf.go
28
runebuf.go
|
@ -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)))
|
||||||
|
|
|
@ -14,6 +14,7 @@ const (
|
||||||
MetaPrev = -iota - 1
|
MetaPrev = -iota - 1
|
||||||
MetaNext
|
MetaNext
|
||||||
MetaDelete
|
MetaDelete
|
||||||
|
MetaBackspace
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
46
utils.go
46
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue