diff options
| author | Galen Abell <galen@galenabell.com> | 2019-07-23 12:52:33 -0400 | 
|---|---|---|
| committer | Drew DeVault <sir@cmpwn.com> | 2019-07-26 14:29:34 -0400 | 
| commit | 8635c70fda20b91f97c42f4e23e97bc01a14a89d (patch) | |
| tree | ea70a40f7617782ca28060965ad253fa0e686161 | |
| parent | 67fb0938a66605a0b6a837005804637b348b250d (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.go | 2 | ||||
| -rw-r--r-- | commands/history.go | 62 | ||||
| -rw-r--r-- | doc/aerc.1.scd | 6 | ||||
| -rw-r--r-- | lib/history.go | 13 | ||||
| -rw-r--r-- | widgets/aerc.go | 13 | ||||
| -rw-r--r-- | widgets/compose.go | 9 | ||||
| -rw-r--r-- | widgets/exline.go | 16 | 
7 files changed, 113 insertions, 8 deletions
| @@ -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()) | 
