aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--commands/history.go77
2 files changed, 78 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ac429dd..7454b2a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for bindings with the Alt modifier.
- Zoxide support with `:z`.
- Hide local timezone with `send-as-utc = true` in `accounts.conf`.
+- Persistent command history in `~/.cache/aerc/history`.
### Changed
diff --git a/commands/history.go b/commands/history.go
index 1c0a1ec8..5cc0bfb3 100644
--- a/commands/history.go
+++ b/commands/history.go
@@ -1,5 +1,18 @@
package commands
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "sync"
+
+ "git.sr.ht/~rjarry/aerc/logging"
+ "github.com/kyoh86/xdg"
+)
+
type cmdHistory struct {
// rolling buffer of prior commands
//
@@ -9,6 +22,10 @@ type cmdHistory struct {
// current placement in list
current int
+
+ // initialize history storage
+ initHistfile sync.Once
+ histfile io.ReadWriter
}
// number of commands to keep in history
@@ -18,6 +35,8 @@ const cmdLimit = 1000
var CmdHistory = cmdHistory{}
func (h *cmdHistory) Add(cmd string) {
+ h.initHistfile.Do(h.initialize)
+
// if we're at cap, cut off the first element
if len(h.cmdList) >= cmdLimit {
h.cmdList = h.cmdList[1:]
@@ -25,6 +44,8 @@ func (h *cmdHistory) Add(cmd string) {
if len(h.cmdList) == 0 || h.cmdList[len(h.cmdList)-1] != cmd {
h.cmdList = append(h.cmdList, cmd)
+
+ h.writeHistory()
}
// whenever we add a new command, reset the current
@@ -36,6 +57,8 @@ func (h *cmdHistory) Add(cmd string) {
// Since the list is reverse-order, this will return elements
// increasingly towards index 0.
func (h *cmdHistory) Prev() string {
+ h.initHistfile.Do(h.initialize)
+
if h.current <= 0 || len(h.cmdList) == 0 {
h.current = -1
return "(Already at beginning)"
@@ -49,6 +72,8 @@ func (h *cmdHistory) Prev() string {
// Since the list is reverse-order, this will return elements
// increasingly towards index len(cmdList).
func (h *cmdHistory) Next() string {
+ h.initHistfile.Do(h.initialize)
+
if h.current >= len(h.cmdList)-1 || len(h.cmdList) == 0 {
h.current = len(h.cmdList)
return "(Already at end)"
@@ -62,3 +87,55 @@ func (h *cmdHistory) Next() string {
func (h *cmdHistory) Reset() {
h.current = len(h.cmdList)
}
+
+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
+ }
+}