xref: /titanic_52/usr/src/cmd/vi/port/ex_vget.c (revision 1cb6af97c6f66f456d4f726ef056e1ebc0f73305)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /* Copyright (c) 1981 Regents of the University of California */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 /*
31  * Copyright (c) 2000 by Sun Microsystems, Inc.
32  * All rights reserved.
33  */
34 
35 #include "ex.h"
36 #include "ex_tty.h"
37 #include "ex_vis.h"
38 
39 /*
40  * Input routines for open/visual.
41  * We handle upper case only terminals in visual and reading from the
42  * echo area here as well as notification on large changes
43  * which appears in the echo area.
44  */
45 
46 /*
47  * Return the key.
48  */
49 ungetkey(c)
50 	int c;		/* char --> int */
51 {
52 
53 	if (Peekkey != ATTN)
54 		Peekkey = c;
55 }
56 
57 /*
58  * Return a keystroke, but never a ^@.
59  */
60 getkey()
61 {
62 	register int c;		/* char --> int */
63 
64 	do {
65 		c = getbr();
66 		if (c==0)
67 			beep();
68 	} while (c == 0);
69 	return (c);
70 }
71 
72 /*
73  * Tell whether next keystroke would be a ^@.
74  */
75 peekbr()
76 {
77 
78 	Peekkey = getbr();
79 	return (Peekkey == 0);
80 }
81 
82 short	precbksl;
83 
84 /*
85  * Get a keystroke, including a ^@.
86  * If an key was returned with ungetkey, that
87  * comes back first.  Next comes unread input (e.g.
88  * from repeating commands with .), and finally new
89  * keystrokes.
90  *
91  * The hard work here is in mapping of \ escaped
92  * characters on upper case only terminals.
93  */
94 getbr()
95 {
96 	unsigned char ch;
97 	register int c, d;
98 	register unsigned char *colp;
99 	int cnt;
100 	static unsigned char Peek2key;
101 	extern short slevel, ttyindes;
102 
103 getATTN:
104 	if (Peekkey) {
105 		c = Peekkey;
106 		Peekkey = 0;
107 		return (c);
108 	}
109 	if (Peek2key) {
110 		c = Peek2key;
111 		Peek2key = 0;
112 		return (c);
113 	}
114 	if (vglobp) {
115 		if (*vglobp)
116 			return (lastvgk = *vglobp++);
117 		lastvgk = 0;
118 		return (ESCAPE);
119 	}
120 	if (vmacp) {
121 		if (*vmacp)
122 			return(*vmacp++);
123 		/* End of a macro or set of nested macros */
124 		vmacp = 0;
125 		if (inopen == -1)	/* don't mess up undo for esc esc */
126 			vundkind = VMANY;
127 		inopen = 1;	/* restore old setting now that macro done */
128 		vch_mac = VC_NOTINMAC;
129 	}
130 	flusho();
131 again:
132 	if ((c=read(slevel == 0 ? 0 : ttyindes, &ch, 1)) != 1) {
133 		if (errno == EINTR)
134 			goto getATTN;
135 		else if (errno == EIO)
136 		  kill(getpid(), SIGHUP);
137 
138 		error(gettext("Input read error"));
139 	}
140 	c = ch;
141 	if (beehive_glitch && slevel==0 && c == ESCAPE) {
142 		if (read(0, &Peek2key, 1) != 1)
143 			goto getATTN;
144 		switch (Peek2key) {
145 		case 'C':	/* SPOW mode sometimes sends \EC for space */
146 			c = ' ';
147 			Peek2key = 0;
148 			break;
149 		case 'q':	/* f2 -> ^C */
150 			c = CTRL('c');
151 			Peek2key = 0;
152 			break;
153 		case 'p':	/* f1 -> esc */
154 			Peek2key = 0;
155 			break;
156 		}
157 	}
158 
159 	/*
160 	 * The algorithm here is that of the UNIX kernel.
161 	 * See the description in the programmers manual.
162 	 */
163 	if (UPPERCASE) {
164 		if (isupper(c))
165 			c = tolower(c);
166 		if (c == '\\') {
167 			if (precbksl < 2)
168 				precbksl++;
169 			if (precbksl == 1)
170 				goto again;
171 		} else if (precbksl) {
172 			d = 0;
173 			if (islower(c))
174 				d = toupper(c);
175 			else {
176 				colp = (unsigned char *)"({)}!|^~'~";
177 				while (d = *colp++)
178 					if (d == c) {
179 						d = *colp++;
180 						break;
181 					} else
182 						colp++;
183 			}
184 			if (precbksl == 2) {
185 				if (!d) {
186 					Peekkey = c;
187 					precbksl = 0;
188 					c = '\\';
189 				}
190 			} else if (d)
191 				c = d;
192 			else {
193 				Peekkey = c;
194 				precbksl = 0;
195 				c = '\\';
196 			}
197 		}
198 		if (c != '\\')
199 			precbksl = 0;
200 	}
201 #ifdef TRACE
202 	if (trace) {
203 		if (!techoin) {
204 			tfixnl();
205 			techoin = 1;
206 			fprintf(trace, "*** Input: ");
207 		}
208 		tracec(c);
209 	}
210 #endif
211 	lastvgk = 0;
212 	return (c);
213 }
214 
215 /*
216  * Get a key, but if a delete, quit or attention
217  * is typed return 0 so we will abort a partial command.
218  */
219 getesc()
220 {
221 	register int c;
222 
223 	c = getkey();
224 	switch (c) {
225 
226 	case CTRL('v'):
227 	case CTRL('q'):
228 		c = getkey();
229 		return (c);
230 
231 	case ATTN:
232 	case QUIT:
233 		ungetkey(c);
234 		return (0);
235 
236 	case ESCAPE:
237 		return (0);
238 	}
239 	return (c);
240 }
241 
242 /*
243  * Peek at the next keystroke.
244  */
245 peekkey()
246 {
247 
248 	Peekkey = getkey();
249 	return (Peekkey);
250 }
251 
252 /*
253  * Read a line from the echo area, with single character prompt c.
254  * A return value of 1 means the user blewit or blewit away.
255  */
256 readecho(c)
257 	unsigned char c;
258 {
259 	register unsigned char *sc = cursor;
260 	register int (*OP)();
261 	bool waste;
262 	register int OPeek;
263 
264 	if (WBOT == WECHO)
265 		vclean();
266 	else
267 		vclrech(0);
268 	splitw++;
269 	vgoto(WECHO, 0);
270 	putchar(c);
271 	vclreol();
272 	vgoto(WECHO, 1);
273 	cursor = linebuf; linebuf[0] = 0; genbuf[0] = c;
274 	ixlatctl(1);
275 	if (peekbr()) {
276 		if (!INS[0] || (unsigned char)INS[128] == 0200) {
277 			INS[128] = 0;
278 			goto blewit;
279 		}
280 		vglobp = INS;
281 	}
282 	OP = Pline; Pline = normline;
283 	(void)vgetline(0, genbuf + 1, &waste, c);
284 	doomed = 0;	/* don't care about doomed characters in echo line */
285 	ixlatctl(0);
286 	if (Outchar == termchar)
287 		putchar('\n');
288 	vscrap();
289 	Pline = OP;
290 	if (Peekkey != ATTN && Peekkey != QUIT && Peekkey != CTRL('h')) {
291 		cursor = sc;
292 		vclreol();
293 		return (0);
294 	}
295 blewit:
296 	OPeek = Peekkey==CTRL('h') ? 0 : Peekkey; Peekkey = 0;
297 	splitw = 0;
298 	vclean();
299 	vshow(dot, NOLINE);
300 	vnline(sc);
301 	Peekkey = OPeek;
302 	return (1);
303 }
304 
305 /*
306  * A complete command has been defined for
307  * the purposes of repeat, so copy it from
308  * the working to the previous command buffer.
309  */
310 setLAST()
311 {
312 
313 	if (vglobp || vmacp)
314 		return;
315 	lastreg = vreg;
316 	lasthad = Xhadcnt;
317 	lastcnt = Xcnt;
318 	*lastcp = 0;
319 	CP(lastcmd, workcmd);
320 }
321 
322 /*
323  * Gather up some more text from an insert.
324  * If the insertion buffer oveflows, then destroy
325  * the repeatability of the insert.
326  */
327 addtext(cp)
328 	unsigned char *cp;
329 {
330 
331 	if (vglobp)
332 		return;
333 	addto(INS, cp);
334 	if ((unsigned char)INS[128] == 0200)
335 		lastcmd[0] = 0;
336 }
337 
338 setDEL()
339 {
340 
341 	setBUF(DEL);
342 }
343 
344 /*
345  * Put text from cursor upto wcursor in BUF.
346  */
347 setBUF(BUF)
348 	register unsigned char *BUF;
349 {
350 	register int c;
351 	register unsigned char *wp = wcursor;
352 
353 	c = *wp;
354 	*wp = 0;
355 	BUF[0] = 0;
356 	BUF[128] = 0;
357 	addto(BUF, cursor);
358 	*wp = c;
359 }
360 
361 addto(buf, str)
362 	register unsigned char *buf, *str;
363 {
364 
365 	if ((unsigned char)buf[128] == 0200)
366 		return;
367 	if (strlen(buf) + strlen(str) + 1 >= VBSIZE) {
368 		buf[128] = 0200;
369 		return;
370 	}
371 	(void)strcat(buf, str);
372 	buf[128] = 0;
373 }
374 
375 /*
376  * Verbalize command name and embed it in message.
377  */
378 char *
379 verbalize(cnt, cmdstr, sgn)
380 int cnt;
381 char *cmdstr, *sgn;
382 {
383 	if (cmdstr[0] == '\0')
384 		cmdstr = (char *)Command;
385 	if (sgn[0] == '\0') {
386 		switch (cmdstr[0]) {
387 		    case 'c':
388 			if (cmdstr[1] == 'h') {
389 				printf((cnt == 1) ? gettext("1 line changed") :
390 					gettext("%d lines changed"), cnt);
391 				break;
392 			} else if (cmdstr[1] != 'o') {
393 				goto Default;
394 			}
395 		    case 't':
396 			if (cmdstr[1] != '\0')
397 				goto Default;
398 			printf((cnt == 1) ? gettext("1 line copied") :
399 			       gettext("%d lines copied"), cnt);
400 			break;
401 		    case 'd':
402 			printf((cnt == 1) ? gettext("1 line deleted") :
403 			       gettext("%d lines deleted"), cnt);
404 			break;
405 		    case 'j':
406 			printf((cnt == 1) ? gettext("1 line joined") :
407 			       gettext("%d lines joined"), cnt);
408 			break;
409 		    case 'm':
410 			printf((cnt == 1) ? gettext("1 line moved") :
411 			       gettext("%d lines moved"), cnt);
412 			break;
413 		    case 'p':
414 			printf((cnt == 1) ? gettext("1 line put") :
415 			       gettext("%d lines put"), cnt);
416 			break;
417 		    case 'y':
418 			printf((cnt == 1) ? gettext("1 line yanked") :
419 			       gettext("%d lines yanked"), cnt);
420 			break;
421 		    case '>':
422 			printf((cnt == 1) ? gettext("1 line >>ed") :
423 			       gettext("%d lines >>ed"), cnt);
424 			break;
425 		    case '=':
426 			printf((cnt == 1) ? gettext("1 line =ed") :
427 			       gettext("%d lines =ed"), cnt);
428 			break;
429 		    case '<':
430 			printf((cnt == 1) ? gettext("1 line <<ed") :
431 			       gettext("%d lines <<ed"), cnt);
432 			break;
433 		    default:
434 Default:
435 			printf((cnt == 1) ? gettext("1 line") :
436 			       gettext("%d lines"), cnt);
437 			break;
438 		}
439 	} else if (sgn[0] == 'm') {
440 		printf((cnt == 1) ? gettext("1 more line") :
441 			gettext("%d more lines"), cnt);
442 	} else {
443 		printf((cnt == 1) ? gettext("1 fewer line") :
444 			gettext("%d fewer lines"), cnt);
445 	}
446 	return (NULL);
447 }
448 
449 /*
450  * Note a change affecting a lot of lines, or non-visible
451  * lines.  If the parameter must is set, then we only want
452  * to do this for open modes now; return and save for later
453  * notification in visual.
454  */
455 noteit(must)
456 	bool must;
457 {
458 	register int sdl = destline, sdc = destcol;
459 
460 	if (notecnt < 1 || !must && state == VISUAL)
461 		return (0);
462 	splitw++;
463 	if (WBOT == WECHO)
464 		vmoveitup(1, 1);
465 	vigoto(WECHO, 0);
466 
467 	verbalize(notecnt, notenam, notesgn);
468 	vclreol();
469 	notecnt = 0;
470 	if (state != VISUAL)
471 		vcnt = vcline = 0;
472 	splitw = 0;
473 	if (state == ONEOPEN || state == CRTOPEN)
474 		vup1();
475 	destline = sdl; destcol = sdc;
476 	return (1);
477 }
478 
479 /*
480  * Ring or beep.
481  * If possible, flash screen.
482  */
483 beep()
484 {
485 
486 	if (flash_screen && value(vi_FLASH))
487 		vputp(flash_screen, 0);
488 	else if (bell)
489 		vputp(bell, 0);
490 }
491 
492 /*
493  * Map the command input character c,
494  * for keypads and labelled keys which do cursor
495  * motions.  I.e. on an adm3a we might map ^K to ^P.
496  * DM1520 for example has a lot of mappable characters.
497  */
498 
499 map(c,maps,commch)
500 	register int c;
501 	register struct maps *maps;
502 	unsigned char commch;		/* indicate if in append/insert/replace mode */
503 {
504 	register int d;
505 	register unsigned char *p, *q;
506 	unsigned char b[10];	/* Assumption: no keypad sends string longer than 10 */
507 	unsigned char *st;
508 
509 	/*
510 	 * Mapping for special keys on the terminal only.
511 	 * BUG: if there's a long sequence and it matches
512 	 * some chars and then misses, we lose some chars.
513 	 *
514 	 * For this to work, some conditions must be met.
515 	 * 1) Keypad sends SHORT (2 or 3 char) strings
516 	 * 2) All strings sent are same length & similar
517 	 * 3) The user is unlikely to type the first few chars of
518 	 *    one of these strings very fast.
519 	 * Note: some code has been fixed up since the above was laid out,
520 	 * so conditions 1 & 2 are probably not required anymore.
521 	 * However, this hasn't been tested with any first char
522 	 * that means anything else except escape.
523 	 */
524 #ifdef MDEBUG
525 	if (trace)
526 		fprintf(trace,"map(%c): ",c);
527 #endif
528 	/*
529 	 * If c==0, the char came from getesc typing escape.  Pass it through
530 	 * unchanged.  0 messes up the following code anyway.
531 	 */
532 	if (c==0)
533 		return(0);
534 
535 	b[0] = c;
536 	b[1] = 0;
537 	for (d=0; d < MAXNOMACS && maps[d].mapto; d++) {
538 #ifdef MDEBUG
539 		if (trace)
540 			fprintf(trace,"\ntry '%s', ",maps[d].cap);
541 #endif
542 		if (p = maps[d].cap) {
543 			for (q=b; *p; p++, q++) {
544 #ifdef MDEBUG
545 				if (trace)
546 					fprintf(trace,"q->b[%d], ",q-b);
547 #endif
548 				if (*q==0) {
549 					/*
550 					 * Is there another char waiting?
551 					 *
552 					 * This test is oversimplified, but
553 					 * should work mostly. It handles the
554 					 * case where we get an ESCAPE that
555 					 * wasn't part of a keypad string.
556 					 */
557 					if ((c=='#' ? peekkey() : fastpeekkey()) == 0) {
558 #ifdef MDEBUG
559 						if (trace)
560 							fprintf(trace,"fpk=0: will return '%c'",c);
561 #endif
562 						/*
563 						 * Nothing waiting.  Push back
564 						 * what we peeked at & return
565 						 * failure (c).
566 						 *
567 						 * We want to be able to undo
568 						 * commands, but it's nonsense
569 						 * to undo part of an insertion
570 						 * so if in input mode don't.
571 						 */
572 #ifdef MDEBUG
573 						if (trace)
574 							fprintf(trace, "Call macpush, b %d %d %d\n", b[0], b[1], b[2]);
575 #endif
576 						macpush(&b[1],maps == arrows);
577 #ifdef MDEBUG
578 						if (trace)
579 							fprintf(trace, "return %d\n", c);
580 #endif
581 						return(c);
582 					}
583 					*q = getkey();
584 					q[1] = 0;
585 				}
586 				if (*p != *q)
587 					goto contin;
588 			}
589 			macpush(maps[d].mapto,maps == arrows);
590 			/*
591 			 * For all macros performed within insert,
592 			 * append, or replacement mode, we must end
593 			 * up returning back to that mode when we
594 			 * return (except that append will become
595 			 * insert for <home> key, so cursor is not
596 			 * in second column).
597 			 *
598 			 * In order to preserve backward movement
599 			 * when leaving insert mode, an 'l' must be
600 			 * done to compensate for the left done by
601 			 * the <esc> (except when cursor is already
602 			 * in the first column: i.e., outcol = 0).
603 			 */
604 			 if ((maps == immacs)
605 			 && strcmp(maps[d].descr, maps[d].cap)) {
606 				switch (commch) {
607 				  case 'R':
608 					if (!strcmp(maps[d].descr, "home"))
609 						st = (unsigned char *)"R";
610 					else
611 						if (outcol == 0)
612 							st = (unsigned char *)"R";
613 						else
614 							st = (unsigned char *)"lR";
615 					break;
616 				  case 'i':
617 					if (!strcmp(maps[d].descr, "home"))
618 						st = (unsigned char *)"i";
619 					else
620 						if (outcol == 0)
621 							st = (unsigned char *)"i";
622 						else
623 							st = (unsigned char *)"li";
624 					break;
625 				  case 'a':
626 					if (!strcmp(maps[d].descr, "home"))
627 						st = (unsigned char *)"i";
628 					else
629 						st = (unsigned char *)"a";
630 					break;
631 				  default:
632 					st = (unsigned char *)"i";
633 				}
634 				if(strlen(vmacbuf)  + strlen(st) > BUFSIZE)
635 					error(value(vi_TERSE) ?
636 gettext("Macro too long") : gettext("Macro too long  - maybe recursive?"));
637 				else
638 					/*
639 					 * Macros such as function keys are
640 					 * performed by leaving the insert,
641 					 * replace, or append mode, executing
642 					 * the proper cursor movement commands
643 					 * and returning to the mode we are
644 					 * currently in (commch).
645 					 */
646 					strcat(vmacbuf, st);
647 			}
648 			c = getkey();
649 #ifdef MDEBUG
650 			if (trace)
651 				fprintf(trace,"Success: push(%s), return %c",maps[d].mapto, c);
652 #endif
653 			return(c);	/* first char of map string */
654 			contin:;
655 		}
656 	}
657 #ifdef MDEBUG
658 	if (trace)
659 		fprintf(trace,"Fail: push(%s), return %c", &b[1], c);
660 #endif
661 	macpush(&b[1],0);
662 	return(c);
663 }
664 
665 /*
666  * Push st onto the front of vmacp. This is tricky because we have to
667  * worry about where vmacp was previously pointing. We also have to
668  * check for overflow (which is typically from a recursive macro)
669  * Finally we have to set a flag so the whole thing can be undone.
670  * canundo is 1 iff we want to be able to undo the macro.  This
671  * is false for, for example, pushing back lookahead from fastpeekkey(),
672  * since otherwise two fast escapes can clobber our undo.
673  */
674 macpush(st, canundo)
675 unsigned char *st;
676 int canundo;
677 {
678 	unsigned char tmpbuf[BUFSIZE];
679 
680 	if (st==0 || *st==0)
681 		return;
682 #ifdef MDEBUG
683 	if (trace)
684 		fprintf(trace, "macpush(%s), canundo=%d\n",st,canundo);
685 #endif
686 	if ((vmacp ? strlen(vmacp) : 0) + strlen(st) > BUFSIZE)
687 		error(value(vi_TERSE) ? gettext("Macro too long") :
688 gettext("Macro too long  - maybe recursive?"));
689 	if (vmacp) {
690 		strcpy(tmpbuf, vmacp);
691 		if (!FIXUNDO)
692 			canundo = 0;	/* can't undo inside a macro anyway */
693 	}
694 	strcpy(vmacbuf, st);
695 	if (vmacp)
696 		strcat(vmacbuf, tmpbuf);
697 	vmacp = vmacbuf;
698 	/* arrange to be able to undo the whole macro */
699 	if (canundo) {
700 #ifdef notdef
701 		otchng = tchng;
702 		vsave();
703 		saveall();
704 		inopen = -1;	/* no need to save since it had to be 1 or -1 before */
705 		vundkind = VMANY;
706 #endif
707 		vch_mac = VC_NOCHANGE;
708 	}
709 }
710 
711 #ifdef UNDOTRACE
712 visdump(s)
713 unsigned char *s;
714 {
715 	register int i;
716 
717 	if (!trace) return;
718 
719 	fprintf(trace, "\n%s: basWTOP=%d, basWLINES=%d, WTOP=%d, WBOT=%d, WLINES=%d, WCOLS=%d, WECHO=%d\n",
720 		s, basWTOP, basWLINES, WTOP, WBOT, WLINES, WCOLS, WECHO);
721 	fprintf(trace, "   vcnt=%d, vcline=%d, cursor=%d, wcursor=%d, wdot=%d\n",
722 		vcnt, vcline, cursor-linebuf, wcursor-linebuf, wdot-zero);
723 	for (i=0; i<TUBELINES; i++)
724 		if (vtube[i] && *vtube[i])
725 			fprintf(trace, "%d: '%s'\n", i, vtube[i]);
726 	tvliny();
727 }
728 
729 vudump(s)
730 unsigned char *s;
731 {
732 	register line *p;
733 	unsigned char savelb[1024];
734 
735 	if (!trace) return;
736 
737 	fprintf(trace, "\n%s: undkind=%d, vundkind=%d, unddel=%d, undap1=%d, undap2=%d,\n",
738 		s, undkind, vundkind, lineno(unddel), lineno(undap1), lineno(undap2));
739 	fprintf(trace, "  undadot=%d, dot=%d, dol=%d, unddol=%d, truedol=%d\n",
740 		lineno(undadot), lineno(dot), lineno(dol), lineno(unddol), lineno(truedol));
741 	fprintf(trace, "  [\n");
742 	CP(savelb, linebuf);
743 	fprintf(trace, "linebuf = '%s'\n", linebuf);
744 	for (p=zero+1; p<=truedol; p++) {
745 		fprintf(trace, "%o ", *p);
746 		getline(*p);
747 		fprintf(trace, "'%s'\n", linebuf);
748 	}
749 	fprintf(trace, "]\n");
750 	CP(linebuf, savelb);
751 }
752 #endif
753 
754 /*
755  * Get a count from the keyed input stream.
756  * A zero count is indistinguishable from no count.
757  */
758 vgetcnt()
759 {
760 	register int c, cnt;
761 
762 	cnt = 0;
763 	for (;;) {
764 		c = getkey();
765 		if (!isdigit(c))
766 			break;
767 		cnt *= 10, cnt += c - '0';
768 	}
769 	ungetkey(c);
770 	Xhadcnt = 1;
771 	Xcnt = cnt;
772 	return(cnt);
773 }
774 
775 /*
776  * fastpeekkey is just like peekkey but insists the character come in
777  * fast (within 1 second). This will succeed if it is the 2nd char of
778  * a machine generated sequence (such as a function pad from an escape
779  * flavor terminal) but fail for a human hitting escape then waiting.
780  */
781 fastpeekkey()
782 {
783 	void trapalarm();
784 	register int c;
785 
786 	/*
787 	 * If the user has set notimeout, we wait forever for a key.
788 	 * If we are in a macro we do too, but since it's already
789 	 * buffered internally it will return immediately.
790 	 * In other cases we force this to die in 1 second.
791 	 * This is pretty reliable (VMUNIX rounds it to .5 - 1.5 secs,
792 	 * but UNIX truncates it to 0 - 1 secs) but due to system delays
793 	 * there are times when arrow keys or very fast typing get counted
794 	 * as separate.  notimeout is provided for people who dislike such
795 	 * nondeterminism.
796 	 */
797 	CATCH
798 		if (value(vi_TIMEOUT) && inopen >= 0) {
799 			signal(SIGALRM, trapalarm);
800 			setalarm();
801 		}
802 		c = peekkey();
803 		cancelalarm();
804 	ONERR
805 		c = 0;
806 	ENDCATCH
807 	/* Should have an alternative method based on select for 4.2BSD */
808 	return(c);
809 }
810 
811 static int ftfd;
812 struct requestbuf {
813 	short time;
814 	short signo;
815 };
816 
817 /*
818  * Arrange for SIGALRM to come in shortly, so we don't
819  * hang very long if the user didn't type anything.  There are
820  * various ways to do this on different systems.
821  */
822 setalarm()
823 {
824 	unsigned char ftname[20];
825 	struct requestbuf rb;
826 
827 #ifdef FTIOCSET
828 	/*
829 	 * Use nonstandard "fast timer" to get better than
830 	 * one second resolution.  We must wait at least
831 	 * 1/15th of a second because some keypads don't
832 	 * transmit faster than this.
833 	 */
834 
835 	/* Open ft psuedo-device - we need our own copy. */
836 	if (ftfd == 0) {
837 		strcpy(ftname, "/dev/ft0");
838 		while (ftfd <= 0 && ftname[7] <= '~') {
839 			ftfd = open(ftname, 0);
840 			if (ftfd <= 0)
841 				ftname[7] ++;
842 		}
843 	}
844 	if (ftfd <= 0) {	/* Couldn't open a /dev/ft? */
845 		alarm(1);
846 	} else {
847 		rb.time = 6;	/* 6 ticks = 100 ms > 67 ms. */
848 		rb.signo = SIGALRM;
849 		ioctl(ftfd, FTIOCSET, &rb);
850 	}
851 #else
852 	/*
853 	 * No special capabilities, so we use alarm, with 1 sec. resolution.
854 	 */
855 	alarm(1);
856 #endif
857 }
858 
859 /*
860  * Get rid of any impending incoming SIGALRM.
861  */
862 cancelalarm()
863 {
864 	struct requestbuf rb;
865 #ifdef FTIOCSET
866 	if (ftfd > 0) {
867 		rb.time = 0;
868 		rb.signo = SIGALRM;
869 		ioctl(ftfd, FTIOCCANCEL, &rb);
870 	}
871 #endif
872 	alarm(0);	/* Have to do this whether or not FTIOCSET */
873 }
874 
875 void trapalarm() {
876 	alarm(0);
877 	longjmp(vreslab,1);
878 }
879