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