Jonathan Bartlett: 1 Account Specific Bindings 4 files changed, 173 insertions(+), 56 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/22747/mbox | git am -3Learn more about email & git
--- config/bindings.go | 22 ++++++ config/config.go | 178 ++++++++++++++++++++++++++++++------------ doc/aerc-config.5.scd | 15 ++++ widgets/aerc.go | 14 ++-- 4 files changed, 173 insertions(+), 56 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..cfc1757 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,61 +590,122 @@ 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) + + // Base Bindings + for name, group := range baseGroups { + err = config.LoadBinds(binds, name, group) if err != nil { return nil, err } - group, ok := groups[strings.ToLower(name)] - if !ok { - return nil, errors.New("Unknown keybinding group " + name) + } + + config.Bindings.Global.Globals = false + for _, contextBind := range config.ContextualBinds { + if contextBind.BindContext == "default" { + contextBind.Bindings.Globals = false } - 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 + } + + 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 key == "$noinherit" { - if value == "false" { - continue - } - if value != "true" { - return nil, errors.New( - "Error: expected 'true' or 'false' for $noinherit") - } - bindings.Globals = false + if len(strokes) != 1 { + return nil, errors.New("Invalid binding") + } + bindings.ExKey = strokes[0] + continue + } + if key == "$noinherit" { + if value == "false" { continue } - binding, err := ParseBinding(key, value) + 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 nil, err + return err } - bindings.Add(binding) + } else { + return fmt.Errorf("Invalid Bind Context regex in %s", sectionName) } - *group = MergeBindings(bindings, *group) + + 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) } - // Globals can't inherit from themselves - config.Bindings.Global.Globals = false - return config, nil + + return nil } // checkConfigPerms checks for too open permissions 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