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