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