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 #include "mutt.h"
21 #include "mutt_menu.h"
22 #include "mutt_curses.h"
23 #include "keymap.h"
24 #include "history.h"
25
26 #include <string.h>
27
28 /* redraw flags for mutt_enter_string() */
29 enum
30 {
31   M_REDRAW_INIT = 1,        /* go to end of line and redraw */
32   M_REDRAW_LINE                /* redraw entire line */
33 };
34
35 static int my_wcwidth (wchar_t wc)
36 {
37   int n = wcwidth (wc);
38   if (IsWPrint (wc) && n > 0)
39     return n;
40   if (!(wc & ~0x7f))
41     return 2;
42   if (!(wc & ~0xffff))
43     return 6;
44   return 10;
45 }
46
47 /* combining mark / non-spacing character */
48 #define COMB_CHAR(wc) (IsWPrint (wc) && !wcwidth (wc))
49
50 static int my_wcswidth (const wchar_t *s, size_t n)
51 {
52   int w = 0;
53   while (n--)
54     w += my_wcwidth (*s++);
55   return w;
56 }
57
58 static int my_addwch (wchar_t wc)
59 {
60   int n = wcwidth (wc);
61   if (IsWPrint (wc) && n > 0)
62     return mutt_addwch (wc);
63   if (!(wc & ~0x7f))
64     return printw ("^%c", ((int)wc + 0x40) & 0x7f);
65   if (!(wc & ~0xffff))
66     return printw ("\\u%04x", (int)wc);
67   return printw ("\\u%08x", (int)wc);
68 }
69
70 static size_t width_ceiling (const wchar_t *s, size_t n, int w1)
71 {
72   const wchar_t *s0 = s;
73   int w = 0;
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, size_t slen)
81 {
82   mbstate_t st;
83   size_t k;
84
85   /* First convert directly into the destination buffer */
86   memset (&st, 0, sizeof (st));
87   for (; slen && dlen >= MB_LEN_MAX; dest += k, dlen -= k, src++, slen--)
88     if ((k = wcrtomb (dest, *src, &st)) == (size_t)(-1))
89       break;
90
91   /* If this works, we can stop now */
92   if (dlen >= MB_LEN_MAX) {
93     wcrtomb (dest, 0, &st);
94     return;
95   }
96
97   /* Otherwise convert any remaining data into a local buffer */
98   {
99     char buf[3 * MB_LEN_MAX];
100     char *p = buf;
101
102     for (; slen && p - buf < dlen; p += k, src++, slen--)
103       if ((k = wcrtomb (p, *src, &st)) == (size_t)(-1))
104         break;
105     p += wcrtomb (p, 0, &st);
106
107     /* If it fits into the destination buffer, we can stop now */
108     if (p - buf <= dlen) {
109       memcpy (dest, buf, p - buf);
110       return;
111     }
112
113     /* Otherwise we truncate the string in an ugly fashion */
114     memcpy (dest, buf, dlen);
115     dest[dlen - 1] = '\0'; /* assume original dlen > 0 */
116   }
117 }
118
119 size_t my_mbstowcs (wchar_t **pwbuf, size_t *pwbuflen, size_t i, char *buf)
120 {
121   wchar_t wc;
122   mbstate_t st;
123   size_t k;
124   wchar_t *wbuf;
125   size_t wbuflen;
126
127   wbuf = *pwbuf, wbuflen = *pwbuflen;
128   memset (&st, 0, sizeof (st));
129   for (; (k = mbrtowc (&wc, buf, MB_LEN_MAX, &st)) &&
130          k != (size_t)(-1) && k != (size_t)(-2); buf += k)
131   {
132     if (i >= wbuflen)
133     {
134       wbuflen = i + 20;
135       safe_realloc (&wbuf, wbuflen * sizeof (*wbuf));
136     }
137     wbuf[i++] = wc;
138   }
139   *pwbuf = wbuf, *pwbuflen = wbuflen;
140   return i;
141 }
142
143 /*
144  * Replace part of the wchar_t buffer, from FROM to CURPOS, by BUF.
145  */
146
147 static void replace_part (ENTER_STATE *state, size_t from, char *buf)
148 {
149   /* Save the suffix */
150   size_t savelen = state->lastchar - state->curpos;
151   wchar_t *savebuf = safe_calloc (savelen, sizeof (wchar_t));
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   {
160     state->wbuflen = state->curpos + savelen;
161     safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
162   }
163
164   /* Restore suffix */
165   memcpy (state->wbuf + state->curpos, savebuf, savelen * sizeof (wchar_t));
166   state->lastchar = state->curpos + savelen;
167
168   FREE (&savebuf);
169 }
170
171 /*
172  * Returns:
173  *        1 need to redraw the screen and call me again
174  *        0 if input was given
175  *         -1 if abort.
176  */
177
178 int  mutt_enter_string(char *buf, size_t buflen, int y, int x, int flags)
179 {
180   int rv;
181   ENTER_STATE *es = mutt_new_enter_state ();
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   memset (&mbstate, 0, sizeof (mbstate));
205   
206   if (state->wbuf)
207   {
208     /* Coming back after return 1 */
209     redraw = M_REDRAW_LINE;
210   }
211   else
212   {
213     /* Initialise wbuf from buf */
214     state->wbuflen = 0;
215     state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
216     redraw = M_REDRAW_INIT;
217   }
218
219   if (flags & (M_FILE | M_EFILE))
220     hclass = HC_FILE;
221   else if (flags & M_CMD)
222     hclass = HC_CMD;
223   else if (flags & M_ALIAS)
224     hclass = HC_ALIAS;
225   else if (flags & M_COMMAND)
226     hclass = HC_COMMAND;
227   else if (flags & M_PATTERN)
228     hclass = HC_PATTERN;
229   else 
230     hclass = HC_OTHER;
231     
232   for (;;)
233   {
234     if (redraw && !pass)
235     {
236       if (redraw == M_REDRAW_INIT)
237       {
238         /* Go to end of line */
239         state->curpos = state->lastchar;
240         state->begin = width_ceiling (state->wbuf, state->lastchar, my_wcswidth (state->wbuf, state->lastchar) - width + 1);
241       } 
242       if (state->curpos < state->begin ||
243           my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin) >= width)
244         state->begin = width_ceiling (state->wbuf, state->lastchar, my_wcswidth (state->wbuf, state->curpos) - width / 2);
245       move (y, x);
246       w = 0;
247       for (i = state->begin; i < state->lastchar; i++)
248       {
249         w += my_wcwidth (state->wbuf[i]);
250         if (w > width)
251           break;
252         my_addwch (state->wbuf[i]);
253       }
254       clrtoeol ();
255       move (y, x + my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin));
256     }
257     mutt_refresh ();
258
259     if ((ch = km_dokey (MENU_EDITOR)) == -1)
260     {
261       rv = -1; 
262       goto bye;
263     }
264
265     if (ch != OP_NULL)
266     {
267       first = 0;
268       if (ch != OP_EDITOR_COMPLETE)
269         state->tabs = 0;
270       redraw = M_REDRAW_LINE;
271       switch (ch)
272       {
273         case OP_EDITOR_HISTORY_UP:
274           state->curpos = state->lastchar;
275           replace_part (state, 0, mutt_history_prev (hclass));
276           redraw = M_REDRAW_INIT;
277           break;
278
279         case OP_EDITOR_HISTORY_DOWN:
280           state->curpos = state->lastchar;
281           replace_part (state, 0, mutt_history_next (hclass));
282           redraw = M_REDRAW_INIT;
283           break;
284
285         case OP_EDITOR_BACKSPACE:
286           if (state->curpos == 0)
287             BEEP ();
288           else
289           {
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, (state->lastchar - state->curpos) * sizeof (wchar_t));
296             state->lastchar -= state->curpos - i;
297             state->curpos = i;
298           }
299           break;
300
301         case OP_EDITOR_BOL:
302           state->curpos = 0;
303           break;
304
305         case OP_EDITOR_EOL:
306           redraw= M_REDRAW_INIT;
307           break;
308
309         case OP_EDITOR_KILL_LINE:
310           state->curpos = state->lastchar = 0;
311           break;
312
313         case OP_EDITOR_KILL_EOL:
314           state->lastchar = state->curpos;
315           break;
316
317         case OP_EDITOR_BACKWARD_CHAR:
318           if (state->curpos == 0)
319             BEEP ();
320           else
321           {
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           {
334             ++state->curpos;
335             while (state->curpos < state->lastchar && 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           {
345             while (state->curpos && iswspace (state->wbuf[state->curpos - 1]))
346               --state->curpos;
347             while (state->curpos && !iswspace (state->wbuf[state->curpos - 1]))
348               --state->curpos;
349           }
350           break;
351
352         case OP_EDITOR_FORWARD_WORD:
353           if (state->curpos == state->lastchar)
354             BEEP ();
355           else
356           {
357             while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
358               ++state->curpos;
359             while (state->curpos < state->lastchar && !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           {
369             BEEP ();
370             break;
371           }
372           while (state->curpos && !iswspace (state->wbuf[state->curpos]))
373             --state->curpos;
374           while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
375             ++state->curpos;
376           while (state->curpos < state->lastchar && !iswspace (state->wbuf[state->curpos]))
377           {
378             if (ch == OP_EDITOR_DOWNCASE_WORD)
379               state->wbuf[state->curpos] = towlower (state->wbuf[state->curpos]);
380             else
381             {
382               state->wbuf[state->curpos] = towupper (state->wbuf[state->curpos]);
383               if (ch == OP_EDITOR_CAPITALIZE_WORD)
384                 ch = OP_EDITOR_DOWNCASE_WORD;
385             }
386             state->curpos++;
387           }
388           break;
389
390         case OP_EDITOR_DELETE_CHAR:
391           if (state->curpos == state->lastchar)
392             BEEP ();
393           else
394           {
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, (state->lastchar - i) * sizeof (wchar_t));
403             state->lastchar -= i - state->curpos;
404           }
405           break;
406
407         case OP_EDITOR_KILL_WORD:
408           /* delete to begining of word */
409           if (state->curpos != 0)
410           {
411             i = state->curpos;
412             while (i && iswspace (state->wbuf[i - 1]))
413               --i;
414             if (i)
415             {
416               if (iswalnum (state->wbuf[i - 1]))
417               {
418                 for (--i; i && iswalnum (state->wbuf[i - 1]); i--)
419                   ;
420               }
421               else
422                 --i;
423             }
424             memmove (state->wbuf + i, state->wbuf + state->curpos,
425                      (state->lastchar - state->curpos) * sizeof (wchar_t));
426             state->lastchar += i - state->curpos;
427             state->curpos = i;
428           }
429           break;
430
431         case OP_EDITOR_KILL_EOW:
432           /* delete to end of word */
433           for (i = state->curpos;
434                i < state->lastchar && iswspace (state->wbuf[i]); i++)
435             ;
436           for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++)
437             ;
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           {
446             first = 1; /* clear input if user types a real key later */
447             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
448             mutt_buffy (buf, buflen);
449             state->curpos = state->lastchar = 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           state->tabs++;
458           if (flags & M_CMD)
459           {
460             for (i = state->curpos; i && state->wbuf[i-1] != ' '; i--)
461               ;
462             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
463             if (tempbuf && templen == state->lastchar - i &&
464                 !memcmp (tempbuf, state->wbuf + i, (state->lastchar - i) * sizeof (wchar_t)))
465             {
466               mutt_select_file (buf, buflen, (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             {
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           {
485             /* invoke the alias-menu to get more addresses */
486             for (i = state->curpos; i && state->wbuf[i-1] != ',' && 
487                  state->wbuf[i-1] != ':'; i--)
488               ;
489             for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
490               ;
491             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
492             r = mutt_alias_complete (buf, buflen);
493             replace_part (state, i, buf);
494             if (!r)
495             {
496               rv = 1;
497               goto bye;
498             }
499             break;
500           }
501           else if (flags & M_COMMAND)
502           {
503             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
504             i = strlen (buf);
505             if (i && buf[i - 1] == '=' &&
506                 mutt_var_value_complete (buf, buflen, i))
507               state->tabs = 0;
508             else if (!mutt_command_complete (buf, buflen, i, state->tabs))
509               BEEP ();
510             replace_part (state, 0, buf);
511           }
512           else if (flags & (M_FILE | M_EFILE))
513           {
514             my_wcstombs (buf, buflen, state->wbuf, state->curpos);
515
516             /* see if the path has changed from the last time */
517             if ((!tempbuf && !state->lastchar) || (tempbuf && templen == state->lastchar &&
518                 !memcmp (tempbuf, state->wbuf, state->lastchar * sizeof (wchar_t))))
519             {
520               _mutt_select_file (buf, buflen, 
521                                  ((flags & M_EFILE) ? M_SEL_FOLDER : 0) | (multiple ? M_SEL_MULTI : 0), 
522                                  files, numfiles);
523               set_option (OPTNEEDREDRAW);
524               if (*buf)
525               {
526                 mutt_pretty_mailbox (buf);
527                 if (!pass)
528                   mutt_history_add (hclass, buf);
529                 rv = 0;
530                 goto bye;
531               }
532
533               /* file selection cancelled */
534               rv = 1;
535               goto bye;
536             }
537
538             if (!mutt_complete (buf, buflen))
539             {
540               templen = state->lastchar;
541               safe_realloc (&tempbuf, templen * sizeof (wchar_t));
542               memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
543             }
544             else
545               BEEP (); /* let the user know that nothing matched */
546             replace_part (state, 0, buf);
547           }
548           else
549             goto self_insert;
550           break;
551
552         case OP_EDITOR_COMPLETE_QUERY:
553           if (flags & M_ALIAS)
554           {
555             /* invoke the query-menu to get more addresses */
556             if ((i = state->curpos))
557             {
558               for (; i && state->wbuf[i - 1] != ','; i--)
559                 ;
560               for (; i < state->curpos && state->wbuf[i] == ' '; i++)
561                 ;
562             }
563
564             my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
565             mutt_query_complete (buf, buflen);
566             replace_part (state, i, buf);
567
568             rv = 1; 
569             goto bye;
570           }
571           else
572             goto self_insert;
573
574         case OP_EDITOR_QUOTE_CHAR:
575           {
576             event_t event;
577             /*ADDCH (LastKey);*/
578             event = mutt_getch ();
579             if (event.ch != -1)
580             {
581               LastKey = event.ch;
582               goto self_insert;
583             }
584           }
585
586         case OP_EDITOR_TRANSPOSE_CHARS:
587           if (state->lastchar < 2)
588             BEEP ();
589           else
590         {
591             wchar_t t;
592
593             if (state->curpos == 0)
594               state->curpos = 2;
595             else if (state->curpos < state->lastchar)
596               ++state->curpos;
597
598             t = state->wbuf[state->curpos - 2];
599             state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
600             state->wbuf[state->curpos - 1] = t;
601           }
602           break;
603
604         default:
605           BEEP ();
606       }
607     }
608     else
609     {
610       
611 self_insert:
612
613       state->tabs = 0;
614       /* use the raw keypress */
615       ch = LastKey;
616
617       if ((ch == '.') && (flags & (M_FILE | M_EFILE)))
618       {
619         rv = 2;
620         goto bye;
621       }
622       
623 #ifdef KEY_ENTER
624       /* treat ENTER the same as RETURN */
625       if (ch == KEY_ENTER)
626         ch = '\r';
627 #endif
628
629       /* quietly ignore all other function keys */
630       if (ch & ~0xff)
631         continue;
632
633       /* gather the octets into a wide character */
634       {
635         char c;
636         size_t k;
637
638         c = ch;
639         k = mbrtowc (&wc, &c, 1, &mbstate);
640         if (k == (size_t)(-2))
641           continue;
642         else if (k && k != 1)
643         {
644           memset (&mbstate, 0, sizeof (mbstate));
645           continue;
646         }
647       }
648
649       if (first && (flags & M_CLEAR))
650       {
651         first = 0;
652         if (IsWPrint (wc)) /* why? */
653           state->curpos = state->lastchar = 0;
654       }
655
656       if (wc == '\r' || wc == '\n')
657       {
658         /* Convert from wide characters */
659         my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
660         if (!pass)
661           mutt_history_add (hclass, buf);
662
663         if (multiple)
664         {
665           char **tfiles;
666           *numfiles = 1;
667           tfiles = safe_calloc (*numfiles, sizeof (char *));
668           mutt_expand_path (buf, buflen);
669           tfiles[0] = safe_strdup (buf);
670           *files = tfiles;
671         }
672         rv = 0; 
673         goto bye;
674       }
675       else if (wc && (wc < ' ' || IsWPrint (wc))) /* why? */
676       {
677         if (state->lastchar >= state->wbuflen)
678         {
679           state->wbuflen = state->lastchar + 20;
680           safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
681         }
682         memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
683         state->wbuf[state->curpos++] = wc;
684         state->lastchar++;
685       }
686       else
687       {
688         mutt_flushinp ();
689         BEEP ();
690       }
691     }
692   }
693   
694   bye:
695   
696   FREE (&tempbuf);
697   return rv;
698 }
699
700 void mutt_free_enter_state (ENTER_STATE **esp)
701 {
702   if (!esp) return;
703   
704   FREE (&(*esp)->wbuf);
705   FREE (esp);
706 }
707
708 /*
709  * TODO:
710  * very narrow screen might crash it
711  * sort out the input side
712  * unprintable chars
713  */