diff options
author | Robin Jarry <robin@jarry.cc> | 2022-12-11 23:57:30 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-12-14 11:22:53 +0100 |
commit | 9d0297e9d913a92b2d7ae02692e83f0f4093a766 (patch) | |
tree | b34425a8a295b3cc52c4cf20904f85019b6352ab /config/ui.go | |
parent | 2937830491b5adedf79c8c218afb2c80b17c019a (diff) | |
download | aerc-9d0297e9d913a92b2d7ae02692e83f0f4093a766.tar.gz |
config: rework contextual sections implementation
The current contextual binds and ui config API is awkward and cumbersome
to use. Rework it to make it more elegant.
Store the contextual sections as private fields of the UIConfig and
KeyBindings structures. Add cache to avoid recomputation of the composed
UIConfig and KeyBindings objects every time a contextual item is
requested. Replace the cache from DirectoryList with that.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'config/ui.go')
-rw-r--r-- | config/ui.go | 109 |
1 files changed, 72 insertions, 37 deletions
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) } |