1package readline
2
3import (
4	"bufio"
5	"container/list"
6	"fmt"
7	"os"
8	"strings"
9	"sync"
10)
11
12type hisItem struct {
13	Source  []rune
14	Version int64
15	Tmp     []rune
16}
17
18func (h *hisItem) Clean() {
19	h.Source = nil
20	h.Tmp = nil
21}
22
23type opHistory struct {
24	cfg        *Config
25	history    *list.List
26	historyVer int64
27	current    *list.Element
28	fd         *os.File
29	fdLock     sync.Mutex
30	enable     bool
31}
32
33func newOpHistory(cfg *Config) (o *opHistory) {
34	o = &opHistory{
35		cfg:     cfg,
36		history: list.New(),
37		enable:  true,
38	}
39	return o
40}
41
42func (o *opHistory) Reset() {
43	o.history = list.New()
44	o.current = nil
45}
46
47func (o *opHistory) IsHistoryClosed() bool {
48	o.fdLock.Lock()
49	defer o.fdLock.Unlock()
50	return o.fd.Fd() == ^(uintptr(0))
51}
52
53func (o *opHistory) Init() {
54	if o.IsHistoryClosed() {
55		o.initHistory()
56	}
57}
58
59func (o *opHistory) initHistory() {
60	if o.cfg.HistoryFile != "" {
61		o.historyUpdatePath(o.cfg.HistoryFile)
62	}
63}
64
65// only called by newOpHistory
66func (o *opHistory) historyUpdatePath(path string) {
67	o.fdLock.Lock()
68	defer o.fdLock.Unlock()
69	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
70	if err != nil {
71		return
72	}
73	o.fd = f
74	r := bufio.NewReader(o.fd)
75	total := 0
76	for ; ; total++ {
77		line, err := r.ReadString('\n')
78		if err != nil {
79			break
80		}
81		// ignore the empty line
82		line = strings.TrimSpace(line)
83		if len(line) == 0 {
84			continue
85		}
86		o.Push([]rune(line))
87		o.Compact()
88	}
89	if total > o.cfg.HistoryLimit {
90		o.rewriteLocked()
91	}
92	o.historyVer++
93	o.Push(nil)
94	return
95}
96
97func (o *opHistory) Compact() {
98	for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
99		o.history.Remove(o.history.Front())
100	}
101}
102
103func (o *opHistory) Rewrite() {
104	o.fdLock.Lock()
105	defer o.fdLock.Unlock()
106	o.rewriteLocked()
107}
108
109func (o *opHistory) rewriteLocked() {
110	if o.cfg.HistoryFile == "" {
111		return
112	}
113
114	tmpFile := o.cfg.HistoryFile + ".tmp"
115	fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
116	if err != nil {
117		return
118	}
119
120	buf := bufio.NewWriter(fd)
121	for elem := o.history.Front(); elem != nil; elem = elem.Next() {
122		buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
123	}
124	buf.Flush()
125
126	// replace history file
127	if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {
128		fd.Close()
129		return
130	}
131
132	if o.fd != nil {
133		o.fd.Close()
134	}
135	// fd is write only, just satisfy what we need.
136	o.fd = fd
137}
138
139func (o *opHistory) Close() {
140	o.fdLock.Lock()
141	defer o.fdLock.Unlock()
142	if o.fd != nil {
143		o.fd.Close()
144	}
145}
146
147func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
148	for elem := o.current; elem != nil; elem = elem.Prev() {
149		item := o.showItem(elem.Value)
150		if isNewSearch {
151			start += len(rs)
152		}
153		if elem == o.current {
154			if len(item) >= start {
155				item = item[:start]
156			}
157		}
158		idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)
159		if idx < 0 {
160			continue
161		}
162		return idx, elem
163	}
164	return -1, nil
165}
166
167func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
168	for elem := o.current; elem != nil; elem = elem.Next() {
169		item := o.showItem(elem.Value)
170		if isNewSearch {
171			start -= len(rs)
172			if start < 0 {
173				start = 0
174			}
175		}
176		if elem == o.current {
177			if len(item)-1 >= start {
178				item = item[start:]
179			} else {
180				continue
181			}
182		}
183		idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)
184		if idx < 0 {
185			continue
186		}
187		if elem == o.current {
188			idx += start
189		}
190		return idx, elem
191	}
192	return -1, nil
193}
194
195func (o *opHistory) showItem(obj interface{}) []rune {
196	item := obj.(*hisItem)
197	if item.Version == o.historyVer {
198		return item.Tmp
199	}
200	return item.Source
201}
202
203func (o *opHistory) Prev() []rune {
204	if o.current == nil {
205		return nil
206	}
207	current := o.current.Prev()
208	if current == nil {
209		return nil
210	}
211	o.current = current
212	return runes.Copy(o.showItem(current.Value))
213}
214
215func (o *opHistory) Next() ([]rune, bool) {
216	if o.current == nil {
217		return nil, false
218	}
219	current := o.current.Next()
220	if current == nil {
221		return nil, false
222	}
223
224	o.current = current
225	return runes.Copy(o.showItem(current.Value)), true
226}
227
228// Disable the current history
229func (o *opHistory) Disable() {
230	o.enable = false
231}
232
233// Enable the current history
234func (o *opHistory) Enable() {
235	o.enable = true
236}
237
238func (o *opHistory) debug() {
239	Debug("-------")
240	for item := o.history.Front(); item != nil; item = item.Next() {
241		Debug(fmt.Sprintf("%+v", item.Value))
242	}
243}
244
245// save history
246func (o *opHistory) New(current []rune) (err error) {
247
248	// history deactivated
249	if !o.enable {
250		return nil
251	}
252
253	current = runes.Copy(current)
254
255	// if just use last command without modify
256	// just clean lastest history
257	if back := o.history.Back(); back != nil {
258		prev := back.Prev()
259		if prev != nil {
260			if runes.Equal(current, prev.Value.(*hisItem).Source) {
261				o.current = o.history.Back()
262				o.current.Value.(*hisItem).Clean()
263				o.historyVer++
264				return nil
265			}
266		}
267	}
268
269	if len(current) == 0 {
270		o.current = o.history.Back()
271		if o.current != nil {
272			o.current.Value.(*hisItem).Clean()
273			o.historyVer++
274			return nil
275		}
276	}
277
278	if o.current != o.history.Back() {
279		// move history item to current command
280		currentItem := o.current.Value.(*hisItem)
281		// set current to last item
282		o.current = o.history.Back()
283
284		current = runes.Copy(currentItem.Tmp)
285	}
286
287	// err only can be a IO error, just report
288	err = o.Update(current, true)
289
290	// push a new one to commit current command
291	o.historyVer++
292	o.Push(nil)
293	return
294}
295
296func (o *opHistory) Revert() {
297	o.historyVer++
298	o.current = o.history.Back()
299}
300
301func (o *opHistory) Update(s []rune, commit bool) (err error) {
302	o.fdLock.Lock()
303	defer o.fdLock.Unlock()
304	s = runes.Copy(s)
305	if o.current == nil {
306		o.Push(s)
307		o.Compact()
308		return
309	}
310	r := o.current.Value.(*hisItem)
311	r.Version = o.historyVer
312	if commit {
313		r.Source = s
314		if o.fd != nil {
315			// just report the error
316			_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
317		}
318	} else {
319		r.Tmp = append(r.Tmp[:0], s...)
320	}
321	o.current.Value = r
322	o.Compact()
323	return
324}
325
326func (o *opHistory) Push(s []rune) {
327	s = runes.Copy(s)
328	elem := o.history.PushBack(&hisItem{Source: s})
329	o.current = elem
330}
331