implements ability to view headers in message view
This commit is contained in:
parent
2279ac3ab3
commit
b83e7c9fa6
6 changed files with 146 additions and 54 deletions
25
commands/msgview/toggle-headers.go
Normal file
25
commands/msgview/toggle-headers.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package msgview
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.sr.ht/~sircmpwn/aerc/widgets"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("toggle-headers", ToggleHeaders)
|
||||
}
|
||||
|
||||
func toggleHeadersUsage(cmd string) error {
|
||||
return errors.New(fmt.Sprintf("Usage: %s", cmd))
|
||||
}
|
||||
|
||||
func ToggleHeaders(aerc *widgets.Aerc, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return toggleHeadersUsage(args[0])
|
||||
}
|
||||
mv, _ := aerc.SelectedTab().(*widgets.MessageViewer)
|
||||
mv.ToggleHeaders()
|
||||
return nil
|
||||
}
|
|
@ -44,6 +44,13 @@ pager=less -R
|
|||
# Default: text/plain,text/html
|
||||
alternatives=text/plain,text/html
|
||||
|
||||
#
|
||||
# Default setting to determine whether to show full headers or only parsed
|
||||
# ones in message viewer.
|
||||
#
|
||||
# Default: false
|
||||
show-headers=false
|
||||
|
||||
[compose]
|
||||
#
|
||||
# Specifies the command to run the editor with. It will be shown in an embedded
|
||||
|
|
|
@ -72,6 +72,7 @@ type FilterConfig struct {
|
|||
type ViewerConfig struct {
|
||||
Pager string
|
||||
Alternatives []string
|
||||
ShowHeaders bool `ini:"show-headers"`
|
||||
}
|
||||
|
||||
type AercConfig struct {
|
||||
|
|
|
@ -35,8 +35,9 @@ type MessageViewer struct {
|
|||
|
||||
type PartSwitcher struct {
|
||||
ui.Invalidatable
|
||||
parts []*PartViewer
|
||||
selected int
|
||||
parts []*PartViewer
|
||||
selected int
|
||||
showHeaders bool
|
||||
}
|
||||
|
||||
func formatAddresses(addrs []*imap.Address) string {
|
||||
|
@ -98,33 +99,10 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
|||
}).At(2, 0).Span(1, 2)
|
||||
headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2)
|
||||
|
||||
var err error
|
||||
switcher := &PartSwitcher{}
|
||||
if len(msg.BodyStructure.Parts) == 0 {
|
||||
pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure, []int{1})
|
||||
if err != nil {
|
||||
goto handle_error
|
||||
}
|
||||
switcher.parts = []*PartViewer{pv}
|
||||
pv.OnInvalidate(func(_ ui.Drawable) {
|
||||
switcher.Invalidate()
|
||||
})
|
||||
} else {
|
||||
switcher.parts, err = enumerateParts(conf, store,
|
||||
msg, msg.BodyStructure, []int{})
|
||||
if err != nil {
|
||||
goto handle_error
|
||||
}
|
||||
switcher.selected = -1
|
||||
for i, pv := range switcher.parts {
|
||||
pv.OnInvalidate(func(_ ui.Drawable) {
|
||||
switcher.Invalidate()
|
||||
})
|
||||
// TODO: switch to user's preferred mimetype, if configured
|
||||
if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
|
||||
switcher.selected = i
|
||||
}
|
||||
}
|
||||
err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders)
|
||||
if err != nil {
|
||||
goto handle_error
|
||||
}
|
||||
|
||||
grid.AddChild(headers).At(0, 0)
|
||||
|
@ -132,6 +110,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
|||
|
||||
return &MessageViewer{
|
||||
acct: acct,
|
||||
conf: conf,
|
||||
grid: grid,
|
||||
msg: msg,
|
||||
store: store,
|
||||
|
@ -148,7 +127,7 @@ handle_error:
|
|||
|
||||
func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
||||
msg *types.MessageInfo, body *imap.BodyStructure,
|
||||
index []int) ([]*PartViewer, error) {
|
||||
showHeaders bool, index []int) ([]*PartViewer, error) {
|
||||
|
||||
var parts []*PartViewer
|
||||
for i, part := range body.Parts {
|
||||
|
@ -158,14 +137,14 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
|||
pv := &PartViewer{part: part}
|
||||
parts = append(parts, pv)
|
||||
subParts, err := enumerateParts(
|
||||
conf, store, msg, part, curindex)
|
||||
conf, store, msg, part, showHeaders, curindex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts = append(parts, subParts...)
|
||||
continue
|
||||
}
|
||||
pv, err := NewPartViewer(conf, store, msg, part, curindex)
|
||||
pv, err := NewPartViewer(conf, store, msg, part, showHeaders, curindex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -174,6 +153,44 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
|||
return parts, nil
|
||||
}
|
||||
|
||||
func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
|
||||
store *lib.MessageStore, msg *types.MessageInfo, showHeaders bool) error {
|
||||
var err error
|
||||
switcher.showHeaders = showHeaders
|
||||
|
||||
if showHeaders {
|
||||
}
|
||||
|
||||
if len(msg.BodyStructure.Parts) == 0 {
|
||||
pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure,
|
||||
showHeaders, []int{1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switcher.parts = []*PartViewer{pv}
|
||||
pv.OnInvalidate(func(_ ui.Drawable) {
|
||||
switcher.Invalidate()
|
||||
})
|
||||
} else {
|
||||
switcher.parts, err = enumerateParts(conf, store,
|
||||
msg, msg.BodyStructure, showHeaders, []int{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switcher.selected = -1
|
||||
for i, pv := range switcher.parts {
|
||||
pv.OnInvalidate(func(_ ui.Drawable) {
|
||||
switcher.Invalidate()
|
||||
})
|
||||
// TODO: switch to user's preferred mimetype, if configured
|
||||
if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
|
||||
switcher.selected = i
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Draw(ctx *ui.Context) {
|
||||
if mv.err != nil {
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||
|
@ -205,6 +222,15 @@ func (mv *MessageViewer) SelectedMessage() *types.MessageInfo {
|
|||
return mv.msg
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) ToggleHeaders() {
|
||||
switcher := mv.switcher
|
||||
err := createSwitcher(switcher, mv.conf, mv.store, mv.msg, !switcher.showHeaders)
|
||||
if err != nil {
|
||||
mv.acct.Logger().Printf("warning: error during create switcher - %v", err)
|
||||
}
|
||||
switcher.Invalidate()
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) CurrentPart() *PartInfo {
|
||||
switcher := mv.switcher
|
||||
part := switcher.parts[switcher.selected]
|
||||
|
@ -295,18 +321,19 @@ func (mv *MessageViewer) Focus(focus bool) {
|
|||
|
||||
type PartViewer struct {
|
||||
ui.Invalidatable
|
||||
err error
|
||||
fetched bool
|
||||
filter *exec.Cmd
|
||||
index []int
|
||||
msg *types.MessageInfo
|
||||
pager *exec.Cmd
|
||||
pagerin io.WriteCloser
|
||||
part *imap.BodyStructure
|
||||
sink io.WriteCloser
|
||||
source io.Reader
|
||||
store *lib.MessageStore
|
||||
term *Terminal
|
||||
err error
|
||||
fetched bool
|
||||
filter *exec.Cmd
|
||||
index []int
|
||||
msg *types.MessageInfo
|
||||
pager *exec.Cmd
|
||||
pagerin io.WriteCloser
|
||||
part *imap.BodyStructure
|
||||
showHeaders bool
|
||||
sink io.WriteCloser
|
||||
source io.Reader
|
||||
store *lib.MessageStore
|
||||
term *Terminal
|
||||
}
|
||||
|
||||
type PartInfo struct {
|
||||
|
@ -318,7 +345,8 @@ type PartInfo struct {
|
|||
|
||||
func NewPartViewer(conf *config.AercConfig,
|
||||
store *lib.MessageStore, msg *types.MessageInfo,
|
||||
part *imap.BodyStructure, index []int) (*PartViewer, error) {
|
||||
part *imap.BodyStructure, showHeaders bool,
|
||||
index []int) (*PartViewer, error) {
|
||||
|
||||
var (
|
||||
filter *exec.Cmd
|
||||
|
@ -375,15 +403,16 @@ func NewPartViewer(conf *config.AercConfig,
|
|||
}
|
||||
|
||||
pv := &PartViewer{
|
||||
filter: filter,
|
||||
index: index,
|
||||
msg: msg,
|
||||
pager: pager,
|
||||
pagerin: pagerin,
|
||||
part: part,
|
||||
sink: pipe,
|
||||
store: store,
|
||||
term: term,
|
||||
filter: filter,
|
||||
index: index,
|
||||
msg: msg,
|
||||
pager: pager,
|
||||
pagerin: pagerin,
|
||||
part: part,
|
||||
showHeaders: showHeaders,
|
||||
sink: pipe,
|
||||
store: store,
|
||||
term: term,
|
||||
}
|
||||
|
||||
if term != nil {
|
||||
|
@ -439,6 +468,15 @@ func (pv *PartViewer) attemptCopy() {
|
|||
}()
|
||||
}
|
||||
go func() {
|
||||
if pv.showHeaders && pv.msg.RFC822Headers != nil {
|
||||
fields := pv.msg.RFC822Headers.Fields()
|
||||
for fields.Next() {
|
||||
field := fmt.Sprintf("%s: %s\n", fields.Key(), fields.Value())
|
||||
pv.sink.Write([]byte(field))
|
||||
}
|
||||
pv.sink.Write([]byte{'\n'})
|
||||
}
|
||||
|
||||
entity, err := message.New(header, pv.source)
|
||||
if err != nil {
|
||||
pv.err = err
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package imap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/emersion/go-message/mail"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
|
||||
"git.sr.ht/~sircmpwn/aerc/worker/types"
|
||||
)
|
||||
|
@ -10,15 +15,22 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
|
|||
msg *types.FetchMessageHeaders) {
|
||||
|
||||
imapw.worker.Logger.Printf("Fetching message headers")
|
||||
section := &imap.BodySectionName{
|
||||
BodyPartName: imap.BodyPartName{
|
||||
Specifier: imap.HeaderSpecifier,
|
||||
},
|
||||
}
|
||||
|
||||
items := []imap.FetchItem{
|
||||
imap.FetchBodyStructure,
|
||||
imap.FetchEnvelope,
|
||||
imap.FetchInternalDate,
|
||||
imap.FetchFlags,
|
||||
imap.FetchUid,
|
||||
section.FetchItem(),
|
||||
}
|
||||
|
||||
imapw.handleFetchMessages(msg, &msg.Uids, items, nil)
|
||||
imapw.handleFetchMessages(msg, &msg.Uids, items, section)
|
||||
}
|
||||
|
||||
func (imapw *IMAPWorker) handleFetchMessageBodyPart(
|
||||
|
@ -54,12 +66,19 @@ func (imapw *IMAPWorker) handleFetchMessages(
|
|||
imapw.seqMap[_msg.SeqNum-1] = _msg.Uid
|
||||
switch msg.(type) {
|
||||
case *types.FetchMessageHeaders:
|
||||
reader := _msg.GetBody(section)
|
||||
textprotoHeader, err := textproto.ReadHeader(bufio.NewReader(reader))
|
||||
var header *mail.Header
|
||||
if err == nil {
|
||||
header = &mail.Header{message.Header{textprotoHeader}}
|
||||
}
|
||||
imapw.worker.PostMessage(&types.MessageInfo{
|
||||
Message: types.RespondTo(msg),
|
||||
BodyStructure: _msg.BodyStructure,
|
||||
Envelope: _msg.Envelope,
|
||||
Flags: _msg.Flags,
|
||||
InternalDate: _msg.InternalDate,
|
||||
RFC822Headers: header,
|
||||
Uid: _msg.Uid,
|
||||
}, nil)
|
||||
case *types.FetchFullMessages:
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-message/mail"
|
||||
|
||||
"git.sr.ht/~sircmpwn/aerc/config"
|
||||
)
|
||||
|
@ -145,6 +146,7 @@ type MessageInfo struct {
|
|||
Envelope *imap.Envelope
|
||||
Flags []string
|
||||
InternalDate time.Time
|
||||
RFC822Headers *mail.Header
|
||||
Size uint32
|
||||
Uid uint32
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue