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, <csave);
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, <c);
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, <csave);
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, <c);
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