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