VictoriaMetrics/vendor/github.com/ergochat/readline/search.go

189 lines
3.6 KiB
Go

package readline
import (
"bytes"
"container/list"
"fmt"
"sync"
)
type searchState uint
const (
searchStateFound searchState = iota
searchStateFailing
)
type searchDirection uint
const (
searchDirectionForward searchDirection = iota
searchDirectionBackward
)
type opSearch struct {
mutex sync.Mutex
inMode bool
state searchState
dir searchDirection
source *list.Element
w *terminal
buf *runeBuffer
data []rune
history *opHistory
markStart int
markEnd int
}
func newOpSearch(w *terminal, buf *runeBuffer, history *opHistory) *opSearch {
return &opSearch{
w: w,
buf: buf,
history: history,
}
}
func (o *opSearch) IsSearchMode() bool {
o.mutex.Lock()
defer o.mutex.Unlock()
return o.inMode
}
func (o *opSearch) SearchBackspace() {
o.mutex.Lock()
defer o.mutex.Unlock()
if len(o.data) > 0 {
o.data = o.data[:len(o.data)-1]
o.search(true)
}
}
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
if o.dir == searchDirectionBackward {
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
}
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
}
func (o *opSearch) search(isChange bool) bool {
if len(o.data) == 0 {
o.state = searchStateFound
o.searchRefresh(-1)
return true
}
idx, elem := o.findHistoryBy(isChange)
if elem == nil {
o.searchRefresh(-2)
return false
}
o.history.current = elem
item := o.history.showItem(o.history.current.Value)
start, end := 0, 0
if o.dir == searchDirectionBackward {
start, end = idx, idx+len(o.data)
} else {
start, end = idx, idx+len(o.data)
idx += len(o.data)
}
o.buf.SetWithIdx(idx, item)
o.markStart, o.markEnd = start, end
o.searchRefresh(idx)
return true
}
func (o *opSearch) SearchChar(r rune) {
o.mutex.Lock()
defer o.mutex.Unlock()
o.data = append(o.data, r)
o.search(true)
}
func (o *opSearch) SearchMode(dir searchDirection) bool {
o.mutex.Lock()
defer o.mutex.Unlock()
tWidth, _ := o.w.GetWidthHeight()
if tWidth == 0 {
return false
}
alreadyInMode := o.inMode
o.inMode = true
o.dir = dir
o.source = o.history.current
if alreadyInMode {
o.search(false)
} else {
o.searchRefresh(-1)
}
return true
}
func (o *opSearch) ExitSearchMode(revert bool) {
o.mutex.Lock()
defer o.mutex.Unlock()
if revert {
o.history.current = o.source
var redrawValue []rune
if o.history.current != nil {
redrawValue = o.history.showItem(o.history.current.Value)
}
o.buf.Set(redrawValue)
}
o.markStart, o.markEnd = 0, 0
o.state = searchStateFound
o.inMode = false
o.source = nil
o.data = nil
}
func (o *opSearch) searchRefresh(x int) {
tWidth, _ := o.w.GetWidthHeight()
if x == -2 {
o.state = searchStateFailing
} else if x >= 0 {
o.state = searchStateFound
}
if x < 0 {
x = o.buf.idx
}
x = o.buf.CurrentWidth(x)
x += o.buf.PromptLen()
x = x % tWidth
if o.markStart > 0 {
o.buf.SetStyle(o.markStart, o.markEnd, "4")
}
lineCnt := o.buf.CursorLineCount()
buf := bytes.NewBuffer(nil)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
buf.WriteString("\033[J")
if o.state == searchStateFailing {
buf.WriteString("failing ")
}
if o.dir == searchDirectionBackward {
buf.WriteString("bck")
} else if o.dir == searchDirectionForward {
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())
}
func (o *opSearch) RefreshIfNeeded() {
o.mutex.Lock()
defer o.mutex.Unlock()
if o.inMode {
o.searchRefresh(-1)
}
}