ollama
1package readline
2
3import (
4"bufio"
5"errors"
6"io"
7"os"
8"path/filepath"
9"strings"
10
11"github.com/emirpasic/gods/lists/arraylist"
12)
13
14type History struct {
15Buf *arraylist.List
16Autosave bool
17Pos int
18Limit int
19Filename string
20Enabled bool
21}
22
23func NewHistory() (*History, error) {
24h := &History{
25Buf: arraylist.New(),
26Limit: 100, // resizeme
27Autosave: true,
28Enabled: true,
29}
30
31err := h.Init()
32if err != nil {
33return nil, err
34}
35
36return h, nil
37}
38
39func (h *History) Init() error {
40home, err := os.UserHomeDir()
41if err != nil {
42return err
43}
44
45path := filepath.Join(home, ".ollama", "history")
46if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
47return err
48}
49
50h.Filename = path
51
52f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0o600)
53if err != nil {
54if errors.Is(err, os.ErrNotExist) {
55return nil
56}
57return err
58}
59defer f.Close()
60
61r := bufio.NewReader(f)
62for {
63line, err := r.ReadString('\n')
64if err != nil {
65if err == io.EOF {
66break
67}
68return err
69}
70
71line = strings.TrimSpace(line)
72if len(line) == 0 {
73continue
74}
75
76h.Add([]rune(line))
77}
78
79return nil
80}
81
82func (h *History) Add(l []rune) {
83h.Buf.Add(l)
84h.Compact()
85h.Pos = h.Size()
86if h.Autosave {
87_ = h.Save()
88}
89}
90
91func (h *History) Compact() {
92s := h.Buf.Size()
93if s > h.Limit {
94for cnt := 0; cnt < s-h.Limit; cnt++ {
95h.Buf.Remove(0)
96}
97}
98}
99
100func (h *History) Clear() {
101h.Buf.Clear()
102}
103
104func (h *History) Prev() []rune {
105var line []rune
106if h.Pos > 0 {
107h.Pos -= 1
108}
109v, _ := h.Buf.Get(h.Pos)
110line, _ = v.([]rune)
111return line
112}
113
114func (h *History) Next() []rune {
115var line []rune
116if h.Pos < h.Buf.Size() {
117h.Pos += 1
118v, _ := h.Buf.Get(h.Pos)
119line, _ = v.([]rune)
120}
121return line
122}
123
124func (h *History) Size() int {
125return h.Buf.Size()
126}
127
128func (h *History) Save() error {
129if !h.Enabled {
130return nil
131}
132
133tmpFile := h.Filename + ".tmp"
134
135f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0o600)
136if err != nil {
137return err
138}
139defer f.Close()
140
141buf := bufio.NewWriter(f)
142for cnt := 0; cnt < h.Size(); cnt++ {
143v, _ := h.Buf.Get(cnt)
144line, _ := v.([]rune)
145buf.WriteString(string(line) + "\n")
146}
147buf.Flush()
148f.Close()
149
150if err = os.Rename(tmpFile, h.Filename); err != nil {
151return err
152}
153
154return nil
155}
156