Skip to content
Newer
Older
100644 735 lines (658 sloc) 17.7 KB
14c2920 Imported Upstream version 1.5.18
Antonio Radici authored May 24, 2009
1 /*
19304f7 Imported Upstream version 1.5.19
Antonio Radici authored May 24, 2009
2 * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 2000-1 Edmund Grimley Evans <edmundo@rano.org>
14c2920 Imported Upstream version 1.5.18
Antonio Radici authored May 24, 2009
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
19304f7 Imported Upstream version 1.5.19
Antonio Radici authored May 24, 2009
123 static size_t my_mbstowcs (wchar_t **pwbuf, size_t *pwbuflen, size_t i, char *buf)
14c2920 Imported Upstream version 1.5.18
Antonio Radici authored May 24, 2009
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 {
19304f7 Imported Upstream version 1.5.19
Antonio Radici authored May 24, 2009
575 mutt_pretty_mailbox (buf, buflen);
14c2920 Imported Upstream version 1.5.18
Antonio Radici authored May 24, 2009
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 ();
19304f7 Imported Upstream version 1.5.19
Antonio Radici authored May 24, 2009
606 if (event.ch >= 0)
14c2920 Imported Upstream version 1.5.18
Antonio Radici authored May 24, 2009
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 */
Something went wrong with that request. Please try again.