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.
This commit is contained in:
Galen Abell 2019-07-23 12:52:33 -04:00 committed by Drew DeVault
parent 67fb0938a6
commit 8635c70fda
7 changed files with 113 additions and 8 deletions

View file

@ -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 {

62
commands/history.go Normal file
View file

@ -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)
}

View file

@ -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*

13
lib/history.go Normal file
View file

@ -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()
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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())