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