mem_calloc -> p_new
[apps/madmutt.git] / enter.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include <lib-lib/mem.h>
16
17 #include "mutt.h"
18 #include "enter.h"
19 #include "mutt_menu.h"
20 #include "mutt_curses.h"
21 #include "keymap.h"
22 #include "history.h"
23 #include "buffy.h"
24
25 #include "lib/mem.h"
26
27 #include <string.h>
28
29 /* redraw flags for mutt_enter_string() */
30 enum {
31   M_REDRAW_INIT = 1,            /* go to end of line and redraw */
32   M_REDRAW_LINE                 /* redraw entire line */
33 };
34
35 static int my_wcwidth (wchar_t wc)
36 {
37   int n = wcwidth (wc);
38
39   if (IsWPrint (wc) && n > 0)
40     return n;
41   if (!(wc & ~0x7f))
42     return 2;
43   if (!(wc & ~0xffff))
44     return 6;
45   return 10;
46 }
47
48 /* combining mark / non-spacing character */
49 #define COMB_CHAR(wc) (IsWPrint (wc) && !wcwidth (wc))
50
51 static int my_wcswidth (const wchar_t * s, size_t n)
52 {
53   int w = 0;
54
55   while (n--)
56     w += my_wcwidth (*s++);
57   return w;
58 }
59
60 static int my_addwch (wchar_t wc)
61 {
62   int n = wcwidth (wc);
63
64   if (IsWPrint (wc) && n > 0)
65     return mutt_addwch (wc);
66   if (!(wc & ~0x7f))
67     return printw ("^%c", ((int) wc + 0x40) & 0x7f);
68   if (!(wc & ~0xffff))
69     return printw ("\\u%04x", (int) wc);
70   return printw ("\\u%08x", (int) wc);
71 }
72
73 static size_t width_ceiling (const wchar_t * s, size_t n, int w1)
74 {
75   const wchar_t *s0 = s;
76   int w = 0;
77
78   for (; n; s++, n--)
79     if ((w += my_wcwidth (*s)) > w1)
80       break;
81   return s - s0;
82 }
83
84 static void my_wcstombs (char *dest, size_t dlen, const wchar_t * src,
85                          size_t slen)
86 {
87   mbstate_t st;
88   size_t k;
89
90   /* First convert directly into the destination buffer */
91   memset (&st, 0, sizeof (st));
92   for (; slen && dlen >= MB_LEN_MAX; dest += k, dlen -= k, src++, slen--)
93     if ((k = wcrtomb (dest, *src, &st)) == (size_t) (-1))
94       break;
95
96   /* If this works, we can stop now */
97   if (dlen >= MB_LEN_MAX) {
98     wcrtomb (dest, 0, &st);
99     return;
100   }
101
102   /* Otherwise convert any remaining data into a local buffer */
103   {
104     char buf[3 * MB_LEN_MAX];
105     char *p = buf;
106
107     for (; slen && p - buf < dlen; p += k, src++, slen--)
108       if ((k = wcrtomb (p, *src, &st)) == (size_t) (-1))
109         break;
110     p += wcrtomb (p, 0, &st);
111
112     /* If it fits into the destination buffer, we can stop now */
113     if (p - buf <= dlen) {
114       memcpy (dest, buf, p - buf);
115       return;
116     }
117
118     /* Otherwise we truncate the string in an ugly fashion */
119     memcpy (dest, buf, dlen);
120     dest[dlen - 1] = '\0';      /* assume original dlen > 0 */
121   }
122 }
123
124 size_t my_mbstowcs (wchar_t ** pwbuf, size_t * pwbuflen, size_t i, char *buf)
125 {
126   wchar_t wc;
127   mbstate_t st;
128   size_t k;
129   wchar_t *wbuf;
130   size_t wbuflen;
131
132   wbuf = *pwbuf, wbuflen = *pwbuflen;
133   memset (&st, 0, sizeof (st));
134   for (; (k = mbrtowc (&wc, buf, MB_LEN_MAX, &st)) &&
135        k != (size_t) (-1) && k != (size_t) (-2); buf += k) {
136     if (i >= wbuflen) {
137       wbuflen = i + 20;
138       mem_realloc (&wbuf, wbuflen * sizeof (*wbuf));
139     }
140     wbuf[i++] = wc;
141   }
142   *pwbuf = wbuf, *pwbuflen = wbuflen;
143   return i;
144 }
145
146 /*
147  * Replace part of the wchar_t buffer, from FROM to CURPOS, by BUF.
148  */
149
150 static void replace_part (ENTER_STATE *state, size_t from, const char *buf)
151 {
152   /* Save the suffix */
153   size_t savelen = state->lastchar - state->curpos;
154   wchar_t *savebuf = p_new(wchar_t, savelen);
155
156   memcpy (savebuf, state->wbuf + state->curpos, savelen * sizeof (wchar_t));
157
158   /* Convert to wide characters */
159   state->curpos = my_mbstowcs (&state->wbuf, &state->wbuflen, from, buf);
160
161   /* Make space for suffix */
162   if (state->curpos + savelen > state->wbuflen) {
163     state->wbuflen = state->curpos + savelen;
164     mem_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
165   }
166
167   /* Restore suffix */
168   memcpy (state->wbuf + state->curpos, savebuf, savelen * sizeof (wchar_t));
169   state->lastchar = state->curpos + savelen;
170
171   p_delete(&savebuf);
172 }
173
174 /*
175  * Returns:
176  *        1 need to redraw the screen and call me again
177  *        0 if input was given
178  *         -1 if abort.
179  */
180
181 int mutt_enter_string (char *buf, size_t buflen, int y, int x, int flags)
182 {
183   int rv;
184   ENTER_STATE *es = mutt_new_enter_state ();
185
186   rv = _mutt_enter_string (buf, buflen, y, x, flags, 0, NULL, NULL, es);
187   mutt_free_enter_state (&es);
188   return rv;
189 }
190
191 int _mutt_enter_string (char *buf, size_t buflen, int y, int x,
192                         int flags, int multiple, char ***files, int *numfiles,
193                         ENTER_STATE * state)
194 {
195   int width = COLS - x - 1;
196   int redraw;
197   int pass = (flags & M_PASS);
198   int first = 1;
199   int ch, w, r;
200   size_t i;
201   wchar_t *tempbuf = 0;
202   size_t templen = 0;
203   history_class_t hclass;
204   wchar_t wc;
205   mbstate_t mbstate;
206
207   int rv = 0;
208
209   memset (&mbstate, 0, sizeof (mbstate));
210
211   if (state->wbuf) {
212     /* Coming back after return 1 */
213     redraw = M_REDRAW_LINE;
214   }
215   else {
216     /* Initialise wbuf from buf */
217     state->wbuflen = 0;
218     state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
219     redraw = M_REDRAW_INIT;
220   }
221
222   if (flags & (M_FILE | M_EFILE))
223     hclass = HC_FILE;
224   else if (flags & M_CMD)
225     hclass = HC_CMD;
226   else if (flags & M_ALIAS)
227     hclass = HC_ALIAS;
228   else if (flags & M_COMMAND)
229     hclass = HC_COMMAND;
230   else if (flags & M_PATTERN)
231     hclass = HC_PATTERN;
232   else
233     hclass = HC_OTHER;
234
235   for (;;) {
236     if (redraw && !pass) {
237       if (redraw == M_REDRAW_INIT) {
238         /* Go to end of line */
239         state->curpos = state->lastchar;
240         state->begin =
241           width_ceiling (state->wbuf, state->lastchar,
242                          my_wcswidth (state->wbuf,
243                                       state->lastchar) - width + 1);
244       }
245       if (state->curpos < state->begin ||
246           my_wcswidth (state->wbuf + state->begin,
247                        state->curpos - state->begin) >= width)
248         state->begin =
249           width_ceiling (state->wbuf, state->lastchar,
250                          my_wcswidth (state->wbuf,
251                                       state->curpos) - width / 2);
252       move (y, x);
253       w = 0;
254       for (i = state->begin; i < state->lastchar; i++) {
255         w += my_wcwidth (state->wbuf[i]);
256         if (w > width)
257           break;
258         my_addwch (state->wbuf[i]);
259       }
260       clrtoeol ();
261       move (y,
262             x + my_wcswidth (state->wbuf + state->begin,
263                              state->curpos - state->begin));
264     }
265     mutt_refresh ();
266
267     if ((ch = km_dokey (MENU_EDITOR)) == -1) {
268       rv = -1;
269       goto bye;
270     }
271
272     if (ch != OP_NULL) {
273       first = 0;
274       if (ch != OP_EDITOR_COMPLETE && ch != OP_EDITOR_COMPLETE_QUERY)
275         state->tabs = 0;
276       redraw = M_REDRAW_LINE;
277       switch (ch) {
278       case OP_EDITOR_HISTORY_UP:
279         state->curpos = state->lastchar;
280         replace_part(state, 0, mutt_history_prev(hclass));
281         redraw = M_REDRAW_INIT;
282         break;
283
284       case OP_EDITOR_HISTORY_DOWN:
285         state->curpos = state->lastchar;
286         replace_part(state, 0, mutt_history_next(hclass));
287         redraw = M_REDRAW_INIT;
288         break;
289
290       case OP_EDITOR_BACKSPACE:
291         if (state->curpos == 0)
292           BEEP ();
293         else {
294           i = state->curpos;
295           while (i && COMB_CHAR (state->wbuf[i - 1]))
296             --i;
297           if (i)
298             --i;
299           memmove (state->wbuf + i, state->wbuf + state->curpos,
300                    (state->lastchar - state->curpos) * sizeof (wchar_t));
301           state->lastchar -= state->curpos - i;
302           state->curpos = i;
303         }
304         break;
305
306       case OP_EDITOR_BOL:
307         state->curpos = 0;
308         break;
309
310       case OP_EDITOR_EOL:
311         redraw = M_REDRAW_INIT;
312         break;
313
314       case OP_EDITOR_KILL_LINE:
315         state->curpos = state->lastchar = 0;
316         break;
317
318       case OP_EDITOR_KILL_EOL:
319         state->lastchar = state->curpos;
320         break;
321
322       case OP_EDITOR_BACKWARD_CHAR:
323         if (state->curpos == 0)
324           BEEP ();
325         else {
326           while (state->curpos && COMB_CHAR (state->wbuf[state->curpos - 1]))
327             state->curpos--;
328           if (state->curpos)
329             state->curpos--;
330         }
331         break;
332
333       case OP_EDITOR_FORWARD_CHAR:
334         if (state->curpos == state->lastchar)
335           BEEP ();
336         else {
337           ++state->curpos;
338           while (state->curpos < state->lastchar
339                  && COMB_CHAR (state->wbuf[state->curpos]))
340             ++state->curpos;
341         }
342         break;
343
344       case OP_EDITOR_BACKWARD_WORD:
345         if (state->curpos == 0)
346           BEEP ();
347         else {
348           while (state->curpos && iswspace (state->wbuf[state->curpos - 1]))
349             --state->curpos;
350           while (state->curpos && !iswspace (state->wbuf[state->curpos - 1]))
351             --state->curpos;
352         }
353         break;
354
355       case OP_EDITOR_FORWARD_WORD:
356         if (state->curpos == state->lastchar)
357           BEEP ();
358         else {
359           while (state->curpos < state->lastchar
360                  && iswspace (state->wbuf[state->curpos]))
361             ++state->curpos;
362           while (state->curpos < state->lastchar
363                  && !iswspace (state->wbuf[state->curpos]))
364             ++state->curpos;
365         }
366         break;
367
368       case OP_EDITOR_CAPITALIZE_WORD:
369       case OP_EDITOR_UPCASE_WORD:
370       case OP_EDITOR_DOWNCASE_WORD:
371         if (state->curpos == state->lastchar) {
372           BEEP ();
373           break;
374         }
375         while (state->curpos && !iswspace (state->wbuf[state->curpos]))
376           --state->curpos;
377         while (state->curpos < state->lastchar
378                && iswspace (state->wbuf[state->curpos]))
379           ++state->curpos;
380         while (state->curpos < state->lastchar
381                && !iswspace (state->wbuf[state->curpos])) {
382           if (ch == OP_EDITOR_DOWNCASE_WORD)
383             state->wbuf[state->curpos] =
384               towlower (state->wbuf[state->curpos]);
385           else {
386             state->wbuf[state->curpos] =
387               towupper (state->wbuf[state->curpos]);
388             if (ch == OP_EDITOR_CAPITALIZE_WORD)
389               ch = OP_EDITOR_DOWNCASE_WORD;
390           }
391           state->curpos++;
392         }
393         break;
394
395       case OP_EDITOR_DELETE_CHAR:
396         if (state->curpos == state->lastchar)
397           BEEP ();
398         else {
399           i = state->curpos;
400           while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
401             ++i;
402           if (i < state->lastchar)
403             ++i;
404           while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
405             ++i;
406           memmove (state->wbuf + state->curpos, state->wbuf + i,
407                    (state->lastchar - i) * sizeof (wchar_t));
408           state->lastchar -= i - state->curpos;
409         }
410         break;
411
412       case OP_EDITOR_KILL_WORD:
413         /* delete to begining of word */
414         if (state->curpos != 0) {
415           i = state->curpos;
416           while (i && iswspace (state->wbuf[i - 1]))
417             --i;
418           if (i) {
419             if (iswalnum (state->wbuf[i - 1])) {
420               for (--i; i && iswalnum (state->wbuf[i - 1]); i--);
421             }
422             else
423               --i;
424           }
425           memmove (state->wbuf + i, state->wbuf + state->curpos,
426                    (state->lastchar - state->curpos) * sizeof (wchar_t));
427           state->lastchar += i - state->curpos;
428           state->curpos = i;
429         }
430         break;
431
432       case OP_EDITOR_KILL_EOW:
433         /* delete to end of word */
434         for (i = state->curpos;
435              i < state->lastchar && iswspace (state->wbuf[i]); i++);
436         for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++);
437         memmove (state->wbuf + state->curpos, state->wbuf + i,
438                  (state->lastchar - i) * sizeof (wchar_t));
439         state->lastchar += state->curpos - i;
440         break;
441
442       case OP_EDITOR_BUFFY_CYCLE:
443         if (flags & M_EFILE) {
444           first = 1;            /* clear input if user types a real key later */
445           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
446           buffy_next (buf, buflen);
447           state->curpos = state->lastchar =
448             my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
449           break;
450         }
451         else if (!(flags & M_FILE))
452           goto self_insert;
453         /* fall through to completion routine (M_FILE) */
454
455       case OP_EDITOR_COMPLETE:
456       case OP_EDITOR_COMPLETE_QUERY:
457         state->tabs++;
458         if (flags & M_CMD) {
459           for (i = state->curpos; i && state->wbuf[i - 1] != ' '; i--);
460           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
461           if (tempbuf && templen == state->lastchar - i &&
462               !memcmp (tempbuf, state->wbuf + i,
463                        (state->lastchar - i) * sizeof (wchar_t))) {
464             mutt_select_file (buf, buflen,
465                               (flags & M_EFILE) ? M_SEL_FOLDER : 0);
466             set_option (OPTNEEDREDRAW);
467             if (*buf)
468               replace_part (state, i, buf);
469             rv = 1;
470             goto bye;
471           }
472           if (!mutt_complete (buf, buflen)) {
473             templen = state->lastchar - i;
474             mem_realloc (&tempbuf, templen * sizeof (wchar_t));
475           }
476           else
477             BEEP ();
478
479           replace_part (state, i, buf);
480         }
481         else if (flags & M_ALIAS && ch == OP_EDITOR_COMPLETE) {
482           /* invoke the alias-menu to get more addresses */
483           for (i = state->curpos; i && state->wbuf[i - 1] != ',' &&
484                state->wbuf[i - 1] != ':'; i--);
485           for (; i < state->lastchar && state->wbuf[i] == ' '; i++);
486           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
487           r = mutt_alias_complete (buf, buflen);
488           replace_part (state, i, buf);
489           if (!r) {
490             rv = 1;
491             goto bye;
492           }
493           break;
494         } else if (flags & M_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY) {
495           /* invoke the query-menu to get more addresses */
496           if ((i = state->curpos)) {
497             for (; i && state->wbuf[i - 1] != ','; i--);
498             for (; i < state->curpos && state->wbuf[i] == ' '; i++);
499           }
500           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
501           mutt_query_complete (buf, buflen);
502           replace_part (state, i, buf);
503           rv = 1;
504           goto bye;
505         } else if (flags & M_COMMAND) {
506           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
507           i = str_len (buf);
508           if (i && buf[i - 1] == '=' &&
509               mutt_var_value_complete (buf, buflen, i))
510             state->tabs = 0;
511           else if (!mutt_command_complete (buf, buflen, i, state->tabs))
512             BEEP ();
513           replace_part (state, 0, buf);
514         }
515         else if (flags & (M_FILE | M_EFILE)) {
516           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
517
518           /* see if the path has changed from the last time */
519           if ((!tempbuf && !state->lastchar)
520               || (tempbuf && templen == state->lastchar
521                   && !memcmp (tempbuf, state->wbuf,
522                               state->lastchar * sizeof (wchar_t)))) {
523             _mutt_select_file (buf, buflen,
524                                ((flags & M_EFILE) ? M_SEL_FOLDER : 0) |
525                                (multiple ? M_SEL_MULTI : 0), files, numfiles);
526             set_option (OPTNEEDREDRAW);
527             if (*buf) {
528               mutt_pretty_mailbox (buf);
529               if (!pass)
530                 mutt_history_add (hclass, buf);
531               rv = 0;
532               goto bye;
533             }
534
535             /* file selection cancelled */
536             rv = 1;
537             goto bye;
538           }
539
540           if (!mutt_complete (buf, buflen)) {
541             templen = state->lastchar;
542             mem_realloc (&tempbuf, templen * sizeof (wchar_t));
543             memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
544           }
545           else
546             BEEP ();            /* let the user know that nothing matched */
547           replace_part (state, 0, buf);
548         }
549         else
550           goto self_insert;
551         break;
552
553       case OP_EDITOR_QUOTE_CHAR:
554         {
555           event_t event;
556
557           /*ADDCH (LastKey); */
558           event = mutt_getch ();
559           if (event.ch != -1) {
560             LastKey = event.ch;
561             goto self_insert;
562           }
563         }
564
565       case OP_EDITOR_TRANSPOSE_CHARS:
566         if (state->lastchar < 2)
567           BEEP ();
568         else {
569           wchar_t t;
570
571           if (state->curpos == 0)
572             state->curpos = 2;
573           else if (state->curpos < state->lastchar)
574             ++state->curpos;
575
576           t = state->wbuf[state->curpos - 2];
577           state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
578           state->wbuf[state->curpos - 1] = t;
579         }
580         break;
581
582       default:
583         BEEP ();
584       }
585     }
586     else {
587
588     self_insert:
589
590       state->tabs = 0;
591       /* use the raw keypress */
592       ch = LastKey;
593
594       if ((ch == '#') && (flags & M_LASTFOLDER)) {
595         rv = 2;
596         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
597         goto bye;
598       }
599
600 #ifdef KEY_ENTER
601       /* treat ENTER the same as RETURN */
602       if (ch == KEY_ENTER)
603         ch = '\r';
604 #endif
605
606       /* quietly ignore all other function keys */
607       if (ch & ~0xff)
608         continue;
609
610       /* gather the octets into a wide character */
611       {
612         char c;
613         size_t k;
614
615         c = ch;
616         k = mbrtowc (&wc, &c, 1, &mbstate);
617         if (k == (size_t) (-2))
618           continue;
619         else if (k && k != 1) {
620           memset (&mbstate, 0, sizeof (mbstate));
621           continue;
622         }
623       }
624
625       if (first && (flags & M_CLEAR)) {
626         first = 0;
627         if (IsWPrint (wc))      /* why? */
628           state->curpos = state->lastchar = 0;
629       }
630
631       if (wc == '\r' || wc == '\n') {
632         /* Convert from wide characters */
633         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
634         if (!pass)
635           mutt_history_add (hclass, buf);
636
637         if (multiple) {
638           char **tfiles;
639
640           *numfiles = 1;
641           tfiles = p_new(char *, *numfiles);
642           mutt_expand_path (buf, buflen);
643           tfiles[0] = str_dup (buf);
644           *files = tfiles;
645         }
646         rv = 0;
647         goto bye;
648       }
649       else if (wc && (wc < ' ' || IsWPrint (wc))) {     /* why? */
650         if (state->lastchar >= state->wbuflen) {
651           state->wbuflen = state->lastchar + 20;
652           mem_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
653         }
654         memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos,
655                  (state->lastchar - state->curpos) * sizeof (wchar_t));
656         state->wbuf[state->curpos++] = wc;
657         state->lastchar++;
658       }
659       else {
660         mutt_flushinp ();
661         BEEP ();
662       }
663     }
664   }
665
666 bye:
667
668   p_delete(&tempbuf);
669   return rv;
670 }
671
672 void mutt_free_enter_state (ENTER_STATE ** esp)
673 {
674   if (!esp)
675     return;
676
677   p_delete(&(*esp)->wbuf);
678   p_delete(esp);
679 }
680
681 /*
682  * TODO:
683  * very narrow screen might crash it
684  * sort out the input side
685  * unprintable chars
686  */