aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

commit 475b697bdfd7a5821282174f14f8d904e47aff4d
parent 2b3e123cb86f9b4c5853e31d9e76c2b0d083f90a
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 16 May 2019 12:15:34 -0400

Implement (basic form) of :reply

Diffstat:
Acommands/account/reply.go | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgo.mod | 1+
Mgo.sum | 2++
Alib/msgid.go | 34++++++++++++++++++++++++++++++++++
Mwidgets/account.go | 12++++++++----
Mwidgets/compose.go | 14+++++++++++---
6 files changed, 139 insertions(+), 7 deletions(-)

diff --git a/commands/account/reply.go b/commands/account/reply.go @@ -0,0 +1,83 @@ +package account + +import ( + "errors" + "fmt" + "strings" + + "github.com/emersion/go-imap" + + "git.sr.ht/~sircmpwn/aerc2/widgets" +) + +func init() { + register("reply", Reply) +} + +func Reply(aerc *widgets.Aerc, args []string) error { + if len(args) != 1 { + return errors.New("Usage: reply [-aq]") + } + // TODO: Reply all (w/ getopt) + + acct := aerc.SelectedAccount() + msg := acct.Messages().Selected() + acct.Logger().Println("Replying to email " + msg.Envelope.MessageId) + + var ( + to []string + cc []string + toList []*imap.Address + ) + if len(msg.Envelope.ReplyTo) != 0 { + toList = msg.Envelope.ReplyTo + } else { + toList = msg.Envelope.From + } + for _, addr := range toList { + if addr.PersonalName != "" { + to = append(to, fmt.Sprintf("%s <%s@%s>", + addr.PersonalName, addr.MailboxName, addr.HostName)) + } else { + to = append(to, fmt.Sprintf("<%s@%s>", + addr.MailboxName, addr.HostName)) + } + } + // TODO: Only if reply all + for _, addr := range msg.Envelope.Cc { + if addr.PersonalName != "" { + cc = append(cc, fmt.Sprintf("%s <%s@%s>", + addr.PersonalName, addr.MailboxName, addr.HostName)) + } else { + cc = append(cc, fmt.Sprintf("<%s@%s>", + addr.MailboxName, addr.HostName)) + } + } + + subject := "Re: " + msg.Envelope.Subject + + composer := widgets.NewComposer( + aerc.Config(), acct.AccountConfig(), acct.Worker()). + Defaults(map[string]string{ + "To": strings.Join(to, ","), + "Cc": strings.Join(cc, ","), + "Subject": subject, + "In-Reply-To": msg.Envelope.MessageId, + }). + FocusTerminal() + + tab := aerc.NewTab(composer, subject) + + composer.OnSubjectChange(func(subject string) { + if subject == "" { + tab.Name = "New email" + } else { + tab.Name = subject + } + tab.Content.Invalidate() + }) + + return nil +} + + diff --git a/go.mod b/go.mod @@ -15,6 +15,7 @@ require ( github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c // indirect + github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41 github.com/mattn/go-isatty v0.0.3 github.com/mattn/go-runewidth v0.0.2 github.com/miolini/datacounter v0.0.0-20171104152933-fd4e42a1d5e0 diff --git a/go.sum b/go.sum @@ -40,6 +40,8 @@ github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a h1:vLFQnHOnCnmlySdpHAKF github.com/kyoh86/xdg v0.0.0-20171127140545-8db68a8ea76a/go.mod h1:Z5mDqe0fxyxn3W2yTxsBAOQqIrXADQIh02wrTnaRM38= github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c h1:b11Y3yxg40v2/9KUz76a4mSC1DMlgnPGAt+4pJSgmyU= github.com/lucasb-eyer/go-colorful v0.0.0-20180531031333-d9cec903b20c/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= +github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41 h1:CVsnY46BCLkX9XOhALJ/S7yb9ayc4eqjXSXO3tyB66A= +github.com/martinlindhe/base36 v0.0.0-20190418230009-7c6542dfbb41/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791 h1:PfHMsLQJwoc0ccjK0sam6J0wQo4s8mOuAo2yQGw+T2U= diff --git a/lib/msgid.go b/lib/msgid.go @@ -0,0 +1,34 @@ +package lib + +// TODO: Remove this pending merge into github.com/emersion/go-message + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/rand" + "os" + "time" + + "github.com/martinlindhe/base36" +) + +// Generates an RFC 2822-complaint Message-Id based on the informational draft +// "Recommendations for generating Message IDs", for lack of a better +// authoritative source. +func GenerateMessageId() string { + var ( + now bytes.Buffer + nonce bytes.Buffer + ) + binary.Write(&now, binary.BigEndian, time.Now().UnixNano()) + binary.Write(&nonce, binary.BigEndian, rand.Uint64()) + hostname, err := os.Hostname() + if err != nil { + hostname = "localhost" + } + return fmt.Sprintf("<%s.%s@%s>", + base36.EncodeBytes(now.Bytes()), + base36.EncodeBytes(nonce.Bytes()), + hostname) +} diff --git a/widgets/account.go b/widgets/account.go @@ -84,6 +84,14 @@ func (acct *AccountView) AccountConfig() *config.AccountConfig { return acct.acct } +func (acct *AccountView) Worker() *types.Worker { + return acct.worker +} + +func (acct *AccountView) Logger() *log.Logger { + return acct.logger +} + func (acct *AccountView) Name() string { return acct.acct.Name } @@ -110,10 +118,6 @@ func (acct *AccountView) Focus(focus bool) { // TODO: Unfocus children I guess } -func (acct *AccountView) Worker() *types.Worker { - return acct.worker -} - func (acct *AccountView) connected(msg types.WorkerMessage) { switch msg := msg.(type) { case *types.Done: diff --git a/widgets/compose.go b/widgets/compose.go @@ -14,6 +14,7 @@ import ( "github.com/mattn/go-runewidth" "git.sr.ht/~sircmpwn/aerc2/config" + "git.sr.ht/~sircmpwn/aerc2/lib" "git.sr.ht/~sircmpwn/aerc2/lib/ui" "git.sr.ht/~sircmpwn/aerc2/worker/types" ) @@ -123,6 +124,13 @@ func (c *Composer) Defaults(defaults map[string]string) *Composer { return c } +func (c *Composer) FocusTerminal() *Composer { + c.focusable[c.focused].Focus(false) + c.focused = 3 + c.focusable[c.focused].Focus(true) + return c +} + func (c *Composer) OnSubjectChange(fn func(subject string)) { c.headers.subject.OnChange(func() { fn(c.headers.subject.input.String()) @@ -197,9 +205,9 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) { c.email.Seek(0, os.SEEK_SET) } // Update headers - // TODO: Custom header fields mhdr := (*message.Header)(&header.Header) mhdr.SetContentType("text/plain", map[string]string{"charset": "UTF-8"}) + mhdr.SetText("Message-Id", lib.GenerateMessageId()) if subject, _ := header.Subject(); subject == "" { header.SetSubject(c.headers.subject.input.String()) } @@ -228,14 +236,14 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) { rcpts = append(rcpts, addr.Address) } } + // TODO: Add cc, bcc to rcpts // Merge in additional headers txthdr := mhdr.Header for key, value := range c.defaults { - if !txthdr.Has(key) { + if !txthdr.Has(key) && value != "" { mhdr.SetText(key, value) } } - // TODO: Add cc, bcc to rcpts return &header, rcpts, nil }