1package readline
2
3import (
4	"bytes"
5	"container/list"
6	"fmt"
7	"io"
8)
9
10const (
11	S_STATE_FOUND = iota
12	S_STATE_FAILING
13)
14
15const (
16	S_DIR_BCK = iota
17	S_DIR_FWD
18)
19
20type opSearch struct {
21	inMode    bool
22	state     int
23	dir       int
24	source    *list.Element
25	w         io.Writer
26	buf       *RuneBuffer
27	data      []rune
28	history   *opHistory
29	cfg       *Config
30	markStart int
31	markEnd   int
32	width     int
33}
34
35func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch {
36	return &opSearch{
37		w:       w,
38		buf:     buf,
39		cfg:     cfg,
40		history: history,
41		width:   width,
42	}
43}
44
45func (o *opSearch) OnWidthChange(newWidth int) {
46	o.width = newWidth
47}
48
49func (o *opSearch) IsSearchMode() bool {
50	return o.inMode
51}
52
53func (o *opSearch) SearchBackspace() {
54	if len(o.data) > 0 {
55		o.data = o.data[:len(o.data)-1]
56		o.search(true)
57	}
58}
59
60func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
61	if o.dir == S_DIR_BCK {
62		return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
63	}
64	return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
65}
66
67func (o *opSearch) search(isChange bool) bool {
68	if len(o.data) == 0 {
69		o.state = S_STATE_FOUND
70		o.SearchRefresh(-1)
71		return true
72	}
73	idx, elem := o.findHistoryBy(isChange)
74	if elem == nil {
75		o.SearchRefresh(-2)
76		return false
77	}
78	o.history.current = elem
79
80	item := o.history.showItem(o.history.current.Value)
81	start, end := 0, 0
82	if o.dir == S_DIR_BCK {
83		start, end = idx, idx+len(o.data)
84	} else {
85		start, end = idx, idx+len(o.data)
86		idx += len(o.data)
87	}
88	o.buf.SetWithIdx(idx, item)
89	o.markStart, o.markEnd = start, end
90	o.SearchRefresh(idx)
91	return true
92}
93
94func (o *opSearch) SearchChar(r rune) {
95	o.data = append(o.data, r)
96	o.search(true)
97}
98
99func (o *opSearch) SearchMode(dir int) bool {
100	if o.width == 0 {
101		return false
102	}
103	alreadyInMode := o.inMode
104	o.inMode = true
105	o.dir = dir
106	o.source = o.history.current
107	if alreadyInMode {
108		o.search(false)
109	} else {
110		o.SearchRefresh(-1)
111	}
112	return true
113}
114
115func (o *opSearch) ExitSearchMode(revert bool) {
116	if revert {
117		o.history.current = o.source
118		o.buf.Set(o.history.showItem(o.history.current.Value))
119	}
120	o.markStart, o.markEnd = 0, 0
121	o.state = S_STATE_FOUND
122	o.inMode = false
123	o.source = nil
124	o.data = nil
125}
126
127func (o *opSearch) SearchRefresh(x int) {
128	if x == -2 {
129		o.state = S_STATE_FAILING
130	} else if x >= 0 {
131		o.state = S_STATE_FOUND
132	}
133	if x < 0 {
134		x = o.buf.idx
135	}
136	x = o.buf.CurrentWidth(x)
137	x += o.buf.PromptLen()
138	x = x % o.width
139
140	if o.markStart > 0 {
141		o.buf.SetStyle(o.markStart, o.markEnd, "4")
142	}
143
144	lineCnt := o.buf.CursorLineCount()
145	buf := bytes.NewBuffer(nil)
146	buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
147	buf.WriteString("\033[J")
148	if o.state == S_STATE_FAILING {
149		buf.WriteString("failing ")
150	}
151	if o.dir == S_DIR_BCK {
152		buf.WriteString("bck")
153	} else if o.dir == S_DIR_FWD {
154		buf.WriteString("fwd")
155	}
156	buf.WriteString("-i-search: ")
157	buf.WriteString(string(o.data))         // keyword
158	buf.WriteString("\033[4m \033[0m")      // _
159	fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
160	if x > 0 {
161		fmt.Fprintf(buf, "\033[%dC", x) // move forward
162	}
163	o.w.Write(buf.Bytes())
164}
165