]> git.llucax.com Git - software/mutt-debian.git/blob - curs_lib.c
refreshed all patches up to mutt.org
[software/mutt-debian.git] / curs_lib.c
1 /*
2  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 2004 g10 Code GmbH
4  * 
5  *     This program is free software; you can redistribute it and/or modify
6  *     it under the terms of the GNU General Public License as published by
7  *     the Free Software Foundation; either version 2 of the License, or
8  *     (at your option) any later version.
9  * 
10  *     This program is distributed in the hope that it will be useful,
11  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *     GNU General Public License for more details.
14  * 
15  *     You should have received a copy of the GNU General Public License
16  *     along with this program; if not, write to the Free Software
17  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */ 
19
20 #if HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "mutt.h"
25 #include "mutt_menu.h"
26 #include "mutt_curses.h"
27 #include "pager.h"
28 #include "mbyte.h"
29
30 #include <termios.h>
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #ifdef HAVE_SYS_TIME_H
39 # include <sys/time.h>
40 #endif
41 #include <time.h>
42
43 #ifdef HAVE_LANGINFO_YESEXPR
44 #include <langinfo.h>
45 #endif
46
47 /* not possible to unget more than one char under some curses libs, and it
48  * is impossible to unget function keys in SLang, so roll our own input
49  * buffering routines.
50  */
51 size_t UngetCount = 0;
52 static size_t UngetBufLen = 0;
53 static event_t *KeyEvent;
54
55 void mutt_refresh (void)
56 {
57   /* don't refresh when we are waiting for a child. */
58   if (option (OPTKEEPQUIET))
59     return;
60
61   /* don't refresh in the middle of macros unless necessary */
62   if (UngetCount && !option (OPTFORCEREFRESH))
63     return;
64
65   /* else */
66   refresh ();
67 }
68
69 /* Make sure that the next refresh does a full refresh.  This could be
70    optmized by not doing it at all if DISPLAY is set as this might
71    indicate that a GUI based pinentry was used.  Having an option to
72    customize this is of course the Mutt way.  */
73 void mutt_need_hard_redraw (void)
74 {
75   keypad (stdscr, TRUE);
76   clearok (stdscr, TRUE);
77   set_option (OPTNEEDREDRAW);
78 }
79
80 event_t mutt_getch (void)
81 {
82   int ch;
83   event_t err = {-1, OP_NULL }, ret;
84   event_t timeout = {-2, OP_NULL};
85
86   if (!option(OPTUNBUFFEREDINPUT) && UngetCount)
87     return (KeyEvent[--UngetCount]);
88
89   SigInt = 0;
90
91   mutt_allow_interrupt (1);
92 #ifdef KEY_RESIZE
93   /* ncurses 4.2 sends this when the screen is resized */
94   ch = KEY_RESIZE;
95   while (ch == KEY_RESIZE)
96 #endif /* KEY_RESIZE */
97     ch = getch ();
98   mutt_allow_interrupt (0);
99
100   if (SigInt)
101   {
102     mutt_query_exit ();
103     return err;
104   }
105
106   if(ch == ERR)
107   {
108     /* either timeout or the terminal has been lost */
109     if (!isatty (0))
110     {
111       endwin ();
112       exit (1);
113     }
114     return timeout;
115   }
116
117   if ((ch & 0x80) && option (OPTMETAKEY))
118   {
119     /* send ALT-x as ESC-x */
120     ch &= ~0x80;
121     mutt_ungetch (ch, 0);
122     ret.ch = '\033';
123     ret.op = 0;
124     return ret;
125   }
126
127   ret.ch = ch;
128   ret.op = 0;
129   return (ch == ctrl ('G') ? err : ret);
130 }
131
132 int _mutt_get_field (/* const */ char *field, char *buf, size_t buflen, int complete, int multiple, char ***files, int *numfiles)
133 {
134   int ret;
135   int x, y;
136
137   ENTER_STATE *es = mutt_new_enter_state();
138   
139   do
140   {
141     CLEARLINE (LINES-1);
142     addstr (field);
143     mutt_refresh ();
144     getyx (stdscr, y, x);
145     ret = _mutt_enter_string (buf, buflen, y, x, complete, multiple, files, numfiles, es);
146   }
147   while (ret == 1);
148   CLEARLINE (LINES-1);
149   mutt_free_enter_state (&es);
150   
151   return (ret);
152 }
153
154 int mutt_get_field_unbuffered (char *msg, char *buf, size_t buflen, int flags)
155 {
156   int rc;
157
158   set_option (OPTUNBUFFEREDINPUT);
159   rc = mutt_get_field (msg, buf, buflen, flags);
160   unset_option (OPTUNBUFFEREDINPUT);
161
162   return (rc);
163 }
164
165 void mutt_clear_error (void)
166 {
167   Errorbuf[0] = 0;
168   if (!option(OPTNOCURSES))
169     CLEARLINE (LINES-1);
170 }
171
172 void mutt_edit_file (const char *editor, const char *data)
173 {
174   char cmd[LONG_STRING];
175   
176   mutt_endwin (NULL);
177   mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
178   if (mutt_system (cmd))
179   {
180     mutt_error (_("Error running \"%s\"!"), cmd);
181     mutt_sleep (2);
182   }
183 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
184   /* the terminal may have been resized while the editor owned it */
185   mutt_resize_screen ();
186 #endif
187   keypad (stdscr, TRUE);
188   clearok (stdscr, TRUE);
189 }
190
191 int mutt_yesorno (const char *msg, int def)
192 {
193   event_t ch;
194   char *yes = _("yes");
195   char *no = _("no");
196   char *answer_string;
197   size_t answer_string_len;
198
199 #ifdef HAVE_LANGINFO_YESEXPR
200   char *expr;
201   regex_t reyes;
202   regex_t reno;
203   int reyes_ok;
204   int reno_ok;
205   char answer[2];
206
207   answer[1] = 0;
208   
209   reyes_ok = (expr = nl_langinfo (YESEXPR)) && expr[0] == '^' &&
210              !REGCOMP (&reyes, expr, REG_NOSUB);
211   reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
212             !REGCOMP (&reno, expr, REG_NOSUB);
213 #endif
214
215   CLEARLINE(LINES-1);
216
217   /*
218    * In order to prevent the default answer to the question to wrapped
219    * around the screen in the even the question is wider than the screen,
220    * ensure there is enough room for the answer and truncate the question
221    * to fit.
222    */
223   answer_string = safe_malloc (COLS + 1);
224   snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no, def == M_YES ? no : yes);
225   answer_string_len = strlen (answer_string);
226   mutt_message ("%.*s%s", COLS - answer_string_len, msg, answer_string);
227   FREE (&answer_string);
228
229   FOREVER
230   {
231     mutt_refresh ();
232     ch = mutt_getch ();
233     if (CI_is_return (ch.ch))
234       break;
235     if (ch.ch < 0)
236     {
237       def = -1;
238       break;
239     }
240
241 #ifdef HAVE_LANGINFO_YESEXPR
242     answer[0] = ch.ch;
243     if (reyes_ok ? 
244         (regexec (& reyes, answer, 0, 0, 0) == 0) :
245 #else
246     if (
247 #endif
248         (tolower (ch.ch) == 'y'))
249     {
250       def = M_YES;
251       break;
252     }
253     else if (
254 #ifdef HAVE_LANGINFO_YESEXPR
255              reno_ok ?
256              (regexec (& reno, answer, 0, 0, 0) == 0) :
257 #endif
258              (tolower (ch.ch) == 'n'))
259     {
260       def = M_NO;
261       break;
262     }
263     else
264     {
265       BEEP();
266     }
267   }
268
269 #ifdef HAVE_LANGINFO_YESEXPR    
270   if (reyes_ok)
271     regfree (& reyes);
272   if (reno_ok)
273     regfree (& reno);
274 #endif
275
276   if (def != -1)
277   {
278     addstr ((char *) (def == M_YES ? yes : no));
279     mutt_refresh ();
280   }
281   else
282   {
283     /* when the users cancels with ^G, clear the message stored with
284      * mutt_message() so it isn't displayed when the screen is refreshed. */
285     mutt_clear_error();
286   }
287   return (def);
288 }
289
290 /* this function is called when the user presses the abort key */
291 void mutt_query_exit (void)
292 {
293   mutt_flushinp ();
294   curs_set (1);
295   if (Timeout)
296     timeout (-1); /* restore blocking operation */
297   if (mutt_yesorno (_("Exit Mutt?"), M_YES) == M_YES)
298   {
299     endwin ();
300     exit (1);
301   }
302   mutt_clear_error();
303   mutt_curs_set (-1);
304   SigInt = 0;
305 }
306
307 static void curses_message (int error, const char *fmt, va_list ap)
308 {
309   char scratch[LONG_STRING];
310
311   vsnprintf (scratch, sizeof (scratch), fmt, ap);
312
313   dprint (1, (debugfile, "%s\n", scratch));
314   mutt_format_string (Errorbuf, sizeof (Errorbuf),
315                       0, COLS, FMT_LEFT, 0, scratch, sizeof (scratch), 0);
316
317   if (!option (OPTKEEPQUIET))
318   {
319     if (error)
320       BEEP ();
321     SETCOLOR (error ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
322     mvaddstr (LINES-1, 0, Errorbuf);
323     clrtoeol ();
324     SETCOLOR (MT_COLOR_NORMAL);
325     mutt_refresh ();
326   }
327
328   if (error)
329     set_option (OPTMSGERR);
330   else
331     unset_option (OPTMSGERR);
332 }
333
334 void mutt_curses_error (const char *fmt, ...)
335 {
336   va_list ap;
337
338   va_start (ap, fmt);
339   curses_message (1, fmt, ap);
340   va_end (ap);
341 }
342
343 void mutt_curses_message (const char *fmt, ...)
344 {
345   va_list ap;
346
347   va_start (ap, fmt);
348   curses_message (0, fmt, ap);
349   va_end (ap);
350 }
351
352 void mutt_progress_init (progress_t* progress, const char *msg,
353                          unsigned short flags, unsigned short inc,
354                          long size)
355 {
356   struct timeval tv = { 0, 0 };
357
358   if (!progress)
359     return;
360   if (option(OPTNOCURSES))
361     return;
362
363   memset (progress, 0, sizeof (progress_t));
364   progress->inc = inc;
365   progress->flags = flags;
366   progress->msg = msg;
367   progress->size = size;
368   if (progress->size) {
369     if (progress->flags & M_PROGRESS_SIZE)
370       mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
371                         progress->size);
372     else
373       snprintf (progress->sizestr, sizeof (progress->sizestr), "%ld",
374                 progress->size);
375   }
376   if (!inc)
377   {
378     if (size)
379       mutt_message ("%s (%s)", msg, progress->sizestr);
380     else
381       mutt_message (msg);
382     return;
383   }
384   if (gettimeofday (&tv, NULL) < 0)
385     dprint (1, (debugfile, "gettimeofday failed: %d\n", errno));
386   /* if timestamp is 0 no time-based suppression is done */
387   if (TimeInc)
388     progress->timestamp = ((unsigned int) tv.tv_sec * 1000)
389         + (unsigned int) (tv.tv_usec / 1000);
390   mutt_progress_update (progress, 0, 0);
391 }
392
393 void mutt_progress_update (progress_t* progress, long pos, int percent)
394 {
395   char posstr[SHORT_STRING];
396   short update = 0;
397   struct timeval tv = { 0, 0 };
398   unsigned int now = 0;
399
400   if (option(OPTNOCURSES))
401     return;
402
403   if (!progress->inc)
404     goto out;
405
406   /* refresh if size > inc */
407   if (progress->flags & M_PROGRESS_SIZE &&
408       (pos >= progress->pos + (progress->inc << 10)))
409     update = 1;
410   else if (pos >= progress->pos + progress->inc)
411     update = 1;
412
413   /* skip refresh if not enough time has passed */
414   if (update && progress->timestamp && !gettimeofday (&tv, NULL)) {
415     now = ((unsigned int) tv.tv_sec * 1000)
416           + (unsigned int) (tv.tv_usec / 1000);
417     if (now && now - progress->timestamp < TimeInc)
418       update = 0;
419   }
420
421   /* always show the first update */
422   if (!pos)
423     update = 1;
424
425   if (update)
426   {
427     if (progress->flags & M_PROGRESS_SIZE)
428     {
429       pos = pos / (progress->inc << 10) * (progress->inc << 10);
430       mutt_pretty_size (posstr, sizeof (posstr), pos);
431     }
432     else
433       snprintf (posstr, sizeof (posstr), "%ld", pos);
434
435     dprint (5, (debugfile, "updating progress: %s\n", posstr));
436
437     progress->pos = pos;
438     if (now)
439       progress->timestamp = now;
440
441     if (progress->size > 0)
442     {
443       mutt_message ("%s %s/%s (%d%%)", progress->msg, posstr, progress->sizestr,
444                     percent > 0 ? percent :
445                         (int) (100.0 * (double) progress->pos / progress->size));
446     }
447     else
448     {
449       if (percent > 0)
450         mutt_message ("%s %s (%d%%)", progress->msg, posstr, percent);
451       else
452         mutt_message ("%s %s", progress->msg, posstr);
453     }
454   }
455
456 out:
457   if (pos >= progress->size)
458     mutt_clear_error ();
459 }
460
461 void mutt_show_error (void)
462 {
463   if (option (OPTKEEPQUIET))
464     return;
465   
466   SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
467   CLEARLINE (LINES-1);
468   addstr (Errorbuf);
469   SETCOLOR (MT_COLOR_NORMAL);
470 }
471
472 void mutt_endwin (const char *msg)
473 {
474   int e = errno;
475
476   if (!option (OPTNOCURSES))
477   {
478     CLEARLINE (LINES - 1);
479     
480     attrset (A_NORMAL);
481     mutt_refresh ();
482     endwin ();
483   }
484   
485   if (msg && *msg)
486   {
487     puts (msg);
488     fflush (stdout);
489   }
490
491   errno = e;
492 }
493
494 void mutt_perror (const char *s)
495 {
496   char *p = strerror (errno);
497
498   dprint (1, (debugfile, "%s: %s (errno = %d)\n", s, 
499       p ? p : "unknown error", errno));
500   mutt_error ("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
501 }
502
503 int mutt_any_key_to_continue (const char *s)
504 {
505   struct termios t;
506   struct termios old;
507   int f, ch;
508
509   f = open ("/dev/tty", O_RDONLY);
510   tcgetattr (f, &t);
511   memcpy ((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
512   t.c_lflag &= ~(ICANON | ECHO);
513   t.c_cc[VMIN] = 1;
514   t.c_cc[VTIME] = 0;
515   tcsetattr (f, TCSADRAIN, &t);
516   fflush (stdout);
517   if (s)
518     fputs (s, stdout);
519   else
520     fputs (_("Press any key to continue..."), stdout);
521   fflush (stdout);
522   ch = fgetc (stdin);
523   fflush (stdin);
524   tcsetattr (f, TCSADRAIN, &old);
525   close (f);
526   fputs ("\r\n", stdout);
527   mutt_clear_error ();
528   return (ch);
529 }
530
531 int mutt_do_pager (const char *banner,
532                    const char *tempfile,
533                    int do_color,
534                    pager_t *info)
535 {
536   int rc;
537   
538   if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
539     rc = mutt_pager (banner, tempfile, do_color, info);
540   else
541   {
542     char cmd[STRING];
543     
544     mutt_endwin (NULL);
545     mutt_expand_file_fmt (cmd, sizeof(cmd), Pager, tempfile);
546     if (mutt_system (cmd) == -1)
547     {
548       mutt_error (_("Error running \"%s\"!"), cmd);
549       rc = -1;
550     }
551     else
552       rc = 0;
553     mutt_unlink (tempfile);
554   }
555
556   return rc;
557 }
558
559 int _mutt_enter_fname (const char *prompt, char *buf, size_t blen, int *redraw, int buffy, int multiple, char ***files, int *numfiles)
560 {
561   event_t ch;
562
563   mvaddstr (LINES-1, 0, (char *) prompt);
564   addstr (_(" ('?' for list): "));
565   if (buf[0])
566     addstr (buf);
567   clrtoeol ();
568   mutt_refresh ();
569
570   ch = mutt_getch();
571   if (ch.ch < 0)
572   {
573     CLEARLINE (LINES-1);
574     return (-1);
575   }
576   else if (ch.ch == '?')
577   {
578     mutt_refresh ();
579     buf[0] = 0;
580     _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0), 
581                        files, numfiles);
582     *redraw = REDRAW_FULL;
583   }
584   else
585   {
586     char *pc = safe_malloc (mutt_strlen (prompt) + 3);
587
588     sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
589     mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
590     if (_mutt_get_field (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files, numfiles)
591         != 0)
592       buf[0] = 0;
593     MAYBE_REDRAW (*redraw);
594     FREE (&pc);
595   }
596
597   return 0;
598 }
599
600 void mutt_ungetch (int ch, int op)
601 {
602   event_t tmp;
603
604   tmp.ch = ch;
605   tmp.op = op;
606
607   if (UngetCount >= UngetBufLen)
608     safe_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof(event_t));
609
610   KeyEvent[UngetCount++] = tmp;
611 }
612
613 void mutt_flushinp (void)
614 {
615   UngetCount = 0;
616   flushinp ();
617 }
618
619 #if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
620 /* The argument can take 3 values:
621  * -1: restore the value of the last call
622  *  0: make the cursor invisible
623  *  1: make the cursor visible
624  */
625 void mutt_curs_set (int cursor)
626 {
627   static int SavedCursor = 1;
628   
629   if (cursor < 0)
630     cursor = SavedCursor;
631   else
632     SavedCursor = cursor;
633   
634   if (curs_set (cursor) == ERR) {
635     if (cursor == 1)    /* cnorm */
636       curs_set (2);     /* cvvis */
637   }
638 }
639 #endif
640
641 int mutt_multi_choice (char *prompt, char *letters)
642 {
643   event_t ch;
644   int choice;
645   char *p;
646
647   mvaddstr (LINES - 1, 0, prompt);
648   clrtoeol ();
649   FOREVER
650   {
651     mutt_refresh ();
652     ch  = mutt_getch ();
653     if (ch.ch < 0 || CI_is_return (ch.ch))
654     {
655       choice = -1;
656       break;
657     }
658     else
659     {
660       p = strchr (letters, ch.ch);
661       if (p)
662       {
663         choice = p - letters + 1;
664         break;
665       }
666       else if (ch.ch <= '9' && ch.ch > '0')
667       {
668         choice = ch.ch - '0';
669         if (choice <= mutt_strlen (letters))
670           break;
671       }
672     }
673     BEEP ();
674   }
675   CLEARLINE (LINES - 1);
676   mutt_refresh ();
677   return choice;
678 }
679
680 /*
681  * addwch would be provided by an up-to-date curses library
682  */
683
684 int mutt_addwch (wchar_t wc)
685 {
686   char buf[MB_LEN_MAX*2];
687   mbstate_t mbstate;
688   size_t n1, n2;
689
690   memset (&mbstate, 0, sizeof (mbstate));
691   if ((n1 = wcrtomb (buf, wc, &mbstate)) == (size_t)(-1) ||
692       (n2 = wcrtomb (buf + n1, 0, &mbstate)) == (size_t)(-1))
693     return -1; /* ERR */
694   else
695     return addstr (buf);
696 }
697
698
699 /*
700  * This formats a string, a bit like
701  * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
702  * except that the widths refer to the number of character cells
703  * when printed.
704  */
705
706 void mutt_format_string (char *dest, size_t destlen,
707                          int min_width, int max_width,
708                          int justify, char m_pad_char,
709                          const char *s, size_t n,
710                          int arboreal)
711 {
712   char *p;
713   wchar_t wc;
714   int w;
715   size_t k, k2;
716   char scratch[MB_LEN_MAX];
717   mbstate_t mbstate1, mbstate2;
718
719   memset(&mbstate1, 0, sizeof (mbstate1));
720   memset(&mbstate2, 0, sizeof (mbstate2));
721   --destlen;
722   p = dest;
723   for (; n && (k = mbrtowc (&wc, s, n, &mbstate1)); s += k, n -= k)
724   {
725     if (k == (size_t)(-1) || k == (size_t)(-2))
726     {
727       if (k == (size_t)(-1) && errno == EILSEQ)
728         memset (&mbstate1, 0, sizeof (mbstate1));
729
730       k = (k == (size_t)(-1)) ? 1 : n;
731       wc = replacement_char ();
732     }
733     if (arboreal && wc < M_TREE_MAX)
734       w = 1; /* hack */
735     else
736     {
737 #ifdef HAVE_ISWBLANK
738       if (iswblank (wc))
739         wc = ' ';
740       else
741 #endif
742       if (!IsWPrint (wc))
743         wc = '?';
744       w = wcwidth (wc);
745     }
746     if (w >= 0)
747     {
748       if (w > max_width || (k2 = wcrtomb (scratch, wc, &mbstate2)) > destlen)
749         break;
750       min_width -= w;
751       max_width -= w;
752       strncpy (p, scratch, k2);
753       p += k2;
754       destlen -= k2;
755     }
756   }
757   w = (int)destlen < min_width ? destlen : min_width;
758   if (w <= 0)
759     *p = '\0';
760   else if (justify == FMT_RIGHT)        /* right justify */
761   {
762     p[w] = '\0';
763     while (--p >= dest)
764       p[w] = *p;
765     while (--w >= 0)
766       dest[w] = m_pad_char;
767   }
768   else if (justify == FMT_CENTER)       /* center */
769   {
770     char *savedp = p;
771     int half = (w+1) / 2; /* half of cushion space */
772
773     p[w] = '\0';
774
775     /* move str to center of buffer */
776     while (--p >= dest)
777       p[half] = *p;
778
779     /* fill rhs */
780     p = savedp + half;
781     while (--w >= half)
782       *p++ = m_pad_char;
783
784     /* fill lhs */
785     while (half--)
786       dest[half] = m_pad_char;
787   }
788   else                                  /* left justify */
789   {
790     while (--w >= 0)
791       *p++ = m_pad_char;
792     *p = '\0';
793   }
794 }
795
796 /*
797  * This formats a string rather like
798  *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
799  *   snprintf (dest, destlen, fmt, s);
800  * except that the numbers in the conversion specification refer to
801  * the number of character cells when printed.
802  */
803
804 static void mutt_format_s_x (char *dest,
805                              size_t destlen,
806                              const char *prefix,
807                              const char *s,
808                              int arboreal)
809 {
810   int justify = FMT_RIGHT;
811   char *p;
812   int min_width;
813   int max_width = INT_MAX;
814
815   if (*prefix == '-')
816     ++prefix, justify = FMT_LEFT;
817   else if (*prefix == '=')
818     ++prefix, justify = FMT_CENTER;
819   min_width = strtol (prefix, &p, 10);
820   if (*p == '.')
821   {
822     prefix = p + 1;
823     max_width = strtol (prefix, &p, 10);
824     if (p <= prefix)
825       max_width = INT_MAX;
826   }
827
828   mutt_format_string (dest, destlen, min_width, max_width,
829                       justify, ' ', s, mutt_strlen (s), arboreal);
830 }
831
832 void mutt_format_s (char *dest,
833                     size_t destlen,
834                     const char *prefix,
835                     const char *s)
836 {
837   mutt_format_s_x (dest, destlen, prefix, s, 0);
838 }
839
840 void mutt_format_s_tree (char *dest,
841                          size_t destlen,
842                          const char *prefix,
843                          const char *s)
844 {
845   mutt_format_s_x (dest, destlen, prefix, s, 1);
846 }
847
848 /*
849  * mutt_paddstr (n, s) is almost equivalent to
850  * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
851  */
852
853 void mutt_paddstr (int n, const char *s)
854 {
855   wchar_t wc;
856   int w;
857   size_t k;
858   size_t len = mutt_strlen (s);
859   mbstate_t mbstate;
860
861   memset (&mbstate, 0, sizeof (mbstate));
862   for (; len && (k = mbrtowc (&wc, s, len, &mbstate)); s += k, len -= k)
863   {
864     if (k == (size_t)(-1) || k == (size_t)(-2))
865     {
866       if (k == (size_t) (-1))
867         memset (&mbstate, 0, sizeof (mbstate));
868       k = (k == (size_t)(-1)) ? 1 : len;
869       wc = replacement_char ();
870     }
871     if (!IsWPrint (wc))
872       wc = '?';
873     w = wcwidth (wc);
874     if (w >= 0)
875     {
876       if (w > n)
877         break;
878       addnstr ((char *)s, k);
879       n -= w;
880     }
881   }
882   while (n-- > 0)
883     addch (' ');
884 }
885
886 /* See how many bytes to copy from string so its at most maxlen bytes
887  * long and maxwid columns wide */
888 int mutt_wstr_trunc (const char *src, size_t maxlen, size_t maxwid, size_t *width)
889 {
890   wchar_t wc;
891   int w = 0, l = 0, cl;
892   int cw, n;
893   mbstate_t mbstate;
894
895   if (!src)
896     goto out;
897
898   n = mutt_strlen (src);
899
900   memset (&mbstate, 0, sizeof (mbstate));
901   for (w = 0; n && (cl = mbrtowc (&wc, src, n, &mbstate)); src += cl, n -= cl)
902   {
903     if (cl == (size_t)(-1) || cl == (size_t)(-2))
904       cw = cl = 1;
905     else
906     {
907       cw = wcwidth (wc);
908       /* hack because M_TREE symbols aren't turned into characters
909        * until rendered by print_enriched_string (#3364) */
910       if (cw < 0 && cl == 1 && src[0] && src[0] < M_TREE_MAX)
911         cw = 1;
912     }
913     if (cl + l > maxlen || cw + w > maxwid)
914       break;
915     l += cl;
916     w += cw;
917   }
918 out:
919   if (width)
920     *width = w;
921   return l;
922 }
923
924 /*
925  * returns the number of bytes the first (multibyte) character
926  * of input consumes:
927  *      < 0 ... conversion error
928  *      = 0 ... end of input
929  *      > 0 ... length (bytes)
930  */
931 int mutt_charlen (const char *s, int *width)
932 {
933   wchar_t wc;
934   mbstate_t mbstate;
935   size_t k, n;
936
937   if (!s || !*s)
938     return 0;
939
940   n = mutt_strlen (s);
941   memset (&mbstate, 0, sizeof (mbstate));
942   k = mbrtowc (&wc, s, n, &mbstate);
943   if (width)
944     *width = wcwidth (wc);
945   return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
946 }
947
948 /*
949  * mutt_strwidth is like mutt_strlen except that it returns the width
950  * refering to the number of characters cells.
951  */
952
953 int mutt_strwidth (const char *s)
954 {
955   wchar_t wc;
956   int w;
957   size_t k, n;
958   mbstate_t mbstate;
959
960   if (!s) return 0;
961
962   n = mutt_strlen (s);
963
964   memset (&mbstate, 0, sizeof (mbstate));
965   for (w=0; n && (k = mbrtowc (&wc, s, n, &mbstate)); s += k, n -= k)
966   {
967     if (k == (size_t)(-1) || k == (size_t)(-2))
968     {
969       k = (k == (size_t)(-1)) ? 1 : n;
970       wc = replacement_char ();
971     }
972     if (!IsWPrint (wc))
973       wc = '?';
974     w += wcwidth (wc);
975   }
976   return w;
977 }