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