Fix compilation errors
[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 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include <wctype.h>
16 #include <wchar.h>
17
18 #include <lib-lib/mem.h>
19
20 #include "curses.h"
21 #include "enter.h"
22 #include "menu.h"
23
24 #include "mutt.h"
25 #include "alias.h"
26 #include "keymap.h"
27 #include "history.h"
28 #include "buffy.h"
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 mutt_addwch (wc);
67   if (!(wc & ~0x7f))
68     return printw ("^%c", ((int) wc + 0x40) & 0x7f);
69   if (!(wc & ~0xffff))
70     return printw ("\\u%04x", (int) wc);
71   return printw ("\\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 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 /*
176  * Returns:
177  *        1 need to redraw the screen and call me again
178  *        0 if input was given
179  *         -1 if abort.
180  */
181
182 int mutt_enter_string (char *buf, size_t buflen, int y, int x, int flags)
183 {
184   int rv;
185   ENTER_STATE *es = mutt_new_enter_state ();
186
187   rv = _mutt_enter_string (buf, buflen, y, x, flags, 0, NULL, NULL, es);
188   mutt_free_enter_state (&es);
189   return rv;
190 }
191
192 int _mutt_enter_string (char *buf, size_t buflen, int y, int x,
193                         int flags, int multiple, char ***files, int *numfiles,
194                         ENTER_STATE * state)
195 {
196   int width = COLS - x - 1;
197   int redraw;
198   int pass = (flags & M_PASS);
199   int first = 1;
200   int ch, w, r;
201   size_t i;
202   wchar_t *tempbuf = 0;
203   size_t templen = 0;
204   history_class_t hclass;
205   wchar_t wc;
206   mbstate_t mbstate;
207
208   int rv = 0;
209
210   p_clear(&mbstate, 1);
211
212   if (state->wbuf) {
213     /* Coming back after return 1 */
214     redraw = M_REDRAW_LINE;
215   }
216   else {
217     /* Initialise wbuf from buf */
218     state->wbuflen = 0;
219     state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
220     redraw = M_REDRAW_INIT;
221   }
222
223   if (flags & (M_FILE | M_EFILE))
224     hclass = HC_FILE;
225   else if (flags & M_CMD)
226     hclass = HC_CMD;
227   else if (flags & M_ALIAS)
228     hclass = HC_ALIAS;
229   else if (flags & M_COMMAND)
230     hclass = HC_COMMAND;
231   else if (flags & M_PATTERN)
232     hclass = HC_PATTERN;
233   else
234     hclass = HC_OTHER;
235
236   for (;;) {
237     if (redraw && !pass) {
238       if (redraw == M_REDRAW_INIT) {
239         /* Go to end of line */
240         state->curpos = state->lastchar;
241         state->begin =
242           width_ceiling (state->wbuf, state->lastchar,
243                          my_wcswidth (state->wbuf,
244                                       state->lastchar) - width + 1);
245       }
246       if (state->curpos < state->begin ||
247           my_wcswidth (state->wbuf + state->begin,
248                        state->curpos - state->begin) >= width)
249         state->begin =
250           width_ceiling (state->wbuf, state->lastchar,
251                          my_wcswidth (state->wbuf,
252                                       state->curpos) - width / 2);
253       move (y, x);
254       w = 0;
255       for (i = state->begin; i < state->lastchar; i++) {
256         w += my_wcwidth (state->wbuf[i]);
257         if (w > width)
258           break;
259         my_addwch (state->wbuf[i]);
260       }
261       clrtoeol ();
262       move (y,
263             x + my_wcswidth (state->wbuf + state->begin,
264                              state->curpos - state->begin));
265     }
266     mutt_refresh ();
267
268     if ((ch = km_dokey (MENU_EDITOR)) == -1) {
269       rv = -1;
270       goto bye;
271     }
272
273     if (ch != OP_NULL) {
274       first = 0;
275       if (ch != OP_EDITOR_COMPLETE && ch != OP_EDITOR_COMPLETE_QUERY)
276         state->tabs = 0;
277       redraw = M_REDRAW_LINE;
278       switch (ch) {
279       case OP_EDITOR_HISTORY_UP:
280         state->curpos = state->lastchar;
281         replace_part(state, 0, mutt_history_prev(hclass));
282         redraw = M_REDRAW_INIT;
283         break;
284
285       case OP_EDITOR_HISTORY_DOWN:
286         state->curpos = state->lastchar;
287         replace_part(state, 0, mutt_history_next(hclass));
288         redraw = M_REDRAW_INIT;
289         break;
290
291       case OP_EDITOR_BACKSPACE:
292         if (state->curpos == 0)
293           BEEP ();
294         else {
295           i = state->curpos;
296           while (i && COMB_CHAR (state->wbuf[i - 1]))
297             --i;
298           if (i)
299             --i;
300           memmove (state->wbuf + i, state->wbuf + state->curpos,
301                    (state->lastchar - state->curpos) * sizeof (wchar_t));
302           state->lastchar -= state->curpos - i;
303           state->curpos = i;
304         }
305         break;
306
307       case OP_EDITOR_BOL:
308         state->curpos = 0;
309         break;
310
311       case OP_EDITOR_EOL:
312         redraw = M_REDRAW_INIT;
313         break;
314
315       case OP_EDITOR_KILL_LINE:
316         state->curpos = state->lastchar = 0;
317         break;
318
319       case OP_EDITOR_KILL_EOL:
320         state->lastchar = state->curpos;
321         break;
322
323       case OP_EDITOR_BACKWARD_CHAR:
324         if (state->curpos == 0)
325           BEEP ();
326         else {
327           while (state->curpos && COMB_CHAR (state->wbuf[state->curpos - 1]))
328             state->curpos--;
329           if (state->curpos)
330             state->curpos--;
331         }
332         break;
333
334       case OP_EDITOR_FORWARD_CHAR:
335         if (state->curpos == state->lastchar)
336           BEEP ();
337         else {
338           ++state->curpos;
339           while (state->curpos < state->lastchar
340                  && COMB_CHAR (state->wbuf[state->curpos]))
341             ++state->curpos;
342         }
343         break;
344
345       case OP_EDITOR_BACKWARD_WORD:
346         if (state->curpos == 0)
347           BEEP ();
348         else {
349           while (state->curpos && iswspace (state->wbuf[state->curpos - 1]))
350             --state->curpos;
351           while (state->curpos && !iswspace (state->wbuf[state->curpos - 1]))
352             --state->curpos;
353         }
354         break;
355
356       case OP_EDITOR_FORWARD_WORD:
357         if (state->curpos == state->lastchar)
358           BEEP ();
359         else {
360           while (state->curpos < state->lastchar
361                  && iswspace (state->wbuf[state->curpos]))
362             ++state->curpos;
363           while (state->curpos < state->lastchar
364                  && !iswspace (state->wbuf[state->curpos]))
365             ++state->curpos;
366         }
367         break;
368
369       case OP_EDITOR_CAPITALIZE_WORD:
370       case OP_EDITOR_UPCASE_WORD:
371       case OP_EDITOR_DOWNCASE_WORD:
372         if (state->curpos == state->lastchar) {
373           BEEP ();
374           break;
375         }
376         while (state->curpos && !iswspace (state->wbuf[state->curpos]))
377           --state->curpos;
378         while (state->curpos < state->lastchar
379                && iswspace (state->wbuf[state->curpos]))
380           ++state->curpos;
381         while (state->curpos < state->lastchar
382                && !iswspace (state->wbuf[state->curpos])) {
383           if (ch == OP_EDITOR_DOWNCASE_WORD)
384             state->wbuf[state->curpos] =
385               towlower (state->wbuf[state->curpos]);
386           else {
387             state->wbuf[state->curpos] =
388               towupper (state->wbuf[state->curpos]);
389             if (ch == OP_EDITOR_CAPITALIZE_WORD)
390               ch = OP_EDITOR_DOWNCASE_WORD;
391           }
392           state->curpos++;
393         }
394         break;
395
396       case OP_EDITOR_DELETE_CHAR:
397         if (state->curpos == state->lastchar)
398           BEEP ();
399         else {
400           i = state->curpos;
401           while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
402             ++i;
403           if (i < state->lastchar)
404             ++i;
405           while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
406             ++i;
407           memmove (state->wbuf + state->curpos, state->wbuf + i,
408                    (state->lastchar - i) * sizeof (wchar_t));
409           state->lastchar -= i - state->curpos;
410         }
411         break;
412
413       case OP_EDITOR_KILL_WORD:
414         /* delete to begining of word */
415         if (state->curpos != 0) {
416           i = state->curpos;
417           while (i && iswspace (state->wbuf[i - 1]))
418             --i;
419           if (i) {
420             if (iswalnum (state->wbuf[i - 1])) {
421               for (--i; i && iswalnum (state->wbuf[i - 1]); i--);
422             }
423             else
424               --i;
425           }
426           memmove (state->wbuf + i, state->wbuf + state->curpos,
427                    (state->lastchar - state->curpos) * sizeof (wchar_t));
428           state->lastchar += i - state->curpos;
429           state->curpos = i;
430         }
431         break;
432
433       case OP_EDITOR_KILL_EOW:
434         /* delete to end of word */
435         for (i = state->curpos;
436              i < state->lastchar && iswspace (state->wbuf[i]); i++);
437         for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++);
438         memmove (state->wbuf + state->curpos, state->wbuf + i,
439                  (state->lastchar - i) * sizeof (wchar_t));
440         state->lastchar += state->curpos - i;
441         break;
442
443       case OP_EDITOR_BUFFY_CYCLE:
444         if (flags & M_EFILE) {
445           first = 1;            /* clear input if user types a real key later */
446           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
447           buffy_next (buf, buflen);
448           state->curpos = state->lastchar =
449             my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
450           break;
451         }
452         else if (!(flags & M_FILE))
453           goto self_insert;
454         /* fall through to completion routine (M_FILE) */
455
456       case OP_EDITOR_COMPLETE:
457       case OP_EDITOR_COMPLETE_QUERY:
458         state->tabs++;
459         if (flags & M_CMD) {
460           for (i = state->curpos; i && state->wbuf[i - 1] != ' '; i--);
461           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
462           if (tempbuf && templen == state->lastchar - i &&
463               !memcmp (tempbuf, state->wbuf + i,
464                        (state->lastchar - i) * sizeof (wchar_t))) {
465             mutt_select_file (buf, buflen,
466                               (flags & M_EFILE) ? M_SEL_FOLDER : 0);
467             set_option (OPTNEEDREDRAW);
468             if (*buf)
469               replace_part (state, i, buf);
470             rv = 1;
471             goto bye;
472           }
473           if (!mutt_complete (buf, buflen)) {
474             templen = state->lastchar - i;
475             p_realloc(&tempbuf, templen);
476           }
477           else
478             BEEP ();
479
480           replace_part (state, i, buf);
481         }
482         else if (flags & M_ALIAS && ch == OP_EDITOR_COMPLETE) {
483           /* invoke the alias-menu to get more addresses */
484           for (i = state->curpos; i && state->wbuf[i - 1] != ',' &&
485                state->wbuf[i - 1] != ':'; i--);
486           for (; i < state->lastchar && state->wbuf[i] == ' '; i++);
487           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
488           r = mutt_alias_complete (buf, buflen);
489           replace_part (state, i, buf);
490           if (!r) {
491             rv = 1;
492             goto bye;
493           }
494           break;
495         } else if (flags & M_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY) {
496           /* invoke the query-menu to get more addresses */
497           if ((i = state->curpos)) {
498             for (; i && state->wbuf[i - 1] != ','; i--);
499             for (; i < state->curpos && state->wbuf[i] == ' '; i++);
500           }
501           my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
502           mutt_query_complete (buf, buflen);
503           replace_part (state, i, buf);
504           rv = 1;
505           goto bye;
506         } else if (flags & M_COMMAND) {
507           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
508           i = m_strlen(buf);
509           if (i && buf[i - 1] == '=' &&
510               mutt_var_value_complete (buf, buflen, i))
511             state->tabs = 0;
512           else if (!mutt_command_complete (buf, buflen, i, state->tabs))
513             BEEP ();
514           replace_part (state, 0, buf);
515         }
516         else if (flags & (M_FILE | M_EFILE)) {
517           my_wcstombs (buf, buflen, state->wbuf, state->curpos);
518
519           /* see if the path has changed from the last time */
520           if ((!tempbuf && !state->lastchar)
521               || (tempbuf && templen == state->lastchar
522                   && !memcmp (tempbuf, state->wbuf,
523                               state->lastchar * sizeof (wchar_t)))) {
524             _mutt_select_file (buf, buflen,
525                                ((flags & M_EFILE) ? M_SEL_FOLDER : 0) |
526                                (multiple ? M_SEL_MULTI : 0), files, numfiles);
527             set_option (OPTNEEDREDRAW);
528             if (*buf) {
529               mutt_pretty_mailbox (buf);
530               if (!pass)
531                 mutt_history_add (hclass, buf);
532               rv = 0;
533               goto bye;
534             }
535
536             /* file selection cancelled */
537             rv = 1;
538             goto bye;
539           }
540
541           if (!mutt_complete (buf, buflen)) {
542             templen = state->lastchar;
543             p_realloc(&tempbuf, templen);
544             memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
545           }
546           else
547             BEEP ();            /* let the user know that nothing matched */
548           replace_part (state, 0, buf);
549         }
550         else
551           goto self_insert;
552         break;
553
554       case OP_EDITOR_QUOTE_CHAR:
555         {
556           event_t event;
557
558           /*ADDCH (LastKey); */
559           event = mutt_getch ();
560           if (event.ch != -1) {
561             LastKey = event.ch;
562             goto self_insert;
563           }
564         }
565
566       case OP_EDITOR_TRANSPOSE_CHARS:
567         if (state->lastchar < 2)
568           BEEP ();
569         else {
570           wchar_t t;
571
572           if (state->curpos == 0)
573             state->curpos = 2;
574           else if (state->curpos < state->lastchar)
575             ++state->curpos;
576
577           t = state->wbuf[state->curpos - 2];
578           state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
579           state->wbuf[state->curpos - 1] = t;
580         }
581         break;
582
583       default:
584         BEEP ();
585       }
586     }
587     else {
588
589     self_insert:
590
591       state->tabs = 0;
592       /* use the raw keypress */
593       ch = LastKey;
594
595       if ((ch == '#') && (flags & M_LASTFOLDER)) {
596         rv = 2;
597         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
598         goto bye;
599       }
600
601 #ifdef KEY_ENTER
602       /* treat ENTER the same as RETURN */
603       if (ch == KEY_ENTER)
604         ch = '\r';
605 #endif
606
607       /* quietly ignore all other function keys */
608       if (ch & ~0xff)
609         continue;
610
611       /* gather the octets into a wide character */
612       {
613         char c;
614         size_t k;
615
616         c = ch;
617         k = mbrtowc (&wc, &c, 1, &mbstate);
618         if (k == (size_t) (-2))
619           continue;
620         else if (k && k != 1) {
621           p_clear(&mbstate, 1);
622           continue;
623         }
624       }
625
626       if (first && (flags & M_CLEAR)) {
627         first = 0;
628         if (IsWPrint (wc))      /* why? */
629           state->curpos = state->lastchar = 0;
630       }
631
632       if (wc == '\r' || wc == '\n') {
633         /* Convert from wide characters */
634         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
635         if (!pass)
636           mutt_history_add (hclass, buf);
637
638         if (multiple) {
639           char **tfiles;
640
641           *numfiles = 1;
642           tfiles = p_new(char *, *numfiles);
643           mutt_expand_path (buf, buflen);
644           tfiles[0] = m_strdup(buf);
645           *files = tfiles;
646         }
647         rv = 0;
648         goto bye;
649       }
650       else if (wc && (wc < ' ' || IsWPrint (wc))) {     /* why? */
651         if (state->lastchar >= state->wbuflen) {
652           state->wbuflen = state->lastchar + 20;
653           p_realloc(&state->wbuf, state->wbuflen);
654         }
655         memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos,
656                  (state->lastchar - state->curpos) * sizeof (wchar_t));
657         state->wbuf[state->curpos++] = wc;
658         state->lastchar++;
659       }
660       else {
661         mutt_flushinp ();
662         BEEP ();
663       }
664     }
665   }
666
667 bye:
668
669   p_delete(&tempbuf);
670   return rv;
671 }
672
673 void mutt_free_enter_state (ENTER_STATE ** esp)
674 {
675   if (!esp)
676     return;
677
678   p_delete(&(*esp)->wbuf);
679   p_delete(esp);
680 }
681
682 /*
683  * TODO:
684  * very narrow screen might crash it
685  * sort out the input side
686  * unprintable chars
687  */