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