Contextual UI Configuration
+ Adds parsing of contextual ui sections to aerc config. + Add GetUiConfig method for AercConfig that is used to get the specialized UI config. + Add UiConfig method to AccountView to get specialized UI Config. + Modifies Aerc codebase to use specialized UIConfig instead. + Adds documentation for Contextual UI Configuration
This commit is contained in:
parent
aa967682bc
commit
b2fa5a16f5
110
config/config.go
110
config/config.go
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/go-ini/ini"
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/kyoh86/xdg"
|
||||
|
||||
"git.sr.ht/~sircmpwn/aerc/lib/templates"
|
||||
|
@ -45,6 +46,18 @@ type UIConfig struct {
|
|||
CompletionPopovers bool `ini:"completion-popovers"`
|
||||
}
|
||||
|
||||
const (
|
||||
UI_CONTEXT_FOLDER = iota
|
||||
UI_CONTEXT_ACCOUNT
|
||||
UI_CONTEXT_SUBJECT
|
||||
)
|
||||
|
||||
type UIConfigContext struct {
|
||||
ContextType int
|
||||
Regex *regexp.Regexp
|
||||
UiConfig UIConfig
|
||||
}
|
||||
|
||||
const (
|
||||
FILTER_MIMETYPE = iota
|
||||
FILTER_HEADER
|
||||
|
@ -112,16 +125,17 @@ 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
|
||||
General GeneralConfig
|
||||
Templates TemplateConfig
|
||||
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
|
||||
}
|
||||
|
||||
// Input: TimestampFormat
|
||||
|
@ -314,6 +328,55 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
for _, sectionName := range file.SectionStrings() {
|
||||
if !strings.Contains(sectionName, "ui:") {
|
||||
continue
|
||||
}
|
||||
|
||||
uiSection, err := file.GetSection(sectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uiSubConfig := UIConfig{}
|
||||
if err := uiSection.MapTo(&uiSubConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
contextualUi :=
|
||||
UIConfigContext{
|
||||
UiConfig: uiSubConfig,
|
||||
}
|
||||
|
||||
var index int
|
||||
if strings.Contains(sectionName, "~") {
|
||||
index = strings.Index(sectionName, "~")
|
||||
regex := string(sectionName[index+1:])
|
||||
contextualUi.Regex, err = regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.Contains(sectionName, "=") {
|
||||
index = strings.Index(sectionName, "=")
|
||||
value := string(sectionName[index+1:])
|
||||
contextualUi.Regex, err = regexp.Compile(regexp.QuoteMeta(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Invalid Ui Context regex in %s", sectionName)
|
||||
}
|
||||
|
||||
switch sectionName[3:index] {
|
||||
case "account":
|
||||
contextualUi.ContextType = UI_CONTEXT_ACCOUNT
|
||||
case "folder":
|
||||
contextualUi.ContextType = UI_CONTEXT_FOLDER
|
||||
case "subject":
|
||||
contextualUi.ContextType = UI_CONTEXT_SUBJECT
|
||||
default:
|
||||
return fmt.Errorf("Unknown Contextual Ui Section: %s", sectionName)
|
||||
}
|
||||
config.ContextualUis = append(config.ContextualUis, contextualUi)
|
||||
}
|
||||
if triggers, err := file.GetSection("triggers"); err == nil {
|
||||
if err := triggers.MapTo(&config.Triggers); err != nil {
|
||||
return err
|
||||
|
@ -395,6 +458,8 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
|
|||
CompletionPopovers: true,
|
||||
},
|
||||
|
||||
ContextualUis: []UIConfigContext{},
|
||||
|
||||
Viewer: ViewerConfig{
|
||||
Pager: "less -R",
|
||||
Alternatives: []string{"text/plain", "text/html"},
|
||||
|
@ -536,3 +601,28 @@ func parseLayout(layout string) [][]string {
|
|||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (config *AercConfig) mergeContextualUi(baseUi *UIConfig, contextType int, s string) {
|
||||
for _, contextualUi := range config.ContextualUis {
|
||||
if contextualUi.ContextType != contextType {
|
||||
continue
|
||||
}
|
||||
|
||||
if !contextualUi.Regex.Match([]byte(s)) {
|
||||
continue
|
||||
}
|
||||
|
||||
mergo.MergeWithOverwrite(baseUi, contextualUi.UiConfig)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (config *AercConfig) GetUiConfig(params map[int]string) UIConfig {
|
||||
baseUi := config.Ui
|
||||
|
||||
for k, v := range params {
|
||||
config.mergeContextualUi(&baseUi, k, v)
|
||||
}
|
||||
|
||||
return baseUi
|
||||
}
|
||||
|
|
|
@ -168,6 +168,46 @@ These options are configured in the *[ui]* section of aerc.conf.
|
|||
|
||||
Default: 250ms
|
||||
|
||||
## Contextual UI Configuration
|
||||
|
||||
The UI configuration can be specialized for accounts, specific mail
|
||||
directories and message subjects. The specializations are added using
|
||||
contextual config sections based on the context.
|
||||
|
||||
The contextual UI configuration is merged to the base UiConfig in the
|
||||
following order:
|
||||
*Base UIConfig > Account Context > Folder Context > Subject Context.*
|
||||
|
||||
*[ui:account=<AccountName>]*
|
||||
Adds account specific configuration with the account name.
|
||||
|
||||
*[ui:folder=<FolderName>]*
|
||||
Add folder specific configuration with the folder name.
|
||||
|
||||
*[ui:folder~<Regex>]*
|
||||
Add folder specific configuration for folders whose names match the regular
|
||||
expression.
|
||||
|
||||
*[ui:subject~<Regex>]*
|
||||
Add specialized ui configuration for messages that match a given regular
|
||||
expression.
|
||||
|
||||
Example:
|
||||
```
|
||||
[ui:account=Work]
|
||||
sidebar-width=...
|
||||
|
||||
[ui:folder=Sent]
|
||||
index-format=...
|
||||
|
||||
[ui:folder~Archive/\d+/.*]
|
||||
index-format=...
|
||||
|
||||
[ui:subject~^\[PATCH]
|
||||
index-format=...
|
||||
```
|
||||
|
||||
|
||||
## VIEWER
|
||||
|
||||
These options are configured in the *[viewer]* section of aerc.conf.
|
||||
|
|
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
|||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
||||
github.com/imdario/mergo v0.3.8
|
||||
github.com/kyoh86/xdg v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.8
|
||||
github.com/mattn/go-runewidth v0.0.4
|
||||
|
|
2
go.sum
2
go.sum
|
@ -52,6 +52,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
|||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kyoh86/xdg v1.0.0 h1:TD1layQ0epNApNwGRblnQnT3S/2UH/gCQN1cmXWotvE=
|
||||
|
|
|
@ -31,13 +31,24 @@ type AccountView struct {
|
|||
worker *types.Worker
|
||||
}
|
||||
|
||||
func (acct *AccountView) UiConfig() config.UIConfig {
|
||||
return acct.conf.GetUiConfig(map[int]string{
|
||||
config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name,
|
||||
config.UI_CONTEXT_FOLDER: acct.Directories().Selected(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig,
|
||||
logger *log.Logger, host TabHost) *AccountView {
|
||||
|
||||
acctUiConf := conf.GetUiConfig(map[int]string{
|
||||
config.UI_CONTEXT_ACCOUNT: acct.Name,
|
||||
})
|
||||
|
||||
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
}).Columns([]ui.GridSpec{
|
||||
{ui.SIZE_EXACT, conf.Ui.SidebarWidth},
|
||||
{ui.SIZE_EXACT, acctUiConf.SidebarWidth},
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
})
|
||||
|
||||
|
@ -54,8 +65,8 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
|
|||
}
|
||||
}
|
||||
|
||||
dirlist := NewDirectoryList(acct, &conf.Ui, logger, worker)
|
||||
if conf.Ui.SidebarWidth > 0 {
|
||||
dirlist := NewDirectoryList(acct, &acctUiConf, logger, worker)
|
||||
if acctUiConf.SidebarWidth > 0 {
|
||||
grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT))
|
||||
}
|
||||
|
||||
|
@ -236,7 +247,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
acct.conf.Triggers.ExecNewEmail(acct.acct,
|
||||
acct.conf, msg)
|
||||
}, func() {
|
||||
if acct.conf.Ui.NewMessageBell {
|
||||
if acct.UiConfig().NewMessageBell {
|
||||
acct.host.Beep()
|
||||
}
|
||||
})
|
||||
|
@ -272,10 +283,10 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
}
|
||||
|
||||
func (acct *AccountView) getSortCriteria() []*types.SortCriterion {
|
||||
if len(acct.conf.Ui.Sort) == 0 {
|
||||
if len(acct.UiConfig().Sort) == 0 {
|
||||
return nil
|
||||
}
|
||||
criteria, err := sort.GetSortCriteria(acct.conf.Ui.Sort)
|
||||
criteria, err := sort.GetSortCriteria(acct.UiConfig().Sort)
|
||||
if err != nil {
|
||||
acct.aerc.PushError(" ui.sort: " + err.Error())
|
||||
return nil
|
||||
|
|
|
@ -106,10 +106,16 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
|||
}
|
||||
|
||||
ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
|
||||
uiConfig := ml.conf.GetUiConfig(map[int]string{
|
||||
config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name,
|
||||
config.UI_CONTEXT_FOLDER: ml.aerc.SelectedAccount().Directories().Selected(),
|
||||
config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject,
|
||||
})
|
||||
|
||||
fmtStr, args, err := format.ParseMessageFormat(
|
||||
ml.aerc.SelectedAccount().acct.From,
|
||||
ml.conf.Ui.IndexFormat,
|
||||
ml.conf.Ui.TimestampFormat, "", i, msg, store.IsMarked(uid))
|
||||
uiConfig.IndexFormat,
|
||||
uiConfig.TimestampFormat, "", i, msg, store.IsMarked(uid))
|
||||
if err != nil {
|
||||
ctx.Printf(0, row, style, "%v", err)
|
||||
} else {
|
||||
|
@ -265,7 +271,7 @@ func (ml *MessageList) Scroll() {
|
|||
}
|
||||
|
||||
func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
|
||||
msg := ml.conf.Ui.EmptyMessage
|
||||
msg := ml.aerc.SelectedAccount().UiConfig().EmptyMessage
|
||||
ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
|
||||
tcell.StyleDefault, "%s", msg)
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
|||
func(header string) ui.Drawable {
|
||||
return &HeaderView{
|
||||
Name: header,
|
||||
Value: fmtHeader(msg, header, conf.Ui.TimestampFormat),
|
||||
Value: fmtHeader(msg, header, acct.UiConfig().TimestampFormat),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue