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