2023-10-25 23:41:18 +00:00
|
|
|
package readline
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/emirpasic/gods/lists/arraylist"
|
|
|
|
)
|
|
|
|
|
|
|
|
type History struct {
|
|
|
|
Buf *arraylist.List
|
|
|
|
Autosave bool
|
|
|
|
Pos int
|
|
|
|
Limit int
|
|
|
|
Filename string
|
|
|
|
Enabled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewHistory() (*History, error) {
|
|
|
|
h := &History{
|
|
|
|
Buf: arraylist.New(),
|
2023-12-15 22:07:34 +00:00
|
|
|
Limit: 100, // resizeme
|
2023-10-25 23:41:18 +00:00
|
|
|
Autosave: true,
|
|
|
|
Enabled: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := h.Init()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Init() error {
|
|
|
|
home, err := os.UserHomeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(home, ".ollama", "history")
|
2023-11-26 20:59:04 +00:00
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-25 23:41:18 +00:00
|
|
|
h.Filename = path
|
|
|
|
|
2023-12-15 22:25:12 +00:00
|
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0o600)
|
2023-10-25 23:41:18 +00:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
r := bufio.NewReader(f)
|
|
|
|
for {
|
|
|
|
line, err := r.ReadString('\n')
|
|
|
|
if err != nil {
|
2024-03-15 14:14:12 +00:00
|
|
|
if errors.Is(err, io.EOF) {
|
2023-10-25 23:41:18 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
if len(line) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
h.Add([]rune(line))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Add(l []rune) {
|
|
|
|
h.Buf.Add(l)
|
|
|
|
h.Compact()
|
2023-10-28 03:38:03 +00:00
|
|
|
h.Pos = h.Size()
|
2023-10-25 23:41:18 +00:00
|
|
|
if h.Autosave {
|
2023-12-15 22:07:34 +00:00
|
|
|
_ = h.Save()
|
2023-10-25 23:41:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Compact() {
|
|
|
|
s := h.Buf.Size()
|
|
|
|
if s > h.Limit {
|
2024-05-22 05:21:04 +00:00
|
|
|
for range s - h.Limit {
|
2023-10-25 23:41:18 +00:00
|
|
|
h.Buf.Remove(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Clear() {
|
|
|
|
h.Buf.Clear()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Prev() []rune {
|
|
|
|
var line []rune
|
|
|
|
if h.Pos > 0 {
|
|
|
|
h.Pos -= 1
|
|
|
|
}
|
|
|
|
v, _ := h.Buf.Get(h.Pos)
|
|
|
|
line, _ = v.([]rune)
|
|
|
|
return line
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Next() []rune {
|
|
|
|
var line []rune
|
|
|
|
if h.Pos < h.Buf.Size() {
|
|
|
|
h.Pos += 1
|
|
|
|
v, _ := h.Buf.Get(h.Pos)
|
|
|
|
line, _ = v.([]rune)
|
|
|
|
}
|
|
|
|
return line
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Size() int {
|
|
|
|
return h.Buf.Size()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *History) Save() error {
|
|
|
|
if !h.Enabled {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpFile := h.Filename + ".tmp"
|
|
|
|
|
2023-12-18 18:53:51 +00:00
|
|
|
f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0o600)
|
2023-10-25 23:41:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
buf := bufio.NewWriter(f)
|
2024-05-22 05:21:04 +00:00
|
|
|
for cnt := range h.Size() {
|
2023-10-25 23:41:18 +00:00
|
|
|
v, _ := h.Buf.Get(cnt)
|
|
|
|
line, _ := v.([]rune)
|
2024-03-29 01:54:01 +00:00
|
|
|
if _, err := buf.WriteString(string(line) + "\n"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-10-25 23:41:18 +00:00
|
|
|
}
|
|
|
|
buf.Flush()
|
|
|
|
f.Close()
|
|
|
|
|
|
|
|
if err = os.Rename(tmpFile, h.Filename); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|