aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/binds.go90
-rw-r--r--config/config.go46
-rw-r--r--config/ui.go109
-rw-r--r--widgets/account.go4
-rw-r--r--widgets/aerc.go19
-rw-r--r--widgets/compose.go5
-rw-r--r--widgets/dirlist.go18
-rw-r--r--widgets/msglist.go14
-rw-r--r--widgets/msgviewer.go7
9 files changed, 173 insertions, 139 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)
}
diff --git a/widgets/account.go b/widgets/account.go
index c8c58b1e..838dd624 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -58,9 +58,7 @@ func (acct *AccountView) UiConfig() *config.UIConfig {
func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig,
host TabHost, deferLoop chan struct{},
) (*AccountView, error) {
- acctUiConf := conf.GetUiConfig(map[config.ContextType]string{
- config.UI_CONTEXT_ACCOUNT: acct.Name,
- })
+ acctUiConf := conf.Ui.ForAccount(acct.Name)
view := &AccountView{
acct: acct,
diff --git a/widgets/aerc.go b/widgets/aerc.go
index ca82ee28..97e3c663 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -229,25 +229,30 @@ func (aerc *Aerc) getBindings() *config.KeyBindings {
}
switch view := aerc.SelectedTabContent().(type) {
case *AccountView:
- binds := aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageList, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "messages")
- return aerc.conf.MergeContextualBinds(binds, config.BIND_CONTEXT_FOLDER, view.SelectedDirectory(), "messages")
+ binds := aerc.conf.Bindings.MessageList.ForAccount(selectedAccountName)
+ return binds.ForFolder(view.SelectedDirectory())
case *AccountWizard:
return aerc.conf.Bindings.AccountWizard
case *Composer:
switch view.Bindings() {
case "compose::editor":
- return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeEditor, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::editor")
+ return aerc.conf.Bindings.ComposeEditor.ForAccount(
+ selectedAccountName)
case "compose::review":
- return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.ComposeReview, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose::review")
+ return aerc.conf.Bindings.ComposeReview.ForAccount(
+ selectedAccountName)
default:
- return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.Compose, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "compose")
+ return aerc.conf.Bindings.Compose.ForAccount(
+ selectedAccountName)
}
case *MessageViewer:
switch view.Bindings() {
case "view::passthrough":
- return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageViewPassthrough, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view::passthrough")
+ return aerc.conf.Bindings.MessageViewPassthrough.ForAccount(
+ selectedAccountName)
default:
- return aerc.conf.MergeContextualBinds(aerc.conf.Bindings.MessageView, config.BIND_CONTEXT_ACCOUNT, selectedAccountName, "view")
+ return aerc.conf.Bindings.MessageView.ForAccount(
+ selectedAccountName)
}
case *Terminal:
return aerc.conf.Bindings.Terminal
diff --git a/widgets/compose.go b/widgets/compose.go
index 37e09ccb..4cf7da16 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -1261,11 +1261,8 @@ type reviewMessage struct {
}
func newReviewMessage(composer *Composer, err error) *reviewMessage {
- bindings := composer.config.MergeContextualBinds(
- composer.config.Bindings.ComposeReview,
- config.BIND_CONTEXT_ACCOUNT,
+ bindings := composer.config.Bindings.ComposeReview.ForAccount(
composer.acctConfig.Name,
- "compose::review",
)
reviewCommands := [][]string{
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index e4f867eb..0b41c024 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -8,7 +8,6 @@ import (
"regexp"
"sort"
"strings"
- "sync"
"time"
"github.com/gdamore/tcell/v2"
@@ -48,7 +47,6 @@ type DirectoryLister interface {
}
type DirectoryList struct {
- sync.Mutex
Scrollable
aercConf *config.AercConfig
acctConf *config.AccountConfig
@@ -60,7 +58,6 @@ type DirectoryList struct {
worker *types.Worker
skipSelect context.Context
skipSelectCancel context.CancelFunc
- uiConf map[string]*config.UIConfig
}
func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
@@ -68,8 +65,6 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
) DirectoryLister {
ctx, cancel := context.WithCancel(context.Background())
- uiConfMap := make(map[string]*config.UIConfig)
-
dirlist := &DirectoryList{
aercConf: conf,
acctConf: acctConf,
@@ -77,7 +72,6 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
worker: worker,
skipSelect: ctx,
skipSelectCancel: cancel,
- uiConf: uiConfMap,
}
uiConf := dirlist.UiConfig("")
dirlist.spinner = NewSpinner(uiConf)
@@ -91,20 +85,10 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
}
func (dirlist *DirectoryList) UiConfig(dir string) *config.UIConfig {
- dirlist.Lock()
- defer dirlist.Unlock()
if dir == "" {
dir = dirlist.Selected()
}
- if ui, ok := dirlist.uiConf[dir]; ok {
- return ui
- }
- ui := dirlist.aercConf.GetUiConfig(map[config.ContextType]string{
- config.UI_CONTEXT_ACCOUNT: dirlist.acctConf.Name,
- config.UI_CONTEXT_FOLDER: dir,
- })
- dirlist.uiConf[dir] = ui
- return ui
+ return dirlist.aercConf.Ui.ForAccount(dirlist.acctConf.Name).ForFolder(dir)
}
func (dirlist *DirectoryList) List() []string {
diff --git a/widgets/msglist.go b/widgets/msglist.go
index 09ee705e..85b8f168 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -202,19 +202,9 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
// TODO deprecate subject contextual UIs? Only related setting is styleset,
// should implement a better per-message styling method
// Check if we have any applicable ContextualUIConfigs
- confs := ml.aerc.conf.GetContextualUIConfigs()
uiConfig := acct.Directories().UiConfig(store.DirInfo.Name)
- for _, c := range confs {
- if c.ContextType == config.UI_CONTEXT_SUBJECT && msg.Envelope != nil {
- if c.Regex.Match([]byte(msg.Envelope.Subject)) {
- confParams := map[config.ContextType]string{
- config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name,
- config.UI_CONTEXT_FOLDER: acct.Directories().Selected(),
- config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject,
- }
- uiConfig = ml.conf.GetUiConfig(confParams)
- }
- }
+ if msg.Envelope != nil {
+ uiConfig = uiConfig.ForSubject(msg.Envelope.Subject)
}
msg_styles := []config.StyleObject{}
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 6c929578..875ff873 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -738,12 +738,7 @@ var noFilterConfiguredCommands = [][]string{
}
func newNoFilterConfigured(pv *PartViewer) *ui.Grid {
- bindings := pv.conf.MergeContextualBinds(
- pv.conf.Bindings.MessageView,
- config.BIND_CONTEXT_ACCOUNT,
- pv.acctConfig.Name,
- "view",
- )
+ bindings := pv.conf.Bindings.MessageView.ForAccount(pv.acctConfig.Name)
var actions []string