about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--commands/compose/send.go2
-rw-r--r--commands/msg/reply.go4
-rw-r--r--lib/msgstore.go9
-rw-r--r--widgets/compose.go9
-rw-r--r--worker/imap/flags.go32
-rw-r--r--worker/imap/worker.go2
-rw-r--r--worker/maildir/message.go20
-rw-r--r--worker/maildir/worker.go35
-rw-r--r--worker/notmuch/message.go33
-rw-r--r--worker/notmuch/worker.go27
-rw-r--r--worker/types/messages.go6
11 files changed, 179 insertions, 0 deletions
diff --git a/commands/compose/send.go b/commands/compose/send.go
index 36b9fd8..59ae5d0 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -244,6 +244,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
 				case *types.Done:
 					aerc.PushStatus("Message sent.", 10*time.Second)
 					r.Close()
+					composer.SetSent()
 					composer.Close()
 				case *types.Error:
 					aerc.PushError(" " + msg.Error.Error())
@@ -256,6 +257,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
 			w.Close()
 		} else {
 			aerc.PushStatus("Message sent.", 10*time.Second)
+			composer.SetSent()
 			composer.Close()
 		}
 	}()
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 8e0feef..28ce245 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -157,6 +157,10 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
 			tab.Content.Invalidate()
 		})
 
+		composer.OnClose(func(c *widgets.Composer) {
+			store.Answered([]uint32{msg.Uid}, c.Sent(), nil)
+		})
+
 		return nil
 	}
 
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 7dc2689..86215a7 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -342,6 +342,15 @@ func (store *MessageStore) Read(uids []uint32, read bool,
 	}, cb)
 }
 
+func (store *MessageStore) Answered(uids []uint32, answered bool,
+	cb func(msg types.WorkerMessage)) {
+
+	store.worker.PostAction(&types.AnsweredMessages{
+		Answered: answered,
+		Uids:     uids,
+	}, cb)
+}
+
 func (store *MessageStore) Uids() []uint32 {
 	if store.filter {
 		return store.results
diff --git a/widgets/compose.go b/widgets/compose.go
index 4281941..01b8dd8 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -52,6 +52,7 @@ type Composer struct {
 	layout    HeaderLayout
 	focusable []ui.MouseableDrawableInteractive
 	focused   int
+	sent      bool
 
 	onClose []func(ti *Composer)
 
@@ -163,6 +164,14 @@ func buildComposeHeader(conf *config.AercConfig, cmpl *completer.Completer,
 	return layout, editors, focusable
 }
 
+func (c *Composer) SetSent() {
+	c.sent = true
+}
+
+func (c *Composer) Sent() bool {
+	return c.sent
+}
+
 // Note: this does not reload the editor. You must call this before the first
 // Draw() call.
 func (c *Composer) SetContents(reader io.Reader) *Composer {
diff --git a/worker/imap/flags.go b/worker/imap/flags.go
index 17c9393..2625086 100644
--- a/worker/imap/flags.go
+++ b/worker/imap/flags.go
@@ -44,6 +44,38 @@ func (imapw *IMAPWorker) handleDeleteMessages(msg *types.DeleteMessages) {
 	}
 }
 
+func (imapw *IMAPWorker) handleAnsweredMessages(msg *types.AnsweredMessages) {
+	item := imap.FormatFlagsOp(imap.AddFlags, true)
+	flags := []interface{}{imap.AnsweredFlag}
+	if !msg.Answered {
+		item = imap.FormatFlagsOp(imap.RemoveFlags, true)
+		flags = []interface{}{imap.AnsweredFlag}
+	}
+	uids := toSeqSet(msg.Uids)
+	emitErr := func(err error) {
+		imapw.worker.PostMessage(&types.Error{
+			Message: types.RespondTo(msg),
+			Error:   err,
+		}, nil)
+	}
+	if err := imapw.client.UidStore(uids, item, flags, nil); err != nil {
+		emitErr(err)
+		return
+	}
+	imapw.worker.PostAction(&types.FetchMessageHeaders{
+		Uids: msg.Uids,
+	}, func(_msg types.WorkerMessage) {
+		switch m := _msg.(type) {
+		case *types.Error:
+			err := fmt.Errorf("handleAnsweredMessages: %v", m.Error)
+			imapw.worker.Logger.Printf("could not fetch headers: %s", err)
+			emitErr(err)
+		case *types.Done:
+			imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
+		}
+	})
+}
+
 func (imapw *IMAPWorker) handleReadMessages(msg *types.ReadMessages) {
 	item := imap.FormatFlagsOp(imap.AddFlags, true)
 	flags := []interface{}{imap.SeenFlag}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 45a2269..a43ac49 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -175,6 +175,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
 		w.handleDeleteMessages(msg)
 	case *types.ReadMessages:
 		w.handleReadMessages(msg)
+	case *types.AnsweredMessages:
+		w.handleAnsweredMessages(msg)
 	case *types.CopyMessages:
 		w.handleCopyMessages(msg)
 	case *types.AppendMessage:
diff --git a/worker/maildir/message.go b/worker/maildir/message.go
index 9d622b4..5c6c930 100644
--- a/worker/maildir/message.go
+++ b/worker/maildir/message.go
@@ -53,6 +53,26 @@ func (m Message) SetFlags(flags []maildir.Flag) error {
 	return m.dir.SetFlags(m.key, flags)
 }
 
+// MarkReplied either adds or removes the maildir.FlagReplied flag from the
+// message.
+func (m Message) MarkReplied(answered bool) error {
+	flags, err := m.Flags()
+	if err != nil {
+		return fmt.Errorf("could not read previous flags: %v", err)
+	}
+	if answered {
+		flags = append(flags, maildir.FlagReplied)
+		return m.SetFlags(flags)
+	}
+	var newFlags []maildir.Flag
+	for _, flag := range flags {
+		if flag != maildir.FlagReplied {
+			newFlags = append(newFlags, flag)
+		}
+	}
+	return m.SetFlags(newFlags)
+}
+
 // MarkRead either adds or removes the maildir.FlagSeen flag from the message.
 func (m Message) MarkRead(seen bool) error {
 	flags, err := m.Flags()
diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go
index dbdb925..f14672e 100644
--- a/worker/maildir/worker.go
+++ b/worker/maildir/worker.go
@@ -195,6 +195,8 @@ func (w *Worker) handleMessage(msg types.WorkerMessage) error {
 		return w.handleDeleteMessages(msg)
 	case *types.ReadMessages:
 		return w.handleReadMessages(msg)
+	case *types.AnsweredMessages:
+		return w.handleAnsweredMessages(msg)
 	case *types.CopyMessages:
 		return w.handleCopyMessages(msg)
 	case *types.AppendMessage:
@@ -438,6 +440,39 @@ func (w *Worker) handleDeleteMessages(msg *types.DeleteMessages) error {
 	return nil
 }
 
+func (w *Worker) handleAnsweredMessages(msg *types.AnsweredMessages) error {
+	for _, uid := range msg.Uids {
+		m, err := w.c.Message(*w.selected, uid)
+		if err != nil {
+			w.worker.Logger.Printf("could not get message: %v", err)
+			w.err(msg, err)
+			continue
+		}
+		if err := m.MarkReplied(msg.Answered); err != nil {
+			w.worker.Logger.Printf(
+				"could not mark message as answered: %v", err)
+			w.err(msg, err)
+			continue
+		}
+		info, err := m.MessageInfo()
+		if err != nil {
+			w.worker.Logger.Printf("could not get message info: %v", err)
+			w.err(msg, err)
+			continue
+		}
+
+		w.worker.PostMessage(&types.MessageInfo{
+			Message: types.RespondTo(msg),
+			Info:    info,
+		}, nil)
+
+		w.worker.PostMessage(&types.DirectoryInfo{
+			Info: w.getDirectoryInfo(w.selectedName),
+		}, nil)
+	}
+	return nil
+}
+
 func (w *Worker) handleReadMessages(msg *types.ReadMessages) error {
 	for _, uid := range msg.Uids {
 		m, err := w.c.Message(*w.selected, uid)
diff --git a/worker/notmuch/message.go b/worker/notmuch/message.go
index ec1adb9..3fd2fb6 100644
--- a/worker/notmuch/message.go
+++ b/worker/notmuch/message.go
@@ -64,6 +64,39 @@ func (m *Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) {
 	return lib.FetchEntityPartReader(msg, requestedParts)
 }
 
+// MarkAnswered either adds or removes the "replied" tag from the message.
+func (m *Message) MarkAnswered(answered bool) error {
+	haveReplied := false
+	tags, err := m.Tags()
+	if err != nil {
+		return err
+	}
+	for _, t := range tags {
+		if t == "replied" {
+			haveReplied = true
+			break
+		}
+	}
+	if haveReplied == answered {
+		// we already have the desired state
+		return nil
+	}
+
+	if haveAnswered {
+		err := m.RemoveTag("replied")
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+
+	err = m.AddTag("replied")
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 // MarkRead either adds or removes the maildir.FlagSeen flag from the message.
 func (m *Message) MarkRead(seen bool) error {
 	haveUnread := false
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index 9929648..b226269 100644
--- a/worker/notmuch/worker.go
+++ b/worker/notmuch/worker.go
@@ -363,6 +363,33 @@ func (w *worker) handleFetchFullMessages(msg *types.FetchFullMessages) error {
 	return nil
 }
 
+func (w *worker) handleAnsweredMessages(msg *types.AnsweredMessages) error {
+	for _, uid := range msg.Uids {
+		m, err := w.msgFromUid(uid)
+		if err != nil {
+			w.w.Logger.Printf("could not get message: %v", err)
+			w.err(msg, err)
+			continue
+		}
+		if err := m.MarkAnswered(msg.Answered); err != nil {
+			w.w.Logger.Printf("could not mark message as answered: %v", err)
+			w.err(msg, err)
+			continue
+		}
+		err = w.emitMessageInfo(m, msg)
+		if err != nil {
+			w.w.Logger.Printf(err.Error())
+			w.err(msg, err)
+			continue
+		}
+	}
+	if err := w.emitDirectoryInfo(w.currentQueryName); err != nil {
+		w.w.Logger.Printf(err.Error())
+	}
+	w.done(msg)
+	return nil
+}
+
 func (w *worker) handleReadMessages(msg *types.ReadMessages) error {
 	for _, uid := range msg.Uids {
 		m, err := w.msgFromUid(uid)
diff --git a/worker/types/messages.go b/worker/types/messages.go
index f1ef36e..475a7aa 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -120,6 +120,12 @@ type ReadMessages struct {
 	Uids []uint32
 }
 
+type AnsweredMessages struct {
+	Message
+	Answered bool
+	Uids     []uint32
+}
+
 type CopyMessages struct {
 	Message
 	Destination string
m> 2018-01-25 22:39:31 -0800 4199' href='/akkartik/mu/commit/html/027call_ingredient.cc.html?h=hlt&id=805d58c6aeeeba3e4989c0eed6781b3861e8fae0'>805d58c6 ^
5fe060d5 ^
805d58c6 ^






4a48bedc ^

805d58c6 ^
5fe060d5 ^
805d58c6 ^




4a48bedc ^











5fe060d5 ^
4a48bedc ^


























c0f84b1f ^
4a48bedc ^


5fe060d5 ^


































































672e3e50 ^


a654e4ec ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259