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