Changes from v5:
- Fixed global binding inheritance bug
---
config/bindings.go | 22 +++++
config/config.go | 195 ++++++++++++++++++++++++++++++------------
doc/aerc-config.5.scd | 15 ++++
widgets/aerc.go | 14 +--
4 files changed, 188 insertions(+), 58 deletions(-)
diff --git a/config/bindings.go b/config/bindings.go
index 9956b41..23a082e 100644
--- a/config/bindings.go
+++ b/config/bindings.go
@@ -54,6 +54,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(bindings, contextualBind.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 af9c63b..8838f40 100644
--- a/config/config.go
+++ b/config/config.go
@@ -56,6 +56,7 @@ const (
UI_CONTEXT_FOLDER ContextType = iota
UI_CONTEXT_ACCOUNT
UI_CONTEXT_SUBJECT
+ BIND_CONTEXT_ACCOUNT
)
type UIConfigContext struct {
@@ -100,6 +101,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:"-"`
@@ -134,17 +142,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
@@ -345,11 +354,13 @@ 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
}
}
+
for _, sectionName := range file.SectionStrings() {
if !strings.Contains(sectionName, "ui:") {
continue
@@ -488,6 +499,9 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
MessageView: NewKeyBindings(),
Terminal: NewKeyBindings(),
},
+
+ ContextualBinds: []BindingConfigContext{},
+
Ini: file,
Ui: UIConfig{
@@ -565,6 +579,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 {
@@ -575,63 +590,137 @@ 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(regexp.QuoteMeta(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":
+ 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 d4de883..fb032cb 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -487,6 +487,21 @@ are:
*[terminal]*
keybindings for terminal tabs
+You may also configure account specific key bindings for each context:
+
+*[context:account=regex]*
+ keybindings for this context and account, where *regex* matches
+ the account name 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 6df0c95..5661260 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:
--
2.31.1