envelope: display message envelope info
Display entire message envelope in a user-friendly dialog popup with the :envelope command. All header fields can be displayed with the -h flag. Fixes: https://todo.sr.ht/~rjarry/aerc/85 Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
b860a64622
commit
19e2750255
3 changed files with 165 additions and 0 deletions
|
@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [Unreleased](https://git.sr.ht/~rjarry/aerc/log/master)
|
||||
|
||||
### Added
|
||||
|
||||
- View common email envelope headers with `:envelope`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `:pipe -m git am -3` on patch series when `Message-Id` headers have not been
|
||||
|
|
152
commands/msg/envelope.go
Normal file
152
commands/msg/envelope.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package msg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/format"
|
||||
"git.sr.ht/~rjarry/aerc/logging"
|
||||
"git.sr.ht/~rjarry/aerc/models"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
"git.sr.ht/~sircmpwn/getopt"
|
||||
"github.com/emersion/go-message/mail"
|
||||
)
|
||||
|
||||
type Envelope struct{}
|
||||
|
||||
func init() {
|
||||
register(Envelope{})
|
||||
}
|
||||
|
||||
func (Envelope) Aliases() []string {
|
||||
return []string{"envelope"}
|
||||
}
|
||||
|
||||
func (Envelope) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Envelope) Execute(aerc *widgets.Aerc, args []string) error {
|
||||
header := false
|
||||
fmtStr := "%-20.20s: %s"
|
||||
opts, _, err := getopt.Getopts(args, "hs:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, opt := range opts {
|
||||
switch opt.Option {
|
||||
case 's':
|
||||
fmtStr = opt.Value
|
||||
case 'h':
|
||||
header = true
|
||||
}
|
||||
}
|
||||
|
||||
acct := aerc.SelectedAccount()
|
||||
if acct == nil {
|
||||
return errors.New("No account selected")
|
||||
}
|
||||
|
||||
var list []string
|
||||
if msg, err := acct.SelectedMessage(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if msg != nil {
|
||||
if header {
|
||||
list = parseHeader(msg, fmtStr)
|
||||
} else {
|
||||
list = parseEnvelope(msg, fmtStr,
|
||||
acct.UiConfig().TimestampFormat)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Selected message is empty.")
|
||||
}
|
||||
}
|
||||
|
||||
n := len(list)
|
||||
aerc.AddDialog(widgets.NewDialog(
|
||||
widgets.NewListBox(
|
||||
"Message Envelope. Press <Esc> or <Enter> to close. "+
|
||||
"Start typing to filter.",
|
||||
list,
|
||||
aerc.SelectedAccountUiConfig(),
|
||||
func(_ string) {
|
||||
aerc.CloseDialog()
|
||||
},
|
||||
),
|
||||
// start pos on screen
|
||||
func(h int) int {
|
||||
if n < h/8*6 {
|
||||
return h/2 - n/2 - 1
|
||||
}
|
||||
return h / 8
|
||||
},
|
||||
// dialog height
|
||||
func(h int) int {
|
||||
if n < h/8*6 {
|
||||
return n + 2
|
||||
}
|
||||
return h / 8 * 6
|
||||
},
|
||||
))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEnvelope(msg *models.MessageInfo, fmtStr, fmtTime string,
|
||||
) (result []string) {
|
||||
if envlp := msg.Envelope; envlp != nil {
|
||||
addStr := func(key, text string) {
|
||||
result = append(result, fmt.Sprintf(fmtStr, key, text))
|
||||
}
|
||||
addAddr := func(key string, ls []*mail.Address) {
|
||||
for _, l := range ls {
|
||||
result = append(result,
|
||||
fmt.Sprintf(fmtStr, key,
|
||||
format.AddressForHumans(l)))
|
||||
}
|
||||
}
|
||||
|
||||
addStr("Date", envlp.Date.Format(fmtTime))
|
||||
addStr("Subject", envlp.Subject)
|
||||
addStr("Message-Id", envlp.MessageId)
|
||||
|
||||
addAddr("From", envlp.From)
|
||||
addAddr("To", envlp.To)
|
||||
addAddr("ReplyTo", envlp.ReplyTo)
|
||||
addAddr("Cc", envlp.Cc)
|
||||
addAddr("Bcc", envlp.Bcc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseHeader(msg *models.MessageInfo, fmtStr string) (result []string) {
|
||||
if h := msg.RFC822Headers; h != nil {
|
||||
hf := h.Fields()
|
||||
for hf.Next() {
|
||||
text, err := hf.Text()
|
||||
if err != nil {
|
||||
logging.Errorf(err.Error())
|
||||
text = hf.Value()
|
||||
}
|
||||
result = append(result,
|
||||
headerExpand(fmtStr, hf.Key(), text)...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func headerExpand(fmtStr, key, text string) []string {
|
||||
var result []string
|
||||
switch strings.ToLower(key) {
|
||||
case "to", "from", "bcc", "cc":
|
||||
for _, item := range strings.Split(text, ",") {
|
||||
result = append(result, fmt.Sprintf(fmtStr, key,
|
||||
strings.TrimSpace(item)))
|
||||
}
|
||||
default:
|
||||
result = append(result, fmt.Sprintf(fmtStr, key, text))
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -162,6 +162,15 @@ message list, the message in the message viewer, etc).
|
|||
*delete-message*
|
||||
Deletes the selected message.
|
||||
|
||||
*envelope [-h] [-s <format-specifier>]*
|
||||
Opens the message envelope in a dialog popup.
|
||||
|
||||
*-h*: Show all header fields
|
||||
|
||||
*-s* <format-specifier>
|
||||
User-defined format specifier requiring two %s for the key and
|
||||
value strings. Default format: '%-20.20s: %s'
|
||||
|
||||
*recall* [-f]
|
||||
Opens the selected message for re-editing. Messages can only be
|
||||
recalled from the postpone directory. The original message is deleted.
|
||||
|
|
Loading…
Reference in a new issue