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