2019-07-23 18:52:33 +02:00
|
|
|
package commands
|
|
|
|
|
2022-09-14 18:34:49 +02:00
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
|
|
|
"github.com/kyoh86/xdg"
|
|
|
|
)
|
|
|
|
|
2019-07-23 18:52:33 +02:00
|
|
|
type cmdHistory struct {
|
|
|
|
// rolling buffer of prior commands
|
|
|
|
//
|
|
|
|
// most recent command is at the end of the list,
|
|
|
|
// least recent is index 0
|
|
|
|
cmdList []string
|
|
|
|
|
|
|
|
// current placement in list
|
|
|
|
current int
|
2022-09-14 18:34:49 +02:00
|
|
|
|
|
|
|
// initialize history storage
|
|
|
|
initHistfile sync.Once
|
|
|
|
histfile io.ReadWriter
|
2019-07-23 18:52:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of commands to keep in history
|
|
|
|
const cmdLimit = 1000
|
|
|
|
|
|
|
|
// CmdHistory is the history of executed commands
|
|
|
|
var CmdHistory = cmdHistory{}
|
|
|
|
|
|
|
|
func (h *cmdHistory) Add(cmd string) {
|
2022-09-14 18:34:49 +02:00
|
|
|
h.initHistfile.Do(h.initialize)
|
|
|
|
|
2019-07-23 18:52:33 +02:00
|
|
|
// if we're at cap, cut off the first element
|
|
|
|
if len(h.cmdList) >= cmdLimit {
|
|
|
|
h.cmdList = h.cmdList[1:]
|
|
|
|
}
|
|
|
|
|
2022-09-14 18:34:48 +02:00
|
|
|
if len(h.cmdList) == 0 || h.cmdList[len(h.cmdList)-1] != cmd {
|
|
|
|
h.cmdList = append(h.cmdList, cmd)
|
2022-09-14 18:34:49 +02:00
|
|
|
|
|
|
|
h.writeHistory()
|
2022-09-14 18:34:48 +02:00
|
|
|
}
|
2019-07-23 18:52:33 +02:00
|
|
|
|
|
|
|
// whenever we add a new command, reset the current
|
|
|
|
// pointer to the "beginning" of the list
|
|
|
|
h.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prev returns the previous command in history.
|
|
|
|
// Since the list is reverse-order, this will return elements
|
|
|
|
// increasingly towards index 0.
|
|
|
|
func (h *cmdHistory) Prev() string {
|
2022-09-14 18:34:49 +02:00
|
|
|
h.initHistfile.Do(h.initialize)
|
|
|
|
|
2019-07-23 18:52:33 +02:00
|
|
|
if h.current <= 0 || len(h.cmdList) == 0 {
|
|
|
|
h.current = -1
|
|
|
|
return "(Already at beginning)"
|
|
|
|
}
|
|
|
|
h.current--
|
|
|
|
|
|
|
|
return h.cmdList[h.current]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next returns the next command in history.
|
|
|
|
// Since the list is reverse-order, this will return elements
|
|
|
|
// increasingly towards index len(cmdList).
|
|
|
|
func (h *cmdHistory) Next() string {
|
2022-09-14 18:34:49 +02:00
|
|
|
h.initHistfile.Do(h.initialize)
|
|
|
|
|
2019-07-23 18:52:33 +02:00
|
|
|
if h.current >= len(h.cmdList)-1 || len(h.cmdList) == 0 {
|
|
|
|
h.current = len(h.cmdList)
|
|
|
|
return "(Already at end)"
|
|
|
|
}
|
|
|
|
h.current++
|
|
|
|
|
|
|
|
return h.cmdList[h.current]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the current pointer to the beginning of history.
|
|
|
|
func (h *cmdHistory) Reset() {
|
|
|
|
h.current = len(h.cmdList)
|
|
|
|
}
|
2022-09-14 18:34:49 +02:00
|
|
|
|
|
|
|
func (h *cmdHistory) initialize() {
|
|
|
|
var err error
|
|
|
|
openFlags := os.O_RDWR | os.O_EXCL
|
|
|
|
|
|
|
|
histPath := path.Join(xdg.CacheHome(), "aerc", "history")
|
|
|
|
if _, err := os.Stat(histPath); os.IsNotExist(err) {
|
|
|
|
_ = os.MkdirAll(path.Join(xdg.CacheHome(), "aerc"), 0o700) // caught by OpenFile
|
|
|
|
openFlags |= os.O_CREATE
|
|
|
|
}
|
|
|
|
|
|
|
|
// O_EXCL to make sure that only one aerc writes to the file
|
|
|
|
h.histfile, err = os.OpenFile(
|
|
|
|
histPath,
|
|
|
|
openFlags,
|
|
|
|
0o600,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
logging.Errorf("failed to open history file: %v", err)
|
|
|
|
// basically mirror the old behavior
|
|
|
|
h.histfile = bytes.NewBuffer([]byte{})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s := bufio.NewScanner(h.histfile)
|
|
|
|
|
|
|
|
for s.Scan() {
|
|
|
|
h.cmdList = append(h.cmdList, s.Text())
|
|
|
|
}
|
|
|
|
|
|
|
|
h.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *cmdHistory) writeHistory() {
|
|
|
|
if fh, ok := h.histfile.(*os.File); ok {
|
|
|
|
err := fh.Truncate(0)
|
|
|
|
if err != nil {
|
|
|
|
// if we can't delete it, don't break it.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = fh.Seek(0, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
// if we can't delete it, don't break it.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, entry := range h.cmdList {
|
|
|
|
fmt.Fprintln(fh, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
fh.Sync() //nolint:errcheck // if your computer can't sync you're in bigger trouble
|
|
|
|
}
|
|
|
|
}
|