Skip to content

Commit 902ed8c

Browse files
committed
simplify history package
1 parent 4ca9713 commit 902ed8c

1 file changed

Lines changed: 29 additions & 80 deletions

File tree

pkg/history/history.go

Lines changed: 29 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package history
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"os"
67
"path/filepath"
78
"slices"
8-
"strconv"
99
"strings"
1010
)
1111

@@ -86,55 +86,40 @@ func (h *History) migrateOldHistory(homeDir string) error {
8686
}
8787

8888
func (h *History) Add(message string) error {
89-
// Update in-memory list: remove duplicate and append to end
90-
h.Messages = slices.DeleteFunc(h.Messages, func(m string) bool {
91-
return m == message
92-
})
93-
h.Messages = append(h.Messages, message)
89+
h.addInMemory(message)
9490
h.current = len(h.Messages)
95-
9691
return h.append(message)
9792
}
9893

9994
func (h *History) Previous() string {
10095
if len(h.Messages) == 0 {
10196
return ""
10297
}
103-
104-
// If we're at -1 (initial state), start from the end
105-
if h.current == -1 {
98+
switch {
99+
case h.current == -1:
106100
h.current = len(h.Messages) - 1
107-
return h.Messages[h.current]
108-
}
109-
110-
// If we're at the beginning, stay there
111-
if h.current <= 0 {
112-
return h.Messages[0]
101+
case h.current > 0:
102+
h.current--
113103
}
114-
115-
h.current--
116104
return h.Messages[h.current]
117105
}
118106

119107
func (h *History) Next() string {
120108
if len(h.Messages) == 0 {
121109
return ""
122110
}
123-
124111
if h.current >= len(h.Messages)-1 {
125112
h.current = len(h.Messages)
126113
return ""
127114
}
128-
129115
h.current++
130116
return h.Messages[h.current]
131117
}
132118

133119
// LatestMatch returns the most recent history entry that extends the provided
134-
// prefix, or the latest message when no prefix is supplied.
120+
// prefix, or an empty string when none does.
135121
func (h *History) LatestMatch(prefix string) string {
136-
for i := len(h.Messages) - 1; i >= 0; i-- {
137-
msg := h.Messages[i]
122+
for _, msg := range slices.Backward(h.Messages) {
138123
if strings.HasPrefix(msg, prefix) && len(msg) > len(prefix) {
139124
return msg
140125
}
@@ -147,46 +132,42 @@ func (h *History) LatestMatch(prefix string) string {
147132
// Returns the matched message, its index, and whether a match was found.
148133
// An empty query matches any entry.
149134
func (h *History) FindPrevContains(query string, from int) (msg string, idx int, ok bool) {
150-
if len(h.Messages) == 0 {
151-
return "", -1, false
152-
}
153-
154-
start := min(from-1, len(h.Messages)-1)
155-
156135
query = strings.ToLower(query)
157-
for i := start; i >= 0; i-- {
136+
for i := min(from-1, len(h.Messages)-1); i >= 0; i-- {
158137
if query == "" || strings.Contains(strings.ToLower(h.Messages[i]), query) {
159138
return h.Messages[i], i, true
160139
}
161140
}
162-
163141
return "", -1, false
164142
}
165143

166144
// FindNextContains searches forward through history for a message containing query.
167145
// from is an exclusive lower bound index. Pass -1 to start from the oldest.
168146
// Returns the matched message, its index, and whether a match was found.
147+
// An empty query matches any entry.
169148
func (h *History) FindNextContains(query string, from int) (msg string, idx int, ok bool) {
170-
if len(h.Messages) == 0 {
171-
return "", -1, false
172-
}
173-
174-
start := max(from+1, 0)
175-
176149
query = strings.ToLower(query)
177-
for i := start; i < len(h.Messages); i++ {
150+
for i := max(from+1, 0); i < len(h.Messages); i++ {
178151
if query == "" || strings.Contains(strings.ToLower(h.Messages[i]), query) {
179152
return h.Messages[i], i, true
180153
}
181154
}
182-
183155
return "", -1, false
184156
}
185157

186158
func (h *History) SetCurrent(i int) {
187159
h.current = i
188160
}
189161

162+
// addInMemory removes any prior occurrence of message and appends it as the
163+
// most recent entry.
164+
func (h *History) addInMemory(message string) {
165+
h.Messages = slices.DeleteFunc(h.Messages, func(m string) bool {
166+
return m == message
167+
})
168+
h.Messages = append(h.Messages, message)
169+
}
170+
190171
func (h *History) append(message string) error {
191172
if err := os.MkdirAll(filepath.Dir(h.path), 0o755); err != nil {
192173
return err
@@ -213,51 +194,19 @@ func (h *History) load() error {
213194
return err
214195
}
215196

216-
// Count lines to pre-size the slice.
217-
n := 0
218-
for _, b := range data {
219-
if b == '\n' {
220-
n++
221-
}
222-
}
223-
224-
// Parse all lines. Each line is a JSON-encoded string (e.g. "hello").
225-
// strconv.Unquote handles the same escape sequences as JSON and is
226-
// much faster than json.Unmarshal for quoted strings.
227-
all := make([]string, 0, n)
228-
s := string(data)
229-
for s != "" {
230-
i := strings.IndexByte(s, '\n')
231-
var line string
232-
if i < 0 {
233-
line = s
234-
s = ""
235-
} else {
236-
line = s[:i]
237-
s = s[i+1:]
238-
}
239-
if line == "" {
197+
// The file is append-only with one JSON-encoded string per line.
198+
// Replaying each entry through addInMemory naturally deduplicates,
199+
// keeping the latest occurrence of each message.
200+
for line := range bytes.Lines(data) {
201+
line = bytes.TrimRight(line, "\n")
202+
if len(line) == 0 {
240203
continue
241204
}
242-
243-
message, err := strconv.Unquote(line)
244-
if err != nil {
205+
var msg string
206+
if err := json.Unmarshal(line, &msg); err != nil {
245207
continue
246208
}
247-
all = append(all, message)
209+
h.addInMemory(msg)
248210
}
249-
250-
// Deduplicate keeping the latest occurrence of each message.
251-
seen := make(map[string]struct{}, len(all))
252-
h.Messages = make([]string, 0, len(all))
253-
for i := len(all) - 1; i >= 0; i-- {
254-
if _, dup := seen[all[i]]; dup {
255-
continue
256-
}
257-
seen[all[i]] = struct{}{}
258-
h.Messages = append(h.Messages, all[i])
259-
}
260-
slices.Reverse(h.Messages)
261-
262211
return nil
263212
}

0 commit comments

Comments
 (0)