2 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
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.
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.
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.
21 #include "mutt_menu.h"
22 #include "mutt_curses.h"
28 /* redraw flags for mutt_enter_string() */
31 M_REDRAW_INIT = 1, /* go to end of line and redraw */
32 M_REDRAW_LINE /* redraw entire line */
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);
37 static int my_wcwidth (wchar_t wc)
40 if (IsWPrint (wc) && n > 0)
49 /* combining mark / non-spacing character */
50 #define COMB_CHAR(wc) (IsWPrint (wc) && !wcwidth (wc))
52 static int my_wcswidth (const wchar_t *s, size_t n)
56 w += my_wcwidth (*s++);
60 static int my_addwch (wchar_t wc)
63 if (IsWPrint (wc) && n > 0)
64 return mutt_addwch (wc);
66 return printw ("^%c", ((int)wc + 0x40) & 0x7f);
68 return printw ("\\u%04x", (int)wc);
69 return printw ("\\u%08x", (int)wc);
72 static size_t width_ceiling (const wchar_t *s, size_t n, int w1)
74 const wchar_t *s0 = s;
77 if ((w += my_wcwidth (*s)) > w1)
82 static void my_wcstombs (char *dest, size_t dlen, const wchar_t *src, size_t slen)
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))
93 /* If this works, we can stop now */
94 if (dlen >= MB_LEN_MAX) {
95 wcrtomb (dest, 0, &st);
99 /* Otherwise convert any remaining data into a local buffer */
101 char buf[3 * MB_LEN_MAX];
104 for (; slen && p - buf < dlen; p += k, src++, slen--)
105 if ((k = wcrtomb (p, *src, &st)) == (size_t)(-1))
107 p += wcrtomb (p, 0, &st);
109 /* If it fits into the destination buffer, we can stop now */
110 if (p - buf <= dlen) {
111 memcpy (dest, buf, p - buf);
115 /* Otherwise we truncate the string in an ugly fashion */
116 memcpy (dest, buf, dlen);
117 dest[dlen - 1] = '\0'; /* assume original dlen > 0 */
121 size_t my_mbstowcs (wchar_t **pwbuf, size_t *pwbuflen, size_t i, char *buf)
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)
137 safe_realloc (&wbuf, wbuflen * sizeof (*wbuf));
141 *pwbuf = wbuf, *pwbuflen = wbuflen;
146 * Replace part of the wchar_t buffer, from FROM to CURPOS, by BUF.
149 static void replace_part (ENTER_STATE *state, size_t from, char *buf)
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));
156 /* Convert to wide characters */
157 state->curpos = my_mbstowcs (&state->wbuf, &state->wbuflen, from, buf);
159 /* Make space for suffix */
160 if (state->curpos + savelen > state->wbuflen)
162 state->wbuflen = state->curpos + savelen;
163 safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
167 memcpy (state->wbuf + state->curpos, savebuf, savelen * sizeof (wchar_t));
168 state->lastchar = state->curpos + savelen;
175 * 1 need to redraw the screen and call me again
176 * 0 if input was given
180 int mutt_enter_string(char *buf, size_t buflen, int y, int x, int flags)
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);
189 int _mutt_enter_string(char *buf, size_t buflen, int y, int x,
190 int flags, int multiple, char ***files, int *numfiles,
193 return _mutt_enter_string_foo(buf,buflen,y,x,flags,multiple,files,numfiles,state,0);
196 int _mutt_enter_string_att(char *buf, size_t buflen, int y, int x,
197 int flags, int multiple, char ***files, int *numfiles,
200 return _mutt_enter_string_foo(buf,buflen,y,x,flags,multiple,files,numfiles,state,1);
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!! */
207 int width = COLS - x - 1;
209 int pass = (flags & M_PASS);
213 wchar_t *tempbuf = 0;
215 history_class_t hclass;
220 memset (&mbstate, 0, sizeof (mbstate));
224 /* Coming back after return 1 */
225 redraw = M_REDRAW_LINE;
229 /* Initialise wbuf from buf */
231 state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
232 redraw = M_REDRAW_INIT;
235 if (flags & (M_FILE | M_EFILE))
237 else if (flags & M_CMD)
239 else if (flags & M_ALIAS)
241 else if (flags & M_COMMAND)
243 else if (flags & M_PATTERN)
252 if (redraw == M_REDRAW_INIT)
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);
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);
263 for (i = state->begin; i < state->lastchar; i++)
265 w += my_wcwidth (state->wbuf[i]);
268 my_addwch (state->wbuf[i]);
271 move (y, x + my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin));
275 if ((ch = km_dokey (MENU_EDITOR)) == -1)
284 if (ch != OP_EDITOR_COMPLETE)
286 redraw = M_REDRAW_LINE;
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;
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;
301 case OP_EDITOR_BACKSPACE:
302 if (state->curpos == 0)
307 while (i && COMB_CHAR (state->wbuf[i - 1]))
311 memmove (state->wbuf + i, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
312 state->lastchar -= state->curpos - i;
322 redraw= M_REDRAW_INIT;
325 case OP_EDITOR_KILL_LINE:
326 state->curpos = state->lastchar = 0;
329 case OP_EDITOR_KILL_EOL:
330 state->lastchar = state->curpos;
333 case OP_EDITOR_BACKWARD_CHAR:
334 if (state->curpos == 0)
338 while (state->curpos && COMB_CHAR (state->wbuf[state->curpos - 1]))
345 case OP_EDITOR_FORWARD_CHAR:
346 if (state->curpos == state->lastchar)
351 while (state->curpos < state->lastchar && COMB_CHAR (state->wbuf[state->curpos]))
356 case OP_EDITOR_BACKWARD_WORD:
357 if (state->curpos == 0)
361 while (state->curpos && iswspace (state->wbuf[state->curpos - 1]))
363 while (state->curpos && !iswspace (state->wbuf[state->curpos - 1]))
368 case OP_EDITOR_FORWARD_WORD:
369 if (state->curpos == state->lastchar)
373 while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
375 while (state->curpos < state->lastchar && !iswspace (state->wbuf[state->curpos]))
380 case OP_EDITOR_CAPITALIZE_WORD:
381 case OP_EDITOR_UPCASE_WORD:
382 case OP_EDITOR_DOWNCASE_WORD:
383 if (state->curpos == state->lastchar)
388 while (state->curpos && !iswspace (state->wbuf[state->curpos]))
390 while (state->curpos < state->lastchar && iswspace (state->wbuf[state->curpos]))
392 while (state->curpos < state->lastchar && !iswspace (state->wbuf[state->curpos]))
394 if (ch == OP_EDITOR_DOWNCASE_WORD)
395 state->wbuf[state->curpos] = towlower (state->wbuf[state->curpos]);
398 state->wbuf[state->curpos] = towupper (state->wbuf[state->curpos]);
399 if (ch == OP_EDITOR_CAPITALIZE_WORD)
400 ch = OP_EDITOR_DOWNCASE_WORD;
406 case OP_EDITOR_DELETE_CHAR:
407 if (state->curpos == state->lastchar)
412 while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
414 if (i < state->lastchar)
416 while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
418 memmove (state->wbuf + state->curpos, state->wbuf + i, (state->lastchar - i) * sizeof (wchar_t));
419 state->lastchar -= i - state->curpos;
423 case OP_EDITOR_KILL_WORD:
424 /* delete to begining of word */
425 if (state->curpos != 0)
428 while (i && iswspace (state->wbuf[i - 1]))
432 if (iswalnum (state->wbuf[i - 1]))
434 for (--i; i && iswalnum (state->wbuf[i - 1]); i--)
440 memmove (state->wbuf + i, state->wbuf + state->curpos,
441 (state->lastchar - state->curpos) * sizeof (wchar_t));
442 state->lastchar += i - state->curpos;
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++)
452 for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++)
454 memmove (state->wbuf + state->curpos, state->wbuf + i,
455 (state->lastchar - i) * sizeof (wchar_t));
456 state->lastchar += state->curpos - i;
459 case OP_EDITOR_BUFFY_CYCLE:
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);
468 else if (!(flags & M_FILE))
470 /* fall through to completion routine (M_FILE) */
472 case OP_EDITOR_COMPLETE:
476 for (i = state->curpos; i && state->wbuf[i-1] != ' '; i--)
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)))
482 mutt_select_file (buf, buflen, (flags & M_EFILE) ? M_SEL_FOLDER : 0);
483 set_option (OPTNEEDREDRAW);
485 replace_part (state, i, buf);
489 if (!mutt_complete (buf, buflen))
491 templen = state->lastchar - i;
492 safe_realloc (&tempbuf, templen * sizeof (wchar_t));
497 replace_part (state, i, buf);
499 else if (flags & M_ALIAS)
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--)
505 for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
507 my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
508 r = mutt_alias_complete (buf, buflen);
509 replace_part (state, i, buf);
517 else if (flags & M_COMMAND)
519 my_wcstombs (buf, buflen, state->wbuf, state->curpos);
521 if (i && buf[i - 1] == '=' &&
522 mutt_var_value_complete (buf, buflen, i))
524 else if (!mutt_command_complete (buf, buflen, i, state->tabs))
526 replace_part (state, 0, buf);
528 else if (flags & (M_FILE | M_EFILE))
530 my_wcstombs (buf, buflen, state->wbuf, state->curpos);
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))))
536 _mutt_select_file (buf, buflen,
537 ((flags & M_EFILE) ? M_SEL_FOLDER : 0) | (multiple ? M_SEL_MULTI : 0),
539 set_option (OPTNEEDREDRAW);
542 mutt_pretty_mailbox (buf);
544 mutt_history_add (hclass, buf);
549 /* file selection cancelled */
554 if (!mutt_complete (buf, buflen))
556 templen = state->lastchar;
557 safe_realloc (&tempbuf, templen * sizeof (wchar_t));
558 memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
561 BEEP (); /* let the user know that nothing matched */
562 replace_part (state, 0, buf);
568 case OP_EDITOR_COMPLETE_QUERY:
571 /* invoke the query-menu to get more addresses */
572 if ((i = state->curpos))
574 for (; i && state->wbuf[i - 1] != ','; i--)
576 for (; i < state->curpos && state->wbuf[i] == ' '; i++)
580 my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
581 mutt_query_complete (buf, buflen);
582 replace_part (state, i, buf);
590 case OP_EDITOR_QUOTE_CHAR:
594 event = mutt_getch ();
602 case OP_EDITOR_TRANSPOSE_CHARS:
603 if (state->lastchar < 2)
609 if (state->curpos == 0)
611 else if (state->curpos < state->lastchar)
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;
630 /* use the raw keypress */
633 if (att_save && ch == '.')
640 /* treat ENTER the same as RETURN */
645 /* quietly ignore all other function keys */
649 /* gather the octets into a wide character */
655 k = mbrtowc (&wc, &c, 1, &mbstate);
656 if (k == (size_t)(-2))
658 else if (k && k != 1)
660 memset (&mbstate, 0, sizeof (mbstate));
665 if (first && (flags & M_CLEAR))
668 if (IsWPrint (wc)) /* why? */
669 state->curpos = state->lastchar = 0;
672 if (wc == '\r' || wc == '\n')
674 /* Convert from wide characters */
675 my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
677 mutt_history_add (hclass, buf);
683 tfiles = safe_calloc (*numfiles, sizeof (char *));
684 mutt_expand_path (buf, buflen);
685 tfiles[0] = safe_strdup (buf);
691 else if (wc && (wc < ' ' || IsWPrint (wc))) /* why? */
693 if (state->lastchar >= state->wbuflen)
695 state->wbuflen = state->lastchar + 20;
696 safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
698 memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
699 state->wbuf[state->curpos++] = wc;
716 void mutt_free_enter_state (ENTER_STATE **esp)
720 FREE (&(*esp)->wbuf);
726 * very narrow screen might crash it
727 * sort out the input side