diff options
Diffstat (limited to 'config')
-rw-r--r-- | config/binds.go | 90 | ||||
-rw-r--r-- | config/config.go | 46 | ||||
-rw-r--r-- | config/ui.go | 109 |
3 files changed, 155 insertions, 90 deletions
diff --git a/config/binds.go b/config/binds.go index 6ddeb1e1..abaff78c 100644 --- a/config/binds.go +++ b/config/binds.go @@ -27,11 +27,17 @@ type BindingConfig struct { Terminal *KeyBindings } +type bindsContextType int + +const ( + bindsContextFolder bindsContextType = iota + bindsContextAccount +) + type BindingConfigContext struct { - ContextType ContextType + ContextType bindsContextType Regex *regexp.Regexp Bindings *KeyBindings - BindContext string } type KeyStroke struct { @@ -47,11 +53,20 @@ type Binding struct { type KeyBindings struct { Bindings []*Binding - // If false, disable global keybindings in this context Globals bool // Which key opens the ex line (default is :) ExKey KeyStroke + + // private + contextualBinds []*BindingConfigContext + contextualCounts map[bindsContextType]int + contextualCache map[bindsContextKey]*KeyBindings +} + +type bindsContextKey struct { + ctxType bindsContextType + value string } const ( @@ -115,13 +130,6 @@ func (config *AercConfig) parseBinds(root string) error { } } - config.Bindings.Global.Globals = false - for _, contextBind := range config.ContextualBinds { - if contextBind.BindContext == "default" { - contextBind.Bindings.Globals = false - } - } - log.Debugf("binds.conf: %#v", config.Bindings) return nil } @@ -168,6 +176,12 @@ func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup *baseGroup = MergeBindings(binds, *baseGroup) } + b := *baseGroup + + if baseName == "default" { + b.Globals = false + } + for _, sectionName := range binds.SectionStrings() { if !strings.Contains(sectionName, baseName+":") || strings.Contains(sectionName, baseName+"::") { @@ -183,10 +197,12 @@ func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup if err != nil { return err } + if baseName == "default" { + binds.Globals = false + } contextualBind := BindingConfigContext{ - Bindings: binds, - BindContext: baseName, + Bindings: binds, } var index int @@ -215,15 +231,16 @@ func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup log.Warnf("binds.conf: unexistent account: %s", acctName) continue } - contextualBind.ContextType = BIND_CONTEXT_ACCOUNT + contextualBind.ContextType = bindsContextAccount case "folder": // No validation needed. If the folder doesn't exist, the binds // never get used - contextualBind.ContextType = BIND_CONTEXT_FOLDER + contextualBind.ContextType = bindsContextFolder default: return fmt.Errorf("Unknown Context Bind Section: %s", sectionName) } - config.ContextualBinds = append(config.ContextualBinds, contextualBind) + b.contextualBinds = append(b.contextualBinds, &contextualBind) + b.contextualCounts[contextualBind.ContextType]++ } return nil @@ -245,8 +262,10 @@ func defaultBindsConfig() BindingConfig { func NewKeyBindings() *KeyBindings { return &KeyBindings{ - ExKey: KeyStroke{tcell.ModNone, tcell.KeyRune, ':'}, - Globals: true, + ExKey: KeyStroke{tcell.ModNone, tcell.KeyRune, ':'}, + Globals: true, + contextualCache: make(map[bindsContextKey]*KeyBindings), + contextualCounts: make(map[bindsContextType]int), } } @@ -260,26 +279,41 @@ func MergeBindings(bindings ...*KeyBindings) *KeyBindings { return merged } -func (config AercConfig) MergeContextualBinds(baseBinds *KeyBindings, - contextType ContextType, reg string, bindCtx string, +func (base *KeyBindings) contextual( + contextType bindsContextType, reg string, ) *KeyBindings { - bindings := baseBinds - for _, contextualBind := range config.ContextualBinds { + if base.contextualCounts[contextType] == 0 { + // shortcut if no contextual binds for that type + return base + } + + key := bindsContextKey{ctxType: contextType, value: reg} + c, found := base.contextualCache[key] + if found { + return c + } + + c = base + for _, contextualBind := range base.contextualBinds { if contextualBind.ContextType != contextType { continue } - if !contextualBind.Regex.Match([]byte(reg)) { continue } + c = MergeBindings(contextualBind.Bindings, c) + } + base.contextualCache[key] = c - if contextualBind.BindContext != bindCtx { - continue - } + return c +} - bindings = MergeBindings(contextualBind.Bindings, bindings) - } - return bindings +func (bindings *KeyBindings) ForAccount(account string) *KeyBindings { + return bindings.contextual(bindsContextAccount, account) +} + +func (bindings *KeyBindings) ForFolder(folder string) *KeyBindings { + return bindings.contextual(bindsContextFolder, folder) } func (bindings *KeyBindings) Add(binding *Binding) { diff --git a/config/config.go b/config/config.go index b5cc0d60..90951985 100644 --- a/config/config.go +++ b/config/config.go @@ -15,20 +15,18 @@ import ( ) type AercConfig struct { - Bindings BindingConfig - ContextualBinds []BindingConfigContext - Compose ComposeConfig - Converters map[string]string - Accounts []AccountConfig `ini:"-"` - Filters []FilterConfig `ini:"-"` - Viewer ViewerConfig `ini:"-"` - Statusline StatuslineConfig `ini:"-"` - Triggers TriggersConfig `ini:"-"` - Ui UIConfig - ContextualUis []UIConfigContext - General GeneralConfig - Templates TemplateConfig - Openers map[string][]string + Bindings BindingConfig + Compose ComposeConfig + Converters map[string]string + Accounts []AccountConfig `ini:"-"` + Filters []FilterConfig `ini:"-"` + Viewer ViewerConfig `ini:"-"` + Statusline StatuslineConfig `ini:"-"` + Triggers TriggersConfig `ini:"-"` + Ui UIConfig + General GeneralConfig + Templates TemplateConfig + Openers map[string][]string } // Input: TimestampFormat @@ -133,17 +131,15 @@ func LoadConfigFromFile(root *string, accts []string) (*AercConfig, error) { } file.NameMapper = mapName config := &AercConfig{ - Bindings: defaultBindsConfig(), - ContextualBinds: []BindingConfigContext{}, - General: defaultGeneralConfig(), - Ui: defaultUiConfig(), - ContextualUis: []UIConfigContext{}, - Viewer: defaultViewerConfig(), - Statusline: defaultStatuslineConfig(), - Compose: defaultComposeConfig(), - Converters: make(map[string]string), - Templates: defaultTemplatesConfig(), - Openers: make(map[string][]string), + Bindings: defaultBindsConfig(), + General: defaultGeneralConfig(), + Ui: defaultUiConfig(), + Viewer: defaultViewerConfig(), + Statusline: defaultStatuslineConfig(), + Compose: defaultComposeConfig(), + Converters: make(map[string]string), + Templates: defaultTemplatesConfig(), + Openers: make(map[string][]string), } if err := config.parseGeneral(file); err != nil { diff --git a/config/ui.go b/config/ui.go index 9af47085..36b2a9a2 100644 --- a/config/ui.go +++ b/config/ui.go @@ -64,24 +64,32 @@ type UIConfig struct { ReverseOrder bool `ini:"reverse-msglist-order"` ReverseThreadOrder bool `ini:"reverse-thread-order"` SortThreadSiblings bool `ini:"sort-thread-siblings"` + + // private + contextualUis []*UiConfigContext + contextualCounts map[uiContextType]int + contextualCache map[uiContextKey]*UIConfig } -type ContextType int +type uiContextType int const ( - UI_CONTEXT_FOLDER ContextType = iota - UI_CONTEXT_ACCOUNT - UI_CONTEXT_SUBJECT - BIND_CONTEXT_ACCOUNT - BIND_CONTEXT_FOLDER + uiContextFolder uiContextType = iota + uiContextAccount + uiContextSubject ) -type UIConfigContext struct { - ContextType ContextType +type UiConfigContext struct { + ContextType uiContextType Regex *regexp.Regexp UiConfig UIConfig } +type uiContextKey struct { + ctxType uiContextType + value string +} + func defaultUiConfig() UIConfig { return UIConfig{ AutoMarkRead: true, @@ -122,6 +130,9 @@ func defaultUiConfig() UIConfig { // border defaults BorderCharVertical: ' ', BorderCharHorizontal: ' ', + // private + contextualCache: make(map[uiContextKey]*UIConfig), + contextualCounts: make(map[uiContextType]int), } } @@ -145,7 +156,7 @@ func (config *AercConfig) parseUi(file *ini.File) error { if err := uiSubConfig.parse(uiSection); err != nil { return err } - contextualUi := UIConfigContext{ + contextualUi := UiConfigContext{ UiConfig: uiSubConfig, } @@ -171,15 +182,16 @@ func (config *AercConfig) parseUi(file *ini.File) error { switch sectionName[3:index] { case "account": - contextualUi.ContextType = UI_CONTEXT_ACCOUNT + contextualUi.ContextType = uiContextAccount case "folder": - contextualUi.ContextType = UI_CONTEXT_FOLDER + contextualUi.ContextType = uiContextFolder case "subject": - contextualUi.ContextType = UI_CONTEXT_SUBJECT + contextualUi.ContextType = uiContextSubject default: return fmt.Errorf("Unknown Contextual Ui Section: %s", sectionName) } - config.ContextualUis = append(config.ContextualUis, contextualUi) + config.Ui.contextualUis = append(config.Ui.contextualUis, &contextualUi) + config.Ui.contextualCounts[contextualUi.ContextType]++ } // append default paths to styleset-dirs @@ -193,20 +205,20 @@ func (config *AercConfig) parseUi(file *ini.File) error { return err } - for idx, contextualUi := range config.ContextualUis { + for _, contextualUi := range config.Ui.contextualUis { if contextualUi.UiConfig.StyleSetName == "" && len(contextualUi.UiConfig.StyleSetDirs) == 0 { continue // no need to do anything if nothing is overridden } // fill in the missing part from the base if contextualUi.UiConfig.StyleSetName == "" { - config.ContextualUis[idx].UiConfig.StyleSetName = config.Ui.StyleSetName + contextualUi.UiConfig.StyleSetName = config.Ui.StyleSetName } else if len(contextualUi.UiConfig.StyleSetDirs) == 0 { - config.ContextualUis[idx].UiConfig.StyleSetDirs = config.Ui.StyleSetDirs + contextualUi.UiConfig.StyleSetDirs = config.Ui.StyleSetDirs } // since at least one of them has changed, load the styleset - if err := config.ContextualUis[idx].UiConfig.loadStyleSet( - config.ContextualUis[idx].UiConfig.StyleSetDirs); err != nil { + if err := contextualUi.UiConfig.loadStyleSet( + contextualUi.UiConfig.StyleSetDirs); err != nil { return err } } @@ -280,29 +292,30 @@ func (ui *UIConfig) loadStyleSet(styleSetDirs []string) error { return nil } -func (config *AercConfig) mergeContextualUi(baseUi UIConfig, - contextType ContextType, s string, -) UIConfig { - for _, contextualUi := range config.ContextualUis { +func (base *UIConfig) mergeContextual( + contextType uiContextType, s string, +) *UIConfig { + for _, contextualUi := range base.contextualUis { if contextualUi.ContextType != contextType { continue } - if !contextualUi.Regex.Match([]byte(s)) { continue } - - err := mergo.Merge(&baseUi, contextualUi.UiConfig, mergo.WithOverride) + // Try to make this as lightweight as possible and avoid copying + // the base UIConfig object unless necessary. + ui := *base + err := mergo.Merge(&ui, contextualUi.UiConfig, mergo.WithOverride) if err != nil { log.Warnf("merge ui failed: %v", err) } + ui.contextualCache = make(map[uiContextKey]*UIConfig) if contextualUi.UiConfig.StyleSetName != "" { - baseUi.style = contextualUi.UiConfig.style + ui.style = contextualUi.UiConfig.style } - return baseUi + return &ui } - - return baseUi + return base } func (uiConfig *UIConfig) GetStyle(so StyleObject) tcell.Style { @@ -325,16 +338,38 @@ func (uiConfig *UIConfig) GetComposedStyleSelected( return uiConfig.style.ComposeSelected(base, styles) } -func (config *AercConfig) GetUiConfig(params map[ContextType]string) *UIConfig { - baseUi := config.Ui - - for k, v := range params { - baseUi = config.mergeContextualUi(baseUi, k, v) +func (base *UIConfig) contextual( + ctxType uiContextType, value string, useCache bool, +) *UIConfig { + if base.contextualCounts[ctxType] == 0 { + // shortcut if no contextual ui for that type + return base + } + if !useCache { + return base.mergeContextual(ctxType, value) + } + key := uiContextKey{ctxType: ctxType, value: value} + c, found := base.contextualCache[key] + if !found { + c = base.mergeContextual(ctxType, value) + base.contextualCache[key] = c } + return c +} + +func (base *UIConfig) ForAccount(account string) *UIConfig { + return base.contextual(uiContextAccount, account, true) +} - return &baseUi +func (base *UIConfig) ForFolder(folder string) *UIConfig { + return base.contextual(uiContextFolder, folder, true) } -func (config *AercConfig) GetContextualUIConfigs() []UIConfigContext { - return config.ContextualUis +func (base *UIConfig) ForSubject(subject string) *UIConfig { + // TODO: this [ui:subject] contextual config should be dropped and + // replaced by another solution. Possibly something in the stylesets. + // Do not use a cache for contextual subject config as this + // could consume all available memory given enough time and + // enough messages. + return base.contextual(uiContextSubject, subject, false) } |