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