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)
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         state->tabs++;
454         if (flags & M_CMD) {
455           for (i = state->curpos; i && state->wbuf[i - 1] != ' '; i--);
456           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
457           if (tempbuf && templen == state->lastchar - i &&
458               !memcmp (tempbuf, state->wbuf + i,
459                        (state->lastchar - i) * sizeof (wchar_t))) {
460             mutt_select_file (buf, buflen,
461                               (flags & M_EFILE) ? M_SEL_FOLDER : 0);
462             set_option (OPTNEEDREDRAW);
463             if (*buf)
464               replace_part (state, i, buf);
465             rv = 1;
466             goto bye;
467           }
468           if (!mutt_complete (buf, buflen)) {
469             templen = state->lastchar - i;
470             safe_realloc (&tempbuf, templen * sizeof (wchar_t));
471           }
472           else
473             BEEP ();
474
475           replace_part (state, i, buf);
476         }
477         else if (flags & M_ALIAS) {
478           /* invoke the alias-menu to get more addresses */
479           for (i = state->curpos; i && state->wbuf[i - 1] != ',' &&
480                state->wbuf[i - 1] != ':'; i--);
481           for (; i < state->lastchar && state->wbuf[i] == ' '; i++);
482           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
483           r = mutt_alias_complete (buf, buflen);
484           replace_part (state, i, buf);
485           if (!r) {
486             rv = 1;
487             goto bye;
488           }
489           break;
490         }
491         else if (flags & M_COMMAND) {
492           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
493           i = safe_strlen (buf);
494           if (i && buf[i - 1] == '=' &&
495               mutt_var_value_complete (buf, buflen, i))
496             state->tabs = 0;
497           else if (!mutt_command_complete (buf, buflen, i, state->tabs))
498             BEEP ();
499           replace_part (state, 0, buf);
500         }
501         else if (flags & (M_FILE | M_EFILE)) {
502           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
503
504           /* see if the path has changed from the last time */
505           if ((!tempbuf && !state->lastchar)
506               || (tempbuf && templen == state->lastchar
507                   && !memcmp (tempbuf, state->wbuf,
508                               state->lastchar * sizeof (wchar_t)))) {
509             _mutt_select_file (buf, buflen,
510                                ((flags & M_EFILE) ? M_SEL_FOLDER : 0) |
511                                (multiple ? M_SEL_MULTI : 0), files, numfiles);
512             set_option (OPTNEEDREDRAW);
513             if (*buf) {
514               mutt_pretty_mailbox (buf);
515               if (!pass)
516                 mutt_history_add (hclass, buf);
517               rv = 0;
518               goto bye;
519             }
520
521             /* file selection cancelled */
522             rv = 1;
523             goto bye;
524           }
525
526           if (!mutt_complete (buf, buflen)) {
527             templen = state->lastchar;
528             safe_realloc (&tempbuf, templen * sizeof (wchar_t));
529             memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
530           }
531           else
532             BEEP ();            /* let the user know that nothing matched */
533           replace_part (state, 0, buf);
534         }
535         else
536           goto self_insert;
537         break;
538
539       case OP_EDITOR_COMPLETE_QUERY:
540         if (flags & M_ALIAS) {
541           /* invoke the query-menu to get more addresses */
542           if ((i = state->curpos)) {
543             for (; i && state->wbuf[i - 1] != ','; i--);
544             for (; i < state->curpos && state->wbuf[i] == ' '; i++);
545           }
546
547           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
548           mutt_query_complete (buf, buflen);
549           replace_part (state, i, buf);
550
551           rv = 1;
552           goto bye;
553         }
554         else
555           goto self_insert;
556
557       case OP_EDITOR_QUOTE_CHAR:
558         {
559           event_t event;
560
561           /*ADDCH (LastKey); */
562           event = mutt_getch ();
563           if (event.ch != -1) {
564             LastKey = event.ch;
565             goto self_insert;
566           }
567         }
568
569       case OP_EDITOR_TRANSPOSE_CHARS:
570         if (state->lastchar < 2)
571           BEEP ();
572         else {
573           wchar_t t;
574
575           if (state->curpos == 0)
576             state->curpos = 2;
577           else if (state->curpos < state->lastchar)
578             ++state->curpos;
579
580           t = state->wbuf[state->curpos - 2];
581           state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
582           state->wbuf[state->curpos - 1] = t;
583         }
584         break;
585
586       default:
587         BEEP ();
588       }
589     }
590     else {
591
592     self_insert:
593
594       state->tabs = 0;
595       /* use the raw keypress */
596       ch = LastKey;
597
598       if ((ch == '#') && (flags & M_LASTFOLDER)) {
599         rv = 2;
600         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
601         goto bye;
602       }
603
604 #ifdef KEY_ENTER
605       /* treat ENTER the same as RETURN */
606       if (ch == KEY_ENTER)
607         ch = '\r';
608 #endif
609
610       /* quietly ignore all other function keys */
611       if (ch & ~0xff)
612         continue;
613
614       /* gather the octets into a wide character */
615       {
616         char c;
617         size_t k;
618
619         c = ch;
620         k = mbrtowc (&wc, &c, 1, &mbstate);
621         if (k == (size_t) (-2))
622           continue;
623         else if (k && k != 1) {
624           memset (&mbstate, 0, sizeof (mbstate));
625           continue;
626         }
627       }
628
629       if (first && (flags & M_CLEAR)) {
630         first = 0;
631         if (IsWPrint (wc))      /* why? */
632           state->curpos = state->lastchar = 0;
633       }
634
635       if (wc == '\r' || wc == '\n') {
636         /* Convert from wide characters */
637         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
638         if (!pass)
639           mutt_history_add (hclass, buf);
640
641         if (multiple) {
642           char **tfiles;
643
644           *numfiles = 1;
645           tfiles = safe_calloc (*numfiles, sizeof (char *));
646           mutt_expand_path (buf, buflen);
647           tfiles[0] = safe_strdup (buf);
648           *files = tfiles;
649         }
650         rv = 0;
651         goto bye;
652       }
653       else if (wc && (wc < ' ' || IsWPrint (wc))) {     /* why? */
654         if (state->lastchar >= state->wbuflen) {
655           state->wbuflen = state->lastchar + 20;
656           safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
657         }
658         memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos,
659                  (state->lastchar - state->curpos) * sizeof (wchar_t));
660         state->wbuf[state->curpos++] = wc;
661         state->lastchar++;
662       }
663       else {
664         mutt_flushinp ();
665         BEEP ();
666       }
667     }
668   }
669
670 bye:
671
672   FREE (&tempbuf);
673   return rv;
674 }
675
676 void mutt_free_enter_state (ENTER_STATE ** esp)
677 {
678   if (!esp)
679     return;
680
681   FREE (&(*esp)->wbuf);
682   FREE (esp);
683 }
684
685 /*
686  * TODO:
687  * very narrow screen might crash it
688  * sort out the input side
689  * unprintable chars
690  */