about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--config/bindings.go22
-rw-r--r--config/config.go206
-rw-r--r--doc/aerc-config.5.scd15
-rw-r--r--widgets/aerc.go14
4 files changed, 199 insertions, 58 deletions
diff --git a/config/bindings.go b/config/bindings.go
index eff48a6..62956d7 100644
--- a/config/bindings.go
+++ b/config/bindings.go
@@ -55,6 +55,28 @@ func MergeBindings(bindings ...*KeyBindings) *KeyBindings {
 	return merged
 }
 
+func (config AercConfig) MergeContextualBinds(baseBinds *KeyBindings,
+	contextType ContextType, reg string, bindCtx string) *KeyBindings {
+
+	bindings := baseBinds
+	for _, contextualBind := range config.ContextualBinds {
+		if contextualBind.ContextType != contextType {
+			continue
+		}
+
+		if !contextualBind.Regex.Match([]byte(reg)) {
+			continue
+		}
+
+		if contextualBind.BindContext != bindCtx {
+			continue
+		}
+
+		bindings = MergeBindings(contextualBind.Bindings, bindings)
+	}
+	return bindings
+}
+
 func (bindings *KeyBindings) Add(binding *Binding) {
 	// TODO: Search for conflicts?
 	bindings.bindings = append(bindings.bindings, binding)
diff --git a/config/config.go b/config/config.go
index cbd5860..cf0ded6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -64,6 +64,7 @@ const (
 	UI_CONTEXT_FOLDER ContextType = iota
 	UI_CONTEXT_ACCOUNT
 	UI_CONTEXT_SUBJECT
+	BIND_CONTEXT_ACCOUNT
 )
 
 type UIConfigContext struct {
@@ -109,6 +110,13 @@ type BindingConfig struct {
 	Terminal      *KeyBindings
 }
 
+type BindingConfigContext struct {
+	ContextType ContextType
+	Regex       *regexp.Regexp
+	Bindings    *KeyBindings
+	BindContext string
+}
+
 type ComposeConfig struct {
 	Editor         string     `ini:"editor"`
 	HeaderLayout   [][]string `ini:"-"`
@@ -143,17 +151,18 @@ type TemplateConfig struct {
 }
 
 type AercConfig struct {
-	Bindings      BindingConfig
-	Compose       ComposeConfig
-	Ini           *ini.File       `ini:"-"`
-	Accounts      []AccountConfig `ini:"-"`
-	Filters       []FilterConfig  `ini:"-"`
-	Viewer        ViewerConfig    `ini:"-"`
-	Triggers      TriggersConfig  `ini:"-"`
-	Ui            UIConfig
-	ContextualUis []UIConfigContext
-	General       GeneralConfig
-	Templates     TemplateConfig
+	Bindings        BindingConfig
+	ContextualBinds []BindingConfigContext
+	Compose         ComposeConfig
+	Ini             *ini.File       `ini:"-"`
+	Accounts        []AccountConfig `ini:"-"`
+	Filters         []FilterConfig  `ini:"-"`
+	Viewer          ViewerConfig    `ini:"-"`
+	Triggers        TriggersConfig  `ini:"-"`
+	Ui              UIConfig
+	ContextualUis   []UIConfigContext
+	General         GeneralConfig
+	Templates       TemplateConfig
 }
 
 // Input: TimestampFormat
@@ -357,6 +366,7 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
 			}
 		}
 	}
+
 	if ui, err := file.GetSection("ui"); err == nil {
 		if err := ui.MapTo(&config.Ui); err != nil {
 			return err
@@ -365,6 +375,7 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
 			return err
 		}
 	}
+
 	for _, sectionName := range file.SectionStrings() {
 		if !strings.Contains(sectionName, "ui:") {
 			continue
@@ -526,6 +537,9 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 			MessageView:   NewKeyBindings(),
 			Terminal:      NewKeyBindings(),
 		},
+
+		ContextualBinds: []BindingConfigContext{},
+
 		Ini: file,
 
 		Ui: UIConfig{
@@ -609,6 +623,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 	} else {
 		config.Accounts = accounts
 	}
+
 	filename = path.Join(*root, "binds.conf")
 	binds, err := ini.Load(filename)
 	if err != nil {
@@ -619,63 +634,148 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 			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,
 
+	baseGroups := map[string]**KeyBindings{
+		"default":         &config.Bindings.Global,
+		"compose":         &config.Bindings.Compose,
+		"messages":        &config.Bindings.MessageList,
+		"terminal":        &config.Bindings.Terminal,
+		"view":            &config.Bindings.MessageView,
 		"compose::editor": &config.Bindings.ComposeEditor,
 		"compose::review": &config.Bindings.ComposeReview,
 	}
-	for _, name := range binds.SectionStrings() {
-		sec, err := binds.GetSection(name)
-		if err != nil {
-			return nil, err
-		}
-		group, ok := groups[strings.ToLower(name)]
+
+	// Base Bindings
+	for _, sectionName := range binds.SectionStrings() {
+		// Handle :: delimeter
+		baseSectionName := strings.Replace(sectionName, "::", "////", -1)
+		sections := strings.Split(baseSectionName, ":")
+		baseOnly := len(sections) == 1
+		baseSectionName = strings.Replace(sections[0], "////", "::", -1)
+
+		group, ok := baseGroups[strings.ToLower(baseSectionName)]
 		if !ok {
-			return nil, errors.New("Unknown keybinding group " + name)
+			return nil, errors.New("Unknown keybinding group " + sectionName)
 		}
-		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
-				continue
-			}
-			binding, err := ParseBinding(key, value)
+
+		if baseOnly {
+			err = config.LoadBinds(binds, baseSectionName, group)
 			if err != nil {
 				return nil, err
 			}
-			bindings.Add(binding)
 		}
-		*group = MergeBindings(bindings, *group)
 	}
-	// Globals can't inherit from themselves
+
 	config.Bindings.Global.Globals = false
+	for _, contextBind := range config.ContextualBinds {
+		if contextBind.BindContext == "default" {
+			contextBind.Bindings.Globals = false
+		}
+	}
+
 	return config, nil
 }
 
+func LoadBindingSection(sec *ini.Section) (*KeyBindings, error) {
+	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("Invalid binding")
+			}
+			bindings.ExKey = strokes[0]
+			continue
+		}
+		if key == "$noinherit" {
+			if value == "false" {
+				continue
+			}
+			if value != "true" {
+				return nil, errors.New("Invalid binding")
+			}
+			bindings.Globals = false
+			continue
+		}
+		binding, err := ParseBinding(key, value)
+		if err != nil {
+			return nil, err
+		}
+		bindings.Add(binding)
+	}
+	return bindings, nil
+}
+
+func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup **KeyBindings) error {
+
+	if sec, err := binds.GetSection(baseName); err == nil {
+		binds, err := LoadBindingSection(sec)
+		if err != nil {
+			return err
+		}
+		*baseGroup = MergeBindings(binds, *baseGroup)
+	}
+
+	for _, sectionName := range binds.SectionStrings() {
+		if !strings.Contains(sectionName, baseName+":") ||
+			strings.Contains(sectionName, baseName+"::") {
+			continue
+		}
+
+		bindSection, err := binds.GetSection(sectionName)
+		if err != nil {
+			return err
+		}
+
+		binds, err := LoadBindingSection(bindSection)
+		if err != nil {
+			return err
+		}
+
+		contextualBind :=
+			BindingConfigContext{
+				Bindings:    binds,
+				BindContext: baseName,
+			}
+
+		var index int
+		if strings.Contains(sectionName, "=") {
+			index = strings.Index(sectionName, "=")
+			value := string(sectionName[index+1:])
+			contextualBind.Regex, err = regexp.Compile(value)
+			if err != nil {
+				return err
+			}
+		} else {
+			return fmt.Errorf("Invalid Bind Context regex in %s", sectionName)
+		}
+
+		switch sectionName[len(baseName)+1 : index] {
+		case "account":
+            acctName := sectionName[index+1:]
+            valid := false
+            for _, acctConf := range config.Accounts {
+                matches := contextualBind.Regex.FindString(acctConf.Name)
+                if matches != "" {
+                    valid = true
+                }
+            }
+            if !valid {
+                return fmt.Errorf("Invalid Account Name: %s", acctName)
+            }
+			contextualBind.ContextType = BIND_CONTEXT_ACCOUNT
+		default:
+			return fmt.Errorf("Unknown Context Bind Section: %s", sectionName)
+		}
+		config.ContextualBinds = append(config.ContextualBinds, contextualBind)
+	}
+
+	return nil
+}
+
 // checkConfigPerms checks for too open permissions
 // printing the fix on stdout and returning an error
 func checkConfigPerms(filename string) error {
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index e95a86c..ae03074 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -528,6 +528,21 @@ are:
 *[terminal]*
 	keybindings for terminal tabs
 
+You may also configure account specific key bindings for each context:
+
+*[context:account=<AccountName>]*
+	keybindings for this context and account, where <AccountName> matches
+	the account name you provided in *accounts.conf*.
+
+Example:
+```
+[messages:account=Mailbox]
+c = :cf path:mailbox/** and<space>
+
+[compose::editor:account=Mailbox2]
+...
+```
+
 You may also configure global keybindings by placing them at the beginning of
 the file, before specifying any context-specific sections. For each *key=value*
 option specified, the _key_ is the keystrokes pressed (in order) to invoke this
diff --git a/widgets/aerc.go b/widgets/aerc.go
index cbde56c..b84dd87 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -182,22 +182,26 @@ func (aerc *Aerc) Draw(ctx *ui.Context) {
 }
 
 func (aerc *Aerc) getBindings() *config.KeyBindings {
+	selectedAccountName := ""
+	if aerc.SelectedAccount() != nil {
+		selectedAccountName = aerc.SelectedAccount().acct.Name
+	}
 	switch view := aerc.SelectedTab().(type) {
 	case *AccountView:
-		return aerc.conf.Bindings.MessageList
+		return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageList, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "messages")
 	case *AccountWizard:
 		return aerc.conf.Bindings.AccountWizard
 	case *Composer:
 		switch view.Bindings() {
 		case "compose::editor":
-			return aerc.conf.Bindings.ComposeEditor
+			return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeEditor, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::editor")
 		case "compose::review":
-			return aerc.conf.Bindings.ComposeReview
+			return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeReview, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::review")
 		default:
-			return aerc.conf.Bindings.Compose
+			return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.Compose, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose")
 		}
 	case *MessageViewer:
-		return aerc.conf.Bindings.MessageView
+		return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageView, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view")
 	case *Terminal:
 		return aerc.conf.Bindings.Terminal
 	default: