Jonathan Bartlett: 3 Account Specific Bindings Formatting Reworked account specific bindings 9 files changed, 458 insertions(+), 341 deletions(-)
Hi,
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/22743/mbox | git am -3Learn more about email & git
--- config/config.go | 148 +++++++++++++++++++++++++++++------------- doc/aerc-config.5.scd | 15 +++++ widgets/aerc.go | 54 ++++++++++++--- 3 files changed, 163 insertions(+), 54 deletions(-) diff --git a/config/config.go b/config/config.go index af9c63b..3ecf2b7 100644 --- a/config/config.go +++ b/config/config.go @@ -90,14 +90,19 @@ type AccountConfig struct { } type BindingConfig struct { - Global *KeyBindings - AccountWizard *KeyBindings - Compose *KeyBindings - ComposeEditor *KeyBindings - ComposeReview *KeyBindings - MessageList *KeyBindings - MessageView *KeyBindings - Terminal *KeyBindings + Global *BindingGroup + AccountWizard *BindingGroup + Compose *BindingGroup + ComposeEditor *BindingGroup + ComposeReview *BindingGroup + MessageList *BindingGroup + MessageView *BindingGroup + Terminal *BindingGroup +} + +type BindingGroup struct { + Base *KeyBindings + Account map[string]*KeyBindings } type ComposeConfig struct { @@ -478,16 +483,10 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } file.NameMapper = mapName config := &AercConfig{ - Bindings: BindingConfig{ - Global: NewKeyBindings(), - AccountWizard: NewKeyBindings(), - Compose: NewKeyBindings(), - ComposeEditor: NewKeyBindings(), - ComposeReview: NewKeyBindings(), - MessageList: NewKeyBindings(), - MessageView: NewKeyBindings(), - Terminal: NewKeyBindings(), - }, + Bindings: BindingConfig{ + AccountWizard: &BindingGroup{Base: NewKeyBindings()}, + }, + Ini: file, Ui: UIConfig{ @@ -542,12 +541,12 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { }, } - // These bindings are not configurable - config.Bindings.AccountWizard.ExKey = KeyStroke{ + // These bindings are not configurable + config.Bindings.AccountWizard.Base.ExKey = KeyStroke{ Key: tcell.KeyCtrlE, } quit, _ := ParseBinding("<C-q>", ":quit<Enter>") - config.Bindings.AccountWizard.Add(quit) + config.Bindings.AccountWizard.Base.Add(quit) if err = config.LoadConfig(file); err != nil { return nil, err @@ -565,6 +564,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,25 +575,84 @@ 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, - "compose::editor": &config.Bindings.ComposeEditor, - "compose::review": &config.Bindings.ComposeReview, - } + bindSectionMap := map[string][]string { + "default": []string{}, + "compose": []string{}, + "messages": []string{}, + "terminal": []string{}, + "view": []string{}, + "compose::editor": []string{}, + "compose::review": []string{}, + } for _, name := range binds.SectionStrings() { - sec, err := binds.GetSection(name) + parts := strings.Split(name, "//") + base := parts[0] + section, ok := bindSectionMap[strings.ToLower(base)] + if !ok { + return nil, errors.New("Invalid section name " + name + ", Base was: " + base) + } + section = append(section, name) + bindSectionMap[strings.ToLower(base)] = section + } + + acctNames := []string{} + for _, acctConf := range config.Accounts { + acctNames = append(acctNames, acctConf.Name) + } + + bindingGroups := make(map[string]*BindingGroup) + for baseName, secNames := range bindSectionMap { + group, err := NewBindingGroup(binds, acctNames, secNames, baseName) + if err != nil { + return nil, err + } + bindingGroups[baseName] = group + } + + config.Bindings.Global = bindingGroups["default"] + config.Bindings.Compose = bindingGroups["compose"] + config.Bindings.ComposeEditor = bindingGroups["compose::editor"] + config.Bindings.ComposeReview = bindingGroups["compose::review"] + config.Bindings.MessageList = bindingGroups["messages"] + config.Bindings.MessageView = bindingGroups["view"] + config.Bindings.Terminal = bindingGroups["terminal"] + + // Globals can't inherit from themselves + config.Bindings.Global.Base.Globals = false + for _, acctBinds := range config.Bindings.Global.Account { + acctBinds.Globals = false + } + return config, nil +} + +func NewBindingGroup(binds *ini.File, accountNames []string, secNames []string, baseName string) (*BindingGroup, error) { + group := &BindingGroup{ + Base: NewKeyBindings(), Account: make(map[string]*KeyBindings) } + for _, name := range accountNames { + group.Account[strings.ToLower(name)] = NewKeyBindings() + } + for _, name := range secNames { + parts := strings.Split(name, "//") + + 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) - } + + subgroup := &KeyBindings{} + if (len(parts) == 2) { + sub, ok := group.Account[strings.ToLower(parts[1])] + if !ok { + return nil, errors.New("Invalid binding subgroup name " + name) + } + subgroup = sub + } else if (len(parts) == 1) { + subgroup = group.Base + } else { + return nil, errors.New("Invalid binding group name " + name) + } + bindings := NewKeyBindings() for key, value := range sec.KeysHash() { if key == "$ex" { @@ -602,8 +661,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { return nil, err } if len(strokes) != 1 { - return nil, errors.New( - "Error: only one keystroke supported for $ex") + return nil, errors.New("Invalid binding") } bindings.ExKey = strokes[0] continue @@ -613,8 +671,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { continue } if value != "true" { - return nil, errors.New( - "Error: expected 'true' or 'false' for $noinherit") + return nil, errors.New("Invalid binding") } bindings.Globals = false continue @@ -625,11 +682,14 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } bindings.Add(binding) } - *group = MergeBindings(bindings, *group) - } - // Globals can't inherit from themselves - config.Bindings.Global.Globals = false - return config, nil + + if len(parts) == 1 { + group.Base = MergeBindings(bindings, subgroup) + } else { + group.Account[strings.ToLower(parts[1])] = MergeBindings(bindings, subgroup) + } + } + return group, nil } // checkConfigPerms checks for too open permissions diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index d4de883..511864c 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//accountname]* + keybindings for this context and account, where *accountname* matches + the name provided in *accounts.conf* + +Example: +``` +[messages//mailbox] +c = :cf path:mailbox/** and<space> + +[compose::editor//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..d515536 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -182,26 +182,60 @@ func (aerc *Aerc) Draw(ctx *ui.Context) { } func (aerc *Aerc) getBindings() *config.KeyBindings { + selectedAccountName := "" + if aerc.SelectedAccount() != nil { + selectedAccountName = strings.ToLower(aerc.SelectedAccount().acct.Name); + } switch view := aerc.SelectedTab().(type) { case *AccountView: - return aerc.conf.Bindings.MessageList + acctBinds, ok := aerc.conf.Bindings.MessageList.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.MessageList.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.MessageList.Base, acctBinds) + } case *AccountWizard: - return aerc.conf.Bindings.AccountWizard + return aerc.conf.Bindings.AccountWizard.Base case *Composer: switch view.Bindings() { case "compose::editor": - return aerc.conf.Bindings.ComposeEditor + acctBinds, ok := aerc.conf.Bindings.ComposeEditor.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.ComposeEditor.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.ComposeEditor.Base, acctBinds) + } case "compose::review": - return aerc.conf.Bindings.ComposeReview + acctBinds, ok := aerc.conf.Bindings.ComposeReview.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.ComposeReview.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.ComposeReview.Base, acctBinds) + } default: - return aerc.conf.Bindings.Compose + acctBinds, ok := aerc.conf.Bindings.Compose.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.Compose.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.Compose.Base, acctBinds) + } } case *MessageViewer: - return aerc.conf.Bindings.MessageView + acctBinds, ok := aerc.conf.Bindings.MessageView.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.MessageView.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.MessageView.Base, acctBinds) + } case *Terminal: - return aerc.conf.Bindings.Terminal + return aerc.conf.Bindings.Terminal.Base default: - return aerc.conf.Bindings.Global + acctBinds, ok := aerc.conf.Bindings.Global.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.Global.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.Global.Base, acctBinds) + } } } @@ -245,7 +279,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool { case config.BINDING_NOT_FOUND: } if bindings.Globals { - result, strokes = aerc.conf.Bindings.Global. + result, strokes = aerc.conf.Bindings.Global.Base. GetBinding(aerc.pendingKeys) switch result { case config.BINDING_FOUND: @@ -261,7 +295,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool { exKey := bindings.ExKey if aerc.simulating > 0 { // Keybindings still use : even if you change the ex key - exKey = aerc.conf.Bindings.Global.ExKey + exKey = aerc.conf.Bindings.Global.Base.ExKey } if event.Key() == exKey.Key && event.Rune() == exKey.Rune { aerc.BeginExCommand("") -- 2.31.1
Hi, You seem to have a bit of a problem as to how the email patch submission works. Maybe follow that tutorial here: https://git-send-email.io/ ? Please rebase your changes, as explained in that tutorial. Also, not sure why you tend to send multiple patches in very short time increments... Maybe slow down a tad on the submission? Cheers, Reto
--- config/config.go | 176 +++++++++++++++++++++++------------------------ widgets/aerc.go | 82 +++++++++++----------- 2 files changed, 129 insertions(+), 129 deletions(-) diff --git a/config/config.go b/config/config.go index 3ecf2b7..4f39f29 100644 --- a/config/config.go +++ b/config/config.go @@ -90,19 +90,19 @@ type AccountConfig struct { } type BindingConfig struct { - Global *BindingGroup - AccountWizard *BindingGroup - Compose *BindingGroup - ComposeEditor *BindingGroup - ComposeReview *BindingGroup - MessageList *BindingGroup - MessageView *BindingGroup - Terminal *BindingGroup + Global *BindingGroup + AccountWizard *BindingGroup + Compose *BindingGroup + ComposeEditor *BindingGroup + ComposeReview *BindingGroup + MessageList *BindingGroup + MessageView *BindingGroup + Terminal *BindingGroup } type BindingGroup struct { - Base *KeyBindings - Account map[string]*KeyBindings + Base *KeyBindings + Account map[string]*KeyBindings } type ComposeConfig struct { @@ -483,9 +483,9 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } file.NameMapper = mapName config := &AercConfig{ - Bindings: BindingConfig{ - AccountWizard: &BindingGroup{Base: NewKeyBindings()}, - }, + Bindings: BindingConfig{ + AccountWizard: &BindingGroup{Base: NewKeyBindings()}, + }, Ini: file, @@ -541,7 +541,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { }, } - // These bindings are not configurable + // These bindings are not configurable config.Bindings.AccountWizard.Base.ExKey = KeyStroke{ Key: tcell.KeyCtrlE, } @@ -576,82 +576,82 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } } - bindSectionMap := map[string][]string { - "default": []string{}, - "compose": []string{}, - "messages": []string{}, - "terminal": []string{}, - "view": []string{}, - "compose::editor": []string{}, - "compose::review": []string{}, - } + bindSectionMap := map[string][]string{ + "default": []string{}, + "compose": []string{}, + "messages": []string{}, + "terminal": []string{}, + "view": []string{}, + "compose::editor": []string{}, + "compose::review": []string{}, + } for _, name := range binds.SectionStrings() { - parts := strings.Split(name, "//") - base := parts[0] - section, ok := bindSectionMap[strings.ToLower(base)] - if !ok { - return nil, errors.New("Invalid section name " + name + ", Base was: " + base) - } - section = append(section, name) - bindSectionMap[strings.ToLower(base)] = section - } - - acctNames := []string{} - for _, acctConf := range config.Accounts { - acctNames = append(acctNames, acctConf.Name) - } - - bindingGroups := make(map[string]*BindingGroup) - for baseName, secNames := range bindSectionMap { - group, err := NewBindingGroup(binds, acctNames, secNames, baseName) - if err != nil { - return nil, err - } - bindingGroups[baseName] = group - } - - config.Bindings.Global = bindingGroups["default"] - config.Bindings.Compose = bindingGroups["compose"] - config.Bindings.ComposeEditor = bindingGroups["compose::editor"] - config.Bindings.ComposeReview = bindingGroups["compose::review"] - config.Bindings.MessageList = bindingGroups["messages"] - config.Bindings.MessageView = bindingGroups["view"] - config.Bindings.Terminal = bindingGroups["terminal"] - - // Globals can't inherit from themselves + parts := strings.Split(name, "//") + base := parts[0] + section, ok := bindSectionMap[strings.ToLower(base)] + if !ok { + return nil, errors.New("Invalid section name " + name + ", Base was: " + base) + } + section = append(section, name) + bindSectionMap[strings.ToLower(base)] = section + } + + acctNames := []string{} + for _, acctConf := range config.Accounts { + acctNames = append(acctNames, acctConf.Name) + } + + bindingGroups := make(map[string]*BindingGroup) + for baseName, secNames := range bindSectionMap { + group, err := NewBindingGroup(binds, acctNames, secNames, baseName) + if err != nil { + return nil, err + } + bindingGroups[baseName] = group + } + + config.Bindings.Global = bindingGroups["default"] + config.Bindings.Compose = bindingGroups["compose"] + config.Bindings.ComposeEditor = bindingGroups["compose::editor"] + config.Bindings.ComposeReview = bindingGroups["compose::review"] + config.Bindings.MessageList = bindingGroups["messages"] + config.Bindings.MessageView = bindingGroups["view"] + config.Bindings.Terminal = bindingGroups["terminal"] + + // Globals can't inherit from themselves config.Bindings.Global.Base.Globals = false - for _, acctBinds := range config.Bindings.Global.Account { - acctBinds.Globals = false - } + for _, acctBinds := range config.Bindings.Global.Account { + acctBinds.Globals = false + } return config, nil } func NewBindingGroup(binds *ini.File, accountNames []string, secNames []string, baseName string) (*BindingGroup, error) { - group := &BindingGroup{ - Base: NewKeyBindings(), Account: make(map[string]*KeyBindings) } - for _, name := range accountNames { - group.Account[strings.ToLower(name)] = NewKeyBindings() - } - for _, name := range secNames { - parts := strings.Split(name, "//") - - sec, err := binds.GetSection(name) + group := &BindingGroup{ + Base: NewKeyBindings(), Account: make(map[string]*KeyBindings)} + for _, name := range accountNames { + group.Account[strings.ToLower(name)] = NewKeyBindings() + } + for _, name := range secNames { + parts := strings.Split(name, "//") + + sec, err := binds.GetSection(name) if err != nil { return nil, err } - subgroup := &KeyBindings{} - if (len(parts) == 2) { - sub, ok := group.Account[strings.ToLower(parts[1])] - if !ok { - return nil, errors.New("Invalid binding subgroup name " + name) - } - subgroup = sub - } else if (len(parts) == 1) { - subgroup = group.Base - } else { - return nil, errors.New("Invalid binding group name " + name) - } + subgroup := &KeyBindings{} + if len(parts) == 2 { + sub, ok := group.Account[strings.ToLower(parts[1])] + if !ok { + return nil, errors.New("Invalid binding subgroup name " + name) + } + subgroup = sub + } else if len(parts) == 1 { + subgroup = group.Base + } else { + return nil, errors.New("Invalid binding group name " + name) + } bindings := NewKeyBindings() for key, value := range sec.KeysHash() { @@ -682,14 +682,14 @@ func NewBindingGroup(binds *ini.File, accountNames []string, secNames []string, } bindings.Add(binding) } - - if len(parts) == 1 { - group.Base = MergeBindings(bindings, subgroup) - } else { - group.Account[strings.ToLower(parts[1])] = MergeBindings(bindings, subgroup) - } - } - return group, nil + + if len(parts) == 1 { + group.Base = MergeBindings(bindings, subgroup) + } else { + group.Account[strings.ToLower(parts[1])] = MergeBindings(bindings, subgroup) + } + } + return group, nil } // checkConfigPerms checks for too open permissions diff --git a/widgets/aerc.go b/widgets/aerc.go index d515536..35efa3b 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -182,60 +182,60 @@ func (aerc *Aerc) Draw(ctx *ui.Context) { } func (aerc *Aerc) getBindings() *config.KeyBindings { - selectedAccountName := "" - if aerc.SelectedAccount() != nil { - selectedAccountName = strings.ToLower(aerc.SelectedAccount().acct.Name); - } + selectedAccountName := "" + if aerc.SelectedAccount() != nil { + selectedAccountName = strings.ToLower(aerc.SelectedAccount().acct.Name) + } switch view := aerc.SelectedTab().(type) { case *AccountView: - acctBinds, ok := aerc.conf.Bindings.MessageList.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.MessageList.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.MessageList.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.MessageList.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.MessageList.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.MessageList.Base, acctBinds) + } case *AccountWizard: return aerc.conf.Bindings.AccountWizard.Base case *Composer: switch view.Bindings() { case "compose::editor": - acctBinds, ok := aerc.conf.Bindings.ComposeEditor.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.ComposeEditor.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.ComposeEditor.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.ComposeEditor.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.ComposeEditor.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.ComposeEditor.Base, acctBinds) + } case "compose::review": - acctBinds, ok := aerc.conf.Bindings.ComposeReview.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.ComposeReview.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.ComposeReview.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.ComposeReview.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.ComposeReview.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.ComposeReview.Base, acctBinds) + } default: - acctBinds, ok := aerc.conf.Bindings.Compose.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.Compose.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.Compose.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.Compose.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.Compose.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.Compose.Base, acctBinds) + } } case *MessageViewer: - acctBinds, ok := aerc.conf.Bindings.MessageView.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.MessageView.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.MessageView.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.MessageView.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.MessageView.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.MessageView.Base, acctBinds) + } case *Terminal: - return aerc.conf.Bindings.Terminal.Base + return aerc.conf.Bindings.Terminal.Base default: - acctBinds, ok := aerc.conf.Bindings.Global.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.Global.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.Global.Base, acctBinds) - } + acctBinds, ok := aerc.conf.Bindings.Global.Account[selectedAccountName] + if !ok { + return aerc.conf.Bindings.Global.Base + } else { + return config.MergeBindings(aerc.conf.Bindings.Global.Base, acctBinds) + } } } -- 2.31.1
Account specific bindings now follows the same structure as contextual UI configuration. It also supports regular expressions and provides a base for further binding subcontexts in the future. --- config/bindings.go | 22 ++++ config/config.go | 240 ++++++++++++++++++++++-------------------- doc/aerc-config.5.scd | 10 +- widgets/aerc.go | 52 ++------- 4 files changed, 166 insertions(+), 158 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 4f39f29..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 { @@ -90,19 +91,21 @@ type AccountConfig struct { } type BindingConfig struct { - Global *BindingGroup - AccountWizard *BindingGroup - Compose *BindingGroup - ComposeEditor *BindingGroup - ComposeReview *BindingGroup - MessageList *BindingGroup - MessageView *BindingGroup - Terminal *BindingGroup + Global *KeyBindings + AccountWizard *KeyBindings + Compose *KeyBindings + ComposeEditor *KeyBindings + ComposeReview *KeyBindings + MessageList *KeyBindings + MessageView *KeyBindings + Terminal *KeyBindings } -type BindingGroup struct { - Base *KeyBindings - Account map[string]*KeyBindings +type BindingConfigContext struct { + ContextType ContextType + Regex *regexp.Regexp + Bindings *KeyBindings + BindContext string } type ComposeConfig struct { @@ -139,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 @@ -350,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 @@ -484,9 +490,18 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { file.NameMapper = mapName config := &AercConfig{ Bindings: BindingConfig{ - AccountWizard: &BindingGroup{Base: NewKeyBindings()}, + Global: NewKeyBindings(), + AccountWizard: NewKeyBindings(), + Compose: NewKeyBindings(), + ComposeEditor: NewKeyBindings(), + ComposeReview: NewKeyBindings(), + MessageList: NewKeyBindings(), + MessageView: NewKeyBindings(), + Terminal: NewKeyBindings(), }, + ContextualBinds: []BindingConfigContext{}, + Ini: file, Ui: UIConfig{ @@ -542,11 +557,11 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } // These bindings are not configurable - config.Bindings.AccountWizard.Base.ExKey = KeyStroke{ + config.Bindings.AccountWizard.ExKey = KeyStroke{ Key: tcell.KeyCtrlE, } quit, _ := ParseBinding("<C-q>", ":quit<Enter>") - config.Bindings.AccountWizard.Base.Add(quit) + config.Bindings.AccountWizard.Add(quit) if err = config.LoadConfig(file); err != nil { return nil, err @@ -576,120 +591,121 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { } } - bindSectionMap := map[string][]string{ - "default": []string{}, - "compose": []string{}, - "messages": []string{}, - "terminal": []string{}, - "view": []string{}, - "compose::editor": []string{}, - "compose::review": []string{}, + 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() { - parts := strings.Split(name, "//") - base := parts[0] - section, ok := bindSectionMap[strings.ToLower(base)] - if !ok { - return nil, errors.New("Invalid section name " + name + ", Base was: " + base) + + // Base Bindings + for name, group := range baseGroups { + err = config.LoadBinds(binds, name, group) + if err != nil { + return nil, err } - section = append(section, name) - bindSectionMap[strings.ToLower(base)] = section } - acctNames := []string{} - for _, acctConf := range config.Accounts { - acctNames = append(acctNames, acctConf.Name) + config.Bindings.Global.Globals = false + for _, contextBind := range config.ContextualBinds { + if contextBind.BindContext == "default" { + contextBind.Bindings.Globals = false + } } - bindingGroups := make(map[string]*BindingGroup) - for baseName, secNames := range bindSectionMap { - group, err := NewBindingGroup(binds, acctNames, secNames, baseName) + 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 } - bindingGroups[baseName] = group + bindings.Add(binding) } + return bindings, nil +} - config.Bindings.Global = bindingGroups["default"] - config.Bindings.Compose = bindingGroups["compose"] - config.Bindings.ComposeEditor = bindingGroups["compose::editor"] - config.Bindings.ComposeReview = bindingGroups["compose::review"] - config.Bindings.MessageList = bindingGroups["messages"] - config.Bindings.MessageView = bindingGroups["view"] - config.Bindings.Terminal = bindingGroups["terminal"] +func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup **KeyBindings) error { - // Globals can't inherit from themselves - config.Bindings.Global.Base.Globals = false - for _, acctBinds := range config.Bindings.Global.Account { - acctBinds.Globals = false + if sec, err := binds.GetSection(baseName); err == nil { + binds, err := LoadBindingSection(sec) + if err != nil { + return err + } + *baseGroup = MergeBindings(binds, *baseGroup) } - return config, nil -} -func NewBindingGroup(binds *ini.File, accountNames []string, secNames []string, baseName string) (*BindingGroup, error) { - group := &BindingGroup{ - Base: NewKeyBindings(), Account: make(map[string]*KeyBindings)} - for _, name := range accountNames { - group.Account[strings.ToLower(name)] = NewKeyBindings() - } - for _, name := range secNames { - parts := strings.Split(name, "//") + for _, sectionName := range binds.SectionStrings() { + if !strings.Contains(sectionName, baseName+":") || + strings.Contains(sectionName, baseName+"::") { + continue + } - sec, err := binds.GetSection(name) + bindSection, err := binds.GetSection(sectionName) if err != nil { - return nil, err + return err } - subgroup := &KeyBindings{} - if len(parts) == 2 { - sub, ok := group.Account[strings.ToLower(parts[1])] - if !ok { - return nil, errors.New("Invalid binding subgroup name " + name) - } - subgroup = sub - } else if len(parts) == 1 { - subgroup = group.Base - } else { - return nil, errors.New("Invalid binding group name " + name) + binds, err := LoadBindingSection(bindSection) + if err != nil { + return err } - 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 + contextualBind := + BindingConfigContext{ + Bindings: binds, + BindContext: baseName, } - 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) + + 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) } - if len(parts) == 1 { - group.Base = MergeBindings(bindings, subgroup) - } else { - group.Account[strings.ToLower(parts[1])] = MergeBindings(bindings, subgroup) + 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 group, nil + + return nil } // checkConfigPerms checks for too open permissions diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 511864c..fb032cb 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -489,16 +489,16 @@ are: You may also configure account specific key bindings for each context: -*[context//accountname]* - keybindings for this context and account, where *accountname* matches - the name provided in *accounts.conf* +*[context:account=regex]* + keybindings for this context and account, where *regex* matches + the account name provided in *accounts.conf* Example: ``` -[messages//mailbox] +[messages:account=Mailbox] c = :cf path:mailbox/** and<space> -[compose::editor//mailbox2] +[compose::editor:account=Mailbox2] ... ``` diff --git a/widgets/aerc.go b/widgets/aerc.go index 35efa3b..5661260 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -184,58 +184,28 @@ func (aerc *Aerc) Draw(ctx *ui.Context) { func (aerc *Aerc) getBindings() *config.KeyBindings { selectedAccountName := "" if aerc.SelectedAccount() != nil { - selectedAccountName = strings.ToLower(aerc.SelectedAccount().acct.Name) + selectedAccountName = aerc.SelectedAccount().acct.Name } switch view := aerc.SelectedTab().(type) { case *AccountView: - acctBinds, ok := aerc.conf.Bindings.MessageList.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.MessageList.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.MessageList.Base, acctBinds) - } + return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageList, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "messages") case *AccountWizard: - return aerc.conf.Bindings.AccountWizard.Base + return aerc.conf.Bindings.AccountWizard case *Composer: switch view.Bindings() { case "compose::editor": - acctBinds, ok := aerc.conf.Bindings.ComposeEditor.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.ComposeEditor.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.ComposeEditor.Base, acctBinds) - } + return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeEditor, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::editor") case "compose::review": - acctBinds, ok := aerc.conf.Bindings.ComposeReview.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.ComposeReview.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.ComposeReview.Base, acctBinds) - } + return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeReview, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::review") default: - acctBinds, ok := aerc.conf.Bindings.Compose.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.Compose.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.Compose.Base, acctBinds) - } + return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.Compose, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose") } case *MessageViewer: - acctBinds, ok := aerc.conf.Bindings.MessageView.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.MessageView.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.MessageView.Base, acctBinds) - } + return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageView, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view") case *Terminal: - return aerc.conf.Bindings.Terminal.Base + return aerc.conf.Bindings.Terminal default: - acctBinds, ok := aerc.conf.Bindings.Global.Account[selectedAccountName] - if !ok { - return aerc.conf.Bindings.Global.Base - } else { - return config.MergeBindings(aerc.conf.Bindings.Global.Base, acctBinds) - } + return aerc.conf.Bindings.Global } } @@ -279,7 +249,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool { case config.BINDING_NOT_FOUND: } if bindings.Globals { - result, strokes = aerc.conf.Bindings.Global.Base. + result, strokes = aerc.conf.Bindings.Global. GetBinding(aerc.pendingKeys) switch result { case config.BINDING_FOUND: @@ -295,7 +265,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool { exKey := bindings.ExKey if aerc.simulating > 0 { // Keybindings still use : even if you change the ex key - exKey = aerc.conf.Bindings.Global.Base.ExKey + exKey = aerc.conf.Bindings.Global.ExKey } if event.Key() == exKey.Key && event.Rune() == exKey.Rune { aerc.BeginExCommand("") -- 2.31.1