Make the sidebar live in a proper independent window.
[apps/madmutt.git] / lib-ui / hdrline.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000,2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-ui/lib-ui.h>
11
12 #include <lib-mime/mime.h>
13 #include <lib-mx/mx.h>
14
15 #include "mutt.h"
16 #include "alias.h"
17 #include "crypt.h"
18 #include "sort.h"
19 #include "thread.h"
20 #include "charset.h"
21 #include "mutt_idna.h"
22
23 int mutt_is_mail_list (address_t * addr)
24 {
25   if (!rx_list_match (UnMailLists, addr->mailbox))
26     return rx_list_match (MailLists, addr->mailbox);
27   return 0;
28 }
29
30 int mutt_is_subscribed_list (address_t * addr)
31 {
32   if (!rx_list_match (UnMailLists, addr->mailbox)
33       && !rx_list_match (UnSubscribedLists, addr->mailbox))
34     return rx_list_match (SubscribedLists, addr->mailbox);
35   return 0;
36 }
37
38 /* Search for a mailing list in the list of addresses pointed to by adr.
39  * If one is found, print pfx and the name of the list into buf, then
40  * return 1.  Otherwise, simply return 0.
41  */
42 static int
43 check_for_mailing_list (address_t * adr, const char *pfx, char *buf, int buflen)
44 {
45   for (; adr; adr = adr->next) {
46     if (mutt_is_subscribed_list (adr)) {
47       if (pfx && buf && buflen)
48         snprintf (buf, buflen, "%s%s", pfx, mutt_get_name (adr));
49       return 1;
50     }
51   }
52   return 0;
53 }
54
55 /* Search for a mailing list in the list of addresses pointed to by adr.
56  * If one is found, print the address of the list into buf, then return 1.
57  * Otherwise, simply return 0.
58  */
59 static int check_for_mailing_list_addr (address_t * adr, char *buf, int buflen)
60 {
61   for (; adr; adr = adr->next) {
62     if (mutt_is_subscribed_list (adr)) {
63       if (buf && buflen)
64         snprintf (buf, buflen, "%s", adr->mailbox);
65       return 1;
66     }
67   }
68   return 0;
69 }
70
71
72 static int first_mailing_list (char *buf, ssize_t buflen, address_t * a)
73 {
74   for (; a; a = a->next) {
75     if (mutt_is_subscribed_list (a)) {
76       mutt_save_path (buf, buflen, a);
77       return 1;
78     }
79   }
80   return 0;
81 }
82
83 static void make_from (ENVELOPE * hdr, char *buf, ssize_t len, int do_lists)
84 {
85   int me;
86
87   me = mutt_addr_is_user (hdr->from);
88
89   if (do_lists || me) {
90     if (check_for_mailing_list (hdr->to, "To ", buf, len))
91       return;
92     if (check_for_mailing_list (hdr->cc, "Cc ", buf, len))
93       return;
94   }
95
96   if (me && hdr->to)
97     snprintf (buf, len, "To %s", mutt_get_name (hdr->to));
98   else if (me && hdr->cc)
99     snprintf (buf, len, "Cc %s", mutt_get_name (hdr->cc));
100   else if (hdr->from)
101     m_strcpy(buf, len, mutt_get_name(hdr->from));
102   else
103     *buf = 0;
104 }
105
106 static void make_from_addr (ENVELOPE * hdr, char *buf, ssize_t len,
107                             int do_lists)
108 {
109   int me;
110
111   me = mutt_addr_is_user (hdr->from);
112
113   if (do_lists || me) {
114     if (check_for_mailing_list_addr (hdr->to, buf, len))
115       return;
116     if (check_for_mailing_list_addr (hdr->cc, buf, len))
117       return;
118   }
119
120   if (me && hdr->to)
121     snprintf (buf, len, "%s", hdr->to->mailbox);
122   else if (me && hdr->cc)
123     snprintf (buf, len, "%s", hdr->cc->mailbox);
124   else if (hdr->from)
125     m_strcpy(buf, len, hdr->from->mailbox);
126   else
127     *buf = 0;
128 }
129
130 static int user_in_addr (address_t * a)
131 {
132   for (; a; a = a->next)
133     if (mutt_addr_is_user (a))
134       return 1;
135   return 0;
136 }
137
138 /* Return values:
139  * 0: user is not in list
140  * 1: user is unique recipient
141  * 2: user is in the TO list
142  * 3: user is in the CC list
143  * 4: user is originator
144  * 5: sent to a subscribed mailinglist
145  */
146 int mutt_user_is_recipient (HEADER * h)
147 {
148   ENVELOPE *env = h->env;
149
150   if (!h->recip_valid) {
151     h->recip_valid = 1;
152
153     if (mutt_addr_is_user (env->from))
154       h->recipient = 4;
155     else if (user_in_addr (env->to)) {
156       if (env->to->next || env->cc)
157         h->recipient = 2;       /* non-unique recipient */
158       else
159         h->recipient = 1;       /* unique recipient */
160     }
161     else if (user_in_addr (env->cc))
162       h->recipient = 3;
163     else if (check_for_mailing_list (env->to, NULL, NULL, 0))
164       h->recipient = 5;
165     else if (check_for_mailing_list (env->cc, NULL, NULL, 0))
166       h->recipient = 5;
167     else
168       h->recipient = 0;
169   }
170
171   return h->recipient;
172 }
173
174 /* %a = address of author
175  * %A = reply-to address (if present; otherwise: address of author
176  * %b = filename of the originating folder
177  * %B = the list to which the letter was sent
178  * %c = size of message in bytes
179  * %C = current message number
180  * %d = date and time of message using $date_format and sender's timezone
181  * %D = date and time of message using $date_format and local timezone
182  * %e = current message number in thread
183  * %E = number of messages in current thread
184  * %f = entire from line
185  * %F = like %n, unless from self
186  * %g = newsgroup name (if compiled with nntp support)
187  * %i = message-id
188  * %I = initials of author
189  * %l = number of lines in the message
190  * %L = like %F, except `lists' are displayed first
191  * %m = number of messages in the mailbox
192  * %n = name of author
193  * %N = score
194  * %O = like %L, except using address instead of name
195  * %s = subject
196  * %S = short message status (e.g., N/O/D/!/r/-)
197  * %t = `to:' field (recipients)
198  * %T = $to_chars
199  * %u = user (login) name of author
200  * %v = first name of author, unless from self
201  * %W = where user is (organization)
202  * %X = number of MIME attachments
203  * %y = `x-label:' field (if present)
204  * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label)
205  * %Z = status flags    */
206
207 struct hdr_format_info {
208   CONTEXT *ctx;
209   HEADER *hdr;
210 };
211
212 static const char *
213 hdr_format_str(char *dest, ssize_t destlen,
214                char op, const char *src, const char *prefix,
215                const char *ifstr, const char *elstr,
216                anytype data, format_flag flags)
217 {
218   struct hdr_format_info *hfi = data.ptr;
219   HEADER *hdr, *htmp;
220   CONTEXT *ctx;
221   char fmt[STRING], buf2[STRING], ch, *p;
222   int do_locales, i, c;
223   int optional = (flags & M_FORMAT_OPTIONAL);
224   int threads = ((Sort & SORT_MASK) == SORT_THREADS);
225   int is_index = (flags & M_FORMAT_INDEX);
226
227 #define THREAD_NEW (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 1)
228 #define THREAD_OLD (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 2)
229   ssize_t len;
230
231   hdr = hfi->hdr;
232   ctx = hfi->ctx;
233
234   dest[0] = 0;
235   switch (op) {
236   case 'A':
237     if (hdr->env->reply_to && hdr->env->reply_to->mailbox) {
238       mutt_format_s (dest, destlen, prefix,
239                      mutt_addr_for_display (hdr->env->reply_to));
240       break;
241     }
242     /* fall through if 'A' returns nothing */
243
244   case 'a':
245     if (hdr->env->from && hdr->env->from->mailbox) {
246       mutt_format_s (dest, destlen, prefix,
247                      mutt_addr_for_display (hdr->env->from));
248     }
249     else
250       dest[0] = '\0';
251     break;
252
253   case 'B':
254     if (!first_mailing_list (dest, destlen, hdr->env->to) &&
255         !first_mailing_list (dest, destlen, hdr->env->cc))
256       dest[0] = 0;
257     if (dest[0]) {
258       m_strcpy(buf2, sizeof(buf2), dest);
259       mutt_format_s (dest, destlen, prefix, buf2);
260       break;
261     }
262     /* fall through if 'B' returns nothing */
263
264   case 'b':
265     if (ctx) {
266       if ((p = strrchr (ctx->path, '/')))
267         m_strcpy(dest, destlen, p + 1);
268       else
269         m_strcpy(dest, destlen, ctx->path);
270     }
271     else
272       m_strcpy(dest, destlen, "(null)");
273     m_strcpy(buf2, sizeof(buf2), dest);
274     mutt_format_s (dest, destlen, prefix, buf2);
275     break;
276
277   case 'c':
278     mutt_pretty_size (buf2, sizeof (buf2), (long) hdr->content->length);
279     mutt_format_s (dest, destlen, prefix, buf2);
280     break;
281
282   case 'C':
283     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
284     snprintf (dest, destlen, fmt, hdr->msgno + 1);
285     break;
286
287   case 'd':
288   case 'D':
289   case '{':
290   case '[':
291   case '(':
292   case '<':
293
294     /* preprocess $date_format to handle %Z */
295     {
296       const char *cp;
297       struct tm *tm;
298       time_t T;
299
300       p = dest;
301
302       cp = (op == 'd' || op == 'D') ? (NONULL (DateFmt)) : src;
303       if (*cp == '!') {
304         do_locales = 0;
305         cp++;
306       }
307       else
308         do_locales = 1;
309
310       len = destlen - 1;
311       while (len > 0 && (((op == 'd' || op == 'D') && *cp) ||
312                          (op == '{' && *cp != '}') ||
313                          (op == '[' && *cp != ']') ||
314                          (op == '(' && *cp != ')') ||
315                          (op == '<' && *cp != '>'))) {
316         if (*cp == '%') {
317           cp++;
318           if ((*cp == 'Z' || *cp == 'z') && (op == 'd' || op == '{')) {
319             if (len >= 5) {
320               sprintf (p, "%c%02u%02u", hdr->zoccident ? '-' : '+',
321                        hdr->zhours, hdr->zminutes);
322               p += 5;
323               len -= 5;
324             }
325             else
326               break;            /* not enough space left */
327           }
328           else {
329             if (len >= 2) {
330               *p++ = '%';
331               *p++ = *cp;
332               len -= 2;
333             }
334             else
335               break;            /* not enough space */
336           }
337           cp++;
338         }
339         else {
340           *p++ = *cp++;
341           len--;
342         }
343       }
344       *p = 0;
345
346       if (do_locales && Locale)
347         setlocale (LC_TIME, Locale);
348
349       if (op == '[' || op == 'D')
350         tm = localtime (&hdr->date_sent);
351       else if (op == '(')
352         tm = localtime (&hdr->received);
353       else if (op == '<') {
354         T = time (NULL);
355         tm = localtime (&T);
356       }
357       else {
358         /* restore sender's time zone */
359         T = hdr->date_sent;
360         if (hdr->zoccident)
361           T -= (hdr->zhours * 3600 + hdr->zminutes * 60);
362         else
363           T += (hdr->zhours * 3600 + hdr->zminutes * 60);
364         tm = gmtime (&T);
365       }
366
367       strftime (buf2, sizeof (buf2), dest, tm);
368
369       if (do_locales)
370         setlocale (LC_TIME, "C");
371
372       mutt_format_s (dest, destlen, prefix, buf2);
373       if (len > 0 && op != 'd' && op != 'D')    /* Skip ending op */
374         src = cp + 1;
375     }
376     break;
377
378   case 'e':
379     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
380     snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 1));
381     break;
382
383   case 'E':
384     if (!optional) {
385       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
386       snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 0));
387     }
388     else if (mutt_messages_in_thread (ctx, hdr, 0) <= 1)
389       optional = 0;
390     break;
391
392   case 'f':
393     buf2[0] = 0;
394     rfc822_addrcat(buf2, sizeof (buf2), hdr->env->from, 1);
395     mutt_format_s (dest, destlen, prefix, buf2);
396     break;
397
398   case 'F':
399     if (!optional) {
400       make_from (hdr->env, buf2, sizeof (buf2), 0);
401       mutt_format_s (dest, destlen, prefix, buf2);
402     }
403     else if (mutt_addr_is_user (hdr->env->from))
404       optional = 0;
405     break;
406
407 #ifdef USE_NNTP
408   case 'g':
409     mutt_format_s (dest, destlen, prefix,
410                    hdr->env->newsgroups ? hdr->env->newsgroups : "");
411     break;
412 #endif
413
414   case 'H':
415     /* (Hormel) spam score */
416     if (optional)
417       optional = hdr->env->spam ? 1 : 0;
418
419     if (hdr->env->spam)
420       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->spam->data));
421     else
422       mutt_format_s (dest, destlen, prefix, "");
423
424     break;
425
426   case 'i':
427     mutt_format_s (dest, destlen, prefix,
428                    hdr->env->message_id ? hdr->env->message_id : "<no.id>");
429     break;
430
431   case 'I':
432     {
433       int iflag = FALSE;
434       int j = 0;
435
436       for (i = 0; hdr->env->from && hdr->env->from->personal &&
437            hdr->env->from->personal[i] && j < STRING - 1; i++) {
438         if (isalpha ((int) hdr->env->from->personal[i])) {
439           if (!iflag) {
440             buf2[j++] = hdr->env->from->personal[i];
441             iflag = TRUE;
442           }
443         }
444         else
445           iflag = FALSE;
446       }
447
448       buf2[j] = '\0';
449     }
450     mutt_format_s (dest, destlen, prefix, buf2);
451     break;
452
453   case 'l':
454     if (!optional) {
455       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
456       snprintf (dest, destlen, fmt, (int) hdr->lines);
457     }
458     else if (hdr->lines <= 0)
459       optional = 0;
460     break;
461
462   case 'L':
463     if (!optional) {
464       make_from (hdr->env, buf2, sizeof (buf2), 1);
465       mutt_format_s (dest, destlen, prefix, buf2);
466     }
467     else if (!check_for_mailing_list (hdr->env->to, NULL, NULL, 0) &&
468              !check_for_mailing_list (hdr->env->cc, NULL, NULL, 0)) {
469       optional = 0;
470     }
471     break;
472
473   case 'm':
474     if (ctx) {
475       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
476       snprintf (dest, destlen, fmt, ctx->msgcount);
477     }
478     else
479       m_strcpy(dest, destlen, "(null)");
480     break;
481
482   case 'n':
483     mutt_format_s (dest, destlen, prefix, mutt_get_name (hdr->env->from));
484     break;
485
486   case 'N':
487     if (!optional) {
488       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
489       snprintf (dest, destlen, fmt, hdr->score);
490     }
491     else {
492       if (hdr->score == 0)
493         optional = 0;
494     }
495     break;
496
497   case 'O':
498     if (!optional) {
499       make_from_addr (hdr->env, buf2, sizeof (buf2), 1);
500       if ((p = strpbrk (buf2, "%@")))
501         *p = 0;
502       mutt_format_s (dest, destlen, prefix, buf2);
503     }
504     else if (!check_for_mailing_list_addr (hdr->env->to, NULL, 0) &&
505              !check_for_mailing_list_addr (hdr->env->cc, NULL, 0)) {
506       optional = 0;
507     }
508     break;
509
510   case 'M':
511     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
512     if (!optional) {
513       if (threads && is_index && hdr->collapsed && hdr->num_hidden > 1)
514         snprintf (dest, destlen, fmt, hdr->num_hidden);
515       else if (is_index && threads)
516         mutt_format_s (dest, destlen, prefix, " ");
517       else
518         *dest = '\0';
519     }
520     else {
521       if (!(threads && is_index && hdr->collapsed && hdr->num_hidden > 1))
522         optional = 0;
523     }
524     break;
525
526   case 's':
527
528     if (flags & M_FORMAT_TREE && !hdr->collapsed) {
529       if (flags & M_FORMAT_FORCESUBJ) {
530         mutt_format_s (dest, destlen, "", NONULL (hdr->env->subject));
531         snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, dest);
532         mutt_format_s_tree (dest, destlen, prefix, buf2);
533       }
534       else
535         mutt_format_s_tree (dest, destlen, prefix, hdr->tree);
536     }
537     else
538       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->subject));
539     break;
540
541   case 'S':
542     if (hdr->deleted)
543       ch = 'D';
544     else if (hdr->attach_del)
545       ch = 'd';
546     else if (hdr->tagged)
547       ch = '*';
548     else if (hdr->flagged)
549       ch = '!';
550     else if (hdr->replied)
551       ch = 'r';
552     else if (hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
553       ch = '-';
554     else if (hdr->old)
555       ch = 'O';
556     else
557       ch = 'N';
558
559     /* FOO - this is probably unsafe, but we are not likely to have such
560        a short string passed into this routine */
561     *dest = ch;
562     *(dest + 1) = 0;
563     break;
564
565   case 't':
566     buf2[0] = 0;
567     if (!check_for_mailing_list (hdr->env->to, "To ", buf2, sizeof (buf2)) &&
568         !check_for_mailing_list (hdr->env->cc, "Cc ", buf2, sizeof (buf2))) {
569       if (hdr->env->to)
570         snprintf (buf2, sizeof (buf2), "To %s", mutt_get_name (hdr->env->to));
571       else if (hdr->env->cc)
572         snprintf (buf2, sizeof (buf2), "Cc %s", mutt_get_name (hdr->env->cc));
573     }
574     mutt_format_s (dest, destlen, prefix, buf2);
575     break;
576
577   case 'T':
578     i = mutt_user_is_recipient(hdr);
579     assert (i >= 0); /* help compiler to see c is initialized */
580
581     if (Charset_is_utf8) {
582         const char *s = Tochars;
583
584         snprintf (fmt, sizeof (fmt), "%%%slc", prefix);
585         while (i-- >= 0) {
586             c = m_ustrgetc(s, &s);
587             if (c <= 0) {
588                 c = ' ';
589                 break;
590             }
591         }
592
593         snprintf(dest, destlen, fmt, c);
594     } else {
595         snprintf(fmt, sizeof (fmt), "%%%sc", prefix);
596         snprintf(dest, destlen, fmt, i < m_strlen(Tochars) ? Tochars[i] : ' ');
597     }
598     break;
599
600   case 'u':
601     if (hdr->env->from && hdr->env->from->mailbox) {
602       m_strcpy(buf2, sizeof(buf2), mutt_addr_for_display(hdr->env->from));
603       if ((p = strpbrk (buf2, "%@")))
604         *p = 0;
605     }
606     else
607       buf2[0] = 0;
608     mutt_format_s (dest, destlen, prefix, buf2);
609     break;
610
611   case 'v':
612     if (mutt_addr_is_user (hdr->env->from)) {
613       if (hdr->env->to)
614         mutt_format_s (buf2, sizeof (buf2), prefix,
615                        mutt_get_name (hdr->env->to));
616       else if (hdr->env->cc)
617         mutt_format_s (buf2, sizeof (buf2), prefix,
618                        mutt_get_name (hdr->env->cc));
619       else
620         *buf2 = 0;
621     }
622     else
623       mutt_format_s (buf2, sizeof (buf2), prefix,
624                      mutt_get_name (hdr->env->from));
625     if ((p = strpbrk (buf2, " %@")))
626       *p = 0;
627     mutt_format_s (dest, destlen, prefix, buf2);
628     break;
629
630   case 'W':
631     if (!optional)
632       mutt_format_s (dest, destlen, prefix,
633                      hdr->env->organization ? hdr->env->organization : "");
634     else if (!hdr->env->organization)
635       optional = 0;
636     break;
637
638   case 'X':
639     {
640       int count = 0;
641
642       /* The recursion allows messages without depth to return 0. */
643       if (optional)
644         optional = count != 0;
645
646       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
647       snprintf (dest, destlen, fmt, count);
648     }
649     break;
650
651   case 'Z':
652
653     ch = ' ';
654
655     if (hdr->security & GOODSIGN)
656       ch = 'S';
657     else if (hdr->security & ENCRYPT)
658       ch = 'P';
659     else if (hdr->security & SIGN)
660       ch = 's';
661     else if (hdr->security & PGPKEY)
662       ch = 'K';
663
664     if (hdr->tagged) {
665         c = '*';
666     } else
667     if (hdr->flagged) {
668         c = '!';
669     } else {
670         i = mutt_user_is_recipient(hdr);
671         assert (i >= 0); /* help compiler to see c is initialized */
672
673         if (Charset_is_utf8) {
674             const char *s = Tochars;
675
676             snprintf (fmt, sizeof (fmt), "%%%slc", prefix);
677             while (i-- >= 0) {
678                 c = m_ustrgetc(s, &s);
679                 if (c <= 0) {
680                     c = ' ';
681                     break;
682                 }
683             }
684         } else {
685             c = i < m_strlen(Tochars) ? Tochars[i] : ' ';
686         }
687     }
688     snprintf(buf2, sizeof (buf2), Charset_is_utf8 ? "%c%c%lc" : "%c%c%c",
689              (THREAD_NEW ? 'n'
690               : (THREAD_OLD
691                  ?  'o'
692                  : ((hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
693                     ? (hdr->replied ? 'r' : ' ')
694                     : (hdr->old ?  'O' : 'N')))),
695               hdr->deleted ? 'D' : (hdr->attach_del ? 'd' : ch), c);
696     mutt_format_s (dest, destlen, prefix, buf2);
697     break;
698
699   case 'y':
700     if (optional)
701       optional = hdr->env->x_label ? 1 : 0;
702
703     mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
704     break;
705
706   case 'Y':
707     if (hdr->env->x_label) {
708       i = 1;                    /* reduce reuse recycle */
709       htmp = NULL;
710       if (flags & M_FORMAT_TREE
711           && (hdr->thread->prev && hdr->thread->prev->message
712               && hdr->thread->prev->message->env->x_label))
713         htmp = hdr->thread->prev->message;
714       else if (flags & M_FORMAT_TREE
715                && (hdr->thread->parent && hdr->thread->parent->message
716                    && hdr->thread->parent->message->env->x_label))
717         htmp = hdr->thread->parent->message;
718       if (htmp && m_strcasecmp(hdr->env->x_label,
719                                    htmp->env->x_label) == 0)
720         i = 0;
721     }
722     else
723       i = 0;
724
725     if (optional)
726       optional = i;
727
728     if (i)
729       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
730     else
731       mutt_format_s (dest, destlen, prefix, "");
732
733     break;
734
735   default:
736     *dest = 0;
737     break;
738   }
739
740   if (flags & M_FORMAT_OPTIONAL)
741     m_strformat(dest, destlen, 0, optional ? ifstr: elstr,
742                 hdr_format_str, data, flags);
743
744   return src;
745 #undef THREAD_NEW
746 #undef THREAD_OLD
747 }
748
749 void
750 _mutt_make_string (char *dest, ssize_t destlen, const char *s, CONTEXT * ctx,
751                    HEADER * hdr, format_flag flags)
752 {
753   struct hdr_format_info hfi;
754
755   hfi.hdr = hdr;
756   hfi.ctx = ctx;
757
758   m_strformat(dest, destlen, COLS - SW, s, hdr_format_str, &hfi, flags);
759 }