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