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