~sircmpwn/aerc

Add postpone command v3 PROPOSED

Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/%3C20191001180256.94397-1-dev%40jeffas.io%3E/mbox | git am -3
Learn more about email & git

[PATCH v3 1/4] Add postpone command Export this patch

This command uses the Postpone folder from the account config to save
messages to. Messages are saved as though they were sent so have a valid
'to' recipient address and should be able to be read back in for later
editing.
---
 commands/compose/postpone.go | 98 ++++++++++++++++++++++++++++++++++++
 config/config.go             |  1 +
 widgets/compose.go           |  2 +-
 3 files changed, 100 insertions(+), 1 deletion(-)
 create mode 100644 commands/compose/postpone.go

diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
new file mode 100644
index 0000000..4c0f761
--- /dev/null
+++ b/commands/compose/postpone.go
@@ -0,0 +1,98 @@
+package compose
+
+import (
+	"io"
+	"io/ioutil"
+	"time"
+
+	"github.com/emersion/go-imap"
+	"github.com/miolini/datacounter"
+	"github.com/pkg/errors"
+
+	"git.sr.ht/~sircmpwn/aerc/widgets"
+	"git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+type Postpone struct{}
+
+func init() {
+	register(Postpone{})
+}
+
+func (Postpone) Aliases() []string {
+	return []string{"postpone"}
+}
+
+func (Postpone) Complete(aerc *widgets.Aerc, args []string) []string {
+	return nil
+}
+
+func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
+	if len(args) != 1 {
+		return errors.New("Usage: postpone")
+	}
+	composer, _ := aerc.SelectedTab().(*widgets.Composer)
+	config := composer.Config()
+
+	if config.Postpone == "" {
+		return errors.New("No Postpone location configured")
+	}
+
+	aerc.Logger().Println("Postponing mail")
+
+	header, _, err := composer.PrepareHeader()
+	if err != nil {
+		return errors.Wrap(err, "PrepareHeader")
+	}
+	worker := composer.Worker()
+	// to synchronise the creating of the directory
+	errChan := make(chan string)
+	worker.PostAction(&types.CreateDirectory{
+		Directory: config.Postpone,
+	}, func(msg types.WorkerMessage) {
+		switch msg := msg.(type) {
+		case *types.Done:
+			errChan <- ""
+		case *types.Error:
+			errChan <- msg.Error.Error()
+		}
+	})
+
+	// run this as a goroutine so we can make other progress. The message
+	// will be saved once the directory is created.
+	go func() {
+		errStr := <-errChan
+		if errStr != "" {
+			aerc.PushError(" " + errStr)
+			return
+		}
+
+		aerc.RemoveTab(composer)
+
+		ctr := datacounter.NewWriterCounter(ioutil.Discard)
+		composer.WriteMessage(header, ctr)
+		nbytes := int(ctr.Count())
+		r, w := io.Pipe()
+		worker.PostAction(&types.AppendMessage{
+			Destination: config.Postpone,
+			Flags:       []string{imap.SeenFlag},
+			Date:        time.Now(),
+			Reader:      r,
+			Length:      nbytes,
+		}, func(msg types.WorkerMessage) {
+			switch msg := msg.(type) {
+			case *types.Done:
+				aerc.PushStatus("Message postponed.", 10*time.Second)
+				r.Close()
+				composer.Close()
+			case *types.Error:
+				aerc.PushError(" " + msg.Error.Error())
+				r.Close()
+				composer.Close()
+			}
+		})
+		composer.WriteMessage(header, w)
+		w.Close()
+	}()
+	return nil
+}
diff --git a/config/config.go b/config/config.go
index 133a7f4..d639ee4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -49,6 +49,7 @@ type AccountConfig struct {
 	Archive         string
 	CopyTo          string
 	Default         string
+	Postpone        string
 	From            string
 	Name            string
 	Source          string
diff --git a/widgets/compose.go b/widgets/compose.go
index 22c58da..3915309 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -742,7 +742,7 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage {
 	} else {
 		// TODO: source this from actual keybindings?
 		grid.AddChild(ui.NewText(
-			"Send this email? [y]es/[n]o/[e]dit/[a]ttach")).At(0, 0)
+			"Send this email? [y]es/[n]o/[p]ostpone/[e]dit/[a]ttach")).At(0, 0)
 		grid.AddChild(ui.NewText("Attachments:").
 			Reverse(true)).At(1, 0)
 		if len(composer.attachments) == 0 {
-- 
2.23.0

[PATCH v3 2/4] Add documentation for postpone Export this patch

Adds docs for the postpone command and the Postpone accounts.conf config
option. A default for the postpone config option is provided of: Drafts.
---
 config/binds.conf     | 1 +
 config/config.go      | 9 +++++----
 doc/aerc-config.5.scd | 5 +++++
 doc/aerc.1.scd        | 4 ++++
 4 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/config/binds.conf b/config/binds.conf
index ac49bd0..ea6b99e 100644
--- a/config/binds.conf
+++ b/config/binds.conf
@@ -87,6 +87,7 @@ $ex = <C-x>
 # Keybindings used when reviewing a message to be sent
 y = :send<Enter>
 n = :abort<Enter>
+p = :postpone<Enter>
 q = :abort<Enter>
 e = :edit<Enter>
 a = :attach<space>
diff --git a/config/config.go b/config/config.go
index d639ee4..be021c7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -141,10 +141,11 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
 		}
 		sec := file.Section(_sec)
 		account := AccountConfig{
-			Archive: "Archive",
-			Default: "INBOX",
-			Name:    _sec,
-			Params:  make(map[string]string),
+			Archive:  "Archive",
+			Default:  "INBOX",
+			Postpone: "Drafts",
+			Name:     _sec,
+			Params:   make(map[string]string),
 		}
 		if err = sec.MapTo(&account); err != nil {
 			return nil, err
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index f4f02f2..e753b99 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -295,6 +295,11 @@ Note that many of these configuration options are written for you, such as
 
 	Default: none
 
+*postpone*
+	Specifies the folder to save *postpone*d messages to.
+
+	Default: Drafts
+
 *source*
 	Specifies the source for reading incoming emails on this account. This key
 	is required for all accounts. It should be a connection string, and the
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index e76b519..f534f05 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -268,6 +268,10 @@ message list, the message in the message viewer, etc).
 *next-field*, *prev-field*
 	Cycles between input fields in the compose window.
 
+*postpone*
+	Saves the current state of the message to the *postpone* folder for the
+	current account.
+
 *save* [-p] <path>
 	Saves the selected message part to the specified path. If -p is selected,
 	aerc will create any missing directories in the specified path. If the path
-- 
2.23.0

[PATCH v3 3/4] Add recall command Export this patch

This command allows recalling the selected postponed email to edit in
the composer. The command only allows recalling from the postpone
directory.
---
 commands/msg/recall.go | 121 +++++++++++++++++++++++++++++++++++++++++
 widgets/account.go     |   4 ++
 2 files changed, 125 insertions(+)
 create mode 100644 commands/msg/recall.go

diff --git a/commands/msg/recall.go b/commands/msg/recall.go
new file mode 100644
index 0000000..da565aa
--- /dev/null
+++ b/commands/msg/recall.go
@@ -0,0 +1,121 @@
+package msg
+
+import (
+	"io"
+
+	"github.com/emersion/go-message"
+	_ "github.com/emersion/go-message/charset"
+	"github.com/emersion/go-message/mail"
+	"github.com/pkg/errors"
+
+	"git.sr.ht/~sircmpwn/aerc/models"
+	"git.sr.ht/~sircmpwn/aerc/widgets"
+)
+
+type Recall struct{}
+
+func init() {
+	register(Recall{})
+}
+
+func (Recall) Aliases() []string {
+	return []string{"recall"}
+}
+
+func (Recall) Complete(aerc *widgets.Aerc, args []string) []string {
+	return nil
+}
+
+func (Recall) Execute(aerc *widgets.Aerc, args []string) error {
+	if len(args) != 1 {
+		return errors.New("Usage: recall")
+	}
+
+	widget := aerc.SelectedTab().(widgets.ProvidesMessage)
+	acct := widget.SelectedAccount()
+	if acct == nil {
+		return errors.New("No account selected")
+	}
+	if acct.SelectedDirectory() != acct.AccountConfig().Postpone {
+		return errors.New("Can only recall from the postpone directory: " +
+			acct.AccountConfig().Postpone)
+	}
+	store := widget.Store()
+	if store == nil {
+		return errors.New("Cannot perform action. Messages still loading")
+	}
+
+	msgInfo, err := widget.SelectedMessage()
+	if err != nil {
+		return errors.Wrap(err, "Recall failed")
+	}
+	acct.Logger().Println("Recalling message " + msgInfo.Envelope.MessageId)
+
+	// copy the headers to the defaults map for addition to the composition
+	defaults := make(map[string]string)
+	headerFields := msgInfo.RFC822Headers.Fields()
+	for headerFields.Next() {
+		defaults[headerFields.Key()] = headerFields.Value()
+	}
+
+	composer := widgets.NewComposer(aerc, aerc.Config(),
+		acct.AccountConfig(), acct.Worker(), defaults)
+
+	// focus the terminal since the header fields are likely already done
+	composer.FocusTerminal()
+
+	addTab := func() {
+		subject := msgInfo.Envelope.Subject
+		if subject == "" {
+			subject = "Recalled email"
+		}
+		tab := aerc.NewTab(composer, subject)
+		composer.OnHeaderChange("Subject", func(subject string) {
+			if subject == "" {
+				tab.Name = "New email"
+			} else {
+				tab.Name = subject
+			}
+			tab.Content.Invalidate()
+		})
+	}
+
+	// find the main body part and add it to the editor
+	// TODO: copy all parts of the message over?
+	var (
+		path []int
+		part *models.BodyStructure
+	)
+	if len(msgInfo.BodyStructure.Parts) != 0 {
+		part, path = findPlaintext(msgInfo.BodyStructure, path)
+	}
+	if part == nil {
+		part = msgInfo.BodyStructure
+		path = []int{1}
+	}
+
+	store.FetchBodyPart(msgInfo.Uid, path, func(reader io.Reader) {
+		header := message.Header{}
+		header.SetText(
+			"Content-Transfer-Encoding", part.Encoding)
+		header.SetContentType(part.MIMEType, part.Params)
+		header.SetText("Content-Description", part.Description)
+		entity, err := message.New(header, reader)
+		if err != nil {
+			// TODO: Do something with the error
+			addTab()
+			return
+		}
+		mreader := mail.NewReader(entity)
+		part, err := mreader.NextPart()
+		if err != nil {
+			// TODO: Do something with the error
+			addTab()
+			return
+		}
+		composer.SetContents(part.Body)
+		addTab()
+	})
+
+	return nil
+}
diff --git a/widgets/account.go b/widgets/account.go
index 4e8dd17..213f1a6 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -182,6 +182,10 @@ func (acct *AccountView) SelectedAccount() *AccountView {
 	return acct
 }
 
+func (acct *AccountView) SelectedDirectory() string {
+	return acct.dirlist.Selected()
+}
+
 func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) {
 	if len(acct.msglist.Store().Uids()) == 0 {
 		return nil, errors.New("no message selected")
-- 
2.23.0

[PATCH v3 4/4] Add documentation for edit command Export this patch

---
 doc/aerc.1.scd | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index f534f05..cbe8e1f 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -96,6 +96,10 @@ message list, the message in the message viewer, etc).
 *delete*
 	Deletes the selected message.

+*recall*
+	Opens the selected message for re-editing. Messages can only be
+	recalled from the postpone directory.
+
 *forward* [-A] [address...]
 	Opens the composer to forward the selected message to another recipient.

--
2.23.0
View this thread in the archives