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.
25 #include "mutt_menu.h"
26 #include "mutt_curses.h"
32 /* redraw flags for mutt_enter_string() */
34 M_REDRAW_INIT = 1, /* go to end of line and redraw */
35 M_REDRAW_LINE /* redraw entire line */
38 static int my_wcwidth (wchar_t wc)
42 if (IsWPrint (wc) && n > 0)
51 /* combining mark / non-spacing character */
52 #define COMB_CHAR(wc) (IsWPrint (wc) && !wcwidth (wc))
54 static int my_wcswidth (const wchar_t * s, size_t n)
59 w += my_wcwidth (*s++);
63 static int my_addwch (wchar_t wc)
67 if (IsWPrint (wc) && n > 0)
68 return mutt_addwch (wc);
70 return printw ("^%c", ((int) wc + 0x40) & 0x7f);
72 return printw ("\\u%04x", (int) wc);
73 return printw ("\\u%08x", (int) wc);
76 static size_t width_ceiling (const wchar_t * s, size_t n, int w1)
78 const wchar_t *s0 = s;
82 if ((w += my_wcwidth (*s)) > w1)
87 static void my_wcstombs (char *dest, size_t dlen, const wchar_t * src,
93 /* First convert directly into the destination buffer */
94 memset (&st, 0, sizeof (st));
95 for (; slen && dlen >= MB_LEN_MAX; dest += k, dlen -= k, src++, slen--)
96 if ((k = wcrtomb (dest, *src, &st)) == (size_t) (-1))
99 /* If this works, we can stop now */
100 if (dlen >= MB_LEN_MAX) {
101 wcrtomb (dest, 0, &st);
105 /* Otherwise convert any remaining data into a local buffer */
107 char buf[3 * MB_LEN_MAX];
110 for (; slen && p - buf < dlen; p += k, src++, slen--)
111 if ((k = wcrtomb (p, *src, &st)) == (size_t) (-1))
113 p += wcrtomb (p, 0, &st);
115 /* If it fits into the destination buffer, we can stop now */
116 if (p - buf <= dlen) {
117 memcpy (dest, buf, p - buf);
121 /* Otherwise we truncate the string in an ugly fashion */
122 memcpy (dest, buf, dlen);
123 dest[dlen - 1] = '\0'; /* assume original dlen > 0 */
127 size_t my_mbstowcs (wchar_t ** pwbuf, size_t * pwbuflen, size_t i, char *buf)
135 wbuf = *pwbuf, wbuflen = *pwbuflen;
136 memset (&st, 0, sizeof (st));
137 for (; (k = mbrtowc (&wc, buf, MB_LEN_MAX, &st)) &&
138 k != (size_t) (-1) && k != (size_t) (-2); buf += k) {
141 safe_realloc (&wbuf, wbuflen * sizeof (*wbuf));
145 *pwbuf = wbuf, *pwbuflen = wbuflen;
150 * Replace part of the wchar_t buffer, from FROM to CURPOS, by BUF.
153 static void replace_part (ENTER_STATE * state, size_t from, char *buf)
155 /* Save the suffix */
156 size_t savelen = state->lastchar - state->curpos;
157 wchar_t *savebuf = safe_calloc (savelen, sizeof (wchar_t));
159 memcpy (savebuf, state->wbuf + state->curpos, savelen * sizeof (wchar_t));
161 /* Convert to wide characters */
162 state->curpos = my_mbstowcs (&state->wbuf, &state->wbuflen, from, buf);
164 /* Make space for suffix */
165 if (state->curpos + savelen > state->wbuflen) {
166 state->wbuflen = state->curpos + savelen;
167 safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
171 memcpy (state->wbuf + state->curpos, savebuf, savelen * sizeof (wchar_t));
172 state->lastchar = state->curpos + savelen;
179 * 1 need to redraw the screen and call me again
180 * 0 if input was given
184 int mutt_enter_string (char *buf, size_t buflen, int y, int x, int flags)
187 ENTER_STATE *es = mutt_new_enter_state ();
189 rv = _mutt_enter_string (buf, buflen, y, x, flags, 0, NULL, NULL, es);
190 mutt_free_enter_state (&es);
194 int _mutt_enter_string (char *buf, size_t buflen, int y, int x,
195 int flags, int multiple, char ***files, int *numfiles,
198 int width = COLS - x - 1;
200 int pass = (flags & M_PASS);
204 wchar_t *tempbuf = 0;
206 history_class_t hclass;
212 memset (&mbstate, 0, sizeof (mbstate));
215 /* Coming back after return 1 */
216 redraw = M_REDRAW_LINE;
219 /* Initialise wbuf from buf */
221 state->lastchar = my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
222 redraw = M_REDRAW_INIT;
225 if (flags & (M_FILE | M_EFILE))
227 else if (flags & M_CMD)
229 else if (flags & M_ALIAS)
231 else if (flags & M_COMMAND)
233 else if (flags & M_PATTERN)
239 if (redraw && !pass) {
240 if (redraw == M_REDRAW_INIT) {
241 /* Go to end of line */
242 state->curpos = state->lastchar;
244 width_ceiling (state->wbuf, state->lastchar,
245 my_wcswidth (state->wbuf,
246 state->lastchar) - width + 1);
248 if (state->curpos < state->begin ||
249 my_wcswidth (state->wbuf + state->begin,
250 state->curpos - state->begin) >= width)
252 width_ceiling (state->wbuf, state->lastchar,
253 my_wcswidth (state->wbuf,
254 state->curpos) - width / 2);
257 for (i = state->begin; i < state->lastchar; i++) {
258 w += my_wcwidth (state->wbuf[i]);
261 my_addwch (state->wbuf[i]);
265 x + my_wcswidth (state->wbuf + state->begin,
266 state->curpos - state->begin));
270 if ((ch = km_dokey (MENU_EDITOR)) == -1) {
277 if (ch != OP_EDITOR_COMPLETE)
279 redraw = M_REDRAW_LINE;
281 case OP_EDITOR_HISTORY_UP:
282 state->curpos = state->lastchar;
283 replace_part (state, 0, mutt_history_prev (hclass));
284 redraw = M_REDRAW_INIT;
287 case OP_EDITOR_HISTORY_DOWN:
288 state->curpos = state->lastchar;
289 replace_part (state, 0, mutt_history_next (hclass));
290 redraw = M_REDRAW_INIT;
293 case OP_EDITOR_BACKSPACE:
294 if (state->curpos == 0)
298 while (i && COMB_CHAR (state->wbuf[i - 1]))
302 memmove (state->wbuf + i, state->wbuf + state->curpos,
303 (state->lastchar - state->curpos) * sizeof (wchar_t));
304 state->lastchar -= state->curpos - i;
314 redraw = M_REDRAW_INIT;
317 case OP_EDITOR_KILL_LINE:
318 state->curpos = state->lastchar = 0;
321 case OP_EDITOR_KILL_EOL:
322 state->lastchar = state->curpos;
325 case OP_EDITOR_BACKWARD_CHAR:
326 if (state->curpos == 0)
329 while (state->curpos && COMB_CHAR (state->wbuf[state->curpos - 1]))
336 case OP_EDITOR_FORWARD_CHAR:
337 if (state->curpos == state->lastchar)
341 while (state->curpos < state->lastchar
342 && COMB_CHAR (state->wbuf[state->curpos]))
347 case OP_EDITOR_BACKWARD_WORD:
348 if (state->curpos == 0)
351 while (state->curpos && iswspace (state->wbuf[state->curpos - 1]))
353 while (state->curpos && !iswspace (state->wbuf[state->curpos - 1]))
358 case OP_EDITOR_FORWARD_WORD:
359 if (state->curpos == state->lastchar)
362 while (state->curpos < state->lastchar
363 && iswspace (state->wbuf[state->curpos]))
365 while (state->curpos < state->lastchar
366 && !iswspace (state->wbuf[state->curpos]))
371 case OP_EDITOR_CAPITALIZE_WORD:
372 case OP_EDITOR_UPCASE_WORD:
373 case OP_EDITOR_DOWNCASE_WORD:
374 if (state->curpos == state->lastchar) {
378 while (state->curpos && !iswspace (state->wbuf[state->curpos]))
380 while (state->curpos < state->lastchar
381 && iswspace (state->wbuf[state->curpos]))
383 while (state->curpos < state->lastchar
384 && !iswspace (state->wbuf[state->curpos])) {
385 if (ch == OP_EDITOR_DOWNCASE_WORD)
386 state->wbuf[state->curpos] =
387 towlower (state->wbuf[state->curpos]);
389 state->wbuf[state->curpos] =
390 towupper (state->wbuf[state->curpos]);
391 if (ch == OP_EDITOR_CAPITALIZE_WORD)
392 ch = OP_EDITOR_DOWNCASE_WORD;
398 case OP_EDITOR_DELETE_CHAR:
399 if (state->curpos == state->lastchar)
403 while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
405 if (i < state->lastchar)
407 while (i < state->lastchar && COMB_CHAR (state->wbuf[i]))
409 memmove (state->wbuf + state->curpos, state->wbuf + i,
410 (state->lastchar - i) * sizeof (wchar_t));
411 state->lastchar -= i - state->curpos;
415 case OP_EDITOR_KILL_WORD:
416 /* delete to begining of word */
417 if (state->curpos != 0) {
419 while (i && iswspace (state->wbuf[i - 1]))
422 if (iswalnum (state->wbuf[i - 1])) {
423 for (--i; i && iswalnum (state->wbuf[i - 1]); i--);
428 memmove (state->wbuf + i, state->wbuf + state->curpos,
429 (state->lastchar - state->curpos) * sizeof (wchar_t));
430 state->lastchar += i - state->curpos;
435 case OP_EDITOR_KILL_EOW:
436 /* delete to end of word */
437 for (i = state->curpos;
438 i < state->lastchar && iswspace (state->wbuf[i]); i++);
439 for (; i < state->lastchar && !iswspace (state->wbuf[i]); i++);
440 memmove (state->wbuf + state->curpos, state->wbuf + i,
441 (state->lastchar - i) * sizeof (wchar_t));
442 state->lastchar += state->curpos - i;
445 case OP_EDITOR_BUFFY_CYCLE:
446 if (flags & M_EFILE) {
447 first = 1; /* clear input if user types a real key later */
448 my_wcstombs (buf, buflen, state->wbuf, state->curpos);
449 mutt_buffy (buf, buflen);
450 state->curpos = state->lastchar =
451 my_mbstowcs (&state->wbuf, &state->wbuflen, 0, buf);
454 else if (!(flags & M_FILE))
456 /* fall through to completion routine (M_FILE) */
458 case OP_EDITOR_COMPLETE:
461 for (i = state->curpos; i && state->wbuf[i - 1] != ' '; i--);
462 my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
463 if (tempbuf && templen == state->lastchar - i &&
464 !memcmp (tempbuf, state->wbuf + i,
465 (state->lastchar - i) * sizeof (wchar_t))) {
466 mutt_select_file (buf, buflen,
467 (flags & M_EFILE) ? M_SEL_FOLDER : 0);
468 set_option (OPTNEEDREDRAW);
470 replace_part (state, i, buf);
474 if (!mutt_complete (buf, buflen)) {
475 templen = state->lastchar - i;
476 safe_realloc (&tempbuf, templen * sizeof (wchar_t));
481 replace_part (state, i, buf);
483 else if (flags & M_ALIAS) {
484 /* invoke the alias-menu to get more addresses */
485 for (i = state->curpos; i && state->wbuf[i - 1] != ',' &&
486 state->wbuf[i - 1] != ':'; i--);
487 for (; i < state->lastchar && state->wbuf[i] == ' '; i++);
488 my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
489 r = mutt_alias_complete (buf, buflen);
490 replace_part (state, i, buf);
497 else if (flags & M_COMMAND) {
498 my_wcstombs (buf, buflen, state->wbuf, state->curpos);
500 if (i && buf[i - 1] == '=' &&
501 mutt_var_value_complete (buf, buflen, i))
503 else if (!mutt_command_complete (buf, buflen, i, state->tabs))
505 replace_part (state, 0, buf);
507 else if (flags & (M_FILE | M_EFILE)) {
508 my_wcstombs (buf, buflen, state->wbuf, state->curpos);
510 /* see if the path has changed from the last time */
511 if ((!tempbuf && !state->lastchar)
512 || (tempbuf && templen == state->lastchar
513 && !memcmp (tempbuf, state->wbuf,
514 state->lastchar * sizeof (wchar_t)))) {
515 _mutt_select_file (buf, buflen,
516 ((flags & M_EFILE) ? M_SEL_FOLDER : 0) |
517 (multiple ? M_SEL_MULTI : 0), files, numfiles);
518 set_option (OPTNEEDREDRAW);
520 mutt_pretty_mailbox (buf);
522 mutt_history_add (hclass, buf);
527 /* file selection cancelled */
532 if (!mutt_complete (buf, buflen)) {
533 templen = state->lastchar;
534 safe_realloc (&tempbuf, templen * sizeof (wchar_t));
535 memcpy (tempbuf, state->wbuf, templen * sizeof (wchar_t));
538 BEEP (); /* let the user know that nothing matched */
539 replace_part (state, 0, buf);
545 case OP_EDITOR_COMPLETE_QUERY:
546 if (flags & M_ALIAS) {
547 /* invoke the query-menu to get more addresses */
548 if ((i = state->curpos)) {
549 for (; i && state->wbuf[i - 1] != ','; i--);
550 for (; i < state->curpos && state->wbuf[i] == ' '; i++);
553 my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
554 mutt_query_complete (buf, buflen);
555 replace_part (state, i, buf);
563 case OP_EDITOR_QUOTE_CHAR:
567 /*ADDCH (LastKey); */
568 event = mutt_getch ();
569 if (event.ch != -1) {
575 case OP_EDITOR_TRANSPOSE_CHARS:
576 if (state->lastchar < 2)
581 if (state->curpos == 0)
583 else if (state->curpos < state->lastchar)
586 t = state->wbuf[state->curpos - 2];
587 state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
588 state->wbuf[state->curpos - 1] = t;
601 /* use the raw keypress */
604 if ((ch == '#') && (flags & M_LASTFOLDER)) {
606 my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
611 /* treat ENTER the same as RETURN */
616 /* quietly ignore all other function keys */
620 /* gather the octets into a wide character */
626 k = mbrtowc (&wc, &c, 1, &mbstate);
627 if (k == (size_t) (-2))
629 else if (k && k != 1) {
630 memset (&mbstate, 0, sizeof (mbstate));
635 if (first && (flags & M_CLEAR)) {
637 if (IsWPrint (wc)) /* why? */
638 state->curpos = state->lastchar = 0;
641 if (wc == '\r' || wc == '\n') {
642 /* Convert from wide characters */
643 my_wcstombs (buf, buflen, state->wbuf, state->lastchar);
645 mutt_history_add (hclass, buf);
651 tfiles = safe_calloc (*numfiles, sizeof (char *));
652 mutt_expand_path (buf, buflen);
653 tfiles[0] = safe_strdup (buf);
659 else if (wc && (wc < ' ' || IsWPrint (wc))) { /* why? */
660 if (state->lastchar >= state->wbuflen) {
661 state->wbuflen = state->lastchar + 20;
662 safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
664 memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos,
665 (state->lastchar - state->curpos) * sizeof (wchar_t));
666 state->wbuf[state->curpos++] = wc;
682 void mutt_free_enter_state (ENTER_STATE ** esp)
687 FREE (&(*esp)->wbuf);
693 * very narrow screen might crash it
694 * sort out the input side