---
config/aerc.conf.in | 6 +-
config/config.go | 4 +-
lib/indexformat.go | 230 ++++++++++++++++++++++++++++++++++++++++++++
widgets/msglist.go | 3 +-
4 files changed, 237 insertions(+), 6 deletions(-)
create mode 100644 lib/indexformat.go
diff --git a/config/aerc.conf.in b/config/aerc.conf.in
index 090e624..6d352ab 100644
--- a/config/aerc.conf.in
+++ b/config/aerc.conf.in
@@ -10,10 +10,10 @@
index-format=%4C %Z %D %-17.17n %s
#
-# See strftime(3)
+# See time.Time#Format
#
-# Default: %F %l:%M %p (ISO 8501 + 12 hour time)
-timestamp-format=%F %l:%M %p
+# Default: 2006-01-02 03:04 PM (ISO 8501 + 12 hour time)
+timestamp-format=2006-01-02 03:04 PM
#
# Width of the sidebar, including the border.
diff --git a/config/config.go b/config/config.go
index 1fb764b..600c0ba 100644
--- a/config/config.go
+++ b/config/config.go
@@ -242,8 +242,8 @@ func LoadConfig(root *string, sharedir string) (*AercConfig, error) {
Ini: file,
Ui: UIConfig{
- IndexFormat: "%4C %Z %D %-17.17n %s",
- TimestampFormat: "%F %l:%M %p",
+ IndexFormat: "%4C %Z %D %-17.17F %s",
+ TimestampFormat: "2006-01-02 03:04 PM",
ShowHeaders: []string{
"From", "To", "Cc", "Bcc", "Subject", "Date",
},
diff --git a/lib/indexformat.go b/lib/indexformat.go
new file mode 100644
index 0000000..fdda953
--- /dev/null
+++ b/lib/indexformat.go
@@ -0,0 +1,230 @@
+package lib
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "unicode"
+
+ "github.com/emersion/go-imap"
+
+ "git.sr.ht/~sircmpwn/aerc/config"
+ "git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+func ParseIndexFormat(conf *config.AercConfig, number int, msg *types.MessageInfo) (string, []interface{}) {
+ format := conf.Ui.IndexFormat
+ retval := make([]byte, 0, len(format))
+ var args []interface{}
+
+ var c rune
+ for i, ni := 0, 0; i < len(format); {
+ ni = strings.IndexByte(format[i:], '%')
+ if ni < 0 {
+ ni = len(format)
+ retval = append(retval, []byte(format[i:ni])...)
+ break
+ }
+ ni += i
+ ni++
+ // Check for fmt flags
+ if ni == len(format) {
+ goto handle_error
+ }
+ c = rune(format[ni])
+ if c == '+' || c == '-' || c == '#' || c == ' ' || c == '0' {
+ ni++
+ }
+
+ // Check for precision and width
+ if ni == len(format) {
+ goto handle_error
+ }
+ c = rune(format[ni])
+ for unicode.IsDigit(c) {
+ ni++
+ c = rune(format[ni])
+ }
+ if c == '.' {
+ ni++
+ c = rune(format[ni])
+ for unicode.IsDigit(c) {
+ ni++
+ c = rune(format[ni])
+ }
+ }
+
+ retval = append(retval, []byte(format[i:ni])...)
+ // Get final format verb
+ if ni == len(format) {
+ goto handle_error
+ }
+ c = rune(format[ni])
+ switch c {
+ case '%':
+ retval = append(retval, '%')
+ case 'a':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := msg.Envelope.From[0]
+ retval = append(retval, 's')
+ args = append(args, fmt.Sprintf("%s@%s", addr.MailboxName,
+ addr.HostName))
+ case 'A':
+ var addr *imap.Address
+ if len(msg.Envelope.ReplyTo) == 0 {
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ } else {
+ addr = msg.Envelope.From[0]
+ }
+ } else {
+ addr = msg.Envelope.ReplyTo[0]
+ }
+ retval = append(retval, 's')
+ args = append(args, fmt.Sprintf("%s@%s", addr.MailboxName,
+ addr.HostName))
+ case 'C':
+ retval = append(retval, 'd')
+ args = append(args, number)
+ case 'd':
+ retval = append(retval, 's')
+ args = append(args, msg.InternalDate.Format(conf.Ui.TimestampFormat))
+ case 'D':
+ retval = append(retval, 's')
+ args = append(args, msg.InternalDate.Local().Format(conf.Ui.TimestampFormat))
+ case 'e':
+ // TODO: current message number in thread
+ case 'E':
+ // TODO: number of messages in current thread
+ case 'f':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := formatAddresses(msg.Envelope.From[0:1])
+ retval = append(retval, 's')
+ args = append(args, addr)
+ case 'F':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := msg.Envelope.From[0]
+ // TODO: handle case when sender is you. Then
+ // use recipient's name
+ var val string
+ if addr.PersonalName != "" {
+ val = addr.PersonalName
+ } else {
+ val = fmt.Sprintf("%s@%s",
+ addr.MailboxName, addr.HostName)
+ }
+ retval = append(retval, 's')
+ args = append(args, val)
+
+ case 'H':
+ // TODO: spam attribute(s) of this message
+ case 'i':
+ retval = append(retval, 's')
+ args = append(args, msg.Envelope.MessageId)
+ case 'l':
+ // TODO: number of lines in the message
+ retval = append(retval, 'd')
+ args = append(args, msg.Size)
+ case 'L':
+ // TODO:
+ case 'n':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := msg.Envelope.From[0]
+ var val string
+ if addr.PersonalName != "" {
+ val = addr.PersonalName
+ } else {
+ val = fmt.Sprintf("%s@%s",
+ addr.MailboxName, addr.HostName)
+ }
+ retval = append(retval, 's')
+ args = append(args, val)
+ case 'r':
+ addrs := formatAddresses(msg.Envelope.To)
+ retval = append(retval, 's')
+ args = append(args, addrs)
+ case 'R':
+ addrs := formatAddresses(msg.Envelope.Cc)
+ retval = append(retval, 's')
+ args = append(args, addrs)
+ case 's':
+ retval = append(retval, 's')
+ args = append(args, msg.Envelope.Subject)
+ case 'u':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := msg.Envelope.From[0]
+ retval = append(retval, 's')
+ args = append(args, addr.MailboxName)
+ case 'v':
+ if len(msg.Envelope.From) == 0 {
+ goto handle_error
+ }
+ addr := msg.Envelope.From[0]
+ // check if message is form current user
+ if addr.PersonalName != "" {
+ retval = append(retval, 's')
+ args = append(args, strings.Split(addr.PersonalName, " ")[0])
+ }
+ case 'X':
+ // TODO: number of attachments
+ case 'y':
+ // TODO: X-Label field
+ case 'Y':
+ // TODO: X-Label field and some other constraints
+ case 'Z':
+ // calculate all flags
+ var readFlag = ""
+ var delFlag = ""
+ var flaggedFlag = ""
+ for _, flag := range msg.Flags {
+ if flag == "\\Seen" {
+ readFlag = "O" // message is old
+ } else if flag == "\\Recent" {
+ readFlag = "N" // message is new
+ } else if flag == "\\Answered" {
+ readFlag = "r" // message has been replied to
+ } else if flag == "\\Deleted" {
+ delFlag = "D"
+ // TODO: check if attachments
+ } else if flag == "\\Flagged" {
+ flaggedFlag = "!"
+ }
+ // TODO: check gpg stuff
+ }
+ retval = append(retval, '3', 's')
+ args = append(args, readFlag+delFlag+flaggedFlag)
+ }
+ i = ni + 1
+ }
+
+ return string(retval), args
+handle_error:
+ return "error: while parsing index format", nil
+}
+
+func formatAddresses(addrs []*imap.Address) string {
+ val := bytes.Buffer{}
+ for i, addr := range addrs {
+ if addr.PersonalName != "" {
+ val.WriteString(fmt.Sprintf("%s <%s@%s>",
+ addr.PersonalName, addr.MailboxName, addr.HostName))
+ } else {
+ val.WriteString(fmt.Sprintf("%s@%s",
+ addr.MailboxName, addr.HostName))
+ }
+ if i != len(addrs)-1 {
+ val.WriteString(", ")
+ }
+ }
+ return val.String()
+}
diff --git a/widgets/msglist.go b/widgets/msglist.go
index caa868f..5188540 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -80,7 +80,8 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
style = style.Foreground(tcell.ColorGray)
}
ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
- ctx.Printf(0, row, style, "%s", msg.Envelope.Subject)
+ fmtStr, args := lib.ParseIndexFormat(ml.conf, i, msg)
+ ctx.Printf(0, row, style, fmtStr, args...)
row += 1
}
--
2.21.0
This looks pretty good! A few comments, though.
On Thu Jun 6, 2019 at 6:33 PM Yash Srivastav wrote:
> diff --git a/config/aerc.conf.in b/config/aerc.conf.in
> index 090e624..6d352ab 100644
> --- a/config/aerc.conf.in
> +++ b/config/aerc.conf.in
> @@ -10,10 +10,10 @@
> index-format=%4C %Z %D %-17.17n %s
Let's make the default "%D %-17.17n %s" while we're here
> diff --git a/config/config.go b/config/config.go
> index 1fb764b..600c0ba 100644
> --- a/config/config.go
> +++ b/config/config.go
> @@ -242,8 +242,8 @@ func LoadConfig(root *string, sharedir string) (*AercConfig, error) {
> Ini: file,
>
> Ui: UIConfig{
> - IndexFormat: "%4C %Z %D %-17.17n %s",
> - TimestampFormat: "%F %l:%M %p",
> + IndexFormat: "%4C %Z %D %-17.17F %s",
> + TimestampFormat: "2006-01-02 03:04 PM",
Here too
> diff --git a/lib/indexformat.go b/lib/indexformat.go
> new file mode 100644
> index 0000000..fdda953
> --- /dev/null
> +++ b/lib/indexformat.go
> -%<
> +func ParseIndexFormat(conf *config.AercConfig, number int, msg *types.MessageInfo) (string, []interface{}) {
This needs to be wrapped to 80 cols. We should also return an error, not
a string. errors.New("error message...")
> + if ni < 0 {
> + ni = len(format)
> + retval = append(retval, []byte(format[i:ni])...)
> + break
> + }
> + ni += i
> + ni++
This can be simplified to ni += i + 1