aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2019-03-21 17:36:42 -0400
committerDrew DeVault <sir@cmpwn.com>2019-03-21 17:37:19 -0400
commitf5bf4a93243c62b5b30ed0f1d15c124739444c79 (patch)
tree68edf4ecdd4d979ead71f712860d2bd2101c53c2
parent79b459ecb0da7759de617d164cb1cafc4a6be1c8 (diff)
Add context-specific keybindings
-rw-r--r--config/bindings.go10
-rw-r--r--config/binds.conf32
-rw-r--r--config/config.go84
-rw-r--r--widgets/aerc.go46
4 files changed, 152 insertions, 20 deletions
diff --git a/config/bindings.go b/config/bindings.go
index 1882f74..0032d72 100644
--- 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
new file mode 100644
index 0000000..814f9f5
--- /dev/null
+++ 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
index 537626f..7aff1ea 100644
--- 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
index af2d0df..8d456b1 100644
--- 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()