aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGalen Abell <galen@galenabell.com>2019-07-23 12:52:33 -0400
committerDrew DeVault <sir@cmpwn.com>2019-07-26 14:29:34 -0400
commit8635c70fda20b91f97c42f4e23e97bc01a14a89d (patch)
treeea70a40f7617782ca28060965ad253fa0e686161
parent67fb0938a66605a0b6a837005804637b348b250d (diff)
Add command history and cycling
Aerc will keep track of the previous 1000 commands, which the user can cycle through using the arrow keys while in the ex-line. Pressing up will move backwards in history while pressing down will move forward.
-rw-r--r--aerc.go2
-rw-r--r--commands/history.go62
-rw-r--r--doc/aerc.1.scd6
-rw-r--r--lib/history.go13
-rw-r--r--widgets/aerc.go13
-rw-r--r--widgets/compose.go9
-rw-r--r--widgets/exline.go16
7 files changed, 113 insertions, 8 deletions
diff --git a/aerc.go b/aerc.go
index 2420b44..033de7b 100644
--- a/aerc.go
+++ b/aerc.go
@@ -148,7 +148,7 @@ func main() {
return execCommand(aerc, ui, cmd)
}, func(cmd string) []string {
return getCompletions(aerc, cmd)
- })
+ }, &commands.CmdHistory)
ui, err = libui.Initialize(conf, aerc)
if err != nil {
diff --git a/commands/history.go b/commands/history.go
new file mode 100644
index 0000000..77bb155
--- /dev/null
+++ b/commands/history.go
@@ -0,0 +1,62 @@
+package commands
+
+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
+}
+
+// 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) {
+ // if we're at cap, cut off the first element
+ if len(h.cmdList) >= cmdLimit {
+ h.cmdList = h.cmdList[1:]
+ }
+
+ h.cmdList = append(h.cmdList, cmd)
+
+ // 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 {
+ 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 {
+ 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)
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 0c5d7f5..44fb020 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -25,6 +25,10 @@ as the terminal emulator, '<c-x>' is used to bring up the command interface.
Different commands work in different contexts, depending on the kind of tab you
have selected.
+Aerc stores a history of commands, which can be cycled through in command mode.
+Pressing the up key cycles backwards in history, while pressing down cycles
+forwards.
+
## GLOBAL COMMANDS
These commands work in any context.
@@ -113,7 +117,7 @@ message list, the message in the message viewer, etc).
*unread*
Marks the selected message as unread.
-
+
*-t*: Toggle the selected message between read and unread.
*unsubscribe*
diff --git a/lib/history.go b/lib/history.go
new file mode 100644
index 0000000..abc081f
--- /dev/null
+++ b/lib/history.go
@@ -0,0 +1,13 @@
+package lib
+
+// History represents a list of elements ordered by time.
+type History interface {
+ // Add a new element to the history
+ Add(string)
+ // Get the next element in history
+ Next() string
+ // Get the previous element in history
+ Prev() string
+ // Reset the current location in history
+ Reset()
+}
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 050ba77..458c2f9 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -11,6 +11,7 @@ import (
"github.com/google/shlex"
"git.sr.ht/~sircmpwn/aerc/config"
+ "git.sr.ht/~sircmpwn/aerc/lib"
"git.sr.ht/~sircmpwn/aerc/lib/ui"
libui "git.sr.ht/~sircmpwn/aerc/lib/ui"
)
@@ -18,6 +19,7 @@ import (
type Aerc struct {
accounts map[string]*AccountView
cmd func(cmd []string) error
+ cmdHistory lib.History
complete func(cmd string) []string
conf *config.AercConfig
focused libui.Interactive
@@ -31,7 +33,8 @@ type Aerc struct {
}
func NewAerc(conf *config.AercConfig, logger *log.Logger,
- cmd func(cmd []string) error, complete func(cmd string) []string) *Aerc {
+ cmd func(cmd []string) error, complete func(cmd string) []string,
+ cmdHistory lib.History) *Aerc {
tabs := libui.NewTabs()
@@ -54,6 +57,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
accounts: make(map[string]*AccountView),
conf: conf,
cmd: cmd,
+ cmdHistory: cmdHistory,
complete: complete,
grid: grid,
logger: logger,
@@ -323,6 +327,11 @@ func (aerc *Aerc) BeginExCommand() {
aerc.PushStatus(" "+err.Error(), 10*time.Second).
Color(tcell.ColorDefault, tcell.ColorRed)
}
+ // only add to history if this is an unsimulated command,
+ // ie one not executed from a keybinding
+ if aerc.simulating == 0 {
+ aerc.cmdHistory.Add(cmd)
+ }
aerc.statusbar.Pop()
aerc.focus(previous)
}, func() {
@@ -330,7 +339,7 @@ func (aerc *Aerc) BeginExCommand() {
aerc.focus(previous)
}, func(cmd string) []string {
return aerc.complete(cmd)
- })
+ }, aerc.cmdHistory)
aerc.statusbar.Push(exline)
aerc.focus(exline)
}
diff --git a/widgets/compose.go b/widgets/compose.go
index b45892f..4f6f7a1 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -51,7 +51,8 @@ func NewComposer(conf *config.AercConfig,
defaults["From"] = acct.From
}
- layout, editors, focusable := buildComposeHeader(conf.Compose.HeaderLayout, defaults)
+ layout, editors, focusable := buildComposeHeader(
+ conf.Compose.HeaderLayout, defaults)
header, headerHeight := layout.grid(
func(header string) ui.Drawable { return editors[header] },
@@ -90,7 +91,11 @@ func NewComposer(conf *config.AercConfig,
return c
}
-func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (newLayout HeaderLayout, editors map[string]*headerEditor, focusable []ui.DrawableInteractive) {
+func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (
+ newLayout HeaderLayout,
+ editors map[string]*headerEditor,
+ focusable []ui.DrawableInteractive,
+) {
editors = make(map[string]*headerEditor)
focusable = make([]ui.DrawableInteractive, 0)
diff --git a/widgets/exline.go b/widgets/exline.go
index e984ee1..b7b4e3d 100644
--- a/widgets/exline.go
+++ b/widgets/exline.go
@@ -3,6 +3,7 @@ package widgets
import (
"github.com/gdamore/tcell"
+ "git.sr.ht/~sircmpwn/aerc/lib"
"git.sr.ht/~sircmpwn/aerc/lib/ui"
)
@@ -11,17 +12,20 @@ type ExLine struct {
cancel func()
commit func(cmd string)
tabcomplete func(cmd string) []string
+ cmdHistory lib.History
input *ui.TextInput
}
func NewExLine(commit func(cmd string), cancel func(),
- tabcomplete func(cmd string) []string) *ExLine {
+ tabcomplete func(cmd string) []string,
+ cmdHistory lib.History) *ExLine {
input := ui.NewTextInput("").Prompt(":")
exline := &ExLine{
cancel: cancel,
commit: commit,
tabcomplete: tabcomplete,
+ cmdHistory: cmdHistory,
input: input,
}
input.OnInvalidate(func(d ui.Drawable) {
@@ -47,10 +51,18 @@ func (ex *ExLine) Event(event tcell.Event) bool {
case *tcell.EventKey:
switch event.Key() {
case tcell.KeyEnter:
+ cmd := ex.input.String()
ex.input.Focus(false)
- ex.commit(ex.input.String())
+ ex.commit(cmd)
+ case tcell.KeyUp:
+ ex.input.Set(ex.cmdHistory.Prev())
+ ex.Invalidate()
+ case tcell.KeyDown:
+ ex.input.Set(ex.cmdHistory.Next())
+ ex.Invalidate()
case tcell.KeyEsc, tcell.KeyCtrlC:
ex.input.Focus(false)
+ ex.cmdHistory.Reset()
ex.cancel()
case tcell.KeyTab:
complete := ex.tabcomplete(ex.input.StringLeft())