3 * Assorted sorting methods
6 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
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
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
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/>.
25 * @page sort Assorted sorting methods
27 * Assorted sorting methods
34 #include "mutt/mutt.h"
35 #include "address/lib.h"
36 #include "email/lib.h"
42 #include "mutt_logging.h"
43 #include "mutt_thread.h"
47 #include "nntp/nntp.h"
50 /* These Config Variables are only used in sort.c */
51 bool C_ReverseAlias; ///< Config: Display the alias in the index, rather than the message's sender
53 /* function to use as discriminator when normal sort method is equal */
54 static sort_t *AuxSort = NULL;
57 * perform_auxsort - Compare two emails using the auxiliary sort method
58 * @param retval Result of normal sort method
59 * @param a First email
60 * @param b Second email
61 * @retval -1 a precedes b
62 * @retval 0 a and b are identical
63 * @retval 1 b precedes a
65 int perform_auxsort(int retval, const void *a, const void *b)
67 /* If the items compared equal by the main sort
68 * and we're not already doing an 'aux' sort... */
69 if ((retval == 0) && AuxSort && !OptAuxSort)
72 retval = AuxSort(a, b);
77 /* If the items still match, use their index positions
78 * to maintain a stable sort order */
80 retval = (*((struct Email const *const *) a))->index -
81 (*((struct Email const *const *) b))->index;
86 * compare_score - Compare two emails using their scores - Implements ::sort_t
88 static int compare_score(const void *a, const void *b)
90 struct Email const *const *pa = (struct Email const *const *) a;
91 struct Email const *const *pb = (struct Email const *const *) b;
92 int result = (*pb)->score - (*pa)->score; /* note that this is reverse */
93 result = perform_auxsort(result, a, b);
94 return SORT_CODE(result);
98 * compare_size - Compare the size of two emails - Implements ::sort_t
100 static int compare_size(const void *a, const void *b)
102 struct Email const *const *pa = (struct Email const *const *) a;
103 struct Email const *const *pb = (struct Email const *const *) b;
104 int result = (*pa)->content->length - (*pb)->content->length;
105 result = perform_auxsort(result, a, b);
106 return SORT_CODE(result);
110 * compare_date_sent - Compare the sent date of two emails - Implements ::sort_t
112 static int compare_date_sent(const void *a, const void *b)
114 struct Email const *const *pa = (struct Email const *const *) a;
115 struct Email const *const *pb = (struct Email const *const *) b;
116 int result = (*pa)->date_sent - (*pb)->date_sent;
117 result = perform_auxsort(result, a, b);
118 return SORT_CODE(result);
122 * compare_subject - Compare the subject of two emails - Implements ::sort_t
124 static int compare_subject(const void *a, const void *b)
126 struct Email const *const *pa = (struct Email const *const *) a;
127 struct Email const *const *pb = (struct Email const *const *) b;
130 if (!(*pa)->env->real_subj)
132 if (!(*pb)->env->real_subj)
133 rc = compare_date_sent(pa, pb);
137 else if (!(*pb)->env->real_subj)
140 rc = mutt_str_strcasecmp((*pa)->env->real_subj, (*pb)->env->real_subj);
141 rc = perform_auxsort(rc, a, b);
142 return SORT_CODE(rc);
146 * mutt_get_name - Pick the best name to display from an address
147 * @param a Address to use
148 * @retval ptr Display name
150 * This function uses:
151 * 1. Alias for email address
155 const char *mutt_get_name(const struct Address *a)
157 struct Address *ali = NULL;
161 if (C_ReverseAlias && (ali = mutt_alias_reverse_lookup(a)) && ali->personal)
162 return ali->personal;
163 else if (a->personal)
166 return mutt_addr_for_display(a);
168 /* don't return NULL to avoid segfault when printing/comparing */
173 * compare_to - Compare the 'to' fields of two emails - Implements ::sort_t
175 static int compare_to(const void *a, const void *b)
177 struct Email const *const *ppa = (struct Email const *const *) a;
178 struct Email const *const *ppb = (struct Email const *const *) b;
181 mutt_str_strfcpy(fa, mutt_get_name(TAILQ_FIRST(&(*ppa)->env->to)), sizeof(fa));
182 const char *fb = mutt_get_name(TAILQ_FIRST(&(*ppb)->env->to));
183 int result = mutt_str_strncasecmp(fa, fb, sizeof(fa));
184 result = perform_auxsort(result, a, b);
185 return SORT_CODE(result);
189 * compare_from - Compare the 'from' fields of two emails - Implements ::sort_t
191 static int compare_from(const void *a, const void *b)
193 struct Email const *const *ppa = (struct Email const *const *) a;
194 struct Email const *const *ppb = (struct Email const *const *) b;
197 mutt_str_strfcpy(fa, mutt_get_name(TAILQ_FIRST(&(*ppa)->env->from)), sizeof(fa));
198 const char *fb = mutt_get_name(TAILQ_FIRST(&(*ppb)->env->from));
199 int result = mutt_str_strncasecmp(fa, fb, sizeof(fa));
200 result = perform_auxsort(result, a, b);
201 return SORT_CODE(result);
205 * compare_date_received - Compare the date received of two emails - Implements ::sort_t
207 static int compare_date_received(const void *a, const void *b)
209 struct Email const *const *pa = (struct Email const *const *) a;
210 struct Email const *const *pb = (struct Email const *const *) b;
211 int result = (*pa)->received - (*pb)->received;
212 result = perform_auxsort(result, a, b);
213 return SORT_CODE(result);
217 * compare_order - Restore the 'unsorted' order of emails - Implements ::sort_t
219 static int compare_order(const void *a, const void *b)
221 struct Email const *const *ea = (struct Email const *const *) a;
222 struct Email const *const *eb = (struct Email const *const *) b;
224 /* no need to auxsort because you will never have equality here */
225 return SORT_CODE((*ea)->index - (*eb)->index);
229 * compare_spam - Compare the spam values of two emails - Implements ::sort_t
231 static int compare_spam(const void *a, const void *b)
233 struct Email const *const *ppa = (struct Email const *const *) a;
234 struct Email const *const *ppb = (struct Email const *const *) b;
235 char *aptr = NULL, *bptr = NULL;
240 /* Firstly, require spam attributes for both msgs */
241 /* to compare. Determine which msgs have one. */
242 ahas = (*ppa)->env && !mutt_buffer_is_empty(&(*ppa)->env->spam);
243 bhas = (*ppb)->env && !mutt_buffer_is_empty(&(*ppb)->env->spam);
245 /* If one msg has spam attr but other does not, sort the one with first. */
249 return SORT_CODE(-1);
251 /* Else, if neither has a spam attr, presume equality. Fall back on aux. */
254 result = perform_auxsort(result, a, b);
255 return SORT_CODE(result);
258 /* Both have spam attrs. */
260 /* preliminary numeric examination */
262 (strtod((*ppa)->env->spam.data, &aptr) - strtod((*ppb)->env->spam.data, &bptr));
264 /* map double into comparison (-1, 0, or 1) */
265 result = ((difference < 0.0) ? -1 : (difference > 0.0) ? 1 : 0);
267 /* If either aptr or bptr is equal to data, there is no numeric */
268 /* value for that spam attribute. In this case, compare lexically. */
269 if ((aptr == (*ppa)->env->spam.data) || (bptr == (*ppb)->env->spam.data))
270 return SORT_CODE(strcmp(aptr, bptr));
272 /* Otherwise, we have numeric value for both attrs. If these values */
273 /* are equal, then we first fall back upon string comparison, then */
274 /* upon auxiliary sort. */
277 result = strcmp(aptr, bptr);
278 result = perform_auxsort(result, a, b);
281 return SORT_CODE(result);
285 * compare_label - Compare the labels of two emails - Implements ::sort_t
287 static int compare_label(const void *a, const void *b)
289 struct Email const *const *ppa = (struct Email const *const *) a;
290 struct Email const *const *ppb = (struct Email const *const *) b;
291 int ahas, bhas, result = 0;
293 /* As with compare_spam, not all messages will have the x-label
294 * property. Blank X-Labels are treated as null in the index
295 * display, so we'll consider them as null for sort, too. */
296 ahas = (*ppa)->env && (*ppa)->env->x_label && *((*ppa)->env->x_label);
297 bhas = (*ppb)->env && (*ppb)->env->x_label && *((*ppb)->env->x_label);
299 /* First we bias toward a message with a label, if the other does not. */
301 return SORT_CODE(-1);
305 /* If neither has a label, use aux sort. */
308 result = perform_auxsort(result, a, b);
309 return SORT_CODE(result);
312 /* If both have a label, we just do a lexical compare. */
313 result = mutt_str_strcasecmp((*ppa)->env->x_label, (*ppb)->env->x_label);
314 return SORT_CODE(result);
318 * mutt_get_sort_func - Get the sort function for a given sort id
319 * @param method Sort type, see #SortType
320 * @retval ptr sort function - Implements ::sort_t
322 sort_t *mutt_get_sort_func(enum SortType method)
327 return compare_date_sent;
331 return compare_label;
334 if (Context && (Context->mailbox->magic == MUTT_NNTP))
335 return nntp_compare_order;
338 return compare_order;
340 return compare_date_received;
342 return compare_score;
348 return compare_subject;
358 * mutt_sort_headers - Sort emails by their headers
360 * @param init If true, rebuild the thread
362 void mutt_sort_headers(struct Context *ctx, bool init)
364 struct Email *e = NULL;
365 struct MuttThread *thread = NULL, *top = NULL;
366 sort_t *sortfunc = NULL;
368 OptNeedResort = false;
373 if (ctx->mailbox->msg_count == 0)
375 /* this function gets called by mutt_sync_mailbox(), which may have just
376 * deleted all the messages. the virtual message numbers are not updated
377 * in that routine, so we must make sure to zero the vcount member. */
378 ctx->mailbox->vcount = 0;
380 mutt_clear_threads(ctx);
381 return; /* nothing to do! */
384 if (!ctx->mailbox->quiet)
385 mutt_message(_("Sorting mailbox..."));
387 if (OptNeedRescore && C_Score)
389 for (int i = 0; i < ctx->mailbox->msg_count; i++)
390 mutt_score_message(ctx->mailbox, ctx->mailbox->emails[i], true);
392 OptNeedRescore = false;
396 OptResortInit = false;
400 if (init && ctx->tree)
401 mutt_clear_threads(ctx);
403 if ((C_Sort & SORT_MASK) == SORT_THREADS)
406 /* if $sort_aux changed after the mailbox is sorted, then all the
407 * subthreads need to be resorted */
408 if (OptSortSubthreads)
413 ctx->tree = mutt_sort_subthreads(ctx->tree, true);
415 OptSortSubthreads = false;
417 mutt_sort_threads(ctx, init);
419 else if (!(sortfunc = mutt_get_sort_func(C_Sort & SORT_MASK)) ||
420 !(AuxSort = mutt_get_sort_func(C_SortAux & SORT_MASK)))
422 mutt_error(_("Could not find sorting function [report this bug]"));
426 qsort((void *) ctx->mailbox->emails, ctx->mailbox->msg_count,
427 sizeof(struct Email *), sortfunc);
429 /* adjust the virtual message numbers */
430 ctx->mailbox->vcount = 0;
431 for (int i = 0; i < ctx->mailbox->msg_count; i++)
433 struct Email *e_cur = ctx->mailbox->emails[i];
434 if ((e_cur->vnum != -1) || (e_cur->collapsed && (!ctx->pattern || e_cur->limited)))
436 e_cur->vnum = ctx->mailbox->vcount;
437 ctx->mailbox->v2r[ctx->mailbox->vcount] = i;
438 ctx->mailbox->vcount++;
443 /* re-collapse threads marked as collapsed */
444 if ((C_Sort & SORT_MASK) == SORT_THREADS)
447 while ((thread = top))
449 while (!thread->message)
450 thread = thread->child;
454 mutt_collapse_thread(ctx, e);
460 if (!ctx->mailbox->quiet)