/src/neomutt/index/dlg_index.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Index Dialog |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-2000,2002,2010,2012-2013 Michael R. Elkins <me@mutt.org> |
7 | | * Copyright (C) 2020 R Primus <rprimus@gmail.com> |
8 | | * |
9 | | * @copyright |
10 | | * This program is free software: you can redistribute it and/or modify it under |
11 | | * the terms of the GNU General Public License as published by the Free Software |
12 | | * Foundation, either version 2 of the License, or (at your option) any later |
13 | | * version. |
14 | | * |
15 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
18 | | * details. |
19 | | * |
20 | | * You should have received a copy of the GNU General Public License along with |
21 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
22 | | */ |
23 | | |
24 | | /** |
25 | | * @page index_dlg_index Index Dialog |
26 | | * |
27 | | * The Index Dialog is the main screen within NeoMutt. It contains @ref |
28 | | * index_index (a list of emails), @ref pager_dlg_pager (a view of an email) and |
29 | | * @ref sidebar_window (a list of mailboxes). |
30 | | * |
31 | | * ## Windows |
32 | | * |
33 | | * | Name | Type | See Also | |
34 | | * | :----------- | :----------- | :---------------- | |
35 | | * | Index Dialog | WT_DLG_INDEX | mutt_index_menu() | |
36 | | * |
37 | | * **Parent** |
38 | | * - @ref gui_dialog |
39 | | * |
40 | | * **Children** |
41 | | * - See: @ref index_ipanel |
42 | | * - See: @ref pager_ppanel |
43 | | * - See: @ref sidebar_window |
44 | | * |
45 | | * ## Data |
46 | | * - #IndexSharedData |
47 | | * |
48 | | * ## Events |
49 | | * |
50 | | * None. |
51 | | * |
52 | | * Some other events are handled by the dialog's children. |
53 | | */ |
54 | | |
55 | | #include "config.h" |
56 | | #include <assert.h> |
57 | | #include <stdbool.h> |
58 | | #include <stdio.h> |
59 | | #include "private.h" |
60 | | #include "mutt/lib.h" |
61 | | #include "config/lib.h" |
62 | | #include "email/lib.h" |
63 | | #include "core/lib.h" |
64 | | #include "conn/lib.h" |
65 | | #include "gui/lib.h" |
66 | | #include "lib.h" |
67 | | #include "color/lib.h" |
68 | | #include "menu/lib.h" |
69 | | #include "pager/lib.h" |
70 | | #include "pattern/lib.h" |
71 | | #include "format_flags.h" |
72 | | #include "functions.h" |
73 | | #include "globals.h" // IWYU pragma: keep |
74 | | #include "hdrline.h" |
75 | | #include "hook.h" |
76 | | #include "keymap.h" |
77 | | #include "mutt_logging.h" |
78 | | #include "mutt_mailbox.h" |
79 | | #include "mutt_thread.h" |
80 | | #include "mview.h" |
81 | | #include "mx.h" |
82 | | #include "opcodes.h" |
83 | | #include "private_data.h" |
84 | | #include "protos.h" |
85 | | #include "shared_data.h" |
86 | | #include "sort.h" |
87 | | #include "status.h" |
88 | | #ifdef USE_NOTMUCH |
89 | | #include "notmuch/lib.h" |
90 | | #endif |
91 | | #ifdef USE_NNTP |
92 | | #include "nntp/lib.h" |
93 | | #include "nntp/adata.h" |
94 | | #endif |
95 | | #ifdef USE_INOTIFY |
96 | | #include "monitor.h" |
97 | | #endif |
98 | | #ifdef USE_SIDEBAR |
99 | | #include "sidebar/lib.h" |
100 | | #endif |
101 | | |
102 | | /// Help Bar for the Index dialog |
103 | | static const struct Mapping IndexHelp[] = { |
104 | | // clang-format off |
105 | | { N_("Quit"), OP_QUIT }, |
106 | | { N_("Del"), OP_DELETE }, |
107 | | { N_("Undel"), OP_UNDELETE }, |
108 | | { N_("Save"), OP_SAVE }, |
109 | | { N_("Mail"), OP_MAIL }, |
110 | | { N_("Reply"), OP_REPLY }, |
111 | | { N_("Group"), OP_GROUP_REPLY }, |
112 | | { N_("Help"), OP_HELP }, |
113 | | { NULL, 0 }, |
114 | | // clang-format on |
115 | | }; |
116 | | |
117 | | #ifdef USE_NNTP |
118 | | /// Help Bar for the News Index dialog |
119 | | const struct Mapping IndexNewsHelp[] = { |
120 | | // clang-format off |
121 | | { N_("Quit"), OP_QUIT }, |
122 | | { N_("Del"), OP_DELETE }, |
123 | | { N_("Undel"), OP_UNDELETE }, |
124 | | { N_("Save"), OP_SAVE }, |
125 | | { N_("Post"), OP_POST }, |
126 | | { N_("Followup"), OP_FOLLOWUP }, |
127 | | { N_("Catchup"), OP_CATCHUP }, |
128 | | { N_("Help"), OP_HELP }, |
129 | | { NULL, 0 }, |
130 | | // clang-format on |
131 | | }; |
132 | | #endif |
133 | | |
134 | | /** |
135 | | * check_acl - Check the ACLs for a function |
136 | | * @param m Mailbox |
137 | | * @param acl ACL, see #AclFlags |
138 | | * @param msg Error message for failure |
139 | | * @retval true The function is permitted |
140 | | */ |
141 | | bool check_acl(struct Mailbox *m, AclFlags acl, const char *msg) |
142 | 0 | { |
143 | 0 | if (!m) |
144 | 0 | return false; |
145 | | |
146 | 0 | if (!(m->rights & acl)) |
147 | 0 | { |
148 | | /* L10N: %s is one of the CHECK_ACL entries below. */ |
149 | 0 | mutt_error(_("%s: Operation not permitted by ACL"), msg); |
150 | 0 | return false; |
151 | 0 | } |
152 | | |
153 | 0 | return true; |
154 | 0 | } |
155 | | |
156 | | /** |
157 | | * collapse_all - Collapse/uncollapse all threads |
158 | | * @param mv Mailbox View |
159 | | * @param menu current menu |
160 | | * @param toggle toggle collapsed state |
161 | | * |
162 | | * This function is called by the OP_MAIN_COLLAPSE_ALL command and on folder |
163 | | * enter if the `$collapse_all` option is set. In the first case, the @a toggle |
164 | | * parameter is 1 to actually toggle collapsed/uncollapsed state on all |
165 | | * threads. In the second case, the @a toggle parameter is 0, actually turning |
166 | | * this function into a one-way collapse. |
167 | | */ |
168 | | void collapse_all(struct MailboxView *mv, struct Menu *menu, int toggle) |
169 | 0 | { |
170 | 0 | if (!mv || !mv->mailbox || (mv->mailbox->msg_count == 0) || !menu) |
171 | 0 | return; |
172 | | |
173 | 0 | struct Email *e_cur = mutt_get_virt_email(mv->mailbox, menu_get_index(menu)); |
174 | 0 | if (!e_cur) |
175 | 0 | return; |
176 | | |
177 | 0 | int final; |
178 | | |
179 | | /* Figure out what the current message would be after folding / unfolding, |
180 | | * so that we can restore the cursor in a sane way afterwards. */ |
181 | 0 | if (e_cur->collapsed && toggle) |
182 | 0 | final = mutt_uncollapse_thread(e_cur); |
183 | 0 | else if (mutt_thread_can_collapse(e_cur)) |
184 | 0 | final = mutt_collapse_thread(e_cur); |
185 | 0 | else |
186 | 0 | final = e_cur->vnum; |
187 | |
|
188 | 0 | if (final == -1) |
189 | 0 | return; |
190 | | |
191 | 0 | struct Email *base = mutt_get_virt_email(mv->mailbox, final); |
192 | 0 | if (!base) |
193 | 0 | return; |
194 | | |
195 | | /* Iterate all threads, perform collapse/uncollapse as needed */ |
196 | 0 | mv->collapsed = toggle ? !mv->collapsed : true; |
197 | 0 | mutt_thread_collapse(mv->threads, mv->collapsed); |
198 | | |
199 | | /* Restore the cursor */ |
200 | 0 | mutt_set_vnum(mv->mailbox); |
201 | 0 | menu->max = mv->mailbox->vcount; |
202 | 0 | for (int i = 0; i < mv->mailbox->vcount; i++) |
203 | 0 | { |
204 | 0 | struct Email *e = mutt_get_virt_email(mv->mailbox, i); |
205 | 0 | if (!e) |
206 | 0 | break; |
207 | 0 | if (e->index == base->index) |
208 | 0 | { |
209 | 0 | menu_set_index(menu, i); |
210 | 0 | break; |
211 | 0 | } |
212 | 0 | } |
213 | |
|
214 | 0 | menu_queue_redraw(menu, MENU_REDRAW_INDEX); |
215 | 0 | } |
216 | | |
217 | | /** |
218 | | * uncollapse_thread - Open a collapsed thread |
219 | | * @param mv Mailbox View |
220 | | * @param index Message number |
221 | | */ |
222 | | static void uncollapse_thread(struct MailboxView *mv, int index) |
223 | 0 | { |
224 | 0 | if (!mv || !mv->mailbox) |
225 | 0 | return; |
226 | | |
227 | 0 | struct Mailbox *m = mv->mailbox; |
228 | 0 | struct Email *e = mutt_get_virt_email(m, index); |
229 | 0 | if (e && e->collapsed) |
230 | 0 | { |
231 | 0 | mutt_uncollapse_thread(e); |
232 | 0 | mutt_set_vnum(m); |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | /** |
237 | | * find_next_undeleted - Find the next undeleted email |
238 | | * @param mv Mailbox view |
239 | | * @param msgno Message number to start at |
240 | | * @param uncollapse Open collapsed threads |
241 | | * @retval >=0 Message number of next undeleted email |
242 | | * @retval -1 No more undeleted messages |
243 | | */ |
244 | | int find_next_undeleted(struct MailboxView *mv, int msgno, bool uncollapse) |
245 | 0 | { |
246 | 0 | if (!mv || !mv->mailbox) |
247 | 0 | return -1; |
248 | | |
249 | 0 | struct Mailbox *m = mv->mailbox; |
250 | |
|
251 | 0 | int index = -1; |
252 | 0 | for (int i = msgno + 1; i < m->vcount; i++) |
253 | 0 | { |
254 | 0 | struct Email *e = mutt_get_virt_email(m, i); |
255 | 0 | if (!e) |
256 | 0 | continue; |
257 | 0 | if (!e->deleted) |
258 | 0 | { |
259 | 0 | index = i; |
260 | 0 | break; |
261 | 0 | } |
262 | 0 | } |
263 | |
|
264 | 0 | if (uncollapse) |
265 | 0 | uncollapse_thread(mv, index); |
266 | |
|
267 | 0 | return index; |
268 | 0 | } |
269 | | |
270 | | /** |
271 | | * find_previous_undeleted - Find the previous undeleted email |
272 | | * @param mv Mailbox View |
273 | | * @param msgno Message number to start at |
274 | | * @param uncollapse Open collapsed threads |
275 | | * @retval >=0 Message number of next undeleted email |
276 | | * @retval -1 No more undeleted messages |
277 | | */ |
278 | | int find_previous_undeleted(struct MailboxView *mv, int msgno, bool uncollapse) |
279 | 0 | { |
280 | 0 | if (!mv || !mv->mailbox) |
281 | 0 | return -1; |
282 | | |
283 | 0 | struct Mailbox *m = mv->mailbox; |
284 | |
|
285 | 0 | int index = -1; |
286 | 0 | for (int i = msgno - 1; i >= 0; i--) |
287 | 0 | { |
288 | 0 | struct Email *e = mutt_get_virt_email(m, i); |
289 | 0 | if (!e) |
290 | 0 | continue; |
291 | 0 | if (!e->deleted) |
292 | 0 | { |
293 | 0 | index = i; |
294 | 0 | break; |
295 | 0 | } |
296 | 0 | } |
297 | |
|
298 | 0 | if (uncollapse) |
299 | 0 | uncollapse_thread(mv, index); |
300 | |
|
301 | 0 | return index; |
302 | 0 | } |
303 | | |
304 | | /** |
305 | | * find_first_message - Get index of first new message |
306 | | * @param mv Mailbox view |
307 | | * @retval num Index of first new message |
308 | | * |
309 | | * Return the index of the first new message, or failing that, the first |
310 | | * unread message. |
311 | | */ |
312 | | int find_first_message(struct MailboxView *mv) |
313 | 0 | { |
314 | 0 | if (!mv) |
315 | 0 | return 0; |
316 | | |
317 | 0 | struct Mailbox *m = mv->mailbox; |
318 | 0 | if (!m || (m->msg_count == 0)) |
319 | 0 | return 0; |
320 | | |
321 | 0 | int old = -1; |
322 | 0 | for (int i = 0; i < m->vcount; i++) |
323 | 0 | { |
324 | 0 | struct Email *e = mutt_get_virt_email(m, i); |
325 | 0 | if (!e) |
326 | 0 | continue; |
327 | 0 | if (!e->read && !e->deleted) |
328 | 0 | { |
329 | 0 | if (!e->old) |
330 | 0 | return i; |
331 | 0 | if (old == -1) |
332 | 0 | old = i; |
333 | 0 | } |
334 | 0 | } |
335 | 0 | if (old != -1) |
336 | 0 | return old; |
337 | | |
338 | | /* If `$use_threads` is not threaded and `$sort` is reverse, the latest |
339 | | * message is first. Otherwise, the latest message is first if exactly |
340 | | * one of `$use_threads` and `$sort` are reverse. |
341 | | */ |
342 | 0 | enum SortType c_sort = cs_subset_sort(m->sub, "sort"); |
343 | 0 | if ((c_sort & SORT_MASK) == SORT_THREADS) |
344 | 0 | c_sort = cs_subset_sort(m->sub, "sort_aux"); |
345 | 0 | bool reverse = false; |
346 | 0 | switch (mutt_thread_style()) |
347 | 0 | { |
348 | 0 | case UT_FLAT: |
349 | 0 | reverse = c_sort & SORT_REVERSE; |
350 | 0 | break; |
351 | 0 | case UT_THREADS: |
352 | 0 | reverse = c_sort & SORT_REVERSE; |
353 | 0 | break; |
354 | 0 | case UT_REVERSE: |
355 | 0 | reverse = !(c_sort & SORT_REVERSE); |
356 | 0 | break; |
357 | 0 | default: |
358 | 0 | assert(false); |
359 | 0 | } |
360 | | |
361 | 0 | if (reverse || (m->vcount == 0)) |
362 | 0 | return 0; |
363 | | |
364 | 0 | return m->vcount - 1; |
365 | 0 | } |
366 | | |
367 | | /** |
368 | | * resort_index - Resort the index |
369 | | * @param mv Mailbox View |
370 | | * @param menu Current Menu |
371 | | */ |
372 | | void resort_index(struct MailboxView *mv, struct Menu *menu) |
373 | 0 | { |
374 | 0 | if (!mv || !mv->mailbox || !menu) |
375 | 0 | return; |
376 | | |
377 | 0 | struct Mailbox *m = mv->mailbox; |
378 | 0 | const int old_index = menu_get_index(menu); |
379 | 0 | struct Email *e_cur = mutt_get_virt_email(m, old_index); |
380 | |
|
381 | 0 | int new_index = -1; |
382 | 0 | mutt_sort_headers(mv, false); |
383 | | |
384 | | /* Restore the current message */ |
385 | 0 | for (int i = 0; i < m->vcount; i++) |
386 | 0 | { |
387 | 0 | struct Email *e = mutt_get_virt_email(m, i); |
388 | 0 | if (!e) |
389 | 0 | continue; |
390 | 0 | if (e == e_cur) |
391 | 0 | { |
392 | 0 | new_index = i; |
393 | 0 | break; |
394 | 0 | } |
395 | 0 | } |
396 | |
|
397 | 0 | if (mutt_using_threads() && (old_index < 0)) |
398 | 0 | new_index = mutt_parent_message(e_cur, false); |
399 | |
|
400 | 0 | if (old_index < 0) |
401 | 0 | new_index = find_first_message(mv); |
402 | |
|
403 | 0 | menu->max = m->vcount; |
404 | 0 | menu_set_index(menu, new_index); |
405 | 0 | menu_queue_redraw(menu, MENU_REDRAW_INDEX); |
406 | 0 | } |
407 | | |
408 | | /** |
409 | | * update_index_threaded - Update the index (if threaded) |
410 | | * @param mv Mailbox |
411 | | * @param check Flags, e.g. #MX_STATUS_REOPENED |
412 | | * @param oldcount How many items are currently in the index |
413 | | */ |
414 | | static void update_index_threaded(struct MailboxView *mv, enum MxStatus check, int oldcount) |
415 | 0 | { |
416 | 0 | struct Email **save_new = NULL; |
417 | 0 | const bool lmt = mview_has_limit(mv); |
418 | |
|
419 | 0 | struct Mailbox *m = mv->mailbox; |
420 | 0 | int num_new = MAX(0, m->msg_count - oldcount); |
421 | |
|
422 | 0 | const bool c_uncollapse_new = cs_subset_bool(m->sub, "uncollapse_new"); |
423 | | /* save the list of new messages */ |
424 | 0 | if ((check != MX_STATUS_REOPENED) && (oldcount > 0) && |
425 | 0 | (lmt || c_uncollapse_new) && (num_new > 0)) |
426 | 0 | { |
427 | 0 | save_new = mutt_mem_malloc(num_new * sizeof(struct Email *)); |
428 | 0 | for (int i = oldcount; i < m->msg_count; i++) |
429 | 0 | save_new[i - oldcount] = m->emails[i]; |
430 | 0 | } |
431 | | |
432 | | /* Sort first to thread the new messages, because some patterns |
433 | | * require the threading information. |
434 | | * |
435 | | * If the mailbox was reopened, need to rethread from scratch. */ |
436 | 0 | mutt_sort_headers(mv, (check == MX_STATUS_REOPENED)); |
437 | |
|
438 | 0 | if (lmt) |
439 | 0 | { |
440 | 0 | for (int i = 0; i < m->msg_count; i++) |
441 | 0 | { |
442 | 0 | struct Email *e = m->emails[i]; |
443 | |
|
444 | 0 | if ((e->limit_visited && e->visible) || |
445 | 0 | mutt_pattern_exec(SLIST_FIRST(mv->limit_pattern), |
446 | 0 | MUTT_MATCH_FULL_ADDRESS, m, e, NULL)) |
447 | 0 | { |
448 | | /* vnum will get properly set by mutt_set_vnum(), which |
449 | | * is called by mutt_sort_headers() just below. */ |
450 | 0 | e->vnum = 1; |
451 | 0 | e->visible = true; |
452 | 0 | } |
453 | 0 | else |
454 | 0 | { |
455 | 0 | e->vnum = -1; |
456 | 0 | e->visible = false; |
457 | 0 | } |
458 | | |
459 | | // mark email as visited so we don't re-apply the pattern next time |
460 | 0 | e->limit_visited = true; |
461 | 0 | } |
462 | | /* Need a second sort to set virtual numbers and redraw the tree */ |
463 | 0 | mutt_sort_headers(mv, false); |
464 | 0 | } |
465 | | |
466 | | /* uncollapse threads with new mail */ |
467 | 0 | if (c_uncollapse_new) |
468 | 0 | { |
469 | 0 | if (check == MX_STATUS_REOPENED) |
470 | 0 | { |
471 | 0 | mv->collapsed = false; |
472 | 0 | mutt_thread_collapse(mv->threads, mv->collapsed); |
473 | 0 | mutt_set_vnum(m); |
474 | 0 | } |
475 | 0 | else if (oldcount > 0) |
476 | 0 | { |
477 | 0 | for (int j = 0; j < num_new; j++) |
478 | 0 | { |
479 | 0 | if (save_new[j]->visible) |
480 | 0 | { |
481 | 0 | mutt_uncollapse_thread(save_new[j]); |
482 | 0 | } |
483 | 0 | } |
484 | 0 | mutt_set_vnum(m); |
485 | 0 | } |
486 | 0 | } |
487 | |
|
488 | 0 | FREE(&save_new); |
489 | 0 | } |
490 | | |
491 | | /** |
492 | | * update_index_unthreaded - Update the index (if unthreaded) |
493 | | * @param mv Mailbox |
494 | | * @param check Flags, e.g. #MX_STATUS_REOPENED |
495 | | */ |
496 | | static void update_index_unthreaded(struct MailboxView *mv, enum MxStatus check) |
497 | 0 | { |
498 | | /* We are in a limited view. Check if the new message(s) satisfy |
499 | | * the limit criteria. If they do, set their virtual msgno so that |
500 | | * they will be visible in the limited view */ |
501 | 0 | if (mview_has_limit(mv)) |
502 | 0 | { |
503 | 0 | int padding = mx_msg_padding_size(mv->mailbox); |
504 | 0 | mv->mailbox->vcount = mv->vsize = 0; |
505 | 0 | for (int i = 0; i < mv->mailbox->msg_count; i++) |
506 | 0 | { |
507 | 0 | struct Email *e = mv->mailbox->emails[i]; |
508 | 0 | if (!e) |
509 | 0 | break; |
510 | | |
511 | 0 | if ((e->limit_visited && e->visible) || |
512 | 0 | mutt_pattern_exec(SLIST_FIRST(mv->limit_pattern), |
513 | 0 | MUTT_MATCH_FULL_ADDRESS, mv->mailbox, e, NULL)) |
514 | 0 | { |
515 | 0 | assert(mv->mailbox->vcount < mv->mailbox->msg_count); |
516 | 0 | e->vnum = mv->mailbox->vcount; |
517 | 0 | mv->mailbox->v2r[mv->mailbox->vcount] = i; |
518 | 0 | e->visible = true; |
519 | 0 | mv->mailbox->vcount++; |
520 | 0 | struct Body *b = e->body; |
521 | 0 | mv->vsize += b->length + b->offset - b->hdr_offset + padding; |
522 | 0 | } |
523 | 0 | else |
524 | 0 | { |
525 | 0 | e->visible = false; |
526 | 0 | } |
527 | | |
528 | | // mark email as visited so we don't re-apply the pattern next time |
529 | 0 | e->limit_visited = true; |
530 | 0 | } |
531 | 0 | } |
532 | | |
533 | | /* if the mailbox was reopened, need to rethread from scratch */ |
534 | 0 | mutt_sort_headers(mv, (check == MX_STATUS_REOPENED)); |
535 | 0 | } |
536 | | |
537 | | /** |
538 | | * update_index - Update the index |
539 | | * @param menu Current Menu |
540 | | * @param mv Mailbox |
541 | | * @param check Flags, e.g. #MX_STATUS_REOPENED |
542 | | * @param oldcount How many items are currently in the index |
543 | | * @param shared Shared Index data |
544 | | */ |
545 | | void update_index(struct Menu *menu, struct MailboxView *mv, enum MxStatus check, |
546 | | int oldcount, const struct IndexSharedData *shared) |
547 | 0 | { |
548 | 0 | if (!menu || !mv) |
549 | 0 | return; |
550 | | |
551 | 0 | struct Mailbox *m = mv->mailbox; |
552 | 0 | if (mutt_using_threads()) |
553 | 0 | update_index_threaded(mv, check, oldcount); |
554 | 0 | else |
555 | 0 | update_index_unthreaded(mv, check); |
556 | |
|
557 | 0 | const int old_index = menu_get_index(menu); |
558 | 0 | int index = -1; |
559 | 0 | if (oldcount) |
560 | 0 | { |
561 | | /* restore the current message to the message it was pointing to */ |
562 | 0 | for (int i = 0; i < m->vcount; i++) |
563 | 0 | { |
564 | 0 | struct Email *e = mutt_get_virt_email(m, i); |
565 | 0 | if (!e) |
566 | 0 | continue; |
567 | 0 | if (index_shared_data_is_cur_email(shared, e)) |
568 | 0 | { |
569 | 0 | index = i; |
570 | 0 | break; |
571 | 0 | } |
572 | 0 | } |
573 | 0 | } |
574 | |
|
575 | 0 | if (index < 0) |
576 | 0 | { |
577 | 0 | index = (old_index < m->vcount) ? old_index : find_first_message(mv); |
578 | 0 | } |
579 | 0 | menu_set_index(menu, index); |
580 | 0 | } |
581 | | |
582 | | /** |
583 | | * index_mailbox_observer - Notification that a Mailbox has changed - Implements ::observer_t - @ingroup observer_api |
584 | | * |
585 | | * If a Mailbox is closed, then set a pointer to NULL. |
586 | | */ |
587 | | static int index_mailbox_observer(struct NotifyCallback *nc) |
588 | 0 | { |
589 | 0 | if (nc->event_type != NT_MAILBOX) |
590 | 0 | return 0; |
591 | 0 | if (!nc->global_data) |
592 | 0 | return -1; |
593 | 0 | if (nc->event_subtype != NT_MAILBOX_DELETE) |
594 | 0 | return 0; |
595 | | |
596 | 0 | struct Mailbox **ptr = nc->global_data; |
597 | 0 | if (!*ptr) |
598 | 0 | return 0; |
599 | | |
600 | 0 | *ptr = NULL; |
601 | 0 | mutt_debug(LL_DEBUG5, "mailbox done\n"); |
602 | 0 | return 0; |
603 | 0 | } |
604 | | |
605 | | /** |
606 | | * change_folder_mailbox - Change to a different Mailbox by pointer |
607 | | * @param menu Current Menu |
608 | | * @param m Mailbox |
609 | | * @param oldcount How many items are currently in the index |
610 | | * @param shared Shared Index data |
611 | | * @param read_only Open Mailbox in read-only mode |
612 | | */ |
613 | | void change_folder_mailbox(struct Menu *menu, struct Mailbox *m, int *oldcount, |
614 | | struct IndexSharedData *shared, bool read_only) |
615 | 0 | { |
616 | 0 | if (!m) |
617 | 0 | return; |
618 | | |
619 | | /* keepalive failure in mutt_enter_fname may kill connection. */ |
620 | 0 | if (shared->mailbox && (buf_is_empty(&shared->mailbox->pathbuf))) |
621 | 0 | { |
622 | 0 | mview_free(&shared->mailbox_view); |
623 | 0 | mailbox_free(&shared->mailbox); |
624 | 0 | } |
625 | |
|
626 | 0 | if (shared->mailbox) |
627 | 0 | { |
628 | 0 | char *new_last_folder = NULL; |
629 | 0 | #ifdef USE_INOTIFY |
630 | 0 | int monitor_remove_rc = mutt_monitor_remove(NULL); |
631 | 0 | #endif |
632 | 0 | #ifdef USE_COMP_MBOX |
633 | 0 | if (shared->mailbox->compress_info && (shared->mailbox->realpath[0] != '\0')) |
634 | 0 | new_last_folder = mutt_str_dup(shared->mailbox->realpath); |
635 | 0 | else |
636 | 0 | #endif |
637 | 0 | new_last_folder = mutt_str_dup(mailbox_path(shared->mailbox)); |
638 | 0 | *oldcount = shared->mailbox->msg_count; |
639 | |
|
640 | 0 | const enum MxStatus check = mx_mbox_close(shared->mailbox); |
641 | 0 | if (check == MX_STATUS_OK) |
642 | 0 | { |
643 | 0 | mview_free(&shared->mailbox_view); |
644 | 0 | if (shared->mailbox != m) |
645 | 0 | { |
646 | 0 | mailbox_free(&shared->mailbox); |
647 | 0 | } |
648 | 0 | } |
649 | 0 | else |
650 | 0 | { |
651 | 0 | #ifdef USE_INOTIFY |
652 | 0 | if (monitor_remove_rc == 0) |
653 | 0 | mutt_monitor_add(NULL); |
654 | 0 | #endif |
655 | 0 | if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED)) |
656 | 0 | update_index(menu, shared->mailbox_view, check, *oldcount, shared); |
657 | |
|
658 | 0 | FREE(&new_last_folder); |
659 | 0 | OptSearchInvalid = true; |
660 | 0 | menu_queue_redraw(menu, MENU_REDRAW_INDEX); |
661 | 0 | return; |
662 | 0 | } |
663 | 0 | FREE(&LastFolder); |
664 | 0 | LastFolder = new_last_folder; |
665 | 0 | } |
666 | 0 | mutt_str_replace(&CurrentFolder, mailbox_path(m)); |
667 | | |
668 | | /* If the `folder-hook` were to call `unmailboxes`, then the Mailbox (`m`) |
669 | | * could be deleted, leaving `m` dangling. */ |
670 | | // TODO: Refactor this function to avoid the need for an observer |
671 | 0 | notify_observer_add(m->notify, NT_MAILBOX, index_mailbox_observer, &m); |
672 | 0 | char *dup_path = mutt_str_dup(mailbox_path(m)); |
673 | 0 | char *dup_name = mutt_str_dup(m->name); |
674 | |
|
675 | 0 | mutt_folder_hook(dup_path, dup_name); |
676 | 0 | if (m) |
677 | 0 | { |
678 | | /* `m` is still valid, but we won't need the observer again before the end |
679 | | * of the function. */ |
680 | 0 | notify_observer_remove(m->notify, index_mailbox_observer, &m); |
681 | 0 | } |
682 | 0 | else |
683 | 0 | { |
684 | | // Recreate the Mailbox as the folder-hook might have invoked `mailboxes` |
685 | | // and/or `unmailboxes`. |
686 | 0 | m = mx_path_resolve(dup_path); |
687 | 0 | } |
688 | |
|
689 | 0 | FREE(&dup_path); |
690 | 0 | FREE(&dup_name); |
691 | |
|
692 | 0 | if (!m) |
693 | 0 | return; |
694 | | |
695 | 0 | const OpenMailboxFlags flags = read_only ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS; |
696 | 0 | if (mx_mbox_open(m, flags)) |
697 | 0 | { |
698 | 0 | struct MailboxView *mv = mview_new(m, NeoMutt->notify); |
699 | 0 | index_shared_data_set_mview(shared, mv); |
700 | |
|
701 | 0 | menu->max = m->msg_count; |
702 | 0 | menu_set_index(menu, find_first_message(shared->mailbox_view)); |
703 | 0 | #ifdef USE_INOTIFY |
704 | 0 | mutt_monitor_add(NULL); |
705 | 0 | #endif |
706 | 0 | } |
707 | 0 | else |
708 | 0 | { |
709 | 0 | index_shared_data_set_mview(shared, NULL); |
710 | 0 | menu_set_index(menu, 0); |
711 | 0 | } |
712 | |
|
713 | 0 | const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all"); |
714 | 0 | if (mutt_using_threads() && c_collapse_all) |
715 | 0 | collapse_all(shared->mailbox_view, menu, 0); |
716 | |
|
717 | 0 | mutt_clear_error(); |
718 | | /* force the mailbox check after we have changed the folder */ |
719 | 0 | struct EventMailbox ev_m = { shared->mailbox }; |
720 | 0 | mutt_mailbox_check(ev_m.mailbox, MUTT_MAILBOX_CHECK_FORCE); |
721 | 0 | menu_queue_redraw(menu, MENU_REDRAW_FULL); |
722 | 0 | OptSearchInvalid = true; |
723 | 0 | } |
724 | | |
725 | | #ifdef USE_NOTMUCH |
726 | | /** |
727 | | * change_folder_notmuch - Change to a different Notmuch Mailbox by string |
728 | | * @param menu Current Menu |
729 | | * @param buf Folder to change to |
730 | | * @param buflen Length of buffer |
731 | | * @param oldcount How many items are currently in the index |
732 | | * @param shared Shared Index data |
733 | | * @param read_only Open Mailbox in read-only mode |
734 | | * @retval ptr Mailbox |
735 | | */ |
736 | | struct Mailbox *change_folder_notmuch(struct Menu *menu, char *buf, int buflen, int *oldcount, |
737 | | struct IndexSharedData *shared, bool read_only) |
738 | | { |
739 | | if (!nm_url_from_query(NULL, buf, buflen)) |
740 | | { |
741 | | mutt_message(_("Failed to create query, aborting")); |
742 | | return NULL; |
743 | | } |
744 | | |
745 | | struct Mailbox *m_query = mx_path_resolve(buf); |
746 | | change_folder_mailbox(menu, m_query, oldcount, shared, read_only); |
747 | | return m_query; |
748 | | } |
749 | | #endif |
750 | | |
751 | | /** |
752 | | * change_folder_string - Change to a different Mailbox by string |
753 | | * @param menu Current Menu |
754 | | * @param buf Folder to change to |
755 | | * @param buflen Length of buffer |
756 | | * @param oldcount How many items are currently in the index |
757 | | * @param shared Shared Index data |
758 | | * @param read_only Open Mailbox in read-only mode |
759 | | */ |
760 | | void change_folder_string(struct Menu *menu, char *buf, size_t buflen, int *oldcount, |
761 | | struct IndexSharedData *shared, bool read_only) |
762 | 0 | { |
763 | 0 | #ifdef USE_NNTP |
764 | 0 | if (OptNews) |
765 | 0 | { |
766 | 0 | OptNews = false; |
767 | 0 | nntp_expand_path(buf, buflen, &CurrentNewsSrv->conn->account); |
768 | 0 | } |
769 | 0 | else |
770 | 0 | #endif |
771 | 0 | { |
772 | 0 | const char *const c_folder = cs_subset_string(shared->sub, "folder"); |
773 | 0 | mx_path_canon(buf, buflen, c_folder, NULL); |
774 | 0 | } |
775 | |
|
776 | 0 | enum MailboxType type = mx_path_probe(buf); |
777 | 0 | if ((type == MUTT_MAILBOX_ERROR) || (type == MUTT_UNKNOWN)) |
778 | 0 | { |
779 | | // Look for a Mailbox by its description, before failing |
780 | 0 | struct Mailbox *m = mailbox_find_name(buf); |
781 | 0 | if (m) |
782 | 0 | { |
783 | 0 | change_folder_mailbox(menu, m, oldcount, shared, read_only); |
784 | 0 | } |
785 | 0 | else |
786 | 0 | { |
787 | 0 | mutt_error(_("%s is not a mailbox"), buf); |
788 | 0 | } |
789 | 0 | return; |
790 | 0 | } |
791 | | |
792 | 0 | struct Mailbox *m = mx_path_resolve(buf); |
793 | 0 | change_folder_mailbox(menu, m, oldcount, shared, read_only); |
794 | 0 | } |
795 | | |
796 | | /** |
797 | | * index_make_entry - Format a menu item for the index list - Implements Menu::make_entry() - @ingroup menu_make_entry |
798 | | */ |
799 | | void index_make_entry(struct Menu *menu, char *buf, size_t buflen, int line) |
800 | 0 | { |
801 | 0 | buf[0] = '\0'; |
802 | |
|
803 | 0 | if (!menu || !menu->mdata) |
804 | 0 | return; |
805 | | |
806 | 0 | struct IndexPrivateData *priv = menu->mdata; |
807 | 0 | struct IndexSharedData *shared = priv->shared; |
808 | 0 | struct Mailbox *m = shared->mailbox; |
809 | 0 | if (!shared->mailbox_view) |
810 | 0 | menu->current = -1; |
811 | |
|
812 | 0 | if (!m || (line < 0) || (line >= m->email_max)) |
813 | 0 | return; |
814 | | |
815 | 0 | struct Email *e = mutt_get_virt_email(m, line); |
816 | 0 | if (!e) |
817 | 0 | return; |
818 | | |
819 | 0 | MuttFormatFlags flags = MUTT_FORMAT_ARROWCURSOR | MUTT_FORMAT_INDEX; |
820 | 0 | struct MuttThread *tmp = NULL; |
821 | |
|
822 | 0 | const enum UseThreads c_threads = mutt_thread_style(); |
823 | 0 | if ((c_threads > UT_FLAT) && e->tree && e->thread) |
824 | 0 | { |
825 | 0 | flags |= MUTT_FORMAT_TREE; /* display the thread tree */ |
826 | 0 | if (e->display_subject) |
827 | 0 | { |
828 | 0 | flags |= MUTT_FORMAT_FORCESUBJ; |
829 | 0 | } |
830 | 0 | else |
831 | 0 | { |
832 | 0 | const bool reverse = c_threads == UT_REVERSE; |
833 | 0 | int edgemsgno; |
834 | 0 | if (reverse) |
835 | 0 | { |
836 | 0 | if (menu->top + menu->page_len > menu->max) |
837 | 0 | edgemsgno = m->v2r[menu->max - 1]; |
838 | 0 | else |
839 | 0 | edgemsgno = m->v2r[menu->top + menu->page_len - 1]; |
840 | 0 | } |
841 | 0 | else |
842 | 0 | { |
843 | 0 | edgemsgno = m->v2r[menu->top]; |
844 | 0 | } |
845 | |
|
846 | 0 | for (tmp = e->thread->parent; tmp; tmp = tmp->parent) |
847 | 0 | { |
848 | 0 | if (!tmp->message) |
849 | 0 | continue; |
850 | | |
851 | | /* if no ancestor is visible on current screen, provisionally force |
852 | | * subject... */ |
853 | 0 | if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno)) |
854 | 0 | { |
855 | 0 | flags |= MUTT_FORMAT_FORCESUBJ; |
856 | 0 | break; |
857 | 0 | } |
858 | 0 | else if (tmp->message->vnum >= 0) |
859 | 0 | { |
860 | 0 | break; |
861 | 0 | } |
862 | 0 | } |
863 | 0 | if (flags & MUTT_FORMAT_FORCESUBJ) |
864 | 0 | { |
865 | 0 | for (tmp = e->thread->prev; tmp; tmp = tmp->prev) |
866 | 0 | { |
867 | 0 | if (!tmp->message) |
868 | 0 | continue; |
869 | | |
870 | | /* ...but if a previous sibling is available, don't force it */ |
871 | 0 | if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno)) |
872 | 0 | { |
873 | 0 | break; |
874 | 0 | } |
875 | 0 | else if (tmp->message->vnum >= 0) |
876 | 0 | { |
877 | 0 | flags &= ~MUTT_FORMAT_FORCESUBJ; |
878 | 0 | break; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | } |
882 | 0 | } |
883 | 0 | } |
884 | |
|
885 | 0 | const char *const c_index_format = cs_subset_string(shared->sub, "index_format"); |
886 | 0 | int msg_in_pager = shared->mailbox_view ? shared->mailbox_view->msg_in_pager : 0; |
887 | 0 | mutt_make_string(buf, buflen, menu->win->state.cols, NONULL(c_index_format), |
888 | 0 | m, msg_in_pager, e, flags, NULL); |
889 | 0 | } |
890 | | |
891 | | /** |
892 | | * index_color - Calculate the colour for a line of the index - Implements Menu::color() - @ingroup menu_color |
893 | | */ |
894 | | struct AttrColor *index_color(struct Menu *menu, int line) |
895 | 0 | { |
896 | 0 | struct IndexPrivateData *priv = menu->mdata; |
897 | 0 | struct IndexSharedData *shared = priv->shared; |
898 | 0 | struct Mailbox *m = shared->mailbox; |
899 | 0 | if (!m || (line < 0)) |
900 | 0 | return NULL; |
901 | | |
902 | 0 | struct Email *e = mutt_get_virt_email(m, line); |
903 | 0 | if (!e) |
904 | 0 | return NULL; |
905 | | |
906 | 0 | if (e->attr_color) |
907 | 0 | return e->attr_color; |
908 | | |
909 | 0 | mutt_set_header_color(m, e); |
910 | 0 | return e->attr_color; |
911 | 0 | } |
912 | | |
913 | | /** |
914 | | * mutt_draw_statusline - Draw a highlighted status bar |
915 | | * @param win Window |
916 | | * @param cols Maximum number of screen columns |
917 | | * @param buf Message to be displayed |
918 | | * @param buflen Length of the buffer |
919 | | * |
920 | | * Users configure the highlighting of the status bar, e.g. |
921 | | * color status red default "[0-9][0-9]:[0-9][0-9]" |
922 | | * |
923 | | * Where regexes overlap, the one nearest the start will be used. |
924 | | * If two regexes start at the same place, the longer match will be used. |
925 | | */ |
926 | | void mutt_draw_statusline(struct MuttWindow *win, int cols, const char *buf, size_t buflen) |
927 | 0 | { |
928 | 0 | if (!buf || !stdscr) |
929 | 0 | return; |
930 | | |
931 | 0 | size_t i = 0; |
932 | 0 | size_t offset = 0; |
933 | 0 | bool found = false; |
934 | 0 | size_t chunks = 0; |
935 | 0 | size_t len = 0; |
936 | | |
937 | | /** |
938 | | * struct StatusSyntax - Colours of the status bar |
939 | | */ |
940 | 0 | struct StatusSyntax |
941 | 0 | { |
942 | 0 | struct AttrColor *attr_color; |
943 | 0 | int first; ///< First character of that colour |
944 | 0 | int last; ///< Last character of that colour |
945 | 0 | } *syntax = NULL; |
946 | |
|
947 | 0 | struct AttrColor *ac_base = merged_color_overlay(simple_color_get(MT_COLOR_NORMAL), |
948 | 0 | simple_color_get(MT_COLOR_STATUS)); |
949 | 0 | do |
950 | 0 | { |
951 | 0 | struct RegexColor *cl = NULL; |
952 | 0 | found = false; |
953 | |
|
954 | 0 | if (!buf[offset]) |
955 | 0 | break; |
956 | | |
957 | | /* loop through each "color status regex" */ |
958 | 0 | STAILQ_FOREACH(cl, regex_colors_get_list(MT_COLOR_STATUS), entries) |
959 | 0 | { |
960 | 0 | regmatch_t pmatch[cl->match + 1]; |
961 | |
|
962 | 0 | if (regexec(&cl->regex, buf + offset, cl->match + 1, pmatch, 0) != 0) |
963 | 0 | continue; /* regex doesn't match the status bar */ |
964 | | |
965 | 0 | int first = pmatch[cl->match].rm_so + offset; |
966 | 0 | int last = pmatch[cl->match].rm_eo + offset; |
967 | |
|
968 | 0 | if (first == last) |
969 | 0 | continue; /* ignore an empty regex */ |
970 | | |
971 | 0 | if (!found) |
972 | 0 | { |
973 | 0 | chunks++; |
974 | 0 | mutt_mem_realloc(&syntax, chunks * sizeof(struct StatusSyntax)); |
975 | 0 | } |
976 | |
|
977 | 0 | i = chunks - 1; |
978 | 0 | if (!found || (first < syntax[i].first) || |
979 | 0 | ((first == syntax[i].first) && (last > syntax[i].last))) |
980 | 0 | { |
981 | 0 | struct AttrColor *ac_merge = merged_color_overlay(ac_base, &cl->attr_color); |
982 | |
|
983 | 0 | syntax[i].attr_color = ac_merge; |
984 | 0 | syntax[i].first = first; |
985 | 0 | syntax[i].last = last; |
986 | 0 | } |
987 | 0 | found = true; |
988 | 0 | } |
989 | |
|
990 | 0 | if (syntax) |
991 | 0 | { |
992 | 0 | offset = syntax[i].last; |
993 | 0 | } |
994 | 0 | } while (found); |
995 | | |
996 | | /* Only 'len' bytes will fit into 'cols' screen columns */ |
997 | 0 | len = mutt_wstr_trunc(buf, buflen, cols, NULL); |
998 | |
|
999 | 0 | offset = 0; |
1000 | |
|
1001 | 0 | if ((chunks > 0) && (syntax[0].first > 0)) |
1002 | 0 | { |
1003 | | /* Text before the first highlight */ |
1004 | 0 | mutt_window_addnstr(win, buf, MIN(len, syntax[0].first)); |
1005 | 0 | mutt_curses_set_color(ac_base); |
1006 | 0 | if (len <= syntax[0].first) |
1007 | 0 | goto dsl_finish; /* no more room */ |
1008 | | |
1009 | 0 | offset = syntax[0].first; |
1010 | 0 | } |
1011 | | |
1012 | 0 | for (i = 0; i < chunks; i++) |
1013 | 0 | { |
1014 | | /* Highlighted text */ |
1015 | 0 | mutt_curses_set_color(syntax[i].attr_color); |
1016 | 0 | mutt_window_addnstr(win, buf + offset, MIN(len, syntax[i].last) - offset); |
1017 | 0 | if (len <= syntax[i].last) |
1018 | 0 | goto dsl_finish; /* no more room */ |
1019 | | |
1020 | 0 | size_t next; |
1021 | 0 | if ((i + 1) == chunks) |
1022 | 0 | { |
1023 | 0 | next = len; |
1024 | 0 | } |
1025 | 0 | else |
1026 | 0 | { |
1027 | 0 | next = MIN(len, syntax[i + 1].first); |
1028 | 0 | } |
1029 | |
|
1030 | 0 | mutt_curses_set_color(ac_base); |
1031 | 0 | offset = syntax[i].last; |
1032 | 0 | mutt_window_addnstr(win, buf + offset, next - offset); |
1033 | |
|
1034 | 0 | offset = next; |
1035 | 0 | if (offset >= len) |
1036 | 0 | goto dsl_finish; /* no more room */ |
1037 | 0 | } |
1038 | | |
1039 | 0 | mutt_curses_set_color(ac_base); |
1040 | 0 | if (offset < len) |
1041 | 0 | { |
1042 | | /* Text after the last highlight */ |
1043 | 0 | mutt_window_addnstr(win, buf + offset, len - offset); |
1044 | 0 | } |
1045 | |
|
1046 | 0 | int width = mutt_strwidth(buf); |
1047 | 0 | if (width < cols) |
1048 | 0 | { |
1049 | | /* Pad the rest of the line with whitespace */ |
1050 | 0 | mutt_paddstr(win, cols - width, ""); |
1051 | 0 | } |
1052 | 0 | dsl_finish: |
1053 | 0 | FREE(&syntax); |
1054 | 0 | } |
1055 | | |
1056 | | /** |
1057 | | * mutt_index_menu - Display a list of emails |
1058 | | * @param dlg Dialog containing Windows to draw on |
1059 | | * @param m_init Initial mailbox |
1060 | | * @retval ptr Mailbox open in the index |
1061 | | */ |
1062 | | struct Mailbox *mutt_index_menu(struct MuttWindow *dlg, struct Mailbox *m_init) |
1063 | 0 | { |
1064 | | /* Make sure use_threads/sort/sort_aux are coherent */ |
1065 | 0 | index_adjust_sort_threads(NeoMutt->sub); |
1066 | |
|
1067 | 0 | struct IndexSharedData *shared = dlg->wdata; |
1068 | 0 | index_shared_data_set_mview(shared, mview_new(m_init, NeoMutt->notify)); |
1069 | |
|
1070 | 0 | struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX); |
1071 | |
|
1072 | 0 | struct IndexPrivateData *priv = panel_index->wdata; |
1073 | 0 | priv->attach_msg = OptAttachMsg; |
1074 | 0 | priv->win_index = window_find_child(panel_index, WT_MENU); |
1075 | |
|
1076 | 0 | int op = OP_NULL; |
1077 | |
|
1078 | 0 | #ifdef USE_NNTP |
1079 | 0 | if (shared->mailbox && (shared->mailbox->type == MUTT_NNTP)) |
1080 | 0 | dlg->help_data = IndexNewsHelp; |
1081 | 0 | else |
1082 | 0 | #endif |
1083 | 0 | dlg->help_data = IndexHelp; |
1084 | 0 | dlg->help_menu = MENU_INDEX; |
1085 | |
|
1086 | 0 | priv->menu = priv->win_index->wdata; |
1087 | 0 | priv->menu->make_entry = index_make_entry; |
1088 | 0 | priv->menu->color = index_color; |
1089 | 0 | priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0; |
1090 | 0 | menu_set_index(priv->menu, find_first_message(shared->mailbox_view)); |
1091 | 0 | mutt_window_reflow(NULL); |
1092 | |
|
1093 | 0 | if (!priv->attach_msg) |
1094 | 0 | { |
1095 | | /* force the mailbox check after we enter the folder */ |
1096 | 0 | mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_FORCE); |
1097 | 0 | } |
1098 | 0 | #ifdef USE_INOTIFY |
1099 | 0 | mutt_monitor_add(NULL); |
1100 | 0 | #endif |
1101 | |
|
1102 | 0 | const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all"); |
1103 | 0 | if (mutt_using_threads() && c_collapse_all) |
1104 | 0 | { |
1105 | 0 | collapse_all(shared->mailbox_view, priv->menu, 0); |
1106 | 0 | menu_queue_redraw(priv->menu, MENU_REDRAW_FULL); |
1107 | 0 | } |
1108 | |
|
1109 | 0 | int rc = 0; |
1110 | 0 | do |
1111 | 0 | { |
1112 | | /* Clear the tag prefix unless we just started it. |
1113 | | * Don't clear the prefix on a timeout, but do clear on an abort */ |
1114 | 0 | if (priv->tag_prefix && (op != OP_TAG_PREFIX) && |
1115 | 0 | (op != OP_TAG_PREFIX_COND) && (op != OP_TIMEOUT)) |
1116 | 0 | { |
1117 | 0 | priv->tag_prefix = false; |
1118 | 0 | } |
1119 | | |
1120 | | /* check if we need to resort the index because just about |
1121 | | * any 'op' below could do mutt_enter_command(), either here or |
1122 | | * from any new priv->menu launched, and change $sort/$sort_aux */ |
1123 | 0 | if (OptNeedResort && shared->mailbox && (shared->mailbox->msg_count != 0) && |
1124 | 0 | (menu_get_index(priv->menu) >= 0)) |
1125 | 0 | { |
1126 | 0 | resort_index(shared->mailbox_view, priv->menu); |
1127 | 0 | } |
1128 | |
|
1129 | 0 | priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0; |
1130 | 0 | priv->oldcount = shared->mailbox ? shared->mailbox->msg_count : 0; |
1131 | |
|
1132 | 0 | if (shared->mailbox_view && OptRedrawTree && shared->mailbox && |
1133 | 0 | (shared->mailbox->msg_count != 0) && mutt_using_threads()) |
1134 | 0 | { |
1135 | 0 | mutt_draw_tree(shared->mailbox_view->threads); |
1136 | 0 | OptRedrawTree = false; |
1137 | 0 | } |
1138 | |
|
1139 | 0 | if (shared->mailbox && shared->mailbox_view) |
1140 | 0 | { |
1141 | 0 | mailbox_gc_run(); |
1142 | |
|
1143 | 0 | shared->mailbox_view->menu = priv->menu; |
1144 | | /* check for new mail in the mailbox. If nonzero, then something has |
1145 | | * changed about the file (either we got new mail or the file was |
1146 | | * modified underneath us.) */ |
1147 | 0 | enum MxStatus check = mx_mbox_check(shared->mailbox); |
1148 | |
|
1149 | 0 | if (check == MX_STATUS_ERROR) |
1150 | 0 | { |
1151 | 0 | if (buf_is_empty(&shared->mailbox->pathbuf)) |
1152 | 0 | { |
1153 | | /* fatal error occurred */ |
1154 | 0 | mview_free(&shared->mailbox_view); |
1155 | 0 | menu_queue_redraw(priv->menu, MENU_REDRAW_FULL); |
1156 | 0 | } |
1157 | |
|
1158 | 0 | OptSearchInvalid = true; |
1159 | 0 | } |
1160 | 0 | else if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED) || |
1161 | 0 | (check == MX_STATUS_FLAGS)) |
1162 | 0 | { |
1163 | | /* notify the user of new mail */ |
1164 | 0 | if (check == MX_STATUS_REOPENED) |
1165 | 0 | { |
1166 | 0 | mutt_error(_("Mailbox was externally modified. Flags may be wrong.")); |
1167 | 0 | } |
1168 | 0 | else if (check == MX_STATUS_NEW_MAIL) |
1169 | 0 | { |
1170 | 0 | for (size_t i = 0; i < shared->mailbox->msg_count; i++) |
1171 | 0 | { |
1172 | 0 | const struct Email *e = shared->mailbox->emails[i]; |
1173 | 0 | if (e && !e->read && !e->old) |
1174 | 0 | { |
1175 | 0 | mutt_message(_("New mail in this mailbox")); |
1176 | 0 | const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new"); |
1177 | 0 | if (c_beep_new) |
1178 | 0 | mutt_beep(true); |
1179 | 0 | const char *const c_new_mail_command = cs_subset_string(shared->sub, "new_mail_command"); |
1180 | 0 | if (c_new_mail_command) |
1181 | 0 | { |
1182 | 0 | char cmd[1024] = { 0 }; |
1183 | 0 | menu_status_line(cmd, sizeof(cmd), shared, NULL, sizeof(cmd), |
1184 | 0 | NONULL(c_new_mail_command)); |
1185 | 0 | if (mutt_system(cmd) != 0) |
1186 | 0 | mutt_error(_("Error running \"%s\""), cmd); |
1187 | 0 | } |
1188 | 0 | break; |
1189 | 0 | } |
1190 | 0 | } |
1191 | 0 | } |
1192 | 0 | else if (check == MX_STATUS_FLAGS) |
1193 | 0 | { |
1194 | 0 | mutt_message(_("Mailbox was externally modified")); |
1195 | 0 | } |
1196 | | |
1197 | | /* avoid the message being overwritten by mailbox */ |
1198 | 0 | priv->do_mailbox_notify = false; |
1199 | |
|
1200 | 0 | bool verbose = shared->mailbox->verbose; |
1201 | 0 | shared->mailbox->verbose = false; |
1202 | 0 | update_index(priv->menu, shared->mailbox_view, check, priv->oldcount, shared); |
1203 | 0 | shared->mailbox->verbose = verbose; |
1204 | 0 | priv->menu->max = shared->mailbox->vcount; |
1205 | 0 | menu_queue_redraw(priv->menu, MENU_REDRAW_FULL); |
1206 | 0 | OptSearchInvalid = true; |
1207 | 0 | } |
1208 | |
|
1209 | 0 | index_shared_data_set_email(shared, mutt_get_virt_email(shared->mailbox, |
1210 | 0 | menu_get_index(priv->menu))); |
1211 | 0 | } |
1212 | |
|
1213 | 0 | if (!priv->attach_msg) |
1214 | 0 | { |
1215 | | /* check for new mail in the incoming folders */ |
1216 | 0 | priv->oldcount = priv->newcount; |
1217 | 0 | priv->newcount = mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_NO_FLAGS); |
1218 | 0 | if (priv->do_mailbox_notify) |
1219 | 0 | { |
1220 | 0 | if (mutt_mailbox_notify(shared->mailbox)) |
1221 | 0 | { |
1222 | 0 | const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new"); |
1223 | 0 | if (c_beep_new) |
1224 | 0 | mutt_beep(true); |
1225 | 0 | const char *const c_new_mail_command = cs_subset_string(shared->sub, "new_mail_command"); |
1226 | 0 | if (c_new_mail_command) |
1227 | 0 | { |
1228 | 0 | char cmd[1024] = { 0 }; |
1229 | 0 | menu_status_line(cmd, sizeof(cmd), shared, priv->menu, sizeof(cmd), |
1230 | 0 | NONULL(c_new_mail_command)); |
1231 | 0 | if (mutt_system(cmd) != 0) |
1232 | 0 | mutt_error(_("Error running \"%s\""), cmd); |
1233 | 0 | } |
1234 | 0 | } |
1235 | 0 | } |
1236 | 0 | else |
1237 | 0 | { |
1238 | 0 | priv->do_mailbox_notify = true; |
1239 | 0 | } |
1240 | 0 | } |
1241 | |
|
1242 | 0 | window_redraw(NULL); |
1243 | | |
1244 | | /* give visual indication that the next command is a tag- command */ |
1245 | 0 | if (priv->tag_prefix) |
1246 | 0 | msgwin_set_text(MT_COLOR_NORMAL, "tag-"); |
1247 | |
|
1248 | 0 | const bool c_arrow_cursor = cs_subset_bool(shared->sub, "arrow_cursor"); |
1249 | 0 | const bool c_braille_friendly = cs_subset_bool(shared->sub, "braille_friendly"); |
1250 | 0 | const int index = menu_get_index(priv->menu); |
1251 | 0 | if (c_arrow_cursor) |
1252 | 0 | { |
1253 | 0 | mutt_window_move(priv->menu->win, 2, index - priv->menu->top); |
1254 | 0 | } |
1255 | 0 | else if (c_braille_friendly) |
1256 | 0 | { |
1257 | 0 | mutt_window_move(priv->menu->win, 0, index - priv->menu->top); |
1258 | 0 | } |
1259 | 0 | else |
1260 | 0 | { |
1261 | 0 | mutt_window_move(priv->menu->win, priv->menu->win->state.cols - 1, |
1262 | 0 | index - priv->menu->top); |
1263 | 0 | } |
1264 | 0 | mutt_refresh(); |
1265 | |
|
1266 | 0 | if (SigWinch) |
1267 | 0 | { |
1268 | 0 | SigWinch = false; |
1269 | 0 | window_invalidate_all(); |
1270 | 0 | mutt_resize_screen(); |
1271 | 0 | priv->menu->top = 0; /* so we scroll the right amount */ |
1272 | | /* force a real complete redraw. clrtobot() doesn't seem to be able |
1273 | | * to handle every case without this. */ |
1274 | 0 | clearok(stdscr, true); |
1275 | 0 | msgwin_clear_text(); |
1276 | 0 | continue; |
1277 | 0 | } |
1278 | | |
1279 | 0 | window_redraw(NULL); |
1280 | 0 | op = km_dokey(MENU_INDEX); |
1281 | | |
1282 | | /* either user abort or timeout */ |
1283 | 0 | if (op < OP_NULL) |
1284 | 0 | { |
1285 | 0 | mutt_timeout_hook(); |
1286 | 0 | if (priv->tag_prefix) |
1287 | 0 | msgwin_clear_text(); |
1288 | 0 | continue; |
1289 | 0 | } |
1290 | | |
1291 | 0 | mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op); |
1292 | | |
1293 | | /* special handling for the priv->tag-prefix function */ |
1294 | 0 | const bool c_auto_tag = cs_subset_bool(shared->sub, "auto_tag"); |
1295 | 0 | if ((op == OP_TAG_PREFIX) || (op == OP_TAG_PREFIX_COND)) |
1296 | 0 | { |
1297 | | /* A second priv->tag-prefix command aborts */ |
1298 | 0 | if (priv->tag_prefix) |
1299 | 0 | { |
1300 | 0 | priv->tag_prefix = false; |
1301 | 0 | msgwin_clear_text(); |
1302 | 0 | continue; |
1303 | 0 | } |
1304 | | |
1305 | 0 | if (!shared->mailbox) |
1306 | 0 | { |
1307 | 0 | mutt_error(_("No mailbox is open")); |
1308 | 0 | continue; |
1309 | 0 | } |
1310 | | |
1311 | 0 | if (shared->mailbox->msg_tagged == 0) |
1312 | 0 | { |
1313 | 0 | if (op == OP_TAG_PREFIX) |
1314 | 0 | { |
1315 | 0 | mutt_error(_("No tagged messages")); |
1316 | 0 | } |
1317 | 0 | else if (op == OP_TAG_PREFIX_COND) |
1318 | 0 | { |
1319 | 0 | mutt_flush_macro_to_endcond(); |
1320 | 0 | mutt_message(_("Nothing to do")); |
1321 | 0 | } |
1322 | 0 | continue; |
1323 | 0 | } |
1324 | | |
1325 | | /* get the real command */ |
1326 | 0 | priv->tag_prefix = true; |
1327 | 0 | continue; |
1328 | 0 | } |
1329 | 0 | else if (c_auto_tag && shared->mailbox && (shared->mailbox->msg_tagged != 0)) |
1330 | 0 | { |
1331 | 0 | priv->tag_prefix = true; |
1332 | 0 | } |
1333 | | |
1334 | 0 | mutt_clear_error(); |
1335 | |
|
1336 | 0 | #ifdef USE_NNTP |
1337 | 0 | OptNews = false; /* for any case */ |
1338 | 0 | #endif |
1339 | |
|
1340 | | #ifdef USE_NOTMUCH |
1341 | | nm_db_debug_check(shared->mailbox); |
1342 | | #endif |
1343 | |
|
1344 | 0 | rc = index_function_dispatcher(priv->win_index, op); |
1345 | |
|
1346 | 0 | if (rc == FR_UNKNOWN) |
1347 | 0 | rc = menu_function_dispatcher(priv->win_index, op); |
1348 | |
|
1349 | 0 | #ifdef USE_SIDEBAR |
1350 | 0 | if (rc == FR_UNKNOWN) |
1351 | 0 | { |
1352 | 0 | struct MuttWindow *win_sidebar = window_find_child(dlg, WT_SIDEBAR); |
1353 | 0 | rc = sb_function_dispatcher(win_sidebar, op); |
1354 | 0 | } |
1355 | 0 | #endif |
1356 | 0 | if (rc == FR_UNKNOWN) |
1357 | 0 | rc = global_function_dispatcher(NULL, op); |
1358 | |
|
1359 | 0 | if (rc == FR_UNKNOWN) |
1360 | 0 | km_error_key(MENU_INDEX); |
1361 | |
|
1362 | | #ifdef USE_NOTMUCH |
1363 | | nm_db_debug_check(shared->mailbox); |
1364 | | #endif |
1365 | 0 | } while (rc != FR_DONE); |
1366 | | |
1367 | 0 | mview_free(&shared->mailbox_view); |
1368 | |
|
1369 | 0 | return shared->mailbox; |
1370 | 0 | } |
1371 | | |
1372 | | /** |
1373 | | * mutt_set_header_color - Select a colour for a message |
1374 | | * @param m Mailbox |
1375 | | * @param e Current Email |
1376 | | */ |
1377 | | void mutt_set_header_color(struct Mailbox *m, struct Email *e) |
1378 | 0 | { |
1379 | 0 | if (!e) |
1380 | 0 | return; |
1381 | | |
1382 | 0 | struct RegexColor *color = NULL; |
1383 | 0 | struct PatternCache cache = { 0 }; |
1384 | |
|
1385 | 0 | struct AttrColor *ac_merge = NULL; |
1386 | 0 | STAILQ_FOREACH(color, regex_colors_get_list(MT_COLOR_INDEX), entries) |
1387 | 0 | { |
1388 | 0 | if (mutt_pattern_exec(SLIST_FIRST(color->color_pattern), |
1389 | 0 | MUTT_MATCH_FULL_ADDRESS, m, e, &cache)) |
1390 | 0 | { |
1391 | 0 | ac_merge = merged_color_overlay(ac_merge, &color->attr_color); |
1392 | 0 | } |
1393 | 0 | } |
1394 | |
|
1395 | 0 | struct AttrColor *ac_normal = simple_color_get(MT_COLOR_NORMAL); |
1396 | 0 | if (ac_merge) |
1397 | 0 | ac_merge = merged_color_overlay(ac_normal, ac_merge); |
1398 | 0 | else |
1399 | 0 | ac_merge = ac_normal; |
1400 | |
|
1401 | 0 | e->attr_color = ac_merge; |
1402 | 0 | } |
1403 | | |
1404 | | /** |
1405 | | * index_pager_init - Allocate the Windows for the Index/Pager |
1406 | | * @retval ptr Dialog containing nested Windows |
1407 | | */ |
1408 | | struct MuttWindow *index_pager_init(void) |
1409 | 0 | { |
1410 | 0 | struct MuttWindow *dlg = mutt_window_new(WT_DLG_INDEX, MUTT_WIN_ORIENT_HORIZONTAL, |
1411 | 0 | MUTT_WIN_SIZE_MAXIMISE, MUTT_WIN_SIZE_UNLIMITED, |
1412 | 0 | MUTT_WIN_SIZE_UNLIMITED); |
1413 | |
|
1414 | 0 | struct IndexSharedData *shared = index_shared_data_new(); |
1415 | 0 | notify_set_parent(shared->notify, dlg->notify); |
1416 | |
|
1417 | 0 | dlg->wdata = shared; |
1418 | 0 | dlg->wdata_free = index_shared_data_free; |
1419 | |
|
1420 | 0 | const bool c_status_on_top = cs_subset_bool(NeoMutt->sub, "status_on_top"); |
1421 | |
|
1422 | 0 | struct MuttWindow *panel_index = ipanel_new(c_status_on_top, shared); |
1423 | 0 | struct MuttWindow *panel_pager = ppanel_new(c_status_on_top, shared); |
1424 | |
|
1425 | 0 | mutt_window_add_child(dlg, panel_index); |
1426 | 0 | mutt_window_add_child(dlg, panel_pager); |
1427 | |
|
1428 | 0 | dlg->focus = panel_index; |
1429 | |
|
1430 | 0 | return dlg; |
1431 | 0 | } |
1432 | | |
1433 | | /** |
1434 | | * dlg_change_folder - Change the current folder, cautiously |
1435 | | * @param dlg Dialog holding the Index |
1436 | | * @param m Mailbox to change to |
1437 | | */ |
1438 | | void dlg_change_folder(struct MuttWindow *dlg, struct Mailbox *m) |
1439 | 0 | { |
1440 | 0 | if (!dlg || !m) |
1441 | 0 | return; |
1442 | | |
1443 | 0 | struct IndexSharedData *shared = dlg->wdata; |
1444 | 0 | if (!shared) |
1445 | 0 | return; |
1446 | | |
1447 | 0 | struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX); |
1448 | 0 | if (!panel_index) |
1449 | 0 | return; |
1450 | | |
1451 | 0 | struct IndexPrivateData *priv = panel_index->wdata; |
1452 | 0 | if (!priv) |
1453 | 0 | return; |
1454 | | |
1455 | 0 | change_folder_mailbox(priv->menu, m, &priv->oldcount, shared, false); |
1456 | 0 | } |