aboutsummaryrefslogtreecommitdiffstats
path: root/config
diff options
context:
space:
mode:
Diffstat (limited to 'config')
-rw-r--r--config/binds.go90
-rw-r--r--config/config.go46
-rw-r--r--config/ui.go109
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)
}