forked from mirror/readline
add fwd/bck search
This commit is contained in:
parent
3f23122fec
commit
3b1cf6b8fb
22
history.go
22
history.go
|
@ -58,6 +58,28 @@ func (o *opHistory) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) FindHistoryBck(rs []rune) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Prev() {
|
||||
idx := RunesIndex(o.showItem(elem.Value), rs)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) FindHistoryFwd(rs []rune) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Next() {
|
||||
idx := RunesIndex(o.showItem(elem.Value), rs)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) showItem(obj interface{}) []rune {
|
||||
item := obj.(*HisItem)
|
||||
if item.Version == o.historyVer {
|
||||
|
|
52
operation.go
52
operation.go
|
@ -11,19 +11,23 @@ type Operation struct {
|
|||
buf *RuneBuffer
|
||||
outchan chan []rune
|
||||
*opHistory
|
||||
*opSearch
|
||||
}
|
||||
|
||||
const (
|
||||
CharLineStart = 0x1
|
||||
CharLineEnd = 0x5
|
||||
CharLineStart = 1
|
||||
CharLineEnd = 5
|
||||
CharKill = 11
|
||||
CharNext = 0xe
|
||||
CharPrev = 0x10
|
||||
CharBackward = 0x2
|
||||
CharForward = 0x6
|
||||
CharNext = 14
|
||||
CharPrev = 16
|
||||
CharBackward = 2
|
||||
CharForward = 6
|
||||
CharBackspace = 0x7f
|
||||
CharEnter = 0xd
|
||||
CharEnter2 = 0xa
|
||||
CharBckSearch = 18
|
||||
CharFwdSearch = 19
|
||||
CharCannel = 7
|
||||
)
|
||||
|
||||
type wrapWriter struct {
|
||||
|
@ -47,14 +51,27 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
|||
outchan: make(chan []rune),
|
||||
opHistory: newOpHistory(cfg.HistoryFile),
|
||||
}
|
||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||
go op.ioloop()
|
||||
return op
|
||||
}
|
||||
|
||||
func (l *Operation) ioloop() {
|
||||
for {
|
||||
keepInSearchMode := false
|
||||
r := l.t.ReadRune()
|
||||
switch r {
|
||||
case CharCannel:
|
||||
if l.IsSearchMode() {
|
||||
l.ExitSearchMode(true)
|
||||
l.buf.Refresh()
|
||||
}
|
||||
case CharBckSearch:
|
||||
l.SearchMode(S_DIR_BCK)
|
||||
keepInSearchMode = true
|
||||
case CharFwdSearch:
|
||||
l.SearchMode(S_DIR_FWD)
|
||||
keepInSearchMode = true
|
||||
case CharKill:
|
||||
l.buf.Kill()
|
||||
case MetaNext:
|
||||
|
@ -70,7 +87,12 @@ func (l *Operation) ioloop() {
|
|||
case KeyDelete:
|
||||
l.buf.Delete()
|
||||
case CharBackspace:
|
||||
l.buf.Backspace()
|
||||
if l.IsSearchMode() {
|
||||
l.SearchBackspace()
|
||||
keepInSearchMode = true
|
||||
} else {
|
||||
l.buf.Backspace()
|
||||
}
|
||||
case MetaBackspace:
|
||||
l.buf.BackEscapeWord()
|
||||
case CharEnter, CharEnter2:
|
||||
|
@ -95,10 +117,24 @@ func (l *Operation) ioloop() {
|
|||
l.buf.Set(buf)
|
||||
}
|
||||
case KeyInterrupt:
|
||||
if l.IsSearchMode() {
|
||||
l.ExitSearchMode(false)
|
||||
}
|
||||
l.buf.MoveToLineEnd()
|
||||
l.buf.Refresh()
|
||||
l.buf.WriteString("^C\n")
|
||||
l.outchan <- nil
|
||||
default:
|
||||
l.buf.WriteRune(r)
|
||||
if l.IsSearchMode() {
|
||||
l.SearchChar(r)
|
||||
keepInSearchMode = true
|
||||
} else {
|
||||
l.buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if !keepInSearchMode && l.IsSearchMode() {
|
||||
l.ExitSearchMode(false)
|
||||
l.buf.Refresh()
|
||||
}
|
||||
l.UpdateHistory(l.buf.Runes(), false)
|
||||
}
|
||||
|
|
27
runebuf.go
27
runebuf.go
|
@ -170,6 +170,25 @@ func (r *RuneBuffer) MoveToLineEnd() {
|
|||
r.Refresh()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) LineCount() int {
|
||||
return LineCount(RunesWidth(r.buf) + len(r.prompt))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) IdxLine() int {
|
||||
totalWidth := RunesWidth(r.buf[:r.idx]) + len(r.prompt)
|
||||
w := getWidth()
|
||||
line := 0
|
||||
for totalWidth >= w {
|
||||
totalWidth -= w
|
||||
line++
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) CursorLineCount() int {
|
||||
return r.LineCount() - r.IdxLine()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Refresh() {
|
||||
r.w.Write(r.Output())
|
||||
}
|
||||
|
@ -205,8 +224,12 @@ func (r *RuneBuffer) Reset() []rune {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Set(buf []rune) {
|
||||
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
||||
r.buf = buf
|
||||
r.idx = len(r.buf)
|
||||
r.idx = idx
|
||||
r.Refresh()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Set(buf []rune) {
|
||||
r.SetWithIdx(len(buf), buf)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
S_STATE_FOUND = iota
|
||||
S_STATE_FAILING
|
||||
)
|
||||
|
||||
const (
|
||||
S_DIR_BCK = iota
|
||||
S_DIR_FWD
|
||||
)
|
||||
|
||||
type opSearch struct {
|
||||
inMode bool
|
||||
state int
|
||||
dir int
|
||||
source *list.Element
|
||||
w io.Writer
|
||||
buf *RuneBuffer
|
||||
data []rune
|
||||
history *opHistory
|
||||
}
|
||||
|
||||
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory) *opSearch {
|
||||
return &opSearch{
|
||||
w: w,
|
||||
buf: buf,
|
||||
history: history,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) IsSearchMode() bool {
|
||||
return o.inMode
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchBackspace() {
|
||||
if len(o.data) > 0 {
|
||||
o.data = o.data[:len(o.data)-1]
|
||||
o.search()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) findHistoryBy() (int, *list.Element) {
|
||||
if o.dir == S_DIR_BCK {
|
||||
return o.history.FindHistoryBck(o.data)
|
||||
}
|
||||
return o.history.FindHistoryFwd(o.data)
|
||||
}
|
||||
|
||||
func (o *opSearch) search() bool {
|
||||
idx, elem := o.findHistoryBy()
|
||||
if elem == nil {
|
||||
o.SearchRefresh(-2)
|
||||
return false
|
||||
}
|
||||
o.history.current = elem
|
||||
o.buf.SetWithIdx(idx, o.history.showItem(o.history.current.Value))
|
||||
o.SearchRefresh(idx)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchChar(r rune) {
|
||||
o.data = append(o.data, r)
|
||||
o.search()
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchMode(dir int) {
|
||||
o.inMode = true
|
||||
o.dir = dir
|
||||
o.source = o.history.current
|
||||
o.SearchRefresh(-1)
|
||||
}
|
||||
|
||||
func (o *opSearch) ExitSearchMode(revert bool) {
|
||||
if revert {
|
||||
o.history.current = o.source
|
||||
o.buf.Set(o.history.showItem(o.history.current.Value))
|
||||
}
|
||||
o.inMode = false
|
||||
o.source = nil
|
||||
o.data = nil
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchRefresh(x int) {
|
||||
if x == -2 {
|
||||
o.state = S_STATE_FAILING
|
||||
} else if x >= 0 {
|
||||
o.state = S_STATE_FOUND
|
||||
}
|
||||
if x < 0 {
|
||||
x = o.buf.idx
|
||||
}
|
||||
x += len(o.buf.prompt)
|
||||
x = x % getWidth()
|
||||
|
||||
lineCnt := o.buf.CursorLineCount()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
buf.WriteString("\033[J")
|
||||
if o.state == S_STATE_FAILING {
|
||||
buf.WriteString("failing ")
|
||||
}
|
||||
if o.dir == S_DIR_BCK {
|
||||
buf.WriteString("bck")
|
||||
} else if o.dir == S_DIR_FWD {
|
||||
buf.WriteString("fwd")
|
||||
}
|
||||
buf.WriteString("-i-search: ")
|
||||
buf.WriteString(string(o.data)) // keyword
|
||||
buf.WriteString("\033[4m \033[0m") // _
|
||||
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
|
||||
if x > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dC", x) // move forward
|
||||
}
|
||||
o.w.Write(buf.Bytes())
|
||||
}
|
|
@ -102,6 +102,8 @@ func (t *Terminal) ioloop() {
|
|||
isEscape = true
|
||||
case CharEnter, CharEnter2, KeyPrevChar, KeyNextChar, KeyDelete:
|
||||
fallthrough
|
||||
case CharFwdSearch, CharBckSearch, CharCannel:
|
||||
fallthrough
|
||||
case CharLineEnd, CharLineStart, CharNext, CharPrev, CharKill:
|
||||
t.outchan <- r
|
||||
default:
|
||||
|
|
40
utils.go
40
utils.go
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
@ -109,3 +110,42 @@ func sleep(n int) {
|
|||
Debug(n)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
}
|
||||
|
||||
func LineCount(w int) int {
|
||||
screenWidth := getWidth()
|
||||
r := w / screenWidth
|
||||
if w%screenWidth != 0 {
|
||||
r++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func RunesWidth(r []rune) (length int) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
if utf8.RuneLen(r[i]) > 1 {
|
||||
length += 2
|
||||
} else {
|
||||
length += 1
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RunesIndex(r, sub []rune) int {
|
||||
for i := 0; i < len(r); i++ {
|
||||
found := true
|
||||
if len(r[i:]) < len(sub) {
|
||||
return -1
|
||||
}
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if r[i+j] != sub[j] {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue