summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSrivathsan Murali <sri@vathsan.com>2020-01-23 13:56:48 +0100
committerDrew DeVault <sir@cmpwn.com>2020-01-24 10:50:21 -0500
commitb2fa5a16f52741a6f7f6e5f33561457d702dc31d (patch)
tree3c44cd8f100e0e8c156ad2bbe17ba46fb68a2e01
parentaa967682bcdbeaa11b3e79d66b1d68b129dd4161 (diff)
downloadaerc-b2fa5a16f52741a6f7f6e5f33561457d702dc31d.tar.gz
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
-rw-r--r--config/config.go110
-rw-r--r--doc/aerc-config.5.scd40
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--widgets/account.go23
-rw-r--r--widgets/msglist.go12
-rw-r--r--widgets/msgviewer.go2
7 files changed, 170 insertions, 20 deletions
diff --git a/config/config.go b/config/config.go
index fe548ff..0b46014 100644
--- a/config/config.go
+++ b/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"
@@ -46,6 +47,18 @@ type UIConfig struct {
 }
 
 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
+}
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 791a39d..c747c61 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -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.
diff --git a/go.mod b/go.mod
index 824185e..c7839fd 100644
--- a/go.mod
+++ b/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
diff --git a/go.sum b/go.sum
index 119d317..01024b4 100644
--- a/go.sum
+++ b/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=
diff --git a/widgets/account.go b/widgets/account.go
index 404a9ea..66320a3 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -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
diff --git a/widgets/msglist.go b/widgets/msglist.go
index 243c5db..24a9940 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -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)
 }
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 0bfd2d8..93d3d89 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -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),
 			}
 		},
 	)