1 /**
2  * @file
3  * GUI ask the user to enter a string
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2007,2011,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2000-2001 Edmund Grimley Evans <edmundo@rano.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page neo_enter GUI ask the user to enter a string
26  *
27  * GUI ask the user to enter a string
28  */
29 
30 #include "config.h"
31 #include <stddef.h>
32 #include <stdbool.h>
33 #include <string.h>
34 #include <wchar.h>
35 #include <wctype.h>
36 #include "mutt/lib.h"
37 #include "config/lib.h"
38 #include "core/lib.h"
39 #include "alias/lib.h"
40 #include "gui/lib.h"
41 #include "mutt.h"
42 #include "history/lib.h"
43 #include "menu/lib.h"
44 #include "pattern/lib.h"
45 #include "browser.h"
46 #include "enter_state.h"
47 #include "init.h"
48 #include "keymap.h"
49 #include "mutt_globals.h"
50 #include "mutt_history.h"
51 #include "mutt_mailbox.h"
52 #include "muttlib.h"
53 #include "opcodes.h"
54 #include "protos.h"
55 
56 /**
57  * enum EnterRedrawFlags - Redraw flags for mutt_enter_string_full()
58  */
59 enum EnterRedrawFlags
60 {
61   ENTER_REDRAW_NONE = 0, ///< Nothing to redraw
62   ENTER_REDRAW_INIT,     ///< Go to end of line and redraw
63   ENTER_REDRAW_LINE,     ///< Redraw entire line
64 };
65 
66 /* combining mark / non-spacing character */
67 #define COMB_CHAR(wc) (IsWPrint(wc) && !wcwidth(wc))
68 
69 /**
70  * my_addwch - Display one wide character on screen
71  * @param win Window
72  * @param wc  Character to display
73  * @retval OK  Success
74  * @retval ERR Failure
75  */
my_addwch(struct MuttWindow * win,wchar_t wc)76 static int my_addwch(struct MuttWindow *win, wchar_t wc)
77 {
78   int n = wcwidth(wc);
79   if (IsWPrint(wc) && (n > 0))
80     return mutt_addwch(win, wc);
81   if (!(wc & ~0x7f))
82     return mutt_window_printf(win, "^%c", ((int) wc + 0x40) & 0x7f);
83   if (!(wc & ~0xffff))
84     return mutt_window_printf(win, "\\u%04x", (int) wc);
85   return mutt_window_printf(win, "\\u%08x", (int) wc);
86 }
87 
88 /**
89  * replace_part - Search and replace on a buffer
90  * @param state Current state of the input buffer
91  * @param from  Starting point for the replacement
92  * @param buf   Replacement string
93  */
replace_part(struct EnterState * state,size_t from,char * buf)94 static void replace_part(struct EnterState *state, size_t from, char *buf)
95 {
96   /* Save the suffix */
97   size_t savelen = state->lastchar - state->curpos;
98   wchar_t *savebuf = NULL;
99 
100   if (savelen)
101   {
102     savebuf = mutt_mem_calloc(savelen, sizeof(wchar_t));
103     memcpy(savebuf, state->wbuf + state->curpos, savelen * sizeof(wchar_t));
104   }
105 
106   /* Convert to wide characters */
107   state->curpos = mutt_mb_mbstowcs(&state->wbuf, &state->wbuflen, from, buf);
108 
109   if (savelen)
110   {
111     /* Make space for suffix */
112     if (state->curpos + savelen > state->wbuflen)
113     {
114       state->wbuflen = state->curpos + savelen;
115       mutt_mem_realloc(&state->wbuf, state->wbuflen * sizeof(wchar_t));
116     }
117 
118     /* Restore suffix */
119     memcpy(state->wbuf + state->curpos, savebuf, savelen * sizeof(wchar_t));
120     FREE(&savebuf);
121   }
122 
123   state->lastchar = state->curpos + savelen;
124 }
125 
126 /**
127  * mutt_enter_state_new - Create a new EnterState
128  * @retval ptr New EnterState
129  */
mutt_enter_state_new(void)130 struct EnterState *mutt_enter_state_new(void)
131 {
132   return mutt_mem_calloc(1, sizeof(struct EnterState));
133 }
134 
135 /**
136  * mutt_enter_string_full - Ask the user for a string
137  * @param[in]  buf      Buffer to store the string
138  * @param[in]  buflen   Buffer length
139  * @param[in]  col      Initial cursor position
140  * @param[in]  flags    Flags, see #CompletionFlags
141  * @param[in]  multiple Allow multiple matches
142  * @param[in]  m        Mailbox
143  * @param[out] files    List of files selected
144  * @param[out] numfiles Number of files selected
145  * @param[out] state    Current state (if function is called repeatedly)
146  * @retval 1  Redraw the screen and call the function again
147  * @retval 0  Selection made
148  * @retval -1 Aborted
149  */
mutt_enter_string_full(char * buf,size_t buflen,int col,CompletionFlags flags,bool multiple,struct Mailbox * m,char *** files,int * numfiles,struct EnterState * state)150 int mutt_enter_string_full(char *buf, size_t buflen, int col, CompletionFlags flags,
151                            bool multiple, struct Mailbox *m, char ***files,
152                            int *numfiles, struct EnterState *state)
153 {
154   struct MuttWindow *win = msgwin_get_window();
155   if (!win)
156     return -1;
157 
158   int width = win->state.cols - col - 1;
159   enum EnterRedrawFlags redraw = ENTER_REDRAW_NONE;
160   bool pass = (flags & MUTT_PASS);
161   bool first = true;
162   int ch;
163   wchar_t *tempbuf = NULL;
164   size_t templen = 0;
165   enum HistoryClass hclass;
166   int rc = 0;
167   mbstate_t mbstate;
168   memset(&mbstate, 0, sizeof(mbstate));
169 
170   if (state->wbuf)
171   {
172     /* Coming back after return 1 */
173     redraw = ENTER_REDRAW_LINE;
174     first = false;
175   }
176   else
177   {
178     /* Initialise wbuf from buf */
179     state->wbuflen = 0;
180     state->lastchar = mutt_mb_mbstowcs(&state->wbuf, &state->wbuflen, 0, buf);
181     redraw = ENTER_REDRAW_INIT;
182   }
183 
184   if (flags & MUTT_FILE)
185     hclass = HC_FILE;
186   else if (flags & MUTT_EFILE)
187     hclass = HC_MBOX;
188   else if (flags & MUTT_CMD)
189     hclass = HC_CMD;
190   else if (flags & MUTT_ALIAS)
191     hclass = HC_ALIAS;
192   else if (flags & MUTT_COMMAND)
193     hclass = HC_COMMAND;
194   else if (flags & MUTT_PATTERN)
195     hclass = HC_PATTERN;
196   else
197     hclass = HC_OTHER;
198 
199   while (true)
200   {
201     if (!pass)
202     {
203       if (redraw == ENTER_REDRAW_INIT)
204       {
205         /* Go to end of line */
206         state->curpos = state->lastchar;
207         state->begin = mutt_mb_width_ceiling(
208             state->wbuf, state->lastchar,
209             mutt_mb_wcswidth(state->wbuf, state->lastchar) - width + 1);
210       }
211       if ((state->curpos < state->begin) ||
212           (mutt_mb_wcswidth(state->wbuf + state->begin, state->curpos - state->begin) >= width))
213       {
214         state->begin = mutt_mb_width_ceiling(
215             state->wbuf, state->lastchar,
216             mutt_mb_wcswidth(state->wbuf, state->curpos) - (width / 2));
217       }
218       mutt_window_move(win, col, 0);
219       int w = 0;
220       for (size_t i = state->begin; i < state->lastchar; i++)
221       {
222         w += mutt_mb_wcwidth(state->wbuf[i]);
223         if (w > width)
224           break;
225         my_addwch(win, state->wbuf[i]);
226       }
227       mutt_window_clrtoeol(win);
228       mutt_window_move(win,
229                        col + mutt_mb_wcswidth(state->wbuf + state->begin,
230                                               state->curpos - state->begin),
231                        0);
232     }
233     mutt_refresh();
234 
235     ch = km_dokey(MENU_EDITOR);
236     if (ch < 0)
237     {
238       rc = (SigWinch && (ch == -2)) ? 1 : -1;
239       goto bye;
240     }
241 
242     if (ch != OP_NULL)
243     {
244       first = false;
245       if ((ch != OP_EDITOR_COMPLETE) && (ch != OP_EDITOR_COMPLETE_QUERY))
246         state->tabs = 0;
247       redraw = ENTER_REDRAW_LINE;
248       switch (ch)
249       {
250         case OP_EDITOR_HISTORY_UP:
251           state->curpos = state->lastchar;
252           if (mutt_hist_at_scratch(hclass))
253           {
254             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
255             mutt_hist_save_scratch(hclass, buf);
256           }
257           replace_part(state, 0, mutt_hist_prev(hclass));
258           redraw = ENTER_REDRAW_INIT;
259           break;
260 
261         case OP_EDITOR_HISTORY_DOWN:
262           state->curpos = state->lastchar;
263           if (mutt_hist_at_scratch(hclass))
264           {
265             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
266             mutt_hist_save_scratch(hclass, buf);
267           }
268           replace_part(state, 0, mutt_hist_next(hclass));
269           redraw = ENTER_REDRAW_INIT;
270           break;
271 
272         case OP_EDITOR_HISTORY_SEARCH:
273           state->curpos = state->lastchar;
274           mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
275           mutt_hist_complete(buf, buflen, hclass);
276           replace_part(state, 0, buf);
277           rc = 1;
278           goto bye;
279           break;
280 
281         case OP_EDITOR_BACKSPACE:
282           if (state->curpos == 0)
283           {
284             // Pressing backspace when no text is in the command prompt should exit the prompt
285             const bool c_abort_backspace =
286                 cs_subset_bool(NeoMutt->sub, "abort_backspace");
287             if (c_abort_backspace && (state->lastchar == 0))
288               goto bye;
289             // Pressing backspace with text in the command prompt should just beep
290             mutt_beep(false);
291           }
292           else
293           {
294             size_t i = state->curpos;
295             while ((i > 0) && COMB_CHAR(state->wbuf[i - 1]))
296               i--;
297             if (i > 0)
298               i--;
299             memmove(state->wbuf + i, state->wbuf + state->curpos,
300                     (state->lastchar - state->curpos) * sizeof(wchar_t));
301             state->lastchar -= state->curpos - i;
302             state->curpos = i;
303           }
304           break;
305 
306         case OP_EDITOR_BOL:
307           state->curpos = 0;
308           break;
309 
310         case OP_EDITOR_EOL:
311           redraw = ENTER_REDRAW_INIT;
312           break;
313 
314         case OP_EDITOR_KILL_LINE:
315           state->curpos = 0;
316           state->lastchar = 0;
317           break;
318 
319         case OP_EDITOR_KILL_EOL:
320           state->lastchar = state->curpos;
321           break;
322 
323         case OP_EDITOR_BACKWARD_CHAR:
324           if (state->curpos == 0)
325             mutt_beep(false);
326           else
327           {
328             while (state->curpos && COMB_CHAR(state->wbuf[state->curpos - 1]))
329               state->curpos--;
330             if (state->curpos)
331               state->curpos--;
332           }
333           break;
334 
335         case OP_EDITOR_FORWARD_CHAR:
336           if (state->curpos == state->lastchar)
337             mutt_beep(false);
338           else
339           {
340             state->curpos++;
341             while ((state->curpos < state->lastchar) &&
342                    COMB_CHAR(state->wbuf[state->curpos]))
343             {
344               state->curpos++;
345             }
346           }
347           break;
348 
349         case OP_EDITOR_BACKWARD_WORD:
350           if (state->curpos == 0)
351             mutt_beep(false);
352           else
353           {
354             while (state->curpos && iswspace(state->wbuf[state->curpos - 1]))
355               state->curpos--;
356             while (state->curpos && !iswspace(state->wbuf[state->curpos - 1]))
357               state->curpos--;
358           }
359           break;
360 
361         case OP_EDITOR_FORWARD_WORD:
362           if (state->curpos == state->lastchar)
363             mutt_beep(false);
364           else
365           {
366             while ((state->curpos < state->lastchar) &&
367                    iswspace(state->wbuf[state->curpos]))
368             {
369               state->curpos++;
370             }
371             while ((state->curpos < state->lastchar) &&
372                    !iswspace(state->wbuf[state->curpos]))
373             {
374               state->curpos++;
375             }
376           }
377           break;
378 
379         case OP_EDITOR_CAPITALIZE_WORD:
380         case OP_EDITOR_UPCASE_WORD:
381         case OP_EDITOR_DOWNCASE_WORD:
382           if (state->curpos == state->lastchar)
383           {
384             mutt_beep(false);
385             break;
386           }
387           while (state->curpos && !iswspace(state->wbuf[state->curpos]))
388             state->curpos--;
389           while ((state->curpos < state->lastchar) && iswspace(state->wbuf[state->curpos]))
390             state->curpos++;
391           while ((state->curpos < state->lastchar) &&
392                  !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             mutt_beep(false);
409           else
410           {
411             size_t 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,
419                     (state->lastchar - i) * sizeof(wchar_t));
420             state->lastchar -= i - state->curpos;
421           }
422           break;
423 
424         case OP_EDITOR_KILL_WORD:
425           /* delete to beginning of word */
426           if (state->curpos != 0)
427           {
428             size_t i = state->curpos;
429             while (i && iswspace(state->wbuf[i - 1]))
430               i--;
431             if (i > 0)
432             {
433               if (iswalnum(state->wbuf[i - 1]))
434               {
435                 for (--i; (i > 0) && iswalnum(state->wbuf[i - 1]); i--)
436                   ; // do nothing
437               }
438               else
439                 i--;
440             }
441             memmove(state->wbuf + i, state->wbuf + state->curpos,
442                     (state->lastchar - state->curpos) * sizeof(wchar_t));
443             state->lastchar += i - state->curpos;
444             state->curpos = i;
445           }
446           break;
447 
448         case OP_EDITOR_KILL_EOW:
449         {
450           /* delete to end of word */
451 
452           /* first skip over whitespace */
453           size_t i;
454           for (i = state->curpos; (i < state->lastchar) && iswspace(state->wbuf[i]); i++)
455             ; // do nothing
456 
457           /* if there are any characters left.. */
458           if (i < state->lastchar)
459           {
460             /* if the current character is alphanumeric.. */
461             if (iswalnum(state->wbuf[i]))
462             {
463               /* skip over the rest of the word consistent of only alphanumerics */
464               for (; (i < state->lastchar) && iswalnum(state->wbuf[i]); i++)
465                 ; // do nothing
466             }
467             else
468             {
469               /* skip over one non-alphanumeric character */
470               i++;
471             }
472           }
473 
474           memmove(state->wbuf + state->curpos, state->wbuf + i,
475                   (state->lastchar - i) * sizeof(wchar_t));
476           state->lastchar += state->curpos - i;
477           break;
478         }
479 
480         case OP_EDITOR_MAILBOX_CYCLE:
481           if (flags & MUTT_EFILE)
482           {
483             first = true; /* clear input if user types a real key later */
484             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
485 
486             struct Buffer *pool = mutt_buffer_pool_get();
487             mutt_buffer_addstr(pool, buf);
488             mutt_mailbox_next(m, pool);
489             mutt_str_copy(buf, mutt_buffer_string(pool), buflen);
490             mutt_buffer_pool_release(&pool);
491 
492             state->curpos = state->lastchar =
493                 mutt_mb_mbstowcs(&state->wbuf, &state->wbuflen, 0, buf);
494             break;
495           }
496           else if (!(flags & MUTT_FILE))
497           {
498             goto self_insert;
499           }
500           /* fallthrough */
501 
502         case OP_EDITOR_COMPLETE:
503         case OP_EDITOR_COMPLETE_QUERY:
504           state->tabs++;
505           if (flags & MUTT_CMD)
506           {
507             size_t i;
508             for (i = state->curpos;
509                  (i > 0) && !mutt_mb_is_shell_char(state->wbuf[i - 1]); i--)
510             {
511             }
512             mutt_mb_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
513             if (tempbuf && (templen == (state->lastchar - i)) &&
514                 (memcmp(tempbuf, state->wbuf + i, (state->lastchar - i) * sizeof(wchar_t)) == 0))
515             {
516               mutt_select_file(buf, buflen, (flags & MUTT_EFILE) ? MUTT_SEL_FOLDER : MUTT_SEL_NO_FLAGS,
517                                m, NULL, NULL);
518               if (buf[0] != '\0')
519                 replace_part(state, i, buf);
520               rc = 1;
521               goto bye;
522             }
523             if (mutt_complete(buf, buflen) == 0)
524             {
525               templen = state->lastchar - i;
526               mutt_mem_realloc(&tempbuf, templen * sizeof(wchar_t));
527             }
528             else
529               mutt_beep(false);
530 
531             replace_part(state, i, buf);
532           }
533           else if ((flags & MUTT_ALIAS) && (ch == OP_EDITOR_COMPLETE))
534           {
535             /* invoke the alias-menu to get more addresses */
536             size_t i;
537             for (i = state->curpos;
538                  (i > 0) && (state->wbuf[i - 1] != ',') && (state->wbuf[i - 1] != ':'); i--)
539             {
540             }
541             for (; (i < state->lastchar) && (state->wbuf[i] == ' '); i++)
542               ; // do nothing
543 
544             mutt_mb_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
545             int rc2 = alias_complete(buf, buflen, NeoMutt->sub);
546             replace_part(state, i, buf);
547             if (rc2 != 1)
548             {
549               rc = 1;
550               goto bye;
551             }
552             break;
553           }
554           else if ((flags & MUTT_LABEL) && (ch == OP_EDITOR_COMPLETE))
555           {
556             size_t i;
557             for (i = state->curpos;
558                  (i > 0) && (state->wbuf[i - 1] != ',') && (state->wbuf[i - 1] != ':'); i--)
559             {
560             }
561             for (; (i < state->lastchar) && (state->wbuf[i] == ' '); i++)
562               ; // do nothing
563 
564             mutt_mb_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
565             int rc2 = mutt_label_complete(buf, buflen, state->tabs);
566             replace_part(state, i, buf);
567             if (rc2 != 1)
568             {
569               rc = 1;
570               goto bye;
571             }
572             break;
573           }
574           else if ((flags & MUTT_PATTERN) && (ch == OP_EDITOR_COMPLETE))
575           {
576             size_t i = state->curpos;
577             if (i && (state->wbuf[i - 1] == '~'))
578             {
579               if (dlg_select_pattern(buf, buflen))
580                 replace_part(state, i - 1, buf);
581               rc = 1;
582               goto bye;
583             }
584             for (; (i > 0) && (state->wbuf[i - 1] != '~'); i--)
585               ; // do nothing
586 
587             if ((i > 0) && (i < state->curpos) && (state->wbuf[i - 1] == '~') &&
588                 (state->wbuf[i] == 'y'))
589             {
590               i++;
591               mutt_mb_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
592               int rc2 = mutt_label_complete(buf, buflen, state->tabs);
593               replace_part(state, i, buf);
594               if (rc2 != 1)
595               {
596                 rc = 1;
597                 goto bye;
598               }
599             }
600             else
601               goto self_insert;
602             break;
603           }
604           else if ((flags & MUTT_ALIAS) && (ch == OP_EDITOR_COMPLETE_QUERY))
605           {
606             size_t i = state->curpos;
607             if (i != 0)
608             {
609               for (; (i > 0) && (state->wbuf[i - 1] != ','); i--)
610                 ; // do nothing
611 
612               for (; (i < state->curpos) && (state->wbuf[i] == ' '); i++)
613                 ; // do nothing
614             }
615 
616             mutt_mb_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
617             query_complete(buf, buflen, NeoMutt->sub);
618             replace_part(state, i, buf);
619 
620             rc = 1;
621             goto bye;
622           }
623           else if (flags & MUTT_COMMAND)
624           {
625             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
626             size_t i = strlen(buf);
627             if ((i != 0) && (buf[i - 1] == '=') &&
628                 (mutt_var_value_complete(buf, buflen, i) != 0))
629             {
630               state->tabs = 0;
631             }
632             else if (mutt_command_complete(buf, buflen, i, state->tabs) == 0)
633               mutt_beep(false);
634             replace_part(state, 0, buf);
635           }
636           else if (flags & (MUTT_FILE | MUTT_EFILE))
637           {
638             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
639 
640             /* see if the path has changed from the last time */
641             if ((!tempbuf && !state->lastchar) ||
642                 (tempbuf && (templen == state->lastchar) &&
643                  (memcmp(tempbuf, state->wbuf, state->lastchar * sizeof(wchar_t)) == 0)))
644             {
645               mutt_select_file(buf, buflen,
646                                ((flags & MUTT_EFILE) ? MUTT_SEL_FOLDER : MUTT_SEL_NO_FLAGS) |
647                                    (multiple ? MUTT_SEL_MULTI : MUTT_SEL_NO_FLAGS),
648                                m, files, numfiles);
649               if (buf[0] != '\0')
650               {
651                 mutt_pretty_mailbox(buf, buflen);
652                 if (!pass)
653                   mutt_hist_add(hclass, buf, true);
654                 rc = 0;
655                 goto bye;
656               }
657 
658               /* file selection cancelled */
659               rc = 1;
660               goto bye;
661             }
662 
663             if (mutt_complete(buf, buflen) == 0)
664             {
665               templen = state->lastchar;
666               mutt_mem_realloc(&tempbuf, templen * sizeof(wchar_t));
667               memcpy(tempbuf, state->wbuf, templen * sizeof(wchar_t));
668             }
669             else
670               mutt_beep(false); /* let the user know that nothing matched */
671             replace_part(state, 0, buf);
672           }
673 #ifdef USE_NOTMUCH
674           else if (flags & MUTT_NM_QUERY)
675           {
676             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
677             size_t len = strlen(buf);
678             if (!mutt_nm_query_complete(buf, buflen, len, state->tabs))
679               mutt_beep(false);
680 
681             replace_part(state, 0, buf);
682           }
683           else if (flags & MUTT_NM_TAG)
684           {
685             mutt_mb_wcstombs(buf, buflen, state->wbuf, state->curpos);
686             if (!mutt_nm_tag_complete(buf, buflen, state->tabs))
687               mutt_beep(false);
688 
689             replace_part(state, 0, buf);
690           }
691 #endif
692           else
693             goto self_insert;
694           break;
695 
696         case OP_EDITOR_QUOTE_CHAR:
697         {
698           struct KeyEvent event;
699           do
700           {
701             event = mutt_getch();
702           } while (event.ch == -2); // Timeout
703           if (event.ch >= 0)
704           {
705             LastKey = event.ch;
706             goto self_insert;
707           }
708           break;
709         }
710 
711         case OP_EDITOR_TRANSPOSE_CHARS:
712           if (state->lastchar < 2)
713             mutt_beep(false);
714           else
715           {
716             wchar_t t;
717 
718             if (state->curpos == 0)
719               state->curpos = 2;
720             else if (state->curpos < state->lastchar)
721               state->curpos++;
722 
723             t = state->wbuf[state->curpos - 2];
724             state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
725             state->wbuf[state->curpos - 1] = t;
726           }
727           break;
728 
729         default:
730           mutt_beep(false);
731       }
732     }
733     else
734     {
735     self_insert:
736       state->tabs = 0;
737       wchar_t wc;
738       /* use the raw keypress */
739       ch = LastKey;
740 
741       /* quietly ignore all other function keys */
742       if (ch & ~0xff)
743         continue;
744 
745       /* gather the octets into a wide character */
746       {
747         char c = ch;
748         size_t k = mbrtowc(&wc, &c, 1, &mbstate);
749         if (k == (size_t) (-2))
750           continue;
751         else if ((k != 0) && (k != 1))
752         {
753           memset(&mbstate, 0, sizeof(mbstate));
754           continue;
755         }
756       }
757 
758       if (first && (flags & MUTT_CLEAR))
759       {
760         first = false;
761         if (IsWPrint(wc)) /* why? */
762         {
763           state->curpos = 0;
764           state->lastchar = 0;
765         }
766       }
767 
768       if ((wc == '\r') || (wc == '\n'))
769       {
770         /* Convert from wide characters */
771         mutt_mb_wcstombs(buf, buflen, state->wbuf, state->lastchar);
772         if (!pass)
773           mutt_hist_add(hclass, buf, true);
774 
775         if (multiple)
776         {
777           char **tfiles = NULL;
778           *numfiles = 1;
779           tfiles = mutt_mem_calloc(*numfiles, sizeof(char *));
780           mutt_expand_path(buf, buflen);
781           tfiles[0] = mutt_str_dup(buf);
782           *files = tfiles;
783         }
784         rc = 0;
785         goto bye;
786       }
787       else if (wc && ((wc < ' ') || IsWPrint(wc))) /* why? */
788       {
789         if (state->lastchar >= state->wbuflen)
790         {
791           state->wbuflen = state->lastchar + 20;
792           mutt_mem_realloc(&state->wbuf, state->wbuflen * sizeof(wchar_t));
793         }
794         memmove(state->wbuf + state->curpos + 1, state->wbuf + state->curpos,
795                 (state->lastchar - state->curpos) * sizeof(wchar_t));
796         state->wbuf[state->curpos++] = wc;
797         state->lastchar++;
798       }
799       else
800       {
801         mutt_flushinp();
802         mutt_beep(false);
803       }
804     }
805   }
806 
807 bye:
808 
809   mutt_hist_reset_state(hclass);
810   FREE(&tempbuf);
811   return rc;
812 }
813 
814 /**
815  * mutt_enter_state_free - Free an EnterState
816  * @param[out] ptr EnterState to free
817  */
mutt_enter_state_free(struct EnterState ** ptr)818 void mutt_enter_state_free(struct EnterState **ptr)
819 {
820   if (!ptr || !*ptr)
821     return;
822 
823   struct EnterState *es = *ptr;
824 
825   FREE(&es->wbuf);
826   FREE(ptr);
827 }
828