config: add columns based index format

The index-format option comes from mutt and is neither user friendly,
nor intuitive. Introduce a new way of configuring the message list
contents. Replace index-format with multiple settings to make everything
more intuitive. Reuse the table widget added in the previous commit.

index-columns
	Comma-separated list of column names followed by optional
	alignment and width specifiers.

column-separator
	String separator between columns.

column-$name
	One setting for every name defined in index-columns. This
	supports golang text/template syntax and allows access to the
	same message information than before and much more.

When index-format is still defined in aerc.conf (which will most likely
happen when users will update after this patch), convert it to the new
index-columns + column-$name and column-separator system and a warning
is displayed on startup so that users are aware that they need to update
their config.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
This commit is contained in:
Robin Jarry
2022-09-06 07:33:21 +02:00
parent 012be0192c
commit 535300cfdb
9 changed files with 474 additions and 149 deletions

View File

@@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
external command configured via `[compose].file-picker-cmd` in `aerc.conf`.
- Sample stylesets are now installed in `$PREFIX/share/aerc/stylesets`.
- The built-in `colorize` filter now has different themes.
- New column-based message list format with `index-columns`.
### Changed
@@ -52,6 +53,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Deprecated
- Removed broken `:set` command.
- `[ui].index-format` setting has been replaced by `index-columns`.
## [0.13.0](https://git.sr.ht/~rjarry/aerc/refs/0.13.0) - 2022-10-20

View File

@@ -40,11 +40,38 @@
[ui]
#
# Describes the format for each row in a mailbox view. This field is compatible
# with mutt's printf-like syntax.
# Describes the format for each row in a mailbox view. This is a comma
# separated list of column names with an optional align and width suffix. After
# the column name, one of the '<' (left), ':' (center) or '>' (right) alignment
# characters can be added (by default, left) followed by an optional width
# specifier. The width is either an integer representing a fixed number of
# characters, or a percentage between 1% and 99% representing a fraction of the
# terminal width. It can also be one of the '*' (auto) or '=' (fit) special
# width specifiers. Auto width columns will be equally attributed the remaining
# terminal width. Fit width columns take the width of their contents. If no
# width specifier is set, '*' is used by default.
#
# Default: %-20.20D %-17.17n %Z %s
#index-format=%-20.20D %-17.17n %Z %s
# Default: date<20,name<17,flags>4,subject<*
#index-columns=date<20,name<17,flags>4,subject<*
#
# Each name in index-columns must have a corresponding column-$name setting.
# All column-$name settings accept golang text/template syntax. See
# aerc-templates(7) for available template attributes and functions.
#
# Default settings
#column-date={{.DateAutoFormat .Date.Local}}
#column-name={{index (.From | names) 0}}
#column-flags={{.Flags | join ""}}
#column-subject={{.Subject}}
#
# String separator inserted between columns. When the column width specifier is
# an exact number of characters, the separator is added to it (i.e. the exact
# width will be fully available for the column contents).
#
# Default: " "
#column-separator=" "
#
# See time.Time#Format at https://godoc.org/time#Time.Format

View File

@@ -3,6 +3,7 @@ package config
import (
"bytes"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
@@ -118,3 +119,42 @@ func ParseColumnDefs(key *ini.Key, section *ini.Section) ([]*ColumnDef, error) {
}
return columns, nil
}
func ColumnDefsToIni(defs []*ColumnDef, keyName string) string {
var s strings.Builder
var cols []string
templates := make(map[string]string)
for _, def := range defs {
col := def.Name
switch {
case def.Flags.Has(ALIGN_LEFT):
col += "<"
case def.Flags.Has(ALIGN_CENTER):
col += ":"
case def.Flags.Has(ALIGN_RIGHT):
col += ">"
}
switch {
case def.Flags.Has(WIDTH_FIT):
col += "="
case def.Flags.Has(WIDTH_AUTO):
col += "*"
case def.Flags.Has(WIDTH_FRACTION):
col += fmt.Sprintf("%.0f%%", def.Width*100)
default:
col += fmt.Sprintf("%.0f", def.Width)
}
cols = append(cols, col)
tree := reflect.ValueOf(def.Template.Tree)
text := tree.Elem().FieldByName("text").String()
templates[fmt.Sprintf("column-%s", def.Name)] = text
}
s.WriteString(fmt.Sprintf("%s = %s\n", keyName, strings.Join(cols, ",")))
for name, text := range templates {
s.WriteString(fmt.Sprintf("%s = %s\n", name, text))
}
return s.String()
}

View File

@@ -4,9 +4,11 @@ import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
"time"
"git.sr.ht/~rjarry/aerc/lib/templates"
"git.sr.ht/~rjarry/aerc/log"
"github.com/gdamore/tcell/v2"
"github.com/go-ini/ini"
@@ -14,8 +16,12 @@ import (
)
type UIConfig struct {
IndexColumns []*ColumnDef `ini:"-"`
ColumnSeparator string `ini:"column-separator"`
// deprecated
IndexFormat string `ini:"index-format"`
AutoMarkRead bool `ini:"auto-mark-read"`
IndexFormat string `ini:"index-format"`
TimestampFormat string `ini:"timestamp-format"`
ThisDayTimeFormat string `ini:"this-day-time-format"`
ThisWeekTimeFormat string `ini:"this-week-time-format"`
@@ -92,9 +98,39 @@ type uiContextKey struct {
}
func defaultUiConfig() *UIConfig {
date, _ := templates.ParseTemplate("column-date", "{{.DateAutoFormat .Date.Local}}")
name, _ := templates.ParseTemplate("column-name", "{{index (.From | names) 0}}")
flags, _ := templates.ParseTemplate("column-flags", `{{.Flags | join ""}}`)
subject, _ := templates.ParseTemplate("column-subject", "{{.Subject}}")
return &UIConfig{
IndexFormat: "", // deprecated
IndexColumns: []*ColumnDef{
{
Name: "date",
Width: 20,
Flags: ALIGN_LEFT | WIDTH_EXACT,
Template: date,
},
{
Name: "name",
Width: 17,
Flags: ALIGN_LEFT | WIDTH_EXACT,
Template: name,
},
{
Name: "flags",
Width: 4,
Flags: ALIGN_RIGHT | WIDTH_EXACT,
Template: flags,
},
{
Name: "subject",
Flags: ALIGN_LEFT | WIDTH_AUTO,
Template: subject,
},
},
ColumnSeparator: " ",
AutoMarkRead: true,
IndexFormat: "%-20.20D %-17.17n %Z %s",
TimestampFormat: "2006-01-02 03:04 PM",
ThisDayTimeFormat: "",
ThisWeekTimeFormat: "",
@@ -283,9 +319,147 @@ func (config *UIConfig) parse(section *ini.Section) error {
config.MessageViewThisDayTimeFormat = config.TimestampFormat
}
if config.IndexFormat != "" {
log.Warnf("%s %s",
"The index-format setting has been replaced by index-columns.",
"index-format will be removed in aerc 0.17.")
}
if key, err := section.GetKey("index-columns"); err == nil {
columns, err := ParseColumnDefs(key, section)
if err != nil {
return err
}
config.IndexColumns = columns
config.IndexFormat = "" // to silence popup at startup
} else if config.IndexFormat != "" {
columns, err := convertIndexFormat(config.IndexFormat)
if err != nil {
return err
}
config.IndexColumns = columns
}
return nil
}
var indexFmtRegexp = regexp.MustCompile(`%(-?\d+)?(\.\d+)?([A-Za-z%])`)
func convertIndexFormat(indexFormat string) ([]*ColumnDef, error) {
matches := indexFmtRegexp.FindAllStringSubmatch(indexFormat, -1)
if matches == nil {
return nil, fmt.Errorf("invalid index-format")
}
var columns []*ColumnDef
for _, m := range matches {
alignWidth := m[1]
verb := m[3]
var f string
var width float64 = 0
var flags ColumnFlags = ALIGN_LEFT
name := ""
switch verb {
case "%":
f = verb
case "a":
f = `{{(index .From 0).Address}}`
name = "sender"
case "A":
f = `{{if eq (len .ReplyTo) 0}}{{(index .From 0).Address}}{{else}}{{(index .ReplyTo 0).Address}}{{end}}`
name = "reply-to"
case "C":
f = "{{.Number}}"
name = "num"
case "d", "D":
f = "{{.DateAutoFormat .Date.Local}}"
name = "date"
case "f":
f = `{{index (.From | persons) 0}}`
name = "from"
case "F":
f = `{{.Peer | names | join ", "}}`
name = "peers"
case "g":
f = `{{.Labels | join ", "}}`
name = "labels"
case "i":
f = "{{.MessageId}}"
name = "msg-id"
case "n":
f = `{{index (.From | names) 0}}`
name = "name"
case "r":
f = `{{.To | persons | join ", "}}`
name = "to"
case "R":
f = `{{.Cc | persons | join ", "}}`
name = "cc"
case "s":
f = "{{.Subject}}"
name = "subject"
case "t":
f = "{{(index .To 0).Address}}"
name = "to0"
case "T":
f = "{{.Account}}"
name = "account"
case "u":
f = "{{index (.From | mboxes) 0}}"
name = "mboxes"
case "v":
f = "{{index (.From | names) 0}}"
name = "name"
case "Z":
f = `{{.Flags | join ""}}`
name = "flags"
width = 4
flags = ALIGN_RIGHT
case "l":
f = "{{.Size}}"
name = "size"
default:
f = "%" + verb
}
if name == "" {
name = "wtf"
}
t, err := templates.ParseTemplate(fmt.Sprintf("column-%s", name), f)
if err != nil {
return nil, err
}
if alignWidth != "" {
width, err = strconv.ParseFloat(alignWidth, 64)
if err != nil {
return nil, err
}
if width < 0 {
width = -width
} else {
flags = ALIGN_RIGHT
}
}
if width == 0 {
flags |= WIDTH_AUTO
} else {
flags |= WIDTH_EXACT
}
columns = append(columns, &ColumnDef{
Name: name,
Width: width,
Flags: flags,
Template: t,
})
}
return columns, nil
}
func (ui *UIConfig) loadStyleSet(styleSetDirs []string) error {
ui.style = NewStyleSet()
err := ui.style.LoadStyleSet(ui.StyleSetName, styleSetDirs)

46
config/ui_test.go Normal file
View File

@@ -0,0 +1,46 @@
package config
import (
"bytes"
"testing"
"git.sr.ht/~rjarry/aerc/lib/templates"
"github.com/stretchr/testify/assert"
)
func TestConvertIndexFormat(t *testing.T) {
columns, err := convertIndexFormat("%-20.20D %-17.17n %Z %s")
if err != nil {
t.Fatal(err)
}
assert.Len(t, columns, 4)
data := templates.DummyData()
var buf bytes.Buffer
assert.Equal(t, "date", columns[0].Name)
assert.Equal(t, 20.0, columns[0].Width)
assert.Equal(t, ALIGN_LEFT|WIDTH_EXACT, columns[0].Flags)
assert.Nil(t, columns[0].Template.Execute(&buf, data))
buf.Reset()
assert.Equal(t, "name", columns[1].Name)
assert.Equal(t, 17.0, columns[1].Width)
assert.Equal(t, ALIGN_LEFT|WIDTH_EXACT, columns[1].Flags)
assert.Nil(t, columns[1].Template.Execute(&buf, data))
assert.Equal(t, "John Doe", buf.String())
buf.Reset()
assert.Equal(t, "flags", columns[2].Name)
assert.Equal(t, 4.0, columns[2].Width)
assert.Equal(t, ALIGN_RIGHT|WIDTH_EXACT, columns[2].Flags)
assert.Nil(t, columns[2].Template.Execute(&buf, data))
assert.Equal(t, "O!*", buf.String())
buf.Reset()
assert.Equal(t, "subject", columns[3].Name)
assert.Equal(t, 0.0, columns[3].Width)
assert.Equal(t, ALIGN_LEFT|WIDTH_AUTO, columns[3].Flags)
assert.Nil(t, columns[3].Template.Execute(&buf, data))
assert.Equal(t, "[PATCH aerc 2/3] foo: baz bar buz", buf.String())
}

View File

@@ -66,53 +66,43 @@ These options are configured in the *[general]* section of _aerc.conf_.
These options are configured in the *[ui]* section of _aerc.conf_.
*index-format* = _<format>_
Describes the format for each row in a mailbox view. This field is
compatible with mutt's printf-like syntax.
*index-columns* = _<column1,column2,column3...>_
Describes the format for each row in a mailbox view. This is a comma
separated list of column names with an optional align and width suffix.
After the column name, one of the _<_ (left), _:_ (center) or _>_
(right) alignment characters can be added (by default, left) followed by
an optional width specifier. The width is either an integer representing
a fixed number of characters, or a percentage between _1%_ and _99%_
representing a fraction of the terminal width. It can also be one of the
_\*_ (auto) or _=_ (fit) special width specifiers. Auto width columns
will be equally attributed the remaining terminal width. Fit width
columns take the width of their contents. If no width specifier is set,
_\*_ is used by default.
Default: _%D %-17.17n %s_
Default: _date<20,name<17,flags>4,subject<\*_
[- *Format specifier*
:[ *Description*
| _%%_
: literal %
| _%a_
: sender address
| _%A_
: reply-to address, or sender address if none
| _%C_
: message number
| _%d_
: formatted message timestamp
| _%D_
: formatted message timestamp converted to local timezone
| _%f_
: sender name and address
| _%F_
: author name, or recipient name if the message is from you.
The address is shown if no name part.
| _%g_
: message labels (for example notmuch tags)
| _%i_
: message id
| _%n_
: sender name, or sender address if none
| _%r_
: comma-separated list of formatted recipient names and addresses
| _%R_
: comma-separated list of formatted CC names and addresses
| _%s_
: subject
| _%t_
: the (first) address the new email was sent to
| _%T_
: the account name which received the email
| _%u_
: sender mailbox name (e.g. "smith" in "smith@example.net")
| _%v_
: sender first name (e.g. "Alex" in "Alex Smith <smith@example.net>")
| _%Z_
: flags (O=old, N=new, r=answered, D=deleted, !=flagged, \*=marked, a=attachment)
*column-separator* = _"<separator>"_
String separator inserted between columns. When a column width specifier
is an exact number of characters, the separator is added to it (i.e. the
exact width will be fully available for that column contents).
Default: _" "_
*column-<name>* = _<go template>_
Each name in *index-columns* must have a corresponding *column-<name>*
setting. All *column-<name>* settings accept golang text/template
syntax.
By default, these columns are defined:
```
column-date = {{.DateAutoFormat .Date.Local}}
column-name = {{index (.From | names) 0}}
column-flags = {{.Flags | join ""}}
column-subject = {{.Subject}}
```
See *aerc-templates*(7) for all available symbols and functions.
*timestamp-format* = _<timeformat>_
See time.Time#Format at https://godoc.org/time#Time.Format

View File

@@ -20,6 +20,10 @@ type TemplateData struct {
marked bool
msgNum int
// message list threading
ThreadSameSubject bool
ThreadPrefix string
// account config
myAddresses map[string]bool
account string
@@ -215,7 +219,10 @@ func (d *TemplateData) Subject() string {
case d.headers != nil:
subject = d.Header("subject")
}
return subject
if d.ThreadSameSubject {
subject = ""
}
return d.ThreadPrefix + subject
}
func (d *TemplateData) SubjectBase() string {

View File

@@ -126,6 +126,30 @@ func NewAerc(
}
}
if config.Ui.IndexFormat != "" {
ini := config.ColumnDefsToIni(
config.Ui.IndexColumns, "index-columns")
title := "DEPRECATION WARNING"
text := `
The index-format setting is deprecated. It has been replaced by index-columns.
Your configuration in this instance was automatically converted to:
[ui]
` + ini + `
Your configuration file was not changed. To make this change permanent and to
dismiss this deprecation warning on launch, copy the above lines into aerc.conf
and remove index-format from it. See aerc-config(5) for more details.
index-format will be removed in aerc 0.17.
`
aerc.AddDialog(NewSelectorDialog(
title, text, []string{"OK"}, 0,
aerc.SelectedAccountUiConfig(),
func(string, error) { aerc.CloseDialog() },
))
}
return aerc
}

View File

@@ -1,18 +1,17 @@
package widgets
import (
"bytes"
"fmt"
"math"
"strings"
sortthread "github.com/emersion/go-imap-sortthread"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/format"
"git.sr.ht/~rjarry/aerc/lib/iterator"
"git.sr.ht/~rjarry/aerc/lib/templates"
"git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
@@ -45,6 +44,13 @@ func (ml *MessageList) Invalidate() {
ui.Invalidate()
}
type messageRowParams struct {
uid uint32
needsHeaders bool
uiConfig *config.UIConfig
styles []config.StyleObject
}
func (ml *MessageList) Draw(ctx *ui.Context) {
ml.height = ctx.Height()
ml.width = ctx.Width()
@@ -54,25 +60,22 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
acct := ml.aerc.SelectedAccount()
store := ml.Store()
if store == nil || acct == nil {
if store == nil || acct == nil || len(store.Uids()) == 0 {
if ml.isInitalizing {
ml.spinner.Draw(ctx)
return
} else {
ml.spinner.Stop()
ml.drawEmptyMessage(ctx)
return
}
return
}
ml.UpdateScroller(ml.height, len(store.Uids()))
if store := ml.Store(); store != nil && len(store.Uids()) > 0 {
iter := store.UidsIterator()
for i := 0; iter.Next(); i++ {
if store.SelectedUid() == iter.Value().(uint32) {
ml.EnsureScroll(i)
break
}
iter := store.UidsIterator()
for i := 0; iter.Next(); i++ {
if store.SelectedUid() == iter.Value().(uint32) {
ml.EnsureScroll(i)
break
}
}
@@ -80,25 +83,57 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
if ml.NeedScrollbar() {
textWidth -= 1
}
if textWidth < 0 {
textWidth = 0
if textWidth <= 0 {
return
}
var (
needsHeaders []uint32
row int = 0
data := templates.NewTemplateData(
acct.acct.From,
acct.acct.Aliases,
acct.Name(),
acct.Directories().Selected(),
uiConfig.TimestampFormat,
uiConfig.ThisDayTimeFormat,
uiConfig.ThisWeekTimeFormat,
uiConfig.ThisYearTimeFormat,
uiConfig.IconAttachment,
)
createBaseCtx := func(uid uint32, row int) format.Ctx {
return format.Ctx{
FromAddress: format.AddressForHumans(acct.acct.From),
AccountName: acct.Name(),
MsgInfo: store.Messages[uid],
MsgNum: row,
MsgIsMarked: store.Marker().IsMarked(uid),
var needsHeaders []uint32
customDraw := func(t *ui.Table, r int, c *ui.Context) bool {
row := &t.Rows[r]
params, _ := row.Priv.(messageRowParams)
if params.needsHeaders {
needsHeaders = append(needsHeaders, params.uid)
ml.spinner.Draw(ctx.Subcontext(0, r, t.Width, 1))
return true
}
return false
}
getRowStyle := func(t *ui.Table, r int) tcell.Style {
var style tcell.Style
row := &t.Rows[r]
params, _ := row.Priv.(messageRowParams)
if params.uid == store.SelectedUid() {
style = params.uiConfig.GetComposedStyleSelected(
config.STYLE_MSGLIST_DEFAULT, params.styles)
} else {
style = params.uiConfig.GetComposedStyle(
config.STYLE_MSGLIST_DEFAULT, params.styles)
}
return style
}
table := ui.NewTable(
textWidth, ml.height,
uiConfig.IndexColumns,
uiConfig.ColumnSeparator,
customDraw,
getRowStyle,
)
if store.ThreadedView() {
var (
lastSubject string
@@ -126,23 +161,22 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
i++
continue
}
if thread := curIter.Value().(*types.Thread); thread != nil {
fmtCtx := createBaseCtx(thread.Uid, row)
fmtCtx.ThreadPrefix = threadPrefix(thread,
store.ReverseThreadOrder())
if fmtCtx.MsgInfo != nil && fmtCtx.MsgInfo.Envelope != nil {
baseSubject, _ := sortthread.GetBaseSubject(
fmtCtx.MsgInfo.Envelope.Subject)
fmtCtx.ThreadSameSubject = baseSubject == lastSubject &&
sameParent(thread, prevThread) &&
!isParent(thread)
lastSubject = baseSubject
prevThread = thread
}
if ml.drawRow(textWidth, ctx, thread.Uid, row, &needsHeaders, fmtCtx) {
break threadLoop
}
row += 1
thread := curIter.Value().(*types.Thread)
if thread == nil {
continue
}
baseSubject := data.SubjectBase()
data.ThreadSameSubject = baseSubject == lastSubject &&
sameParent(thread, prevThread) &&
!isParent(thread)
data.ThreadPrefix = threadPrefix(thread,
store.ReverseThreadOrder())
lastSubject = baseSubject
prevThread = thread
if addMessage(store, thread.Uid, &table, data, uiConfig) {
break threadLoop
}
}
}
@@ -153,14 +187,14 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
continue
}
uid := iter.Value().(uint32)
fmtCtx := createBaseCtx(uid, row)
if ml.drawRow(textWidth, ctx, uid, row, &needsHeaders, fmtCtx) {
if addMessage(store, uid, &table, data, uiConfig) {
break
}
row += 1
}
}
table.Draw(ctx)
if ml.NeedScrollbar() {
scrollbarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
ml.drawScrollbar(scrollbarCtx)
@@ -184,79 +218,60 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
}
func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row int, needsHeaders *[]uint32, fmtCtx format.Ctx) bool {
store := ml.store
func addMessage(
store *lib.MessageStore, uid uint32,
table *ui.Table, data *templates.TemplateData,
uiConfig *config.UIConfig,
) bool {
msg := store.Messages[uid]
acct := ml.aerc.SelectedAccount()
if row >= ctx.Height() || acct == nil {
return true
cells := make([]string, len(table.Columns))
params := messageRowParams{uid: uid}
if msg == nil || msg.Envelope == nil {
params.needsHeaders = true
return table.AddRow(cells, params)
}
if msg == nil {
*needsHeaders = append(*needsHeaders, uid)
ml.spinner.Draw(ctx.Subcontext(0, row, textWidth, 1))
return false
}
// 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
uiConfig := acct.Directories().UiConfig(store.DirInfo.Name)
if msg.Envelope != nil {
uiConfig = uiConfig.ForSubject(msg.Envelope.Subject)
}
msg_styles := []config.StyleObject{}
if msg.Flags.Has(models.SeenFlag) {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_READ)
params.styles = append(params.styles, config.STYLE_MSGLIST_READ)
} else {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_UNREAD)
params.styles = append(params.styles, config.STYLE_MSGLIST_UNREAD)
}
if msg.Flags.Has(models.FlaggedFlag) {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_FLAGGED)
params.styles = append(params.styles, config.STYLE_MSGLIST_FLAGGED)
}
// deleted message
if _, ok := store.Deleted[msg.Uid]; ok {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_DELETED)
params.styles = append(params.styles, config.STYLE_MSGLIST_DELETED)
}
// search result
if store.IsResult(msg.Uid) {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_RESULT)
params.styles = append(params.styles, config.STYLE_MSGLIST_RESULT)
}
// marked message
if store.Marker().IsMarked(msg.Uid) {
msg_styles = append(msg_styles, config.STYLE_MSGLIST_MARKED)
marked := store.Marker().IsMarked(msg.Uid)
if marked {
params.styles = append(params.styles, config.STYLE_MSGLIST_MARKED)
}
var style tcell.Style
// current row
if msg.Uid == ml.store.SelectedUid() {
style = uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, msg_styles)
} else {
style = uiConfig.GetComposedStyle(config.STYLE_MSGLIST_DEFAULT, msg_styles)
data.SetInfo(msg, len(table.Rows), marked)
for c, col := range table.Columns {
var buf bytes.Buffer
err := col.Def.Template.Execute(&buf, data)
if err != nil {
cells[c] = err.Error()
} else {
cells[c] = buf.String()
}
}
ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
fmtStr, args, err := format.ParseMessageFormat(
uiConfig.IndexFormat, uiConfig.TimestampFormat,
uiConfig.ThisDayTimeFormat,
uiConfig.ThisWeekTimeFormat,
uiConfig.ThisYearTimeFormat,
uiConfig.IconAttachment,
fmtCtx)
if err != nil {
ctx.Printf(0, row, style, "%v", err)
} else {
line := fmt.Sprintf(fmtStr, args...)
line = runewidth.Truncate(line, textWidth, "…")
ctx.Printf(0, row, style, "%s", line)
}
// TODO deprecate subject contextual UIs? Only related setting is
// styleset, should implement a better per-message styling method
params.uiConfig = uiConfig.ForSubject(msg.Envelope.Subject)
return false
return table.AddRow(cells, params)
}
func (ml *MessageList) drawScrollbar(ctx *ui.Context) {