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