1 /*
2  *  tty.c -- terminal handling routines for powwow
3  *
4  *  Copyright (C) 1998 by Massimiliano Ghilardi
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  */
12 #include <assert.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <limits.h>
16 #include <stdarg.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/types.h>
21 #include <time.h>
22 #include <unistd.h>
23 
24 #ifdef USE_LOCALE
25 #include <wchar.h>
26 #include <locale.h>
27 #endif
28 
29 #include "defines.h"
30 #include "main.h"
31 #include "edit.h"
32 #include "utils.h"
33 #include "list.h"
34 #include "tty.h"
35 #include "tcp.h"
36 
37 #ifdef POSIX
38 #    include <termios.h>
39 #elif !defined(USE_SGTTY)
40 #  ifdef APOLLO
41 #    include "/sys5.3/usr/include/sys/termio.h"
42 #  else
43 /*
44  * including both termio.h and termios.h might be an overkill, and gives
45  * many warnings, but seems to be necessary at times. works anyway.
46  */
47 #    include <termios.h>
48 #    include <termio.h>
49 #  endif
50 /* #else USE_SGTTY */
51 #endif
52 
53 /*
54  * SunOS 4 doesn't have function headers and has the defs needed from
55  * ioctl.h in termios.h.  Does it compile with USE_SGTTY?
56  */
57 #if (defined(sun) && defined(sparc) && ! defined(__SVR4))
58    extern int printf();
59 #else
60 #  include <sys/ioctl.h>
61 #endif
62 
63 #ifdef BSD_LIKE
64 #  include <sys/ioctl_compat.h>
65 #  define O_RAW RAW
66 #  define O_ECHO ECHO
67 #  define O_CBREAK CBREAK
68 #endif
69 
70 #ifdef POSIX
71 typedef struct termios termiostruct;
72 #else
73 #if defined(TCSETS) || defined(TCSETATTR)
74 #  ifndef TCSETS		/* cc for HP-UX  SHOULD define this... */
75 #    define TCSETS TCSETATTR
76 #    define TCGETS TCGETATTR
77 #  endif
78 typedef struct termios termiostruct;
79 #else
80 #  define TCSETS TCSETA
81 #  define TCGETS TCGETA
82 typedef struct termio termiostruct;
83 #endif
84 #endif /* POSIX */
85 
86 #ifdef VSUSP
87 #  define O_SUSP VSUSP
88 #else
89 #  ifdef SWTCH
90 #    define O_SUSP SWTCH
91 #  else
92 #    define O_SUSP SUSP
93 #  endif
94 #endif
95 
96 /* int ioctl(); */
97 
98 #ifdef USE_VT100	/* hard-coded vt100 features if no termcap: */
99 
100 static char kpadstart[] = "", kpadend[] = "", begoln[] = "\r",
101 	clreoln[] = "\033[K", clreoscr[] = "\033[J",
102 	leftcur[] = "\033[D", rightcur[] = "\033[C",  upcur[] = "\033[A",
103 	modebold[] = "\033[1m", modeblink[] = "\033[5m", modeinv[] = "\033[7m",
104 	modeuline[] = "\033[4m", modestandon[] = "", modestandoff[] = "",
105         modenorm[] = "\033[m", modenormbackup[4],
106 	cursor_left[] = "\033[D", cursor_right[] = "\033[C",
107         cursor_up[] = "\033[A", cursor_down[] = "\033[B";
108 
109 #define insertfinish (0)
110 static int len_begoln = 1, len_leftcur = 3, len_upcur = 3, gotocost = 8;
111 
112 #else /* not USE_VT100, termcap function declarations */
113 
114 int tgetent();
115 int tgetnum();
116 int tgetflag();
117 char *tgetstr();
118 char *tgoto();
119 
120 /* terminal escape sequences */
121 static char kpadstart[CAPLEN], kpadend[CAPLEN],
122 	leftcur[CAPLEN], rightcur[CAPLEN], upcur[CAPLEN], curgoto[CAPLEN],
123 	delchar[CAPLEN], insstart[CAPLEN], insstop[CAPLEN],
124         inschar[CAPLEN],
125 	begoln[CAPLEN], clreoln[CAPLEN], clreoscr[CAPLEN],
126 	cursor_left[CAPLEN], cursor_right[CAPLEN], cursor_up[CAPLEN],
127 	cursor_down[CAPLEN];
128 
129 /* attribute changers: */
130 static char modebold[CAPLEN], modeblink[CAPLEN], modeinv[CAPLEN],
131             modeuline[CAPLEN], modestandon[CAPLEN], modestandoff[CAPLEN],
132             modenorm[CAPLEN], modenormbackup[CAPLEN];
133 
134 static int len_begoln, len_clreoln, len_leftcur, len_upcur, gotocost,
135            deletecost, insertcost, insertfinish, inscharcost;
136 
137 static int extract __P ((char *cap, char *buf));
138 
139 #endif /* USE_VT100 */
140 
141 
142 char *tty_modebold = modebold,       *tty_modeblink = modeblink,
143      *tty_modeinv  = modeinv,        *tty_modeuline = modeuline,
144      *tty_modestandon = modestandon, *tty_modestandoff = modestandoff,
145      *tty_modenorm = modenorm,       *tty_modenormbackup = modenormbackup,
146      *tty_begoln   = begoln,         *tty_clreoln = clreoln,
147      *tty_clreoscr = clreoscr;
148 
149 int tty_read_fd = 0;
150 static int wrapglitch = 0;
151 
152 #ifdef USE_LOCALE
153 FILE *tty_read_stream;
154 static int orig_read_fd_fl;
155 static struct {
156     mbstate_t mbstate;              /* multibyte output shift state */
157     char      data[4096];           /* buffer for pending data */
158     size_t    used;                 /* bytes used of data */
159     int       fd;                   /* file descriptor to write to */
160 } tty_write_state;
161 #endif  /* USE_LOCALE */
162 
163 #ifdef USE_SGTTY
164 static struct sgttyb ttybsave;
165 static struct tchars tcsave;
166 static struct ltchars ltcsave;
167 #else /* not USE_SGTTY */
168 static termiostruct ttybsave;
169 #endif /* USE_SGTTY */
170 
171 /*
172  * Terminal handling routines:
173  * These are one big mess of left-justified chicken scratches.
174  * It should be handled more cleanly...but unix portability is what it is.
175  */
176 
177 /*
178  * Set the terminal to character-at-a-time-without-echo mode, and save the
179  * original state in ttybsave
180  */
__P0(void)181 void tty_start __P0 (void)
182 {
183 #ifdef USE_SGTTY
184     struct sgttyb ttyb;
185     struct ltchars ltc;
186     ioctl(tty_read_fd, TIOCGETP, &ttybsave);
187     ioctl(tty_read_fd, TIOCGETC, &tcsave);
188     ioctl(tty_read_fd, TIOCGLTC, &ltcsave);
189     ttyb = ttybsave;
190     ttyb.sg_flags = (ttyb.sg_flags|O_CBREAK) & ~O_ECHO;
191     ioctl(tty_read_fd, TIOCSETP, &ttyb);
192     ltc = ltcsave;
193     ltc.t_suspc = -1;
194     ioctl(tty_read_fd, TIOCSLTC, &ltc);
195 #else /* not USE_SGTTY */
196     termiostruct ttyb;
197 #if POSIX
198 	tcgetattr(tty_read_fd, &ttyb);
199 #else
200     ioctl(tty_read_fd, TCGETS, &ttyb);
201 #endif
202     ttybsave = ttyb;
203     ttyb.c_lflag &= ~(ECHO|ICANON);
204     ttyb.c_cc[VTIME] = 0;
205     ttyb.c_cc[VMIN] = 1;
206     /* disable the special handling of the suspend key (handle it ourselves) */
207     ttyb.c_cc[O_SUSP] = 0;
208 #if POSIX
209 	tcsetattr(tty_read_fd, TCSANOW, &ttyb);
210 #else
211     ioctl(tty_read_fd, TCSETS, &ttyb);
212 #endif
213 #endif /* USE_SGTTY */
214 
215 #ifdef USE_LOCALE
216     orig_read_fd_fl = fcntl(tty_read_fd, F_GETFL);
217     fcntl(tty_read_fd, F_SETFL, O_NONBLOCK | orig_read_fd_fl);
218 #endif
219 
220     tty_puts(kpadstart);
221     tty_flush();
222 
223 #ifdef USE_LOCALE
224     tty_write_state.fd = 1;
225     wcrtomb(NULL, L'\0', &tty_write_state.mbstate);
226 #else  /* ! USE_LOCALE */
227  #ifdef DEBUG_TTY
228     setvbuf(stdout, NULL, _IONBF, BUFSIZ);
229  #else
230     setvbuf(stdout, NULL, _IOFBF, BUFSIZ);
231  #endif
232 #endif  /* ! USE_LOCALE */
233 }
234 
235 /*
236  * Reset the terminal to its original state
237  */
__P0(void)238 void tty_quit __P0 (void)
239 {
240 #ifdef USE_SGTTY
241     ioctl(tty_read_fd, TIOCSETP, &ttybsave);
242     ioctl(tty_read_fd, TIOCSETC, &tcsave);
243     ioctl(tty_read_fd, TIOCSLTC, &ltcsave);
244 #else /* not USE_SGTTY */
245 #if POSIX
246 	tcsetattr(tty_read_fd, TCSANOW, &ttybsave);
247 #else
248     ioctl(tty_read_fd, TCSETS, &ttybsave);
249 #endif
250 #endif /* USE_SGTTY */
251     tty_puts(kpadend);
252     tty_flush();
253 #ifdef USE_LOCALE
254     fcntl(tty_read_fd, F_SETFL, orig_read_fd_fl);
255 #endif
256 }
257 
258 /*
259  * enable/disable special keys depending on the current linemode
260  */
__P0(void)261 void tty_special_keys __P0 (void)
262 {
263 #ifdef USE_SGTTY
264     struct tchars tc = {-1, -1, -1, -1, -1, -1};
265     struct ltchars ltc = {-1, -1, -1, -1, -1, -1};
266     struct sgttyb ttyb;
267     ioctl(tty_read_fd, TIOCGETP, &ttyb);
268     if (linemode & LM_CHAR) {
269 	/* char-by-char mode: set RAW mode*/
270 	ttyb.sg_flags |= RAW;
271     } else {
272 	/* line-at-a-time mode: enable spec keys, disable RAW */
273 	tc = tcsave;
274 	ltc = ltcsave;
275 	ltc.t_suspc = -1;	/* suspend key remains disabled */
276 	ttyb.sg_flags &= ~RAW;
277     }
278     ioctl(tty_read_fd, TIOCSETP, &ttyb);
279     ioctl(tty_read_fd, TIOCSETC, &tc);
280     ioctl(tty_read_fd, TIOCSLTC, &ltc);
281 #else /* not USE_SGTTY */
282     int i;
283     termiostruct ttyb;
284 #if POSIX
285 	tcgetattr(tty_read_fd, &ttyb);
286 #else
287     ioctl(tty_read_fd, TCGETS, &ttyb);
288 #endif
289     if (linemode & LM_CHAR)  {
290 	/* char-by-char mode: disable all special keys and set raw mode */
291 	for(i = 0; i < NCCS; i++)
292 	    ttyb.c_cc[i] = 0;
293 	ttyb.c_oflag &= ~OPOST;
294     } else {
295 	/* line at a time mode: enable them, except suspend */
296 	for(i = 0; i < NCCS; i++)
297 	    ttyb.c_cc[i] = ttybsave.c_cc[i];
298 	/* disable the suspend key (handle it ourselves) */
299 	ttyb.c_cc[O_SUSP] = 0;
300 	/* set cooked mode */
301 	ttyb.c_oflag |= OPOST;
302     }
303 #if POSIX
304 	tcsetattr(tty_read_fd, TCSANOW, &ttyb);
305 #else
306     ioctl(tty_read_fd, TCSETS, &ttyb);
307 #endif
308 #endif /* USE_SGTTY */
309 }
310 
311 /*
312  * get window size and react to any window size change
313  */
__P0(void)314 void tty_sig_winch_bottomhalf __P0 (void)
315 {
316     struct winsize wsiz;
317         /* if ioctl fails or gives silly values, don't change anything */
318 
319     if (ioctl(tty_read_fd, TIOCGWINSZ, &wsiz) == 0
320 	&& wsiz.ws_row > 0 && wsiz.ws_col > 0
321 	&& (lines != wsiz.ws_row || cols != wsiz.ws_col))
322     {
323 	lines = wsiz.ws_row;
324 	cols_1 = cols = wsiz.ws_col;
325 	if (!wrapglitch)
326 	    cols_1--;
327 
328 	if (tcp_main_fd != -1)
329 	    tcp_write_tty_size();
330 	line0 += lines - olines;
331 
332 	tty_gotoxy(0, line0);
333 	/* so we know where the cursor is */
334 #ifdef BUG_ANSI
335 	if (edattrbg)
336 	    tty_printf("%s%s", edattrend, tty_clreoscr);
337 	else
338 #endif
339 	    tty_puts(tty_clreoscr);
340 
341 	olines = lines;
342 	status(1);
343     }
344 }
345 
346 /*
347  * read termcap definitions
348  */
__P0(void)349 void tty_bootstrap __P0 (void)
350 {
351 #ifdef USE_LOCALE
352     tty_read_stream = stdin;
353 #endif
354 
355 #ifndef USE_VT100
356     struct tc_init_node {
357 	char cap[4], *buf;
358 	int *len, critic;
359     };
360     static struct tc_init_node tc_init[] = {
361 	{ "cm", curgoto, 0, 1 },
362 	{ "ce", clreoln, &len_clreoln, 1 },
363 	{ "cd", clreoscr, 0, 1 },
364 	{ "nd", rightcur, 0, 1 },
365 	{ "le", leftcur, &len_leftcur, 0 },
366 	{ "up", upcur,   &len_upcur,  0 },
367 	{ "cr", begoln,  &len_begoln, 0 },
368 	{ "ic", inschar, &inscharcost, 0 },
369 	{ "im", insstart, &insertcost, 0 },
370 	{ "ei", insstop, &insertcost, 0 },
371 	{ "dm", delchar, &deletecost, 0 },
372 	{ "dc", delchar, &deletecost, 0 },
373 	{ "ed", delchar, &deletecost, 0 },
374 	{ "me", modenorm, 0, 0 },
375 	{ "md", modebold, 0, 0 },
376 	{ "mb", modeblink, 0, 0 },
377 	{ "mr", modeinv, 0, 0 },
378 	{ "us", modeuline, 0, 0 },
379 	{ "so", modestandon, 0, 0 },
380 	{ "se", modestandoff, 0, 0 },
381 	{ "ks", kpadstart, 0, 0 },
382 	{ "ke", kpadend, 0, 0 },
383 	{ "kl", cursor_left, 0, 0 },
384 	{ "kr", cursor_right, 0, 0 },
385 	{ "ku", cursor_up, 0, 0 },
386 	{ "kd", cursor_down, 0, 0 },
387 	{ "", NULL, 0, 0 }
388     };
389     struct tc_init_node *np;
390     char tcbuf[2048];		/* by convention, this is enough */
391     int i;
392 #endif /* not USE_VT100 */
393 #if !defined(USE_VT100) || defined(BUG_TELNET)
394     char *term = getenv("TERM");
395     if (!term) {
396 	fprintf(stderr, "$TERM not set\n");
397 	exit(1);
398     }
399 #endif /* !defined(USE_VT100) || defined(BUG_TELNET) */
400 #ifdef USE_VT100
401     cols = 80;
402 #  ifdef LINES
403     lines = LINES;
404 #  else  /* not LINES */
405     lines = 24;
406 #  endif /* LINES */
407 #else   /* not USE_VT100 */
408     switch(tgetent(tcbuf, term)) {
409      case 1:
410 	break;
411      case 0:
412 	fprintf(stderr,
413 		"There is no entry for \"%s\" in the terminal data base.\n", term);
414 	fprintf(stderr,
415 		"Please set your $TERM environment variable correctly.\n");
416 	exit(1);
417      default:
418 	syserr("tgetent");
419     }
420     for(np = tc_init; np->cap[0]; np++)
421 	if ((i = extract(np->cap, np->buf))) {
422 	    if (np->len) *np->len += i;
423 	} else if (np->critic) {
424 	    fprintf(stderr,
425 		    "Your \"%s\" terminal is not powerful enough, missing \"%s\".\n",
426 		    term, np->cap);
427 	    exit(1);
428 	}
429     if (!len_begoln)
430 	strcpy(begoln, "\r"), len_begoln = 1;
431     if (!len_leftcur)
432 	strcpy(leftcur, "\b"), len_leftcur = 1;
433 
434     gotocost = strlen(tgoto(curgoto, cols - 1, lines - 1));
435     insertfinish = gotocost + len_clreoln;
436 
437     /* this must be before getting window size */
438     wrapglitch = tgetflag("xn");
439 
440     tty_sig_winch_bottomhalf(); /* get window size */
441 
442 #endif  /* not USE_VT100 */
443     strcpy(modenormbackup, modenorm);
444 #ifdef BUG_TELNET
445     if (strncmp(term, "vt10", 4) == 0) {
446 	/* might be NCSA Telnet 2.2 for PC, which doesn't reset colours */
447 	sprintf(modenorm, "\033[;%c%d;%s%dm",
448 		DEFAULTFG<LOWCOLORS ? '3' : '9',  DEFAULTFG % LOWCOLORS,
449 		DEFAULTBG<LOWCOLORS ? "4" : "10", DEFAULTBG % LOWCOLORS);
450     }
451 #endif /* BUG_TELNET */
452 }
453 
454 /*
455  * add the default keypad bindings to the list
456  */
__P0(void)457 void tty_add_walk_binds __P0 (void)
458 {
459     /*
460      * Note: termcap doesn't have sequences for the numeric keypad, so we just
461      * assume they are the same as for a vt100. They can be redefined
462      * at runtime anyway (using #bind or #rebind)
463      */
464     add_keynode("KP2", "\033Or", 0, key_run_command, "s");
465     add_keynode("KP3", "\033Os", 0, key_run_command, "d");
466     add_keynode("KP4", "\033Ot", 0, key_run_command, "w");
467     add_keynode("KP5", "\033Ou", 0, key_run_command, "exits");
468     add_keynode("KP6", "\033Ov", 0, key_run_command, "e");
469     add_keynode("KP7", "\033Ow", 0, key_run_command, "look");
470     add_keynode("KP8", "\033Ox", 0, key_run_command, "n");
471     add_keynode("KP9", "\033Oy", 0, key_run_command, "u");
472 }
473 
474 /*
475  * initialize the key binding list
476  */
__P0(void)477 void tty_add_initial_binds __P0 (void)
478 {
479     struct b_init_node {
480 	char *label, *seq;
481 	function_any funct;
482     };
483     static struct b_init_node b_init[] = {
484 	{ "LF",		"\n",		enter_line },
485 	{ "Ret",	"\r",		enter_line },
486 	{ "BS",		"\b",		del_char_left },
487 	{ "Del",	"\177",		del_char_left },
488 	{ "Tab",	"\t",		complete_word },
489 	{ "C-a",	"\001",		begin_of_line },
490 	{ "C-b",	"\002",		prev_char },
491 	{ "C-d",	"\004",		del_char_right },
492 	{ "C-e",	"\005",		end_of_line },
493 	{ "C-f",	"\006",		next_char },
494 	{ "C-k",	"\013",		kill_to_eol },
495 	{ "C-l",	"\014",		redraw_line },
496 	{ "C-n",	"\016",		next_line },
497 	{ "C-p",	"\020",		prev_line },
498 	{ "C-t",	"\024",		transpose_chars },
499 	{ "C-w",	"\027",		to_history },
500 	{ "C-z",	"\032",		suspend_powwow },
501 	{ "M-Tab",	"\033\t",	complete_line },
502 	{ "M-b",	"\033b",	prev_word },
503 	{ "M-d",	"\033d",	del_word_right },
504 	{ "M-f",	"\033f",	next_word },
505 	{ "M-k",	"\033k",	redraw_line_noprompt },
506 	{ "M-t",	"\033t",	transpose_words },
507 	{ "M-u",	"\033u",	upcase_word },
508 	{ "M-l",	"\033l",	downcase_word },
509 	{ "M-BS",	"\033\b",	del_word_left },
510 	{ "M-Del",	"\033\177",	del_word_left },
511 	{ "",		"",		0 }
512     };
513     struct b_init_node *p = b_init;
514     do {
515 	add_keynode(p->label, p->seq, 0, p->funct, NULL);
516     } while((++p)->seq[0]);
517 
518     if (*cursor_left ) add_keynode("Left" , cursor_left , 0, prev_char, NULL);
519     if (*cursor_right) add_keynode("Right", cursor_right, 0, next_char, NULL);
520     if (*cursor_up   ) add_keynode("Up"   , cursor_up   , 0, prev_line, NULL);
521     if (*cursor_down ) add_keynode("Down" , cursor_down , 0, next_line, NULL);
522 }
523 
524 #ifndef USE_VT100
525 /*
526  * extract termcap 'cap' and strcat it to buf.
527  * return the lenght of the extracted string.
528  */
__P2(char *,cap,char *,buf)529 static int extract __P2 (char *,cap, char *,buf)
530 {
531     static char *bp;
532     char *d = buf + strlen(buf);
533     char *s = tgetstr(cap, (bp = d, &bp));
534     int len;
535     if (!s) return (*bp = 0);
536     /*
537      * Remove the padding information. We assume that no terminals
538      * need padding nowadays. At least it makes things much easier.
539      */
540     s += strspn(s, "0123456789*");
541     for(len = 0; *s; *d++ = *s++, len++)
542 	if (*s == '$' && *(s + 1) == '<')
543 	if (!(s = strchr(s, '>')) || !*++s) break;
544     *d = 0;
545     return len;
546 }
547 #endif /* not USE_VT100 */
548 
549 /*
550  * position the cursor using absolute coordinates
551  * note: does not flush the output buffer
552  */
__P2(int,col,int,line)553 void tty_gotoxy __P2 (int,col, int,line)
554 {
555 #ifdef USE_VT100
556     tty_printf("\033[%d;%dH", line + 1, col + 1);
557 #else
558     tty_puts(tgoto(curgoto, col, line));
559 #endif
560 }
561 
562 /*
563  * optimized cursor movement
564  * from (fromcol, fromline) to (tocol, toline)
565  * if tocol > 0, (tocol, toline) must lie on editline.
566  */
__P4(int,fromcol,int,fromline,int,tocol,int,toline)567 void tty_gotoxy_opt __P4 (int,fromcol, int,fromline, int,tocol, int,toline)
568 {
569     static char buf[BUFSIZE];
570     char *cp = buf;
571     int cost, i, dist;
572 
573     CLIP(fromline, 0, lines-1);
574     CLIP(toline  , 0, lines-1);
575 
576     /* First, move vertically to the correct line, then horizontally
577      * to the right column. If this turns out to be fewer characters
578      * than a direct cursor positioning (tty_gotoxy), use that.
579      */
580     for (;;) {	/* gotoless */
581 	if ((i = toline - fromline) < 0) {
582 	    if (!len_upcur || (cost = -i * len_upcur) >= gotocost)
583 		break;
584 	    do {
585 		strcpy(cp, upcur);
586 	        cp += len_upcur;
587 	    } while(++i);
588 	} else if ((cost = 2 * i)) {	/* lf is mapped to crlf on output */
589 	    if (cost >= gotocost)
590 		break;
591 	    do
592 	        *cp++ = '\n';
593 	    while (--i);
594 	    fromcol = 0;
595 	}
596 	if ((i = tocol - fromcol) < 0) {
597 	    dist = -i * len_leftcur;
598 	    if (dist <= len_begoln + tocol) {
599 		if ((cost += dist) > gotocost)
600 		    break;
601 		do {
602 		    strcpy(cp, leftcur);
603 		    cp += len_leftcur;
604 		} while(++i);
605 	    } else {
606 		if ((cost += len_begoln) > gotocost)
607 		    break;
608 		strcpy(cp, begoln);
609 		cp += len_begoln;
610 		fromcol = 0;  i = tocol;
611 	    }
612 	}
613 	if (i) {
614 	    /*
615 	     * if hiliting in effect or prompt contains escape sequences,
616 	     * just use tty_gotoxy
617 	     */
618 	    if (cost + i > gotocost || *edattrbeg || promptlen != col0)
619 		break;
620 	    if (fromcol < col0 && toline == line0) {
621 		strcpy(cp, promptstr+fromcol);
622 		cp += promptlen-fromcol;
623 		fromcol = col0;
624 	    }
625 	    my_strncpy(cp, edbuf + (toline - line0) * cols_1 + fromcol - col0,
626 		    tocol - fromcol);
627 	    cp += tocol - fromcol;
628 	}
629 	*cp = 0;
630 	tty_puts(buf);
631 	return;
632     }
633     tty_gotoxy(tocol, toline);
634 }
635 
636 
637 /*
638  * GH: change the position on input line (gotoxy there, and set pos)
639  *     from cancan 2.6.3a
640  */
__P1(int,new_pos)641 void input_moveto __P1 (int,new_pos)
642 {
643     /*
644      * FEATURE: the line we are moving to might be less than 0, or greater
645      * than lines - 1, if the display is too small to hold the whole editline.
646      * In that case, the input line should be (partially) redrawn.
647      */
648     if (new_pos < 0)
649 	new_pos = 0;
650     else if (new_pos > edlen)
651 	new_pos = edlen;
652     if (new_pos == pos)
653 	return;
654 
655     if (line_status == 0) {
656 	int fromline = CURLINE(pos), toline = CURLINE(new_pos);
657 	if (toline < 0)
658 	    line0 -= toline, toline = 0;
659 	else if (toline > lines - 1)
660 	    line0 -= toline - lines + 1, toline = lines - 1;
661 	tty_gotoxy_opt(CURCOL(pos), fromline, CURCOL(new_pos), toline);
662     }
663     pos = new_pos;
664 }
665 
666 /*
667  * delete n characters at current position (the position is unchanged)
668  * assert(n < edlen - pos)
669  */
__P1(int,n)670 void input_delete_nofollow_chars __P1 (int,n)
671 {
672     int r_cost, p = pos, d_cost;
673     int nl = pos - CURCOL(pos);	/* this line's starting pos (can be <= 0) */
674     int cl = CURLINE(pos);	/* current line */
675 
676     if (n > edlen - p)
677 	n = edlen - p;
678     if (n <= 0)
679 	return;
680 
681     d_cost = p + n;
682     if (line_status != 0) {
683 	memmove(edbuf + p, edbuf + d_cost, edlen - d_cost + 1);
684 	edlen -= n;
685 	return;
686     }
687 
688     memmove(edbuf + p, edbuf + d_cost, edlen - d_cost);
689     memset(edbuf + edlen - n, (int)' ', n);
690     for (;; tty_putc('\n'), p = nl, cl++) {
691 	d_cost = 0;
692 	/* FEATURE: ought to be "d_cost = n > gotocost ? -gotocost : -n;"
693 	 * since redraw will need to goto back. Of little importance */
694 	if ((r_cost = edlen) > (nl += cols_1))
695 	    r_cost = nl, d_cost = n + gotocost;
696 	r_cost -= p;
697 #ifndef USE_VT100
698 	/*
699 	 * FEATURE: no clreoln is used (it might cost less in the occasion
700 	 * we delete more than one char). Simplicity
701 	 */
702 	if (deletecost && deletecost * n + d_cost < r_cost) {
703 #ifdef BUG_ANSI
704 	    if (edattrbg)
705 		tty_puts(edattrend);
706 #endif
707 	    for (d_cost = n; d_cost; d_cost--)
708 	        tty_puts(delchar);
709 #ifdef BUG_ANSI
710 	    if (edattrbg)
711 		tty_puts(edattrbeg);
712 #endif
713 
714 	    if (edlen <= nl)
715 		break;
716 
717 	    tty_gotoxy(cols_1 - n, cl);
718 	    tty_printf("%.*s", n, edbuf + nl - n);
719 	} else
720 #endif /* not USE_VT100 */
721 	{
722 #ifdef BUG_ANSI
723 	    if (edattrbg && p <= edlen - n && p + r_cost >= edlen - n)
724 		tty_printf("%.*s%s%.*s", edlen - p - n, edbuf + p,
725 					 edattrend,
726 					 r_cost - edlen + p + n, edbuf + edlen - n);
727 	    else
728 #endif
729 		tty_printf("%.*s", r_cost, edbuf + p);
730 
731 	    p += r_cost;
732 
733 	    if (edlen <= nl) {
734 #ifdef BUG_ANSI
735 		if (edattrbg)
736 		    tty_puts(edattrbeg);
737 #endif
738 		break;
739 	    }
740 	}
741     }
742     edbuf[edlen -= n] = '\0';
743     switch(pos - p) {
744       case 1:
745 	tty_puts(leftcur);
746 	break;
747       case 0:
748 	break;
749       default:
750 	tty_gotoxy_opt(CURCOL(p), cl, CURCOL(pos), CURLINE(pos));
751 	break;
752     }
753 }
754 
755 /*
756  * GH: print a char on current position (overwrite), advance position
757  *     from cancan 2.6.3a
758  */
__P1(char,c)759 void input_overtype_follow __P1 (char,c)
760 {
761     if (pos >= edlen)
762 	return;
763     edbuf[pos++] = c;
764 
765     if (line_status == 0) {
766 	tty_putc(c);
767 	if (!CURCOL(pos)) {
768 #ifdef BUG_ANSI
769 	    if (edattrbg)
770 		tty_printf("%s\n%s", edattrend, edattrbeg);
771 	    else
772 #endif
773 		tty_putc('\n');
774 	}
775     }
776 }
777 
778 /*
779  * insert n characters at input line current position.
780  * The position is set to after the inserted characters.
781  */
__P2(char *,str,int,n)782 void input_insert_follow_chars __P2 (char *,str, int,n)
783 {
784     int r_cost, i_cost, p = pos;
785     int nl = p - CURCOL(p);	/* next line's starting pos */
786     int cl = CURLINE(p);	/* current line */
787 
788     if (edlen + n >= BUFSIZE)
789 	n = BUFSIZE - edlen - 1;
790     if (n <= 0)
791 	return;
792 
793     memmove(edbuf + p + n, edbuf + p, edlen + 1 - p);
794     memmove(edbuf + p, str, n);
795     edlen += n; pos += n;
796 
797     if (line_status != 0)
798 	return;
799 
800     do {
801 	i_cost = n;
802 	if ((r_cost = edlen) > (nl += cols_1))
803 	    r_cost = nl, i_cost += insertfinish;
804 	r_cost -= p;
805 #ifndef USE_VT100
806 	/* FEATURE: insert mode is used only when one char is inserted
807 	 (which is probably true > 95% of the time). Simplicity */
808 	if (n == 1 && inscharcost && inscharcost + i_cost < r_cost) {
809 	    tty_printf("%s%c", inschar, edbuf[p++]);
810 	    if (edlen > nl && !wrapglitch) {
811 		tty_gotoxy(cols_1, cl);
812 		tty_puts(clreoln);
813 	    }
814 	} else
815 #endif /* not USE_VT100 */
816 	{
817 	    tty_printf("%.*s", r_cost, edbuf + p);  p += r_cost;
818 	}
819 	if (edlen < nl)
820 	    break;
821 #ifdef BUG_ANSI
822 	if (edattrbg)
823 	    tty_printf("%s\n%s", edattrend, edattrbeg);
824 	else
825 #endif
826 	    tty_puts("\n");
827 	p = nl;
828 	if (++cl > lines - 1) {
829 	    cl = lines - 1;
830 	    line0--;
831 	}
832     } while (edlen > nl);
833 
834     if (p != pos)
835 	tty_gotoxy_opt(CURCOL(p), cl, CURCOL(pos), CURLINE(pos));
836 }
837 
838 #ifdef USE_LOCALE
839 /* curses wide character support by Dain */
840 
tty_puts(const char * s)841 void tty_puts __P ((const char *s))
842 {
843     while (*s)
844         tty_putc(*s++);
845 }
846 
tty_putc(char c)847 void tty_putc __P ((char c))
848 {
849     size_t r;
850     int ignore_error = 0;
851     if (tty_write_state.used + MB_LEN_MAX > sizeof tty_write_state.data)
852         tty_flush();
853 again:
854     r = wcrtomb(tty_write_state.data + tty_write_state.used, (unsigned char)c,
855                 &tty_write_state.mbstate);
856     if (r == (size_t)-1) {
857         if (ignore_error)
858             return;
859         if (errno != EILSEQ) {
860             perror("mcrtomb()");
861             abort();
862         }
863         /* character cannot be represented; try to write a question
864          * mark instead, but ignore any errors */
865         ignore_error = 1;
866         c = '?';
867         goto again;
868     }
869     assert(r <= MB_LEN_MAX);
870     tty_write_state.used += r;
871 }
872 
tty_printf(const char * format,...)873 int tty_printf __P ((const char *format, ...))
874 {
875     char buf[1024], *bufp = buf;
876     va_list va;
877     int res;
878 
879     char *old_locale = strdup(setlocale(LC_ALL, NULL));
880 
881     setlocale(LC_ALL, "C");
882 
883     va_start(va, format);
884     res = vsnprintf(buf, sizeof buf, format, va);
885     va_end(va);
886 
887     if (res >= sizeof buf) {
888 	bufp = alloca(res + 1);
889 	va_start(va, format);
890 	vsprintf(bufp, format, va);
891         assert(strlen(bufp) == res);
892 	va_end(va);
893     }
894 
895     setlocale(LC_ALL, old_locale);
896     free(old_locale);
897 
898     tty_puts(bufp);
899 
900     return res;
901 }
902 
903 static char tty_in_buf[MB_LEN_MAX + 1];
904 static int tty_in_buf_used = 0;
905 
tty_has_chars(void)906 int tty_has_chars __P ((void))
907 {
908     return !!tty_in_buf_used;
909 }
910 
tty_read(char * buf,size_t count)911 int tty_read __P ((char *buf, size_t count))
912 {
913     int result = 0;
914     int converted;
915     wchar_t wc;
916     int did_read = 0, should_read = 0;
917 
918     if (count && tty_in_buf_used) {
919 	converted = mbtowc(&wc, tty_in_buf, tty_in_buf_used);
920 	if (converted >= 0)
921 	    goto another;
922     }
923 
924     while (count) {
925 	should_read = sizeof tty_in_buf - tty_in_buf_used;
926 	did_read = read(tty_read_fd, tty_in_buf + tty_in_buf_used,
927                         should_read);
928 
929 	if (did_read < 0 && errno == EINTR)
930 	    continue;
931 	if (did_read <= 0)
932 	    break;
933 
934 	tty_in_buf_used += did_read;
935 
936 	converted = mbtowc(&wc, tty_in_buf, tty_in_buf_used);
937 	if (converted < 0)
938 	    break;
939 
940     another:
941 	if (converted == 0)
942 	    converted = 1;
943 
944 	if (!(wc & ~0xff)) {
945 	    *buf++ = (unsigned char)wc;
946 	    --count;
947 	    ++result;
948 	}
949 
950 	tty_in_buf_used -= converted;
951 	memmove(tty_in_buf, tty_in_buf + converted, tty_in_buf_used);
952 
953 	if (count == 0)
954 	    break;
955 
956 	converted = mbtowc(&wc, tty_in_buf, tty_in_buf_used);
957 	if (converted >= 0 && tty_in_buf_used)
958 	    goto another;
959 
960 	if (did_read < should_read)
961 	    break;
962     }
963 
964     return result;
965 }
966 
967 
tty_gets(char * s,int size)968 void tty_gets __P ((char *s, int size))
969 {
970     wchar_t *ws = alloca(size * sizeof *ws);
971 
972     if (!fgetws(ws, size, stdin))
973 	return;
974 
975     while (*ws) {
976 	if (!(*ws & ~0xff))
977 	    *s++ = (unsigned char)*ws;
978 	++ws;
979     }
980 }
981 
tty_flush(void)982 void tty_flush __P ((void))
983 {
984     size_t n = tty_write_state.used;
985     char *data = tty_write_state.data;
986     while (n > 0) {
987         ssize_t r;
988         for (;;) {
989             r = write(tty_write_state.fd, data, n);
990             if (r >= 0)
991                 break;
992             if (errno == EINTR)
993                 continue;
994             if (errno != EAGAIN) {
995                 fprintf(stderr, "Cannot write to tty: %s\n", strerror(errno));
996                 abort();
997             }
998             fd_set wfds;
999             FD_ZERO(&wfds);
1000             FD_SET(tty_write_state.fd, &wfds);
1001             do {
1002                 r = select(tty_write_state.fd + 1, NULL, &wfds, NULL, NULL);
1003             } while (r < 0 && errno == EINTR);
1004             if (r <= 0) {
1005                 fprintf(stderr, "Cannot write to tty; select failed: %s\n",
1006                         r == 0 ? "returned zero" : strerror(errno));
1007                 abort();
1008             }
1009         }
1010         if (r < 0) {
1011             fprintf(stderr, "Cannot write to tty: %s\n", strerror(errno));
1012             abort();
1013         }
1014         n -= r;
1015         data += r;
1016     }
1017     tty_write_state.used = 0;
1018 }
1019 
tty_raw_write(char * data,size_t len)1020 void tty_raw_write __P ((char *data, size_t len))
1021 {
1022     if (len == 0) return;
1023 
1024     for (;;) {
1025         size_t s = sizeof tty_write_state.data - tty_write_state.used;
1026         if (s > len) s = len;
1027         memcpy(tty_write_state.data + tty_write_state.used, data, s);
1028         len -= s;
1029         tty_write_state.used += s;
1030         if (len == 0)
1031             break;
1032         data += s;
1033         tty_flush();
1034     }
1035 }
1036 
1037 #endif /* USE_LOCALE */
1038