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