aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

commit f5bf4a93243c62b5b30ed0f1d15c124739444c79
parent 79b459ecb0da7759de617d164cb1cafc4a6be1c8
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 21 Mar 2019 17:36:42 -0400

Add context-specific keybindings

Diffstat:
Mconfig/bindings.go | 10++++++++++
Aconfig/binds.conf | 32++++++++++++++++++++++++++++++++
Mconfig/config.go | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mwidgets/aerc.go | 46++++++++++++++++++++++++++++++++++++++--------
4 files changed, 152 insertions(+), 20 deletions(-)

diff --git a/config/bindings.go b/config/bindings.go @@ -44,6 +44,16 @@ func NewKeyBindings() *KeyBindings { } } +func MergeBindings(bindings ...*KeyBindings) *KeyBindings { + merged := NewKeyBindings() + for _, b := range bindings { + merged.bindings = append(merged.bindings, b.bindings...) + } + merged.ExKey = bindings[0].ExKey + merged.Globals = bindings[0].Globals + return merged +} + func (bindings *KeyBindings) Add(binding *Binding) { // TODO: Search for conflicts? bindings.bindings = append(bindings.bindings, binding) diff --git a/config/binds.conf b/config/binds.conf @@ -0,0 +1,32 @@ +# Binds are of the form <key sequence> = <command to run> +# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>" +# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit +q = :quit<Enter> +L = :next-tab<Enter> +H = :prev-tab<Enter> +<C-t> = :term<Enter> + +[messages] +j = :next-message<Enter> +<Down> = :next-message<Enter> +<C-d> = :next-message 50%<Enter> +<C-f> = :next-message 100%<Enter> +<PgDn> = :next-message -s 100%<Enter> + +k = :prev-message<Enter> +<Up> = :prev-message<Enter> +<C-u> = :prev-message 50%<Enter> +<C-b> = :prev-message 100%<Enter> +<PgUp> = :prev-message -s 100%<Enter> +g = :select-message 0<Enter> +G = :select-message -1<Enter> + +J = :next-folder<Enter> +K = :prev-folder<Enter> + +<Enter> = :view-message<Enter> +d = :confirm 'Really delete this message?' ':delete-message<Enter>'<Enter> +D = :delete-message<Enter> + +c = :cf<space> +$ = :term<space> diff --git a/config/config.go b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "path" "strings" @@ -29,8 +30,16 @@ type AccountConfig struct { Params map[string]string } +type BindingConfig struct { + Global *KeyBindings + Compose *KeyBindings + MessageList *KeyBindings + MessageView *KeyBindings + Terminal *KeyBindings +} + type AercConfig struct { - Lbinds *KeyBindings + Bindings BindingConfig Ini *ini.File `ini:"-"` Accounts []AccountConfig `ini:"-"` Ui UIConfig @@ -98,8 +107,14 @@ func LoadConfig(root *string) (*AercConfig, error) { } file.NameMapper = mapName config := &AercConfig{ - Lbinds: NewKeyBindings(), - Ini: file, + Bindings: BindingConfig{ + Global: NewKeyBindings(), + Compose: NewKeyBindings(), + MessageList: NewKeyBindings(), + MessageView: NewKeyBindings(), + Terminal: NewKeyBindings(), + }, + Ini: file, Ui: UIConfig{ IndexFormat: "%4C %Z %D %-17.17n %s", @@ -121,20 +136,65 @@ func LoadConfig(root *string) (*AercConfig, error) { return nil, err } } - if lbinds, err := file.GetSection("lbinds"); err == nil { - for key, value := range lbinds.KeysHash() { - binding, err := ParseBinding(key, value) - if err != nil { - return nil, err - } - config.Lbinds.Add(binding) - } - } accountsPath := path.Join(*root, "accounts.conf") if accounts, err := loadAccountConfig(accountsPath); err != nil { return nil, err } else { config.Accounts = accounts } + binds, err := ini.Load(path.Join(*root, "binds.conf")) + if err != nil { + return nil, err + } + groups := map[string]**KeyBindings{ + "default": &config.Bindings.Global, + "compose": &config.Bindings.Compose, + "messages": &config.Bindings.MessageList, + "terminal": &config.Bindings.Terminal, + "view": &config.Bindings.MessageView, + } + for _, name := range binds.SectionStrings() { + sec, err := binds.GetSection(name) + if err != nil { + return nil, err + } + group, ok := groups[strings.ToLower(name)] + if !ok { + return nil, errors.New("Unknown keybinding group " + name) + } + bindings := NewKeyBindings() + for key, value := range sec.KeysHash() { + if key == "$ex" { + strokes, err := ParseKeyStrokes(value) + if err != nil { + return nil, err + } + if len(strokes) != 1 { + return nil, errors.New( + "Error: only one keystroke supported for $ex") + } + bindings.ExKey = strokes[0] + continue + } + if key == "$noinherit" { + if value == "false" { + continue + } + if value != "true" { + return nil, errors.New( + "Error: expected 'true' or 'false' for $noinherit") + } + bindings.Globals = false + } + binding, err := ParseBinding(key, value) + if err != nil { + return nil, err + } + bindings.Add(binding) + } + *group = MergeBindings(bindings, *group) + } + // Globals can't inherit from themselves + config.Bindings.Global.Globals = false return config, nil } diff --git a/widgets/aerc.go b/widgets/aerc.go @@ -94,6 +94,24 @@ func (aerc *Aerc) Draw(ctx *libui.Context) { aerc.grid.Draw(ctx) } +func (aerc *Aerc) getBindings() *config.KeyBindings { + switch aerc.SelectedTab().(type) { + case *AccountView: + return aerc.conf.Bindings.MessageList + default: + return aerc.conf.Bindings.Global + } +} + +func (aerc *Aerc) simulate(strokes []config.KeyStroke) { + aerc.pendingKeys = []config.KeyStroke{} + for _, stroke := range strokes { + simulated := tcell.NewEventKey( + stroke.Key, stroke.Rune, tcell.ModNone) + aerc.Event(simulated) + } +} + func (aerc *Aerc) Event(event tcell.Event) bool { if aerc.focused != nil { return aerc.focused.Event(event) @@ -105,18 +123,30 @@ func (aerc *Aerc) Event(event tcell.Event) bool { Key: event.Key(), Rune: event.Rune(), }) - result, output := aerc.conf.Lbinds.GetBinding(aerc.pendingKeys) + bindings := aerc.getBindings() + incomplete := false + result, strokes := bindings.GetBinding(aerc.pendingKeys) switch result { case config.BINDING_FOUND: - aerc.pendingKeys = []config.KeyStroke{} - for _, stroke := range output { - simulated := tcell.NewEventKey( - stroke.Key, stroke.Rune, tcell.ModNone) - aerc.Event(simulated) - } + aerc.simulate(strokes) + return true case config.BINDING_INCOMPLETE: - return false + incomplete = true case config.BINDING_NOT_FOUND: + } + if bindings.Globals { + result, strokes = aerc.conf.Bindings.Global. + GetBinding(aerc.pendingKeys) + switch result { + case config.BINDING_FOUND: + aerc.simulate(strokes) + return true + case config.BINDING_INCOMPLETE: + incomplete = true + case config.BINDING_NOT_FOUND: + } + } + if !incomplete { aerc.pendingKeys = []config.KeyStroke{} if event.Rune() == ':' { aerc.BeginExCommand()