1 /*
2 * Line editing code.
3 *
4 * climm Copyright (C) © 2001-2007 Rüdiger Kuhlmann
5 *
6 * climm is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 dated June, 1991.
9 *
10 * climm is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 * License for more details.
14 *
15 * In addition, as a special exception permission is granted to link the
16 * code of this release of climm with the OpenSSL project's "OpenSSL"
17 * library, and distribute the linked executables. You must obey the GNU
18 * General Public License in all respects for all of the code used other
19 * than "OpenSSL". If you modify this file, you may extend this exception
20 * to your version of the file, but you are not obligated to do so. If you
21 * do not wish to do so, delete this exception statement from your version
22 * of this file.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this package; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 * 02111-1307, USA.
28 *
29 * $Id: util_rl.c 2539 2008-03-12 18:27:20Z kuhlmann $
30 */
31
32 #include "climm.h"
33 #include "util_rl.h"
34 #include "util_alias.h"
35 #include "util_tabs.h"
36 #include "conv.h"
37 #include "contact.h"
38 #include "cmd_user.h"
39 #include "preferences.h"
40 #include "connection.h"
41
42 #include <stdarg.h>
43 #include <ctype.h>
44 #include <signal.h>
45 #include <assert.h>
46
47 #if HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50 #if HAVE_TERMIOS_H
51 #include <termios.h>
52 #endif
53 #if HAVE_SYS_IOCTL_H
54 #include <sys/ioctl.h>
55 #endif
56 #ifndef _POSIX_VDISABLE
57 #define _POSIX_VDISABLE 0
58 #endif
59 #if HAVE_SYS_IOCTL_H
60 #include <sys/ioctl.h>
61 #endif
62 #if HAVE_NETINET_IN_H
63 #include <netinet/in.h>
64 #endif
65 #if HAVE_ARPA_INET_H
66 #include <arpa/inet.h>
67 #endif
68 #if HAVE_CONIO_H
69 #include <conio.h>
70 #endif
71 #if HAVE_WCHAR_H
72 /* glibc sucks */
73 #define __USE_XOPEN 1
74 #include <wchar.h>
75 #endif
76 #if HAVE_WCTYPE_H
77 #include <wctype.h>
78 #else
79 #include <ctype.h>
80 #endif
81 #ifndef WEOF
82 #define WEOF (wchar_tt)-1
83 #endif
84
85 #undef DEBUG_RL
86
87 #if defined(_WIN32) && !defined(__CYGWIN__)
88 #define ANSI_CLEAR ""
89 #else
90 #define ANSI_CLEAR ESC "[J"
91 #endif
92
93
94 /* We'd like to use wint_t and wchar_t to store the input line,
95 however not all C implementations store the unicode codepoint
96 in wint_t/wchar_t, which kills the possibility to enter an
97 arbitrary codepoint into the line. Thus, we end up using UDWORDS instead. */
98 #define wint_tt UDWORD
99 #define wchar_tt UDWORD
100 #define rl_ucs_at(str,pos) *(wint_tt *)((str)->txt + sizeof (wint_tt) * (pos))
101
102 #if HAVE_TCGETATTR
103 static struct termios tty_attr;
104 static struct termios tty_saved_attr;
105 static int tty_saved = 0;
106 #endif
107
108 #if defined(SIGTSTP) && defined(SIGCONT)
109 static RETSIGTYPE tty_stop_handler (int);
110 static RETSIGTYPE tty_cont_handler (int);
111 #endif
112 static RETSIGTYPE tty_int_handler (int);
113
114 /* static void rl_dump_line (void); */
115 static int rl_getcolumns (void);
116 static void rl_syncpos (UDWORD pos);
117 static void rl_goto (UDWORD pos);
118 static void rl_recheck (BOOL clear);
119 static void rl_insert_basic (wchar_tt ucs, const char *display, UDWORD len, UDWORD collen);
120 static void rl_analyze_ucs (wchar_tt ucs, const char **display, UWORD *columns);
121 static void rl_insert (wchar_tt ucs);
122 static int rl_delete (void);
123 static int rl_left (UDWORD i);
124 static int rl_right (UDWORD i);
125 static const Contact *rl_tab_getnext (strc_t common);
126 static const Contact *rl_tab_getprev (strc_t common);
127 static void rl_tab_accept (void);
128 static void rl_tab_cancel (void);
129 static void rl_key_tab (void);
130 static void rl_key_shifttab (void);
131 static void rl_key_insert (wchar_tt ucs);
132 static void rl_key_left (void);
133 static void rl_key_right (void);
134 static void rl_key_delete (void);
135 static void rl_key_backspace (void);
136 static void rl_key_delete_backward_word (void);
137 static void rl_key_backward_word (void);
138 static void rl_key_forward_word (void);
139 static void rl_key_end (void);
140 static void rl_key_cut (void);
141 static void rl_key_kill (void);
142 static void rl_linecompress (str_t line, UDWORD from, UDWORD to);
143 static void rl_lineexpand (const char *hist);
144 static void rl_historyback (void);
145 static void rl_historyforward (void);
146 static void rl_historyadd (void);
147 static const char *rl_user_prompt (const char *prompt);
148
149 #if defined(SIGWINCH)
150 static volatile int rl_columns_cur = 0;
151 #endif
152 volatile UBYTE rl_signal = 0;
153
154 UDWORD rl_columns = 0;
155 static UDWORD rl_colpos, rl_ucspos, rl_bytepos;
156 static int rl_stat = 0, rl_inputdone;
157
158 static str_s rl_ucs = { NULL, 0, 0 };
159 static str_s rl_ucscol = { NULL, 0, 0 };
160 static str_s rl_ucsbytes = { NULL, 0, 0 };
161 static str_s rl_display = { NULL, 0, 0 };
162
163 static str_s rl_input = { NULL, 0, 0 };
164 static str_s rl_operate = { NULL, 0, 0 };
165 static str_s rl_temp = { NULL, 0, 0 };
166
167 #define RL_HISTORY_LINES 100
168 static char *rl_yank = NULL;
169 static char *rl_history[RL_HISTORY_LINES + 1];
170 static int rl_history_pos = 0;
171
172 static int rl_tab_state = 0; /* 0 = OFF 1 = Out 2 = Inc 3 = Online 4 = Offline |8 = second try -1 = disallow */
173 static UDWORD rl_tab_index = 0; /* index in list */
174 static UDWORD rl_tab_len = 0; /* number of codepoints tabbed in */
175 static UDWORD rl_tab_pos = 0; /* start of word tabbed in */
176 static UDWORD rl_tab_common = 0; /* number of given codepoints */
177 static str_s rl_colon = { NULL, 0, 0 };
178 static str_s rl_coloff = { NULL, 0, 0 };
179 static const Contact *rl_tab_cont = NULL;
180 static const ContactAlias *rl_tab_alias = NULL;
181
182 static str_s rl_prompt = { NULL, 0, 0 };
183 static UWORD rl_prompt_len = 0;
184 static int rl_prompt_stat = 0;
185 static time_t rl_prompt_time = 0;
186
187 /*
188 * Initialize read line code
189 */
ReadLineInit()190 void ReadLineInit ()
191 {
192 int i;
193 for (i = 0; i <= RL_HISTORY_LINES; i++)
194 rl_history[i] = NULL;
195 rl_columns = rl_getcolumns ();
196 s_init (&rl_ucs, "", 128);
197 s_init (&rl_ucscol, "", 128);
198 s_init (&rl_ucsbytes, "", 128);
199 s_init (&rl_input, "", 128);
200 s_init (&rl_display, "", 128);
201 rl_colpos = rl_ucspos = rl_bytepos = 0;
202 ReadLineTtySet ();
203 atexit (ReadLineTtyUnset);
204 #if defined(SIGTSTP) && defined(SIGCONT)
205 signal (SIGTSTP, &tty_stop_handler);
206 signal (SIGCONT, &tty_cont_handler);
207 #endif
208 #ifndef __amigaos__
209 signal (SIGINT, &tty_int_handler);
210 #else
211 signal (SIGINT, SIG_IGN);
212 #endif
213 ReadLinePromptReset ();
214 ConvTo ("", ENC_UCS2BE);
215 }
216
217 #ifdef DEBUG_RL
218 /*
219 * Debugging function
220 */
rl_dump_line(void)221 static void rl_dump_line (void)
222 {
223 int i, bp;
224 fprintf (stderr, "colpos %ld ucspos %ld bytepos %ld\n", rl_colpos, rl_ucspos, rl_bytepos);
225 for (i = bp = 0; i < rl_ucscol.len; i++)
226 {
227 char *p = strdup (rl_display.txt + bp);
228 p[rl_ucsbytes.txt[i]] = 0;
229 fprintf (stderr, " %08x %d/%d %s",
230 rl_ucs_at (&rl_ucs, i), rl_ucscol.txt[i], rl_ucsbytes.txt[i],
231 s_qquote (p));
232 bp += rl_ucsbytes.txt[i];
233 free (p);
234 }
235 fprintf (stderr, "\n");
236 }
237 #else
238 #define rl_dump_line()
239 #endif
240
241 /*** low-level tty stuff ***/
242
243 /*
244 * Undo changes to tty mode
245 */
ReadLineTtyUnset(void)246 void ReadLineTtyUnset (void)
247 {
248 #if HAVE_TCGETATTR
249 if (!tty_saved)
250 return;
251
252 if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tty_saved_attr) != 0)
253 {
254 rl_printf ("%s%s%s ", COLERROR, i18n (1619, "Warning:"), COLNONE);
255 rl_printf (i18n (2524, "Can't restore terminal modes.\n"));
256 }
257 else
258 tty_saved = 0;
259 #endif
260 }
261
262 /*
263 * Change tty mode for climm
264 */
ReadLineTtySet(void)265 void ReadLineTtySet (void)
266 {
267 #if HAVE_TCGETATTR
268 if (tcgetattr (STDIN_FILENO, &tty_attr) != 0)
269 {
270 rl_printf ("%s%s%s ", COLERROR, i18n (1619, "Warning:"), COLNONE);
271 rl_printf (i18n (2525, "Can't read terminal modes.\n"));
272 return;
273 }
274 if (!tty_saved)
275 {
276 tty_saved_attr = tty_attr;
277 tty_saved = 1;
278 }
279 tty_attr.c_lflag &= ~(ECHO | ICANON);
280 if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tty_attr) != 0)
281 {
282 rl_printf ("%s%s%s ", COLERROR, i18n (1619, "Warning:"), COLNONE);
283 rl_printf (i18n (2526, "Can't modify terminal modes.\n"));
284 return;
285 }
286 tty_attr.c_cc[VMIN] = 1;
287 if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tty_attr) != 0)
288 {
289 rl_printf ("%s%s%s ", COLERROR, i18n (1619, "Warning:"), COLNONE);
290 rl_printf (i18n (2526, "Can't modify terminal modes.\n"));
291 }
292 #endif
293 }
294
295 #if defined(SIGWINCH)
296 /*
297 * Handle a window resize
298 */
tty_sigwinch_handler(int i)299 static RETSIGTYPE tty_sigwinch_handler (int i)
300 {
301 rl_signal |= 16;
302 }
303 #endif
304
305 #if defined(SIGTSTP) && defined(SIGCONT)
306 /*
307 * Handle a ^Z terminal stop signal
308 */
tty_stop_handler(int i)309 static RETSIGTYPE tty_stop_handler (int i)
310 {
311 rl_signal |= 8;
312 }
313
314 /*
315 * Handle continuation after a ^Z terminal stop
316 */
tty_cont_handler(int i)317 static RETSIGTYPE tty_cont_handler (int i)
318 {
319 rl_signal |= 4;
320 signal (SIGTSTP, &tty_stop_handler);
321 signal (SIGCONT, &tty_cont_handler);
322 }
323 #endif
324
325 /*
326 * Handle a ^C interrupt signal - push line into history
327 * or exit after two consecutive ^C
328 */
tty_int_handler(int i)329 static RETSIGTYPE tty_int_handler (int i)
330 {
331 signal (SIGINT, SIG_IGN);
332
333 if (rl_signal & 1)
334 {
335 printf (" %s:-X%s\n", COLERROR, COLNONE);
336 fflush (stdout);
337 exit (1);
338 }
339 rl_signal |= 3;
340 signal (SIGINT, &tty_int_handler);
341 }
342
343 /*
344 * Really handle signals
345 */
ReadLineHandleSig(void)346 void ReadLineHandleSig (void)
347 {
348 UBYTE sig;
349
350 while ((sig = (rl_signal & 30)))
351 {
352 rl_signal &= 1;
353 if (sig & 2)
354 {
355 s_init (&rl_operate, "", 0);
356 rl_key_end ();
357 printf ("%s %s^C%s\n", rl_operate.txt, COLERROR, COLNONE);
358 rl_print ("\r");
359 rl_prompt_stat = 0;
360 rl_historyadd ();
361 rl_tab_state = 0;
362 s_init (&rl_input, "", 0);
363 CmdUserInterrupt ();
364 ReadLinePromptReset ();
365 ReadLinePrompt ();
366 }
367 #if defined(SIGTSTP) && defined(SIGCONT)
368 if (sig & 4)
369 {
370 ReadLineTtySet ();
371 rl_print ("\r");
372 ReadLinePrompt ();
373 }
374 if (sig & 8)
375 {
376 int gpos;
377
378 s_init (&rl_operate, "", 0);
379 gpos = rl_colpos;
380 rl_key_end ();
381 rl_colpos = gpos;
382 printf ("%s", rl_operate.txt);
383 printf (" %s^Z%s", COLERROR, COLNONE);
384 ReadLineTtyUnset ();
385 signal (SIGTSTP, SIG_DFL);
386 raise (SIGTSTP);
387 }
388 #endif
389 #if defined(SIGWINCH)
390 if (sig & 16)
391 {
392 if (rl_columns != rl_getcolumns ())
393 {
394 ReadLinePromptHide ();
395 rl_columns = rl_getcolumns ();
396 }
397 }
398 #endif
399 }
400 }
401
ReadLineAllowTab(UBYTE onoff)402 void ReadLineAllowTab (UBYTE onoff)
403 {
404 if (!onoff == (rl_tab_state == -1))
405 return;
406
407 switch (rl_tab_state)
408 {
409 case -1:
410 rl_tab_state = 0;
411 break;
412 default:
413 rl_tab_cancel ();
414 case 0:
415 rl_tab_state = -1;
416 }
417 }
418
419 /*
420 * Fetch the number of columns of this terminal
421 */
rl_getcolumns()422 static int rl_getcolumns ()
423 {
424 #if defined(SIGWINCH)
425 struct winsize ws;
426 int width = rl_columns_cur;
427
428 rl_columns_cur = 0;
429 ioctl (STDIN_FILENO, TIOCGWINSZ, &ws);
430 rl_columns_cur = ws.ws_col;
431 if ((width = rl_columns_cur))
432 {
433 if (signal (SIGWINCH, &tty_sigwinch_handler) == SIG_ERR)
434 rl_columns_cur = 0;
435 return width;
436 }
437 #endif
438 if (prG->screen)
439 return prG->screen;
440 return 80; /* a reasonable screen width default. */
441 }
442
443 /*
444 * Clear the screen
445 */
ReadLineClrScr()446 void ReadLineClrScr ()
447 {
448 #ifdef ANSI_TERM
449 printf (ESC "[H" ANSI_CLEAR);
450 #else
451 printf ("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
452 #endif
453 rl_prompt_stat = 0;
454 }
455
456 /*** read line basics ***/
457
458 /*
459 * Reset rl_colpos, rl_ucspos, rl_bytepos to correct values for given column
460 */
rl_syncpos(UDWORD pos)461 static void rl_syncpos (UDWORD pos)
462 {
463 rl_colpos = rl_ucspos = rl_bytepos = 0;
464 while (rl_colpos + rl_ucscol.txt[rl_ucspos] <= pos)
465 {
466 rl_colpos += rl_ucscol.txt[rl_ucspos];
467 rl_bytepos += rl_ucsbytes.txt[rl_ucspos];
468 rl_ucspos++;
469 }
470 assert (rl_colpos == pos);
471 }
472
473 /*
474 * Go to given column
475 */
rl_goto(UDWORD pos)476 static void rl_goto (UDWORD pos)
477 {
478 if (pos == rl_colpos)
479 return;
480
481 if (rl_colpos > pos)
482 {
483 #ifdef ANSI_TERM
484 int l = ((rl_prompt_len + rl_colpos) / rl_columns) - ((rl_prompt_len + pos) / rl_columns);
485 if (l)
486 s_catf (&rl_operate, ESC "[%dA", l);
487 rl_colpos -= l * rl_columns;
488 s_catc (&rl_operate, '\r');
489 rl_colpos -= (rl_prompt_len + rl_colpos) % rl_columns;
490 if ((int)rl_colpos < 0)
491 {
492 if (rl_prompt_len)
493 s_catf (&rl_operate, ESC "[%uC", rl_prompt_len);
494 rl_colpos = 0;
495 }
496 #else
497 while (pos < rl_colpos)
498 {
499 s_catc (&rl_operate, '\b');
500 rl_curpos --;
501 }
502 #endif
503 }
504 rl_syncpos (rl_colpos);
505 while (rl_colpos + rl_ucscol.txt[rl_ucspos] <= pos && rl_ucspos < rl_ucscol.len)
506 {
507 s_catn (&rl_operate, rl_display.txt + rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
508 rl_colpos += rl_ucscol.txt[rl_ucspos];
509 rl_bytepos += rl_ucsbytes.txt[rl_ucspos];
510 rl_ucspos++;
511 }
512 if (!((rl_prompt_len + rl_colpos) % rl_columns))
513 {
514 if (rl_ucspos < rl_ucscol.len)
515 s_catn (&rl_operate, rl_display.txt + rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
516 else
517 s_catc (&rl_operate, ' ');
518 s_catc (&rl_operate, '\r');
519 }
520 assert (rl_colpos == pos);
521 }
522
523 /*
524 * Re-check remaining line for multicolumn line break problems
525 */
rl_recheck(BOOL clear)526 static void rl_recheck (BOOL clear)
527 {
528 int gpos, i;
529
530 gpos = rl_colpos;
531
532 while (rl_ucspos < rl_ucscol.len)
533 {
534 if (rl_ucs_at (&rl_ucs, rl_ucspos) == WEOF)
535 {
536 s_deln (&rl_ucs, sizeof (wint_tt) * rl_ucspos, sizeof (wint_tt));
537 s_delc (&rl_ucsbytes, rl_ucspos);
538 s_delc (&rl_ucscol, rl_ucspos);
539 s_delc (&rl_display, rl_bytepos);
540 }
541 else if (((rl_prompt_len + rl_colpos) % rl_columns)
542 + (UBYTE)rl_ucscol.txt[rl_ucspos] > rl_columns)
543 {
544 for (i = (rl_columns - ((rl_prompt_len + rl_colpos) % rl_columns)); i > 0; i--)
545 {
546 wint_tt weof = WEOF;
547 s_insn (&rl_ucs, sizeof (wint_tt) * rl_ucspos, (const char *)&weof, sizeof (wint_tt));
548 s_insc (&rl_ucscol, rl_ucspos, 1);
549 s_insc (&rl_ucsbytes, rl_ucspos++, 1);
550 s_insc (&rl_display, rl_bytepos++, ' ');
551 s_catc (&rl_operate, ' ');
552 rl_colpos++;
553 }
554 }
555 else
556 {
557 s_catn (&rl_operate, rl_display.txt + rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
558 rl_bytepos += rl_ucsbytes.txt[rl_ucspos];
559 rl_colpos += rl_ucscol.txt[rl_ucspos];
560 rl_ucspos++;
561 }
562 }
563 #ifdef ANSI_TERM
564 if (!((rl_prompt_len + rl_colpos + 1) % rl_columns))
565 s_catf (&rl_operate, " \b\r" ESC "[%luC", UD2UL (rl_columns - 1));
566 else
567 s_cat (&rl_operate, " \b");
568 if (clear)
569 s_cat (&rl_operate, ANSI_CLEAR);
570 #else
571 s_cat (&rl_operate, " \b\b\b\b\b");
572 #endif
573 rl_goto (gpos);
574 }
575
576 /*
577 * Insert a character by unicode codepoint, display string in local encoding
578 * and its length, and its width in columns
579 */
rl_insert_basic(wchar_tt ucs,const char * display,UDWORD len,UDWORD collen)580 static void rl_insert_basic (wchar_tt ucs, const char *display, UDWORD len, UDWORD collen)
581 {
582 int i;
583
584 if (((rl_prompt_len + rl_colpos) % rl_columns) + collen > rl_columns)
585 {
586 for (i = (rl_columns - ((rl_prompt_len + rl_colpos) % rl_columns)); i > 0; i--)
587 {
588 wint_tt weof = WEOF;
589 s_insn (&rl_ucs, sizeof (wint_tt) * rl_ucspos, (const char *)&weof, sizeof (wint_tt));
590 s_insc (&rl_ucscol, rl_ucspos, 1);
591 s_insc (&rl_ucsbytes, rl_ucspos++, 1);
592 s_insc (&rl_display, rl_bytepos++, ' ');
593 s_catc (&rl_operate, ' ');
594 rl_colpos++;
595 }
596 }
597
598 s_insn (&rl_ucs, sizeof (wint_tt) * rl_ucspos, (const char *)&ucs, sizeof (wint_tt));
599 s_insc (&rl_ucscol, rl_ucspos, collen);
600 s_insc (&rl_ucsbytes, rl_ucspos++, len);
601 s_insn (&rl_display, rl_bytepos, display, len);
602 rl_colpos += collen;
603 rl_bytepos += len;
604 s_cat (&rl_operate, display);
605 }
606
607 #if HAVE_WCWIDTH
608 /*
609 * Determine width from wcwidth()
610 */
rl_wcwidth(wint_tt ucs,const char * display)611 static int rl_wcwidth (wint_tt ucs, const char *display)
612 {
613 wchar_t wc[20]; /* NOT wchar_tt */
614 int i, l, b, w;
615
616 if (ucs != CHAR_NOT_AVAILABLE && *display == CHAR_NOT_AVAILABLE)
617 return -1;
618
619 if ((l = mbstowcs (wc, display, 20)) <= 0)
620 return -1;
621
622 for (i = w = 0; i < l; i++)
623 if (!iswprint (wc[i]) || ((b = wcwidth (wc[i])) < 0))
624 return -1;
625 else
626 w += b;
627 return w;
628 }
629 #endif
630
631 #define rl_hex(u) ((u) & 15) <= 9 ? '0' + (char)((u) & 15) : 'a' - 10 + (char)((u) & 15)
632
633 /*
634 * Determine for a character is display string and columns width
635 */
rl_analyze_ucs(wint_tt ucs,const char ** display,UWORD * columns)636 static void rl_analyze_ucs (wint_tt ucs, const char **display, UWORD *columns)
637 {
638 char *utf = NULL;
639 #if HAVE_WCWIDTH
640 int width;
641 #endif
642
643 utf = strdup (ConvUTF8 (ucs));
644 *display = ConvTo (utf, ENC(enc_loc))->txt;
645
646 if (ucs < 32 || ucs == 127 || ucs == 173) /* control code */
647 {
648 *display = s_sprintf ("%s^%c%s", COLINVCHAR, (char)((ucs - 1 + 'A') & 0x7f), COLNONE);
649 *columns = 2;
650 }
651 else if (ucs < 127)
652 *columns = 1;
653 #if HAVE_WCWIDTH
654 else if (!prG->locale_broken && (width = rl_wcwidth (ucs, *display)) != -1)
655 *columns = 256 | width;
656 #else
657 else if (ucs >= 160) /* no way I'll hard-code double-width or combining marks here */
658 *columns = 257;
659 #endif
660 else if (prG->locale_broken && !strcmp (ConvFrom (ConvTo (utf, ENC(enc_loc)), ENC(enc_loc))->txt, utf))
661 *columns = 257;
662 else if (!(ucs & 0xffff0000)) /* more control code, or unknown */
663 {
664 *display = s_sprintf ("%s\\u%c%c%c%c%s", COLINVCHAR,
665 rl_hex (ucs / 4096), rl_hex (ucs / 256),
666 rl_hex (ucs / 16), rl_hex (ucs),
667 COLNONE);
668 *columns = 6;
669 }
670 else /* unknown stellar planes */
671 {
672 *display = s_sprintf ("%s\\U%c%c%c%c%c%c%c%c%s", COLINVCHAR,
673 rl_hex (ucs / 268435456), rl_hex (ucs / 16777216),
674 rl_hex (ucs / 1048576), rl_hex (ucs / 65536),
675 rl_hex (ucs / 4096), rl_hex (ucs / 256),
676 rl_hex (ucs / 16), rl_hex (ucs),
677 COLNONE);
678 *columns = 10;
679 }
680 free (utf);
681 }
682
683 /*
684 * Determine width and correct display for given (UTF8) string
685 */
ReadLineAnalyzeWidth(const char * text,UWORD * width)686 strc_t ReadLineAnalyzeWidth (const char *text, UWORD *width)
687 {
688 static str_s str = { NULL, 0, 0 };
689 wchar_tt ucs;
690 UWORD twidth, swidth = 0;
691 const char *dis;
692 int off = 0;
693 str_s in;
694
695 in.txt = (char *)text;
696 in.len = strlen (text);
697 in.max = 0;
698 s_init (&str, "", 100);
699
700 for (off = 0; off < in.len; )
701 {
702 ucs = ConvGetUTF8 (&in, &off);
703 rl_analyze_ucs (ucs, &dis, &twidth);
704 swidth += twidth & 0xff;
705 s_cat (&str, twidth & 0x100 ? ConvUTF8 (ucs) : dis);
706 }
707 *width = swidth;
708 return &str;
709 }
710
ReadLinePrintWidth(const char * text,const char * left,const char * right,UWORD * width)711 const char *ReadLinePrintWidth (const char *text, const char *left, const char *right, UWORD *width)
712 {
713 UWORD pwidth = *width, twidth;
714 strc_t txt;
715
716 txt = ReadLineAnalyzeWidth (text, &twidth);
717 if (twidth > pwidth)
718 *width = pwidth = twidth;
719 return s_sprintf ("%s%s%s%*.*s", left, txt->txt, right, pwidth - twidth, pwidth - twidth, "");
720 }
721
ReadLinePrintCont(const char * nick,const char * left)722 const char *ReadLinePrintCont (const char *nick, const char *left)
723 {
724 static str_s str = { NULL, 0, 0 };
725 UWORD twidth, pwidth = uiG.nick_len;
726 strc_t txt;
727
728 txt = ReadLineAnalyzeWidth (nick, &twidth);
729 if (twidth > uiG.nick_len)
730 uiG.nick_len = pwidth = twidth;
731 s_init (&str, "", 10);
732 s_catf (&str, "%*.*s%s%s%s ", pwidth - twidth, pwidth - twidth, "", left, nick, COLNONE);
733 return str.txt;
734 }
735
736
737 /*
738 * Insert a given unicode codepoint
739 */
rl_insert(wint_tt ucs)740 static void rl_insert (wint_tt ucs)
741 {
742 const char *display;
743 UWORD columns;
744
745 rl_analyze_ucs (ucs, &display, &columns);
746 rl_insert_basic (ucs, display, strlen (display), columns & 0xff);
747 if (columns && ((rl_ucscol.len > rl_ucspos) || !((rl_prompt_len + rl_colpos) % rl_columns)))
748 rl_recheck (FALSE);
749 }
750
751 /*
752 * Delete a glyph
753 */
rl_delete(void)754 static int rl_delete (void)
755 {
756 if (rl_ucspos >= rl_ucscol.len)
757 return FALSE;
758
759 while (rl_ucs_at (&rl_ucs, rl_ucspos) == WEOF)
760 {
761 s_deln (&rl_display, rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
762 s_delc (&rl_ucscol, rl_ucspos);
763 s_delc (&rl_ucsbytes, rl_ucspos);
764 s_deln (&rl_ucs, sizeof (wint_tt) * rl_ucspos, sizeof (wint_tt));
765 }
766
767 s_deln (&rl_display, rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
768 s_delc (&rl_ucscol, rl_ucspos);
769 s_delc (&rl_ucsbytes, rl_ucspos);
770 s_deln (&rl_ucs, sizeof (wint_tt) * rl_ucspos, sizeof (wint_tt));
771
772 while (!rl_ucscol.txt[rl_ucspos] && rl_ucspos < rl_ucscol.len)
773 {
774 s_deln (&rl_display, rl_bytepos, rl_ucsbytes.txt[rl_ucspos]);
775 s_delc (&rl_ucscol, rl_ucspos);
776 s_delc (&rl_ucsbytes, rl_ucspos);
777 s_deln (&rl_ucs, sizeof (wint_tt) * rl_ucspos, sizeof (wint_tt));
778 }
779 return TRUE;
780 }
781
782 /*
783 * Go an amount of glyphs to the left
784 */
rl_left(UDWORD i)785 static int rl_left (UDWORD i)
786 {
787 int gpos;
788
789 if (!rl_ucspos)
790 return FALSE;
791
792 gpos = rl_colpos;
793 for ( ; i > 0; i--)
794 {
795 while (rl_ucspos > 0 && (!rl_ucscol.txt[rl_ucspos - 1] ||
796 (rl_ucs_at (&rl_ucs, rl_ucspos - 1) == WEOF)))
797 gpos -= rl_ucscol.txt[--rl_ucspos];
798 if (rl_ucspos > 0)
799 gpos -= rl_ucscol.txt[--rl_ucspos];
800 }
801 rl_goto (gpos);
802 return TRUE;
803 }
804
805 /*
806 * Go an amount of glyphs to the right
807 */
rl_right(UDWORD i)808 static int rl_right (UDWORD i)
809 {
810 int gpos;
811
812 if (rl_ucspos >= rl_ucscol.len)
813 return FALSE;
814
815 gpos = rl_colpos;
816 for ( ; i > 0; i--)
817 {
818 if (rl_ucspos < rl_ucscol.len)
819 gpos += rl_ucscol.txt[rl_ucspos++];
820 while (rl_ucspos < rl_ucscol.len && (!rl_ucscol.txt[rl_ucspos] ||
821 (rl_ucs_at (&rl_ucs, rl_ucspos) == WEOF)))
822 gpos += rl_ucscol.txt[rl_ucspos++];
823 }
824 rl_goto (gpos);
825 return TRUE;
826 }
827
828
829 /*** history processing ***/
830
831 /*
832 * Compresses part of the current edited line into an UTF8 string
833 */
rl_linecompress(str_t line,UDWORD from,UDWORD to)834 static void rl_linecompress (str_t line, UDWORD from, UDWORD to)
835 {
836 UDWORD i;
837 wint_tt ucs;
838
839 if (to == (UDWORD)-1)
840 to = rl_ucscol.len;
841 s_init (line, "", 0);
842 for (i = from; i < to; i++)
843 {
844 ucs = rl_ucs_at (&rl_ucs, i);
845 #if DEBUG_RL
846 fprintf (stderr, "ucs %x\n", ucs);
847 #endif
848 if (ucs != WEOF)
849 s_cat (line, ConvUTF8 (ucs));
850 }
851 #if DEBUG_RL
852 fprintf (stderr, "compress %s\n", s_qquote (line->txt));
853 #endif
854 }
855
856 /*
857 * Expand given UTF8 string into (and replace) editing line
858 */
rl_lineexpand(const char * hist)859 static void rl_lineexpand (const char *hist)
860 {
861 str_s str = { NULL, 0, 0 };
862 int off;
863
864 s_init (&rl_ucs, "", 0);
865 s_init (&rl_ucscol, "", 0);
866 s_init (&rl_ucsbytes, "", 0);
867 s_init (&rl_display, "", 0);
868 rl_colpos = rl_ucspos = rl_bytepos = 0;
869
870 str.txt = (char *) hist;
871 str.len = strlen (str.txt);
872 for (off = 0; off < str.len; )
873 rl_insert (ConvGetUTF8 (&str, &off));
874 }
875
876 /*
877 * Go back one line in history
878 */
rl_historyback()879 static void rl_historyback ()
880 {
881 if (rl_history_pos == RL_HISTORY_LINES || !rl_history[rl_history_pos + 1])
882 return;
883 if (!rl_history_pos)
884 {
885 s_free (rl_history[0]);
886 rl_linecompress (&rl_temp, 0, -1);
887 rl_history[0] = strdup (rl_temp.txt);
888 }
889 rl_key_kill ();
890 rl_history_pos++;
891 rl_lineexpand (rl_history[rl_history_pos]);
892 }
893
894 /*
895 * Go forward one line in history
896 */
rl_historyforward()897 void rl_historyforward ()
898 {
899 if (!rl_history_pos)
900 return;
901 rl_key_kill ();
902 rl_history_pos--;
903 rl_lineexpand (rl_history[rl_history_pos]);
904 }
905
906 /*
907 * Add current line to history
908 * ALSO FINISHES LINE PROCESSING
909 */
rl_historyadd()910 static void rl_historyadd ()
911 {
912 int i, j;
913
914 rl_linecompress (&rl_temp, 0, -1);
915 if (rl_tab_state >= 0 && rl_temp.txt[0])
916 {
917 for (j = 1; j <= RL_HISTORY_LINES; j++)
918 if (!rl_history[j] || !strcmp (rl_temp.txt, rl_history[j]))
919 break;
920 if (j > RL_HISTORY_LINES)
921 j = RL_HISTORY_LINES;
922 s_free (rl_history[j]);
923 s_free (rl_history[0]);
924 rl_history[0] = strdup (rl_temp.txt);
925 for (i = j; i > 0; i--)
926 rl_history[i] = rl_history[i - 1];
927 rl_history[0] = NULL;
928 }
929 s_init (&rl_ucs, "", 0);
930 s_init (&rl_ucscol, "", 0);
931 s_init (&rl_ucsbytes, "", 0);
932 s_init (&rl_display, "", 0);
933 rl_colpos = rl_ucspos = rl_bytepos = 0;
934 rl_inputdone = 1;
935 rl_history_pos = 0;
936 }
937
938 /*** tab handling ***/
939
940 /*
941 * Fetch next tab contact
942 */
rl_tab_getnext(strc_t common)943 static const Contact *rl_tab_getnext (strc_t common)
944 {
945 const Contact *cont;
946
947 rl_tab_state &= ~8;
948 rl_tab_alias = NULL;
949 while (1)
950 {
951 switch (rl_tab_state & 7)
952 {
953 case 1:
954 while ((cont = TabGet (rl_tab_index++)))
955 if (!strncasecmp (cont->nick, common->txt, common->len))
956 return cont;
957 rl_tab_index = 0;
958 rl_tab_state++;
959 case 2:
960 while ((cont = ContactIndex (NULL, rl_tab_index++)))
961 if (cont->status != ims_offline && !TabHas (cont))
962 {
963 if (!strncasecmp (cont->nick, common->txt, common->len))
964 return cont;
965 for (rl_tab_alias = cont->alias; rl_tab_alias; rl_tab_alias = rl_tab_alias->more)
966 if (!strncasecmp (rl_tab_alias->alias, common->txt, common->len))
967 return cont;
968 }
969
970 rl_tab_index = 0;
971 rl_tab_state++;
972 case 3:
973 while ((cont = ContactIndex (NULL, rl_tab_index++)))
974 if (cont->status == ims_offline && !TabHas (cont))
975 {
976 if (!strncasecmp (cont->nick, common->txt, common->len))
977 return cont;
978 for (rl_tab_alias = cont->alias; rl_tab_alias; rl_tab_alias = rl_tab_alias->more)
979 if (!strncasecmp (rl_tab_alias->alias, common->txt, common->len))
980 return cont;
981 }
982 if (rl_tab_state & 8)
983 return NULL;
984 rl_tab_index = 0;
985 rl_tab_state = 9;
986 continue;
987 }
988 assert (0);
989 }
990 }
991
992 /*
993 * Fetch previous tab contact
994 */
rl_tab_getprev(strc_t common)995 static const Contact *rl_tab_getprev (strc_t common)
996 {
997 const Contact *cont;
998
999 rl_tab_index--;
1000 rl_tab_state &= ~8;
1001 rl_tab_alias = NULL;
1002 while (1)
1003 {
1004 switch (rl_tab_state & 7)
1005 {
1006 case 3:
1007 while (rl_tab_index && (cont = ContactIndex (NULL, --rl_tab_index)))
1008 if (cont->status == ims_offline && !TabHas (cont))
1009 {
1010 rl_tab_index++;
1011 if (!strncasecmp (cont->nick, common->txt, common->len))
1012 return cont;
1013 for (rl_tab_alias = cont->alias; rl_tab_alias; rl_tab_alias = rl_tab_alias->more)
1014 if (!strncasecmp (rl_tab_alias->alias, common->txt, common->len))
1015 return cont;
1016 rl_tab_index--;
1017 }
1018 rl_tab_index = 0;
1019 while (ContactIndex (NULL, rl_tab_index))
1020 rl_tab_index++;
1021 rl_tab_state--;
1022 case 2:
1023 while (rl_tab_index && (cont = ContactIndex (NULL, --rl_tab_index)))
1024 if (cont->status != ims_offline && !TabHas (cont))
1025 {
1026 rl_tab_index++;
1027 if (!strncasecmp (cont->nick, common->txt, common->len))
1028 return cont;
1029 for (rl_tab_alias = cont->alias; rl_tab_alias; rl_tab_alias = rl_tab_alias->more)
1030 if (!strncasecmp (rl_tab_alias->alias, common->txt, common->len))
1031 return cont;
1032 rl_tab_index--;
1033 }
1034 rl_tab_index = 0;
1035 while (TabGet (rl_tab_index))
1036 rl_tab_index++;
1037 rl_tab_state--;
1038 case 1:
1039 while (rl_tab_index && (cont = TabGet (--rl_tab_index)))
1040 if (!strncasecmp (cont->nick, common->txt, common->len))
1041 {
1042 rl_tab_index++;
1043 return cont;
1044 }
1045 if (rl_tab_state & 8)
1046 return NULL;
1047 rl_tab_index = 0;
1048 while (ContactIndex (NULL, rl_tab_index))
1049 rl_tab_index++;
1050 rl_tab_state = 8 + 3;
1051 continue;
1052 }
1053 assert (0);
1054 }
1055 }
1056
1057 /*
1058 * Accept the current selected contact
1059 */
rl_tab_accept(void)1060 static void rl_tab_accept (void)
1061 {
1062 str_s str = { NULL, 0, 0 };
1063 const char *display;
1064 int off;
1065 UWORD columns;
1066
1067 if (rl_tab_state <= 0)
1068 return;
1069 rl_left (rl_tab_common);
1070 for ( ; rl_tab_len; rl_tab_len--)
1071 rl_delete ();
1072 str.txt = rl_tab_alias ? rl_tab_alias->alias : rl_tab_cont->nick;
1073 str.len = strlen (str.txt);
1074 for (off = 0; off < str.len; )
1075 {
1076 wint_tt ucs = ConvGetUTF8 (&str, &off);
1077 rl_analyze_ucs (ucs, &display, &columns);
1078 rl_insert_basic (ucs, display, strlen (display), columns & 0xff);
1079 }
1080 rl_tab_state = 0;
1081 rl_recheck (TRUE);
1082 }
1083
1084 /*
1085 * Cancel the current tab contact
1086 */
rl_tab_cancel(void)1087 static void rl_tab_cancel (void)
1088 {
1089 str_s str = { NULL, 0, 0 };
1090 const char *display;
1091 int i, off;
1092 UWORD columns;
1093
1094 if (rl_tab_state <= 0)
1095 return;
1096 rl_left (rl_tab_common);
1097 for ( ; rl_tab_len; rl_tab_len--)
1098 rl_delete ();
1099 str.txt = rl_tab_alias ? rl_tab_alias->alias : rl_tab_cont->nick;
1100 str.len = strlen (str.txt);
1101 for (off = i = 0; off < str.len && i < rl_tab_common; i++)
1102 {
1103 wint_tt ucs = ConvGetUTF8 (&str, &off);
1104 rl_analyze_ucs (ucs, &display, &columns);
1105 rl_insert_basic (ucs, display, strlen (display), columns & 0xff);
1106 }
1107 rl_tab_state = 0;
1108 rl_recheck (TRUE);
1109 }
1110
1111 /*
1112 * Handle tab key - start or continue tabbing
1113 */
rl_key_tab(void)1114 static void rl_key_tab (void)
1115 {
1116 str_s str = { NULL, 0, 0 };
1117 strc_t ins;
1118 const char *display;
1119 int i, off;
1120 UWORD columns;
1121
1122 if (rl_tab_state == -1)
1123 {
1124 rl_insert (9);
1125 return;
1126 }
1127
1128 if (!rl_tab_state)
1129 {
1130 if (!rl_ucs.len)
1131 {
1132 rl_insert ('m');
1133 rl_insert ('s');
1134 rl_insert ('g');
1135 rl_insert (' ');
1136 }
1137 for (i = 0; i < rl_ucspos; i++)
1138 {
1139 if (rl_ucs_at (&rl_ucs, i) == ' ')
1140 {
1141 rl_tab_index = 0;
1142 rl_tab_state = 1;
1143 rl_tab_pos = i + 1;
1144 rl_linecompress (&rl_temp, rl_tab_pos, rl_ucspos);
1145 if ((rl_tab_cont = rl_tab_getnext (&rl_temp)))
1146 {
1147 ins = ConvTo (COLQUOTE, ENC(enc_loc));
1148 s_init (&rl_colon, "", 0);
1149 s_catn (&rl_colon, ins->txt, ins->len);
1150 ins = ConvTo (COLNONE, ENC(enc_loc));
1151 s_init (&rl_coloff, "", 0);
1152 s_catn (&rl_coloff, ins->txt, ins->len);
1153
1154 while (rl_ucspos > rl_tab_pos)
1155 rl_left (1), rl_tab_len++;
1156 rl_tab_common = rl_tab_len;
1157 break;
1158 }
1159 }
1160 }
1161 if (!rl_tab_state)
1162 {
1163 printf ("\a");
1164 return;
1165 }
1166 }
1167 else
1168 {
1169 rl_linecompress (&rl_temp, rl_tab_pos, rl_ucspos);
1170 rl_tab_cont = rl_tab_getnext (&rl_temp);
1171 rl_left (rl_tab_common);
1172 }
1173 for ( ; rl_tab_len; rl_tab_len--)
1174 rl_delete ();
1175 if (!rl_tab_cont)
1176 {
1177 printf ("\a");
1178 rl_tab_state = 0;
1179 rl_recheck (TRUE);
1180 return;
1181 }
1182
1183 str.txt = rl_tab_alias ? rl_tab_alias->alias : rl_tab_cont->nick;
1184 str.len = strlen (str.txt);
1185 for (off = 0; off < str.len; )
1186 {
1187 wint_tt ucs = ConvGetUTF8 (&str, &off);
1188 rl_analyze_ucs (ucs, &display, &columns);
1189 rl_insert_basic (ucs, s_sprintf ("%s%s%s", rl_colon.txt, display, rl_coloff.txt),
1190 strlen (display) + rl_colon.len + rl_coloff.len, columns & 0xff);
1191 rl_tab_len++;
1192 }
1193 rl_left (rl_tab_len - rl_tab_common);
1194 rl_recheck (TRUE);
1195 }
1196
1197 /*
1198 * Handle tab key - continue tabbing backwards
1199 */
rl_key_shifttab(void)1200 static void rl_key_shifttab (void)
1201 {
1202 str_s str = { NULL, 0, 0 };
1203 const char *display;
1204 int off;
1205 UWORD columns;
1206
1207 if (rl_tab_state <= 0)
1208 {
1209 printf ("\a");
1210 return;
1211 }
1212
1213 rl_linecompress (&rl_temp, rl_tab_pos, rl_ucspos);
1214 rl_tab_cont = rl_tab_getprev (&rl_temp);
1215 rl_left (rl_tab_common);
1216
1217 for ( ; rl_tab_len; rl_tab_len--)
1218 rl_delete ();
1219 if (!rl_tab_cont)
1220 {
1221 printf ("\a");
1222 rl_tab_state = 0;
1223 rl_recheck (TRUE);
1224 return;
1225 }
1226
1227 str.txt = rl_tab_alias ? rl_tab_alias->alias : rl_tab_cont->nick;
1228 str.len = strlen (str.txt);
1229 for (off = 0; off < str.len; )
1230 {
1231 wint_tt ucs = ConvGetUTF8 (&str, &off);
1232 rl_analyze_ucs (ucs, &display, &columns);
1233 rl_insert_basic (ucs, s_sprintf ("%s%s%s", rl_colon.txt, display, rl_coloff.txt),
1234 strlen (display) + rl_colon.len + rl_coloff.len, columns & 0xff);
1235 rl_tab_len++;
1236 }
1237 rl_left (rl_tab_len - rl_tab_common);
1238 rl_recheck (TRUE);
1239 }
1240
1241 /*** key handlers ***/
1242
1243 /*
1244 * Handle key to insert itself
1245 */
rl_key_insert(wchar_tt ucs)1246 static void rl_key_insert (wchar_tt ucs)
1247 {
1248 if (rl_tab_state > 0 && rl_tab_common)
1249 {
1250 rl_tab_cancel ();
1251 rl_insert (ucs);
1252 rl_key_tab ();
1253 }
1254 else
1255 {
1256 if (rl_tab_state > 0)
1257 {
1258 rl_tab_accept ();
1259 rl_insert (' ');
1260 }
1261 rl_insert (ucs);
1262 }
1263 }
1264
1265 /*
1266 * Handle left arrow key
1267 */
rl_key_left(void)1268 static void rl_key_left (void)
1269 {
1270 if (!rl_ucspos)
1271 return;
1272
1273 if (rl_tab_state > 0)
1274 {
1275 if (rl_tab_common)
1276 rl_tab_common--;
1277 else
1278 rl_tab_cancel();
1279 }
1280 rl_left (1);
1281 }
1282
1283 /*
1284 * Handle right arrow key
1285 */
rl_key_right(void)1286 static void rl_key_right (void)
1287 {
1288 if (rl_ucspos >= rl_ucscol.len)
1289 return;
1290
1291 if (rl_tab_state > 0)
1292 {
1293 if (rl_tab_common < rl_tab_len)
1294 rl_tab_common++;
1295 else
1296 rl_tab_accept();
1297 }
1298 rl_right (1);
1299 }
1300
1301 /*
1302 * Handle delete key
1303 */
rl_key_delete(void)1304 static void rl_key_delete (void)
1305 {
1306 if (rl_tab_state > 0)
1307 rl_tab_cancel ();
1308 rl_delete ();
1309 rl_recheck (TRUE);
1310 }
1311
1312 /*
1313 * Handle backspace key
1314 */
rl_key_backspace(void)1315 static void rl_key_backspace (void)
1316 {
1317 if (rl_tab_state > 0)
1318 rl_tab_cancel ();
1319 if (rl_left (1))
1320 rl_key_delete ();
1321 }
1322
1323 /*
1324 * Handle delete backward-word
1325 */
rl_key_delete_backward_word(void)1326 static void rl_key_delete_backward_word (void)
1327 {
1328 if (!rl_ucspos)
1329 return;
1330
1331 rl_key_left ();
1332 if (rl_ucspos > 0 && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1333 while (rl_ucspos > 0 && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1334 {
1335 rl_key_delete ();
1336 rl_key_left ();
1337 }
1338 if (!rl_ucspos)
1339 {
1340 rl_key_delete ();
1341 return;
1342 }
1343 while (rl_ucspos > 0 && iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1344 {
1345 rl_key_delete ();
1346 rl_key_left ();
1347 }
1348
1349 if (!iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1350 rl_key_right ();
1351 else
1352 rl_key_delete ();
1353 }
1354
1355 /*
1356 * Handle backward-word
1357 */
rl_key_backward_word(void)1358 static void rl_key_backward_word (void)
1359 {
1360 if (rl_ucspos > 0)
1361 rl_key_left ();
1362 if (rl_ucspos > 0 && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1363 while (rl_ucspos > 0 && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1364 rl_key_left ();
1365 while (rl_ucspos > 0 && iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1366 rl_key_left ();
1367 if (!iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1368 rl_key_right ();
1369 }
1370
1371 /*
1372 * Handle forward-word
1373 */
rl_key_forward_word(void)1374 static void rl_key_forward_word (void)
1375 {
1376 if (rl_ucspos < rl_ucscol.len)
1377 rl_key_right ();
1378 if (rl_ucspos < rl_ucscol.len && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1379 while (rl_ucspos < rl_ucscol.len && !iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1380 rl_key_right ();
1381 while (rl_ucspos < rl_ucscol.len && iswalnum (rl_ucs_at (&rl_ucs, rl_ucspos)))
1382 rl_key_right ();
1383 }
1384
1385 /*
1386 * Handle end key
1387 */
rl_key_end(void)1388 static void rl_key_end (void)
1389 {
1390 int gpos;
1391
1392 if (rl_tab_state > 0)
1393 rl_tab_accept ();
1394
1395 if (rl_ucspos >= rl_ucscol.len)
1396 return;
1397
1398 gpos = rl_colpos;
1399 while (rl_ucspos < rl_ucscol.len)
1400 gpos += rl_ucscol.txt[rl_ucspos++];
1401 rl_goto (gpos);
1402 }
1403
1404 /*
1405 * Handle key to cut and discard line after cursor
1406 */
rl_key_cut(void)1407 static void rl_key_cut (void)
1408 {
1409 #ifdef ANSI_TERM
1410 while (rl_delete ())
1411 ;
1412 rl_recheck (TRUE);
1413 #else
1414 while (rl_delete ())
1415 rl_recheck;
1416 #endif
1417 rl_tab_state = 0;
1418 }
1419
1420 /*
1421 * Handle kill character
1422 */
rl_key_kill(void)1423 static void rl_key_kill (void)
1424 {
1425 rl_goto (0);
1426 rl_linecompress (&rl_temp, 0, -1);
1427 s_repl (&rl_yank, rl_temp.txt);
1428 rl_tab_state = 0;
1429 rl_tab_len = 0;
1430 rl_key_cut ();
1431 }
1432
1433 /*** read line auto expander ***/
1434
rl_checkautoexpand(void)1435 static void rl_checkautoexpand (void)
1436 {
1437 const char *replacement;
1438
1439 if (!rl_bytepos)
1440 return;
1441
1442 rl_linecompress (&rl_temp, 0, -1);
1443 replacement = AliasExpand (rl_temp.txt, rl_bytepos, TRUE);
1444 if (!replacement)
1445 return;
1446
1447 rl_tab_state = 0;
1448 rl_goto (0);
1449 rl_lineexpand (replacement);
1450 }
1451
1452 /*** read line input processor ***/
1453
1454 /*
1455 * Process one byte of input
1456 */
ReadLine(UBYTE newbyte)1457 str_t ReadLine (UBYTE newbyte)
1458 {
1459 strc_t input, inputucs;
1460 static UWORD ucsesc;
1461 UWORD ucs;
1462
1463 ReadLineHandleSig ();
1464 s_catc (&rl_input, newbyte);
1465
1466 input = ConvFrom (&rl_input, prG->enc_loc);
1467 if (input->txt[0] == CHAR_INCOMPLETE)
1468 {
1469 if (strcmp (rl_input.txt, ConvTo (input->txt, prG->enc_loc)->txt))
1470 return NULL;
1471 }
1472
1473 rl_inputdone = 0;
1474 rl_signal &= ~1;
1475
1476 inputucs = ConvTo (input->txt, ENC_UCS2BE);
1477 ucs = ((UBYTE)inputucs->txt[1]) | (((UBYTE)inputucs->txt[0]) << 8);
1478
1479 s_init (&rl_input, "", 0);
1480 s_init (&rl_operate, "", 0);
1481
1482 rl_dump_line ();
1483 ReadLinePrompt ();
1484
1485 switch (rl_stat)
1486 {
1487 case 0:
1488 if (0) ;
1489 #if HAVE_TCGETATTR
1490 #if defined(VERASE)
1491 else if (ucs == tty_attr.c_cc[VERASE] && tty_attr.c_cc[VERASE] != _POSIX_VDISABLE)
1492 rl_key_backspace ();
1493 #endif
1494 #if defined(VEOF)
1495 else if (ucs == tty_attr.c_cc[VEOF] && tty_attr.c_cc[VEOF] != _POSIX_VDISABLE)
1496 {
1497 rl_tab_cancel ();
1498 if (rl_ucscol.len)
1499 {
1500 rl_key_left ();
1501 rl_key_delete ();
1502 }
1503 else
1504 {
1505 rl_insert ('q');
1506 rl_historyadd ();
1507 }
1508 }
1509 #endif
1510 #if defined(VKILL)
1511 else if (ucs == tty_attr.c_cc[VKILL] && tty_attr.c_cc[VKILL] != _POSIX_VDISABLE)
1512 rl_key_kill ();
1513 #endif
1514 #if defined(VREPRINT)
1515 else if (ucs == tty_attr.c_cc[VREPRINT] && tty_attr.c_cc[VREPRINT] != _POSIX_VDISABLE)
1516 ReadLinePromptHide ();
1517 #endif
1518 #endif
1519 else switch (ucs)
1520 {
1521 case 1: /* ^A */
1522 rl_tab_cancel ();
1523 rl_goto (0);
1524 break;
1525 case 5: /* ^E */
1526 rl_key_end ();
1527 break;
1528 case 8: /* ^H = \b */
1529 rl_key_backspace ();
1530 break;
1531 case 9: /* ^I = \t */
1532 rl_key_tab ();
1533 break;
1534 case 11: /* ^K */
1535 rl_key_cut ();
1536 break;
1537 case 12: /* ^L */
1538 ReadLineClrScr ();
1539 break;
1540 case '\r':
1541 case '\n':
1542 rl_tab_accept ();
1543 if (rl_tab_state == 0)
1544 rl_checkautoexpand ();
1545 rl_key_end ();
1546 rl_historyadd ();
1547 break;
1548 case 23: /* ^W */
1549 rl_key_delete_backward_word ();
1550 break;
1551 case 25: /* ^Y */
1552 if (rl_yank)
1553 {
1554 rl_tab_state = 0;
1555 rl_goto (0);
1556 rl_lineexpand (rl_yank);
1557 }
1558 break;
1559 case 27: /* ^[ = ESC */
1560 #ifdef ANSI_TERM
1561 rl_stat = 1;
1562 #endif
1563 break;
1564 case 32: /* = SPACE */
1565 if (rl_tab_state > 0)
1566 rl_tab_accept ();
1567 else if (rl_tab_state == 0)
1568 rl_checkautoexpand ();
1569 rl_insert (' ');
1570 break;
1571 case 127: /* DEL */
1572 if (prG->flags & FLAG_DELBS)
1573 rl_key_backspace ();
1574 else
1575 rl_key_delete ();
1576 break;
1577 case 0x9b: /* CSI */
1578 #ifdef ANSI_TERM
1579 if (ENC(enc_loc) == ENC_LATIN1)
1580 {
1581 rl_stat = 2;
1582 break;
1583 }
1584 #else
1585 printf ("\a");
1586 break;
1587 #endif
1588 /* fall-through */
1589 default:
1590 rl_key_insert (ucs);
1591 }
1592 break;
1593
1594 #ifdef ANSI_TERM
1595
1596 case 1: /* state 1: ESC was pressed */
1597 rl_stat = 0;
1598 if (ucs == 'u' || ucs == 'U')
1599 rl_stat = 10;
1600 else if (ucs == '[' || ucs == 'O')
1601 rl_stat = 2;
1602 else if (ucs == 'b')
1603 rl_key_backward_word ();
1604 else if (ucs == 'f')
1605 rl_key_forward_word ();
1606 else if (ucs == 127)
1607 rl_key_delete_backward_word ();
1608 else
1609 printf ("\a");
1610 break;
1611
1612 case 2: /* state 2: CSI was typed */
1613 rl_stat = 0;
1614 switch (ucs)
1615 {
1616 case 'A': /* up */
1617 rl_tab_cancel ();
1618 rl_historyback ();
1619 break;
1620 case 'B': /* down */
1621 rl_tab_cancel ();
1622 rl_historyforward ();
1623 break;
1624 case 'C': /* right */
1625 rl_key_right ();
1626 break;
1627 case 'D': /* left */
1628 rl_key_left ();
1629 break;
1630 case 'H': /* home */
1631 rl_tab_cancel ();
1632 rl_goto (0);
1633 break;
1634 case 'F': /* end */
1635 rl_key_end ();
1636 break;
1637 case 'Z': /* shift tab */
1638 rl_key_shifttab ();
1639 break;
1640 case '3': /* + ~ = delete */
1641 rl_stat = 3;
1642 break;
1643 case '1': /* + ~ = home */
1644 case '7': /* + ~ = home */
1645 rl_stat = 4;
1646 break;
1647 case '4': /* + ~ = end */
1648 case '8': /* + ~ = end */
1649 rl_stat = 5;
1650 break;
1651 default:
1652 printf ("\a");
1653 }
1654 break;
1655
1656 case 3: /* state 3: incomplete delete key */
1657 rl_stat = 0;
1658 if (ucs == '~')
1659 rl_key_delete ();
1660 else
1661 printf ("\a");
1662 break;
1663
1664 case 4: /* state 4: incomplete home key */
1665 rl_stat = 0;
1666 if (ucs == '~')
1667 {
1668 rl_tab_cancel ();
1669 rl_goto (0);
1670 }
1671 else
1672 printf ("\a");
1673 break;
1674
1675 case 5: /* state 5: incomplete end key */
1676 rl_stat = 0;
1677 if (ucs == '~')
1678 rl_key_end ();
1679 else
1680 printf ("\a");
1681 break;
1682
1683 case 10: /* state 10: unicode sequence to be entered */
1684 rl_stat++;
1685 if (ucs >= '0' && ucs <= '9')
1686 ucsesc = ucs - '0';
1687 else if (ucs >= 'a' && ucs <= 'f')
1688 ucsesc = ucs - 'a' + 10;
1689 else if (ucs >= 'A' && ucs <= 'F')
1690 ucsesc = ucs - 'A' + 10;
1691 else
1692 {
1693 rl_stat = 0;
1694 printf ("\a");
1695 }
1696 break;
1697 case 11:
1698 rl_stat++;
1699 ucsesc <<= 4;
1700 if (ucs >= '0' && ucs <= '9')
1701 ucsesc |= ucs - '0';
1702 else if (ucs >= 'a' && ucs <= 'f')
1703 ucsesc |= ucs - 'a' + 10;
1704 else if (ucs >= 'A' && ucs <= 'F')
1705 ucsesc |= ucs - 'A' + 10;
1706 else
1707 {
1708 rl_stat = 0;
1709 printf ("\a");
1710 }
1711 break;
1712 case 12:
1713 rl_stat++;
1714 ucsesc <<= 4;
1715 if (ucs >= '0' && ucs <= '9')
1716 ucsesc |= ucs - '0';
1717 else if (ucs >= 'a' && ucs <= 'f')
1718 ucsesc |= ucs - 'a' + 10;
1719 else if (ucs >= 'A' && ucs <= 'F')
1720 ucsesc |= ucs - 'A' + 10;
1721 else
1722 {
1723 rl_stat = 0;
1724 printf ("\a");
1725 }
1726 break;
1727 case 13:
1728 rl_stat = 0;
1729 ucsesc <<= 4;
1730 if (ucs >= '0' && ucs <= '9')
1731 ucsesc |= ucs - '0';
1732 else if (ucs >= 'a' && ucs <= 'f')
1733 ucsesc |= ucs - 'a' + 10;
1734 else if (ucs >= 'A' && ucs <= 'F')
1735 ucsesc |= ucs - 'A' + 10;
1736 else
1737 {
1738 printf ("\a");
1739 break;
1740 }
1741 rl_key_insert (ucsesc);
1742 break;
1743 }
1744 #endif
1745
1746 #if DEBUG_RL
1747 fprintf (stderr, "oper: %s\n", s_qquote (rl_operate.txt));
1748 #endif
1749 rl_dump_line ();
1750 if (rl_operate.len)
1751 printf ("%s", rl_operate.txt);
1752
1753 if (!rl_inputdone)
1754 return NULL;
1755
1756 printf ("\n");
1757 return &rl_temp;
1758 }
1759
1760 /*** prompt management ***/
1761
1762 /*
1763 * Shows the prompt
1764 */
ReadLinePrompt()1765 void ReadLinePrompt ()
1766 {
1767 int gpos = rl_colpos;
1768
1769 if (rl_prompt_stat == 1)
1770 return;
1771 #if DEBUG_RL
1772 fprintf (stderr, "killoper: %s\n", s_qquote (rl_operate.txt));
1773 #endif
1774 s_init (&rl_operate, "", 0);
1775 if (rl_prompt_stat == 2)
1776 rl_goto (0);
1777 if (rl_prompt_stat == 2)
1778 {
1779 s_catc (&rl_operate, '\r');
1780 #ifdef ANSI_TERM
1781 s_cat (&rl_operate, ANSI_CLEAR);
1782 #endif
1783 rl_prompt_stat = 0;
1784 }
1785 #if DEBUG_RL
1786 fprintf (stderr, "oper(rm): %s\n", s_qquote (rl_operate.txt));
1787 #endif
1788 printf ("%s", rl_operate.txt);
1789 if (rl_prompt_stat == 0)
1790 {
1791 rl_print ("\r");
1792 rl_print (rl_prompt.txt);
1793 rl_prompt_len = rl_pos ();
1794 rl_prompt_stat = 1;
1795 rl_colpos = 0;
1796 }
1797 s_init (&rl_operate, "", 0);
1798 rl_recheck (TRUE);
1799 rl_goto (gpos);
1800 printf ("%s", rl_operate.txt);
1801 }
1802
1803 /*
1804 * Hides the prompt
1805 */
ReadLinePromptHide()1806 void ReadLinePromptHide ()
1807 {
1808 int pos = rl_colpos;
1809 ReadLineHandleSig ();
1810 if (rl_prompt_stat == 0)
1811 return;
1812 s_init (&rl_operate, "", 0);
1813 rl_goto (0);
1814 s_catc (&rl_operate, '\r');
1815 #ifdef ANSI_TERM
1816 s_cat (&rl_operate, ANSI_CLEAR);
1817 #endif
1818 printf ("%s", rl_operate.txt);
1819 rl_prompt_stat = 0;
1820 rl_colpos = pos;
1821 rl_print ("\r");
1822 }
1823
1824 /*
1825 * Sets the prompt
1826 */
ReadLinePromptSet(const char * prompt)1827 void ReadLinePromptSet (const char *prompt)
1828 {
1829 rl_prompt_time = time (NULL);
1830 s_init (&rl_prompt, COLSERVER, 0);
1831 s_cat (&rl_prompt, prompt);
1832 s_cat (&rl_prompt, COLNONE);
1833 s_catc (&rl_prompt, ' ');
1834 if (rl_prompt_stat != 0)
1835 rl_prompt_stat = 2;
1836 }
1837
1838 /*
1839 * Updates prompt - ignores too frequent changes
1840 */
ReadLinePromptUpdate(const char * prompt)1841 void ReadLinePromptUpdate (const char *prompt)
1842 {
1843 if (rl_prompt_time == time (NULL))
1844 return;
1845 ReadLinePromptSet (prompt);
1846 }
1847
1848 /*
1849 * Resets prompt to default prompt
1850 */
ReadLinePromptReset(void)1851 void ReadLinePromptReset (void)
1852 {
1853 Contact *cont;
1854 uiG.idle_msgs = 0;
1855 if (prG->flags & FLAG_USERPROMPT)
1856 ReadLinePromptSet (rl_user_prompt (prG->prompt));
1857 else if (prG->flags & FLAG_UINPROMPT && (cont = uiG.last_sent))
1858 ReadLinePromptSet (s_sprintf ("[%s]", cont->nick));
1859 else
1860 ReadLinePromptSet (i18n (2467, "climm>"));
1861 }
1862
rl_user_prompt(const char * prompt)1863 const char *rl_user_prompt (const char *prompt)
1864 {
1865 static str_s t = { NULL, 0, 0 };
1866 long int digit = -1;
1867
1868 const char *p = prompt;
1869 char *endptr;
1870
1871 Contact *cont;
1872
1873 if (prompt == NULL)
1874 return "";
1875
1876 s_init (&t, (prG->flags & FLAG_COLOR) ? SGR0 : "", 20);
1877
1878 while (*p)
1879 {
1880 if (*p == '%')
1881 {
1882 p++;
1883 if (!isdigit (*p))
1884 digit = -1;
1885 else
1886 {
1887 digit = strtol (p, &endptr ,0);
1888 p = endptr;
1889 }
1890
1891 switch (*p)
1892 {
1893 /* === user info === */
1894 case 'n':
1895 if ((uiG.conn != NULL)
1896 && (uiG.conn->conn->cont != NULL)
1897 && (uiG.conn->conn->cont->nick != NULL))
1898 s_cat (&t, s_sprintf ("%s", uiG.conn->conn->cont->nick));
1899 break;
1900 case 'U':
1901 if (uiG.conn != NULL)
1902 s_cat (&t, s_sprintf ("%s", uiG.conn->screen));
1903 break;
1904
1905 /* === status === */
1906 case 'S':
1907 if (uiG.conn != NULL)
1908 s_cat (&t, s_status (uiG.conn->status, uiG.conn->nativestatus));
1909 break;
1910 case 's':
1911 if (uiG.conn != NULL)
1912 s_cat (&t, s_status_short (uiG.conn->status));
1913 break;
1914
1915 /* === server info === */
1916 case 'p':
1917 if (uiG.conn != NULL)
1918 s_cat (&t, ConnectionServerType (uiG.conn->type));
1919 break;
1920 case 'P':
1921 if ((uiG.conn != NULL)
1922 && (uiG.conn->conn->server != NULL))
1923 s_cat (&t, uiG.conn->conn->server);
1924 break;
1925
1926 /* === last nicks === */
1927 case 'a':
1928 if ((cont = uiG.last_sent) != 0)
1929 s_cat (&t, cont->nick);
1930 break;
1931 case 'r':
1932 if ((cont = uiG.last_rcvd) != 0)
1933 s_cat (&t, cont->nick);
1934 break;
1935
1936 /* === times === */
1937 case 't':
1938 s_cat (&t, s_time (NULL));
1939 break;
1940 case 'T':
1941 if (prG->prompt_strftime != NULL)
1942 s_cat (&t, s_strftime (NULL, prG->prompt_strftime, 0));
1943 break;
1944
1945 /* === misc === */
1946 case '%':
1947 s_catc (&t, *p);
1948 break;
1949
1950 /* === colors === */
1951 case 'c':
1952 if ((prG->flags & FLAG_COLOR)
1953 && ((digit >= 0) && (digit <= 9)))
1954 s_cat (&t, s_sprintf (ESC "[0;3%ldm", digit));
1955 break;
1956 case 'C':
1957 if ((prG->flags & FLAG_COLOR)
1958 && ((digit >= 0) && (digit <= 9)))
1959 s_cat (&t, s_sprintf (ESC "[0;4%ldm", digit));
1960 break;
1961 case 'b':
1962 if ((prG->flags & FLAG_COLOR)
1963 && ((digit >= -1) && (digit <= 1)))
1964 s_cat (&t, s_sprintf (ESC "[%s%sm"
1965 , digit == 0 ? "2" : ""
1966 , "1"));
1967 break;
1968 case 'u':
1969 if ((prG->flags & FLAG_COLOR)
1970 && ((digit >= -1) && (digit <= 1)))
1971 s_cat (&t, s_sprintf (ESC "[%s%sm"
1972 , digit == 0 ? "2" : ""
1973 , "4"));
1974 break;
1975 case 'i':
1976 if ((prG->flags & FLAG_COLOR)
1977 && ((digit >= -1) && (digit <= 1)))
1978 s_cat (&t, s_sprintf (ESC "[%s%sm"
1979 , digit == 0 ? "2" : ""
1980 , "7"));
1981 break;
1982 case 'd':
1983 if (prG->flags & FLAG_COLOR)
1984 s_cat (&t, ESC SGR0);
1985 break;
1986 default:
1987 break;
1988 }
1989 p++;
1990 continue;
1991 } else if (*p == '\\')
1992 {
1993 p++;
1994 switch (*p)
1995 {
1996 case 'b': s_catc (&t, '\b'); break;
1997 case 'r': s_catc (&t, '\r'); break;
1998 case 'n': s_catc (&t, '\n'); break;
1999 case 't': s_catc (&t, '\t'); break;
2000 case 'e': s_catc (&t, ESCC); break;
2001 case '\\': s_catc (&t, '\\'); break;
2002 default: s_catc (&t, *p); break;
2003 }
2004 p++;
2005 continue;
2006 }
2007 s_catc (&t, *(p++));
2008 }
2009
2010 s_cat (&t, COLNONE);
2011 return t.txt;
2012 }
2013