Andreas Krennmair:
[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 {
35   M_REDRAW_INIT = 1,        /* go to end of line and redraw */
36   M_REDRAW_LINE                /* redraw entire line */
37 };
38
39 static int my_wcwidth (wchar_t wc)
40 {
41   int n = wcwidth (wc);
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   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   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   for (; n; s++, n--)
79     if ((w += my_wcwidth (*s)) > w1)
80       break;
81   return s - s0;  
82 }
83
84 static void my_wcstombs (char *dest, size_t dlen, const wchar_t *src, size_t slen)
85 {
86   mbstate_t st;
87   size_t k;
88
89   /* First convert directly into the destination buffer */
90   memset (&st, 0, sizeof (st));
91   for (; slen && dlen >= MB_LEN_MAX; dest += k, dlen -= k, src++, slen--)
92     if ((k = wcrtomb (dest, *src, &st)) == (size_t)(-1))
93       break;
94
95   /* If this works, we can stop now */
96   if (dlen >= MB_LEN_MAX) {
97     wcrtomb (dest, 0, &st);
98     return;
99   }
100
101   /* Otherwise convert any remaining data into a local buffer */
102   {
103     char buf[3 * MB_LEN_MAX];
104     char *p = buf;
105
106     for (; slen && p - buf < dlen; p += k, src++, slen--)
107       if ((k = wcrtomb (p, *src, &st)) == (size_t)(-1))
108         break;
109     p += wcrtomb (p, 0, &st);
110
111     /* If it fits into the destination buffer, we can stop now */
112     if (p - buf <= dlen) {
113       memcpy (dest, buf, p - buf);
114       return;
115     }
116
117     /* Otherwise we truncate the string in an ugly fashion */
118     memcpy (dest, buf, dlen);
119     dest[dlen - 1] = '\0'; /* assume original dlen > 0 */
120   }
121 }
122
123 size_t my_mbstowcs (wchar_t **pwbuf, size_t *pwbuflen, size_t i, char *buf)
124 {
125   wchar_t wc;
126   mbstate_t st;
127   size_t k;
128   wchar_t *wbuf;
129   size_t wbuflen;
130
131   wbuf = *pwbuf, wbuflen = *pwbuflen;
132   memset (&st, 0, sizeof (st));
133   for (; (k = mbrtowc (&wc, buf, MB_LEN_MAX, &st)) &&
134          k != (size_t)(-1) && k != (size_t)(-2); buf += k)
135   {
136     if (i >= wbuflen)
137     {
138       wbuflen = i + 20;
139       safe_realloc (&wbuf, wbuflen * sizeof (*wbuf));
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, char *buf)
152 {
153   /* Save the suffix */
154   size_t savelen = state->lastchar - state->curpos;
155   wchar_t *savebuf = safe_calloc (savelen, sizeof (wchar_t));
156   memcpy (savebuf, state->wbuf + state->curpos, savelen * sizeof (wchar_t));
157
158   /* Convert to wide characters */
159   state->curpos = my_mbstowcs (&state->wbuf, &state->wbuflen, from, buf);
160
161   /* Make space for suffix */
162   if (state->curpos + savelen > state->wbuflen)
163   {
164     state->wbuflen = state->curpos + savelen;
165     safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
166   }
167
168   /* Restore suffix */
169   memcpy (state->wbuf + state->curpos, savebuf, savelen * sizeof (wchar_t));
170   state->lastchar = state->curpos + savelen;
171
172   FREE (&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   rv = _mutt_enter_string (buf, buflen, y, x, flags, 0, NULL, NULL, es);
187   mutt_free_enter_state (&es);
188   return rv;
189 }
190
191 int _mutt_enter_string (char *buf, size_t buflen, int y, int x,
192                         int flags, int multiple, char ***files, int *numfiles,
193                         ENTER_STATE *state)
194 {
195   int width = COLS - x - 1;
196   int redraw;
197   int pass = (flags & M_PASS);
198   int first = 1;
199   int ch, w, r;
200   size_t i;
201   wchar_t *tempbuf = 0;
202   size_t templen = 0;
203   history_class_t hclass;
204   wchar_t wc;
205   mbstate_t mbstate;
206
207   int rv = 0;
208   memset (&mbstate, 0, sizeof (mbstate));
209   
210   if (state->wbuf)
211   {
212     /* Coming back after return 1 */
213     redraw = M_REDRAW_LINE;
214   }
215   else
216   {
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   {
238     if (redraw && !pass)
239     {
240       if (redraw == M_REDRAW_INIT)
241       {
242         /* Go to end of line */
243         state->curpos = state->lastchar;
244         state->begin = width_ceiling (state->wbuf, state->lastchar, my_wcswidth (state->wbuf, state->lastchar) - width + 1);
245       } 
246       if (state->curpos < state->begin ||
247           my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin) >= width)
248         state->begin = width_ceiling (state->wbuf, state->lastchar, my_wcswidth (state->wbuf, state->curpos) - width / 2);
249       move (y, x);
250       w = 0;
251       for (i = state->begin; i < state->lastchar; i++)
252       {
253         w += my_wcwidth (state->wbuf[i]);
254         if (w > width)
255           break;
256         my_addwch (state->wbuf[i]);
257       }
258       clrtoeol ();
259       move (y, x + my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin));
260     }
261     mutt_refresh ();
262
263     if ((ch = km_dokey (MENU_EDITOR)) == -1)
264     {
265       rv = -1; 
266       goto bye;
267     }
268
269     if (ch != OP_NULL)
270     {
271       first = 0;
272       if (ch != OP_EDITOR_COMPLETE)
273         state->tabs = 0;
274       redraw = M_REDRAW_LINE;
275       switch (ch)
276       {
277         case OP_EDITOR_HISTORY_UP:
278           state->curpos = state->lastchar;
279           replace_part (state, 0, mutt_history_prev (hclass));
280           redraw = M_REDRAW_INIT;
281           break;
282
283         case OP_EDITOR_HISTORY_DOWN:
284           state->curpos = state->lastchar;
285           replace_part (state, 0, mutt_history_next (hclass));
286           redraw = M_REDRAW_INIT;
287           break;
288
289         case OP_EDITOR_BACKSPACE:
290           if (state->curpos == 0)
291             BEEP ();
292           else
293           {
294             i = state->curpos;
295             while (i && COMB_CHAR (state->wbuf[i - 1]))
296               --i;
297             if (i)
298               --i;
299             memmove (state->wbuf + i, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
300             state->lastchar -= state->curpos - i;
301             state->curpos = i;
302           }
303           break;
304
305         case OP_EDITOR_BOL:
306           state->curpos = 0;
307           break;
308
309         case OP_EDITOR_EOL:
310           redraw= M_REDRAW_INIT;
311           break;
312
313         case OP_EDITOR_KILL_LINE:
314           state->curpos = state->lastchar = 0;
315           break;
316
317         case OP_EDITOR_KILL_EOL:
318           state->lastchar = state->curpos;
319           break;
320
321         case OP_EDITOR_BACKWARD_CHAR:
322           if (state->curpos == 0)
323             BEEP ();
324           else
325           {
326             while (state->curpos && COMB_CHAR (state->wbuf[state->curpos - 1]))
327               state->curpos--;
328             if (state->curpos)
329               state->curpos--;
330           }
331           break;
332
333         case OP_EDITOR_FORWARD_CHAR:
334           if (state->curpos == state->lastchar)
335             BEEP ();
336           else
337           {
338             ++state->curpos;
339             while (state->curpos < state->lastchar && COMB_CHAR (state->wbuf[state->curpos]))
340               ++state->curpos;
341           }
342           break;
343
344         case OP_EDITOR_BACKWARD_WORD:
345           if (state->curpos == 0)
346             BEEP ();
347           else
348           {
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           {
361             while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
362               ++state->curpos;
363             while (state->curpos < state->lastchar && !iswspace (state->wbuf[state->curpos]))
364               ++state->curpos;
365           }
366           break;
367
368         case OP_EDITOR_CAPITALIZE_WORD:
369         case OP_EDITOR_UPCASE_WORD:
370         case OP_EDITOR_DOWNCASE_WORD:
371           if (state->curpos == state->lastchar)
372           {
373             BEEP ();
374             break;
375           }
376           while (state->curpos && !iswspace (state->wbuf[state->curpos]))
377             --state->curpos;
378           while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
379             ++state->curpos;
380           while (state->curpos < state->lastchar && !iswspace (state->wbuf[state->curpos]))
381           {
382             if (ch == OP_EDITOR_DOWNCASE_WORD)
383               state->wbuf[state->curpos] = towlower (state->wbuf[state->curpos]);
384             else
385             {
386               state->wbuf[state->curpos] = towupper (state->wbuf[state->curpos]);
387               if (ch == OP_EDITOR_CAPITALIZE_WORD)
388                 ch = OP_EDITOR_DOWNCASE_WORD;
389             }
390             state->curpos++;
391           }
392           break;
393
394         case OP_EDITOR_DELETE_CHAR:
395           if (state->curpos == state->lastchar)
396             BEEP ();
397           else
398           {
399             i = state->curpos;
400             while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
401               ++i;
402             if (i < state->lastchar)
403               ++i;
404             while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
405               ++i;
406             memmove (state->wbuf + state->curpos, state->wbuf + i, (state->lastchar - i) * sizeof (wchar_t));
407             state->lastchar -= i - state->curpos;
408           }
409           break;
410
411         case OP_EDITOR_KILL_WORD:
412           /* delete to begining of word */
413           if (state->curpos != 0)
414           {
415             i = state->curpos;
416             while (i && iswspace (state->wbuf[i - 1]))
417               --i;
418             if (i)
419             {
420               if (iswalnum (state->wbuf[i - 1]))
421               {
422                 for (--i; i && iswalnum (state->wbuf[i - 1]); i--)
423                   ;
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             ;
440           for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++)
441             ;
442           memmove (state->wbuf + state->curpos, state->wbuf + i,
443                    (state->lastchar - i) * sizeof (wchar_t));
444           state->lastchar += state->curpos - i;
445           break;
446
447         case OP_EDITOR_BUFFY_CYCLE:
448           if (flags & M_EFILE)
449           {
450             first = 1; /* clear input if user types a real key later */
451             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
452             mutt_buffy (buf, buflen);
453             state->curpos = state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
454             break;
455           }
456           else if (!(flags & M_FILE))
457             goto self_insert;
458           /* fall through to completion routine (M_FILE) */
459
460         case OP_EDITOR_COMPLETE:
461           state->tabs++;
462           if (flags & M_CMD)
463           {
464             for (i = state->curpos; i && state->wbuf[i-1] != ' '; i--)
465               ;
466             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
467             if (tempbuf && templen == state->lastchar - i &&
468                 !memcmp (tempbuf, state->wbuf + i, (state->lastchar - i) * sizeof (wchar_t)))
469             {
470               mutt_select_file (buf, buflen, (flags & M_EFILE) ? M_SEL_FOLDER : 0);
471               set_option (OPTNEEDREDRAW);
472               if (*buf)
473                 replace_part (state, i, buf);
474               rv = 1; 
475               goto bye;
476             }
477             if (!mutt_complete (buf, buflen))
478             {
479               templen = state->lastchar - i;
480               safe_realloc (&tempbuf, templen * sizeof (wchar_t));
481             }
482             else
483               BEEP ();
484
485             replace_part (state, i, buf);
486           }
487           else if (flags & M_ALIAS)
488           {
489             /* invoke the alias-menu to get more addresses */
490             for (i = state->curpos; i && state->wbuf[i-1] != ',' && 
491                  state->wbuf[i-1] != ':'; i--)
492               ;
493             for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
494               ;
495             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
496             r = mutt_alias_complete (buf, buflen);
497             replace_part (state, i, buf);
498             if (!r)
499             {
500               rv = 1;
501               goto bye;
502             }
503             break;
504           }
505           else if (flags & M_COMMAND)
506           {
507             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
508             i = 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           {
518             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
519
520             /* see if the path has changed from the last time */
521             if ((!tempbuf && !state->lastchar) || (tempbuf && templen == state->lastchar &&
522                 !memcmp (tempbuf, state->wbuf, state->lastchar * sizeof (wchar_t))))
523             {
524               _mutt_select_file (buf, buflen, 
525                                  ((flags & M_EFILE) ? M_SEL_FOLDER : 0) | (multiple ? M_SEL_MULTI : 0), 
526                                  files, numfiles);
527               set_option (OPTNEEDREDRAW);
528               if (*buf)
529               {
530                 mutt_pretty_mailbox (buf);
531                 if (!pass)
532                   mutt_history_add (hclass, buf);
533                 rv = 0;
534                 goto bye;
535               }
536
537               /* file selection cancelled */
538               rv = 1;
539               goto bye;
540             }
541
542             if (!mutt_complete (buf, buflen))
543             {
544               templen = state->lastchar;
545               safe_realloc (&tempbuf, templen * sizeof (wchar_t));
546               memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
547             }
548             else
549               BEEP (); /* let the user know that nothing matched */
550             replace_part (state, 0, buf);
551           }
552           else
553             goto self_insert;
554           break;
555
556         case OP_EDITOR_COMPLETE_QUERY:
557           if (flags & M_ALIAS)
558           {
559             /* invoke the query-menu to get more addresses */
560             if ((i = state->curpos))
561             {
562               for (; i && state->wbuf[i - 1] != ','; i--)
563                 ;
564               for (; i < state->curpos && state->wbuf[i] == ' '; i++)
565                 ;
566             }
567
568             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
569             mutt_query_complete (buf, buflen);
570             replace_part (state, i, buf);
571
572             rv = 1; 
573             goto bye;
574           }
575           else
576             goto self_insert;
577
578         case OP_EDITOR_QUOTE_CHAR:
579           {
580             event_t event;
581             /*ADDCH (LastKey);*/
582             event = mutt_getch ();
583             if (event.ch != -1)
584             {
585               LastKey = event.ch;
586               goto self_insert;
587             }
588           }
589
590         case OP_EDITOR_TRANSPOSE_CHARS:
591           if (state->lastchar < 2)
592             BEEP ();
593           else
594         {
595             wchar_t t;
596
597             if (state->curpos == 0)
598               state->curpos = 2;
599             else if (state->curpos < state->lastchar)
600               ++state->curpos;
601
602             t = state->wbuf[state->curpos - 2];
603             state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
604             state->wbuf[state->curpos - 1] = t;
605           }
606           break;
607
608         default:
609           BEEP ();
610       }
611     }
612     else
613     {
614       
615 self_insert:
616
617       state->tabs = 0;
618       /* use the raw keypress */
619       ch = LastKey;
620
621       if ((ch == '#') && (flags & M_LASTFOLDER))
622       {
623         rv = 2;
624         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
625         goto bye;
626       }
627       
628 #ifdef KEY_ENTER
629       /* treat ENTER the same as RETURN */
630       if (ch == KEY_ENTER)
631         ch = '\r';
632 #endif
633
634       /* quietly ignore all other function keys */
635       if (ch & ~0xff)
636         continue;
637
638       /* gather the octets into a wide character */
639       {
640         char c;
641         size_t k;
642
643         c = ch;
644         k = mbrtowc (&wc, &c, 1, &mbstate);
645         if (k == (size_t)(-2))
646           continue;
647         else if (k && k != 1)
648         {
649           memset (&mbstate, 0, sizeof (mbstate));
650           continue;
651         }
652       }
653
654       if (first && (flags & M_CLEAR))
655       {
656         first = 0;
657         if (IsWPrint (wc)) /* why? */
658           state->curpos = state->lastchar = 0;
659       }
660
661       if (wc == '\r' || wc == '\n')
662       {
663         /* Convert from wide characters */
664         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
665         if (!pass)
666           mutt_history_add (hclass, buf);
667
668         if (multiple)
669         {
670           char **tfiles;
671           *numfiles = 1;
672           tfiles = safe_calloc (*numfiles, sizeof (char *));
673           mutt_expand_path (buf, buflen);
674           tfiles[0] = safe_strdup (buf);
675           *files = tfiles;
676         }
677         rv = 0; 
678         goto bye;
679       }
680       else if (wc && (wc < ' ' || IsWPrint (wc))) /* why? */
681       {
682         if (state->lastchar >= state->wbuflen)
683         {
684           state->wbuflen = state->lastchar + 20;
685           safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
686         }
687         memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
688         state->wbuf[state->curpos++] = wc;
689         state->lastchar++;
690       }
691       else
692       {
693         mutt_flushinp ();
694         BEEP ();
695       }
696     }
697   }
698   
699   bye:
700   
701   FREE (&tempbuf);
702   return rv;
703 }
704
705 void mutt_free_enter_state (ENTER_STATE **esp)
706 {
707   if (!esp) return;
708   
709   FREE (&(*esp)->wbuf);
710   FREE (esp);
711 }
712
713 /*
714  * TODO:
715  * very narrow screen might crash it
716  * sort out the input side
717  * unprintable chars
718  */