Flesh out multipart switcher
This commit is contained in:
parent
3376f926ed
commit
511fea3944
5 changed files with 146 additions and 70 deletions
|
@ -123,8 +123,8 @@ func Reply(aerc *widgets.Aerc, args []string) error {
|
|||
}
|
||||
|
||||
if quote {
|
||||
// TODO: something more intelligent than fetching the 0th part
|
||||
store.FetchBodyPart(msg.Uid, 0, func(reader io.Reader) {
|
||||
// TODO: something more intelligent than fetching the 1st part
|
||||
store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) {
|
||||
header := message.Header{}
|
||||
header.SetText(
|
||||
"Content-Transfer-Encoding", msg.BodyStructure.Encoding)
|
||||
|
|
|
@ -90,7 +90,7 @@ func (store *MessageStore) FetchFull(uids []uint32, cb func(io.Reader)) {
|
|||
}
|
||||
|
||||
func (store *MessageStore) FetchBodyPart(
|
||||
uid uint32, part int, cb func(io.Reader)) {
|
||||
uid uint32, part []int, cb func(io.Reader)) {
|
||||
|
||||
store.worker.PostAction(&types.FetchMessageBodyPart{
|
||||
Uid: uid,
|
||||
|
|
|
@ -23,10 +23,17 @@ import (
|
|||
)
|
||||
|
||||
type MessageViewer struct {
|
||||
ui.Invalidatable
|
||||
conf *config.AercConfig
|
||||
err error
|
||||
msg *types.MessageInfo
|
||||
grid *ui.Grid
|
||||
msg *types.MessageInfo
|
||||
switcher *PartSwitcher
|
||||
store *lib.MessageStore
|
||||
}
|
||||
|
||||
type PartSwitcher struct {
|
||||
ui.Invalidatable
|
||||
parts []*PartViewer
|
||||
selected int
|
||||
}
|
||||
|
@ -48,8 +55,8 @@ func formatAddresses(addrs []*imap.Address) string {
|
|||
return val.String()
|
||||
}
|
||||
|
||||
func NewMessageViewer(conf *config.AercConfig, store *lib.MessageStore,
|
||||
msg *types.MessageInfo) *MessageViewer {
|
||||
func NewMessageViewer(conf *config.AercConfig,
|
||||
store *lib.MessageStore, msg *types.MessageInfo) *MessageViewer {
|
||||
|
||||
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||
{ui.SIZE_EXACT, 3}, // TODO: Based on number of header rows
|
||||
|
@ -91,28 +98,51 @@ func NewMessageViewer(conf *config.AercConfig, store *lib.MessageStore,
|
|||
{ui.SIZE_EXACT, 20},
|
||||
})
|
||||
|
||||
for i, part := range msg.BodyStructure.Parts {
|
||||
fmt.Println(i, part.MIMEType, part.MIMESubType)
|
||||
}
|
||||
var (
|
||||
err error
|
||||
mv *MessageViewer
|
||||
)
|
||||
|
||||
// TODO: add multipart switcher and configure additional parts
|
||||
pv, err := NewPartViewer(conf, msg, 0)
|
||||
if err != nil {
|
||||
goto handle_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
|
||||
}
|
||||
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 == 0 && pv.part.MIMEType != "multipart" {
|
||||
switcher.selected = i
|
||||
}
|
||||
}
|
||||
}
|
||||
body.AddChild(pv).At(0, 0).Span(1, 2)
|
||||
|
||||
grid.AddChild(headers).At(0, 0)
|
||||
grid.AddChild(body).At(1, 0)
|
||||
|
||||
store.FetchBodyPart(msg.Uid, 0, pv.SetSource)
|
||||
|
||||
return &MessageViewer{
|
||||
grid: grid,
|
||||
msg: msg,
|
||||
parts: []*PartViewer{pv},
|
||||
mv = &MessageViewer{
|
||||
grid: grid,
|
||||
msg: msg,
|
||||
store: store,
|
||||
switcher: switcher,
|
||||
}
|
||||
|
||||
body.AddChild(mv.switcher).At(0, 0).Span(1, 2)
|
||||
return mv
|
||||
|
||||
handle_error:
|
||||
return &MessageViewer{
|
||||
err: err,
|
||||
|
@ -121,6 +151,34 @@ handle_error:
|
|||
}
|
||||
}
|
||||
|
||||
func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
|
||||
msg *types.MessageInfo, body *imap.BodyStructure,
|
||||
index []int) ([]*PartViewer, error) {
|
||||
|
||||
var parts []*PartViewer
|
||||
for i, part := range body.Parts {
|
||||
curindex := append(index, i+1)
|
||||
if part.MIMEType == "multipart" {
|
||||
// Multipart meta-parts are faked
|
||||
pv := &PartViewer{part: part}
|
||||
parts = append(parts, pv)
|
||||
subParts, err := enumerateParts(
|
||||
conf, store, msg, part, curindex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts = append(parts, subParts...)
|
||||
continue
|
||||
}
|
||||
pv, err := NewPartViewer(conf, store, msg, part, curindex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts = append(parts, pv)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Draw(ctx *ui.Context) {
|
||||
if mv.err != nil {
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||
|
@ -140,44 +198,69 @@ func (mv *MessageViewer) OnInvalidate(fn func(d ui.Drawable)) {
|
|||
})
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Event(event tcell.Event) bool {
|
||||
// What is encapsulation even
|
||||
if mv.parts[mv.selected].term != nil {
|
||||
return mv.parts[mv.selected].term.Event(event)
|
||||
func (ps *PartSwitcher) Invalidate() {
|
||||
ps.DoInvalidate(ps)
|
||||
}
|
||||
|
||||
func (ps *PartSwitcher) Focus(focus bool) {
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(focus)
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PartSwitcher) Event(event tcell.Event) bool {
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
return ps.parts[ps.selected].term.Event(event)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Focus(focus bool) {
|
||||
if mv.parts[mv.selected].term != nil {
|
||||
mv.parts[mv.selected].term.Focus(focus)
|
||||
func (ps *PartSwitcher) Draw(ctx *ui.Context) {
|
||||
height := len(ps.parts)
|
||||
if height == 1 {
|
||||
ps.parts[ps.selected].Draw(ctx)
|
||||
return
|
||||
}
|
||||
// TODO: cap height and add scrolling for messages with many parts
|
||||
y := ctx.Height() - height
|
||||
for i, part := range ps.parts {
|
||||
style := tcell.StyleDefault.Reverse(ps.selected == i)
|
||||
ctx.Fill(0, y+i, ctx.Width(), 1, ' ', style)
|
||||
ctx.Printf(len(part.index)*2, y+i, style, "%s/%s",
|
||||
strings.ToLower(part.part.MIMEType),
|
||||
strings.ToLower(part.part.MIMESubType))
|
||||
}
|
||||
ps.parts[ps.selected].Draw(ctx.Subcontext(
|
||||
0, 0, ctx.Width(), ctx.Height()-height))
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Event(event tcell.Event) bool {
|
||||
return mv.switcher.Event(event)
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Focus(focus bool) {
|
||||
mv.switcher.Focus(focus)
|
||||
}
|
||||
|
||||
type PartViewer struct {
|
||||
ui.Invalidatable
|
||||
err error
|
||||
fetched bool
|
||||
filter *exec.Cmd
|
||||
index string
|
||||
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
|
||||
}
|
||||
|
||||
func NewPartViewer(conf *config.AercConfig,
|
||||
msg *types.MessageInfo, index int) (*PartViewer, error) {
|
||||
var (
|
||||
part *imap.BodyStructure
|
||||
)
|
||||
// TODO: Find IMAP index, which may differ
|
||||
if len(msg.BodyStructure.Parts) != 0 {
|
||||
part = msg.BodyStructure.Parts[index]
|
||||
} else {
|
||||
part = msg.BodyStructure
|
||||
}
|
||||
store *lib.MessageStore, msg *types.MessageInfo,
|
||||
part *imap.BodyStructure, index []int) (*PartViewer, error) {
|
||||
|
||||
var (
|
||||
filter *exec.Cmd
|
||||
|
@ -228,26 +311,30 @@ func NewPartViewer(conf *config.AercConfig,
|
|||
if pagerin, _ = pager.StdinPipe(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if pipe, err = pager.StdinPipe(); err != nil {
|
||||
if term, err = NewTerminal(pager); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if term, err = NewTerminal(pager); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pv := &PartViewer{
|
||||
filter: filter,
|
||||
index: index, // TODO: Nested multipart does indicies differently
|
||||
msg: msg,
|
||||
pager: pager,
|
||||
pagerin: pagerin,
|
||||
part: part,
|
||||
sink: pipe,
|
||||
store: store,
|
||||
term: term,
|
||||
}
|
||||
|
||||
term.OnStart = func() {
|
||||
pv.attemptCopy()
|
||||
if term != nil {
|
||||
term.OnStart = func() {
|
||||
pv.attemptCopy()
|
||||
}
|
||||
term.OnInvalidate(func(_ ui.Drawable) {
|
||||
pv.Invalidate()
|
||||
})
|
||||
}
|
||||
|
||||
return pv, nil
|
||||
|
@ -297,17 +384,22 @@ func (pv *PartViewer) attemptCopy() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pv *PartViewer) OnInvalidate(fn func(ui.Drawable)) {
|
||||
pv.term.OnInvalidate(func(_ ui.Drawable) {
|
||||
fn(pv)
|
||||
})
|
||||
}
|
||||
|
||||
func (pv *PartViewer) Invalidate() {
|
||||
pv.term.Invalidate()
|
||||
pv.DoInvalidate(pv)
|
||||
}
|
||||
|
||||
func (pv *PartViewer) Draw(ctx *ui.Context) {
|
||||
if pv.filter == nil {
|
||||
// TODO: Let them download it directly or something
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||
ctx.Printf(0, 0, tcell.StyleDefault,
|
||||
"No filter configured for this mimetype")
|
||||
return
|
||||
}
|
||||
if !pv.fetched {
|
||||
pv.store.FetchBodyPart(pv.msg.Uid, pv.index, pv.SetSource)
|
||||
pv.fetched = true
|
||||
}
|
||||
if pv.err != nil {
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||
ctx.Printf(0, 0, tcell.StyleDefault, "%s", pv.err.Error())
|
||||
|
@ -347,19 +439,3 @@ func (hv *HeaderView) Draw(ctx *ui.Context) {
|
|||
func (hv *HeaderView) Invalidate() {
|
||||
hv.DoInvalidate(hv)
|
||||
}
|
||||
|
||||
type MultipartView struct {
|
||||
ui.Invalidatable
|
||||
}
|
||||
|
||||
func (mpv *MultipartView) Draw(ctx *ui.Context) {
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
|
||||
ctx.Fill(0, 0, ctx.Width(), 1, ' ', tcell.StyleDefault.Reverse(true))
|
||||
ctx.Printf(0, 0, tcell.StyleDefault.Reverse(true), "text/plain")
|
||||
ctx.Printf(0, 1, tcell.StyleDefault, "text/html")
|
||||
ctx.Printf(0, 2, tcell.StyleDefault, "application/pgp-si…")
|
||||
}
|
||||
|
||||
func (mpv *MultipartView) Invalidate() {
|
||||
mpv.DoInvalidate(mpv)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func (imapw *IMAPWorker) handleFetchMessageBodyPart(
|
|||
|
||||
imapw.worker.Logger.Printf("Fetching message part")
|
||||
section := &imap.BodySectionName{}
|
||||
section.Path = []int{msg.Part + 1}
|
||||
section.Path = msg.Part
|
||||
items := []imap.FetchItem{section.FetchItem()}
|
||||
uids := imap.SeqSet{}
|
||||
uids.AddNum(msg.Uid)
|
||||
|
|
|
@ -94,7 +94,7 @@ type FetchFullMessages struct {
|
|||
type FetchMessageBodyPart struct {
|
||||
Message
|
||||
Uid uint32
|
||||
Part int
|
||||
Part []int
|
||||
}
|
||||
|
||||
type DeleteMessages struct {
|
||||
|
|
Loading…
Reference in a new issue