diff --git a/history.go b/history.go index b6e5d46..9dac917 100644 --- a/history.go +++ b/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 } diff --git a/operation.go b/operation.go index 892d265..baf3046 100644 --- a/operation.go +++ b/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) } } diff --git a/runebuf.go b/runebuf.go index a1d82be..6bbb96d 100644 --- a/runebuf.go +++ b/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))) diff --git a/terminal.go b/terminal.go index 302c63b..f1793bf 100644 --- a/terminal.go +++ b/terminal.go @@ -14,6 +14,7 @@ const ( MetaPrev = -iota - 1 MetaNext MetaDelete + MetaBackspace ) const ( diff --git a/utils.go b/utils.go index cc4752a..6218c0a 100644 --- a/utils.go +++ b/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 +}