about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-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()