xref: /illumos-gate/usr/src/cmd/vi/port/ex_vget.c (revision bde334a8dbd66dfa70ce4d7fc9dcad6e1ae45fe4)
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 			/* FALLTHROUGH */
402 		    case 't':
403 			if (cmdstr[1] != '\0')
404 				goto Default;
405 			viprintf((cnt == 1) ? gettext("1 line copied") :
406 			       gettext("%d lines copied"), cnt);
407 			break;
408 		    case 'd':
409 			viprintf((cnt == 1) ? gettext("1 line deleted") :
410 			       gettext("%d lines deleted"), cnt);
411 			break;
412 		    case 'j':
413 			viprintf((cnt == 1) ? gettext("1 line joined") :
414 			       gettext("%d lines joined"), cnt);
415 			break;
416 		    case 'm':
417 			viprintf((cnt == 1) ? gettext("1 line moved") :
418 			       gettext("%d lines moved"), cnt);
419 			break;
420 		    case 'p':
421 			viprintf((cnt == 1) ? gettext("1 line put") :
422 			       gettext("%d lines put"), cnt);
423 			break;
424 		    case 'y':
425 			viprintf((cnt == 1) ? gettext("1 line yanked") :
426 			       gettext("%d lines yanked"), cnt);
427 			break;
428 		    case '>':
429 			viprintf((cnt == 1) ? gettext("1 line >>ed") :
430 			       gettext("%d lines >>ed"), cnt);
431 			break;
432 		    case '=':
433 			viprintf((cnt == 1) ? gettext("1 line =ed") :
434 			       gettext("%d lines =ed"), cnt);
435 			break;
436 		    case '<':
437 			viprintf((cnt == 1) ? gettext("1 line <<ed") :
438 			       gettext("%d lines <<ed"), cnt);
439 			break;
440 		    default:
441 Default:
442 			viprintf((cnt == 1) ? gettext("1 line") :
443 			       gettext("%d lines"), cnt);
444 			break;
445 		}
446 	} else if (sgn[0] == 'm') {
447 		viprintf((cnt == 1) ? gettext("1 more line") :
448 			gettext("%d more lines"), cnt);
449 	} else {
450 		viprintf((cnt == 1) ? gettext("1 fewer line") :
451 			gettext("%d fewer lines"), cnt);
452 	}
453 	return (NULL);
454 }
455 
456 /*
457  * Note a change affecting a lot of lines, or non-visible
458  * lines.  If the parameter must is set, then we only want
459  * to do this for open modes now; return and save for later
460  * notification in visual.
461  */
462 int
463 noteit(must)
464 	bool must;
465 {
466 	int sdl = destline, sdc = destcol;
467 
468 	if (notecnt < 1 || !must && state == VISUAL)
469 		return (0);
470 	splitw++;
471 	if (WBOT == WECHO)
472 		vmoveitup(1, 1);
473 	vigoto(WECHO, 0);
474 
475 	verbalize(notecnt, notenam, notesgn);
476 	vclreol();
477 	notecnt = 0;
478 	if (state != VISUAL)
479 		vcnt = vcline = 0;
480 	splitw = 0;
481 	if (state == ONEOPEN || state == CRTOPEN)
482 		vup1();
483 	destline = sdl; destcol = sdc;
484 	return (1);
485 }
486 
487 /*
488  * Ring or beep.
489  * If possible, flash screen.
490  */
491 int
492 beep(void)
493 {
494 
495 	if (flash_screen && value(vi_FLASH))
496 		vputp(flash_screen, 0);
497 	else if (bell)
498 		vputp(bell, 0);
499 	return (0);
500 }
501 
502 /*
503  * Map the command input character c,
504  * for keypads and labelled keys which do cursor
505  * motions.  I.e. on an adm3a we might map ^K to ^P.
506  * DM1520 for example has a lot of mappable characters.
507  */
508 
509 int
510 map(c, maps, commch)
511 	int c;
512 	struct maps *maps;
513 	unsigned char commch; /* indicate if in append/insert/replace mode */
514 {
515 	int d;
516 	unsigned char *p, *q;
517 	unsigned char b[10];	/* Assumption: no keypad sends string longer than 10 */
518 	unsigned char *st;
519 
520 	/*
521 	 * Mapping for special keys on the terminal only.
522 	 * BUG: if there's a long sequence and it matches
523 	 * some chars and then misses, we lose some chars.
524 	 *
525 	 * For this to work, some conditions must be met.
526 	 * 1) Keypad sends SHORT (2 or 3 char) strings
527 	 * 2) All strings sent are same length & similar
528 	 * 3) The user is unlikely to type the first few chars of
529 	 *    one of these strings very fast.
530 	 * Note: some code has been fixed up since the above was laid out,
531 	 * so conditions 1 & 2 are probably not required anymore.
532 	 * However, this hasn't been tested with any first char
533 	 * that means anything else except escape.
534 	 */
535 #ifdef MDEBUG
536 	if (trace)
537 		fprintf(trace,"map(%c): ",c);
538 #endif
539 	/*
540 	 * If c==0, the char came from getesc typing escape.  Pass it through
541 	 * unchanged.  0 messes up the following code anyway.
542 	 */
543 	if (c==0)
544 		return(0);
545 
546 	b[0] = c;
547 	b[1] = 0;
548 	for (d=0; d < MAXNOMACS && maps[d].mapto; d++) {
549 #ifdef MDEBUG
550 		if (trace)
551 			fprintf(trace,"\ntry '%s', ",maps[d].cap);
552 #endif
553 		if (p = maps[d].cap) {
554 			for (q=b; *p; p++, q++) {
555 #ifdef MDEBUG
556 				if (trace)
557 					fprintf(trace,"q->b[%d], ",q-b);
558 #endif
559 				if (*q==0) {
560 					/*
561 					 * Is there another char waiting?
562 					 *
563 					 * This test is oversimplified, but
564 					 * should work mostly. It handles the
565 					 * case where we get an ESCAPE that
566 					 * wasn't part of a keypad string.
567 					 */
568 					if ((c=='#' ? peekkey() : fastpeekkey()) == 0) {
569 #ifdef MDEBUG
570 						if (trace)
571 							fprintf(trace,"fpk=0: will return '%c'",c);
572 #endif
573 						/*
574 						 * Nothing waiting.  Push back
575 						 * what we peeked at & return
576 						 * failure (c).
577 						 *
578 						 * We want to be able to undo
579 						 * commands, but it's nonsense
580 						 * to undo part of an insertion
581 						 * so if in input mode don't.
582 						 */
583 #ifdef MDEBUG
584 						if (trace)
585 							fprintf(trace, "Call macpush, b %d %d %d\n", b[0], b[1], b[2]);
586 #endif
587 						macpush(&b[1],maps == arrows);
588 #ifdef MDEBUG
589 						if (trace)
590 							fprintf(trace, "return %d\n", c);
591 #endif
592 						return(c);
593 					}
594 					*q = getkey();
595 					q[1] = 0;
596 				}
597 				if (*p != *q)
598 					goto contin;
599 			}
600 			macpush(maps[d].mapto,maps == arrows);
601 			/*
602 			 * For all macros performed within insert,
603 			 * append, or replacement mode, we must end
604 			 * up returning back to that mode when we
605 			 * return (except that append will become
606 			 * insert for <home> key, so cursor is not
607 			 * in second column).
608 			 *
609 			 * In order to preserve backward movement
610 			 * when leaving insert mode, an 'l' must be
611 			 * done to compensate for the left done by
612 			 * the <esc> (except when cursor is already
613 			 * in the first column: i.e., outcol = 0).
614 			 */
615 			 if ((maps == immacs)
616 			 && strcmp(maps[d].descr, maps[d].cap)) {
617 				switch (commch) {
618 				  case 'R':
619 					if (!strcmp(maps[d].descr, "home"))
620 						st = (unsigned char *)"R";
621 					else
622 						if (outcol == 0)
623 							st = (unsigned char *)"R";
624 						else
625 							st = (unsigned char *)"lR";
626 					break;
627 				  case 'i':
628 					if (!strcmp(maps[d].descr, "home"))
629 						st = (unsigned char *)"i";
630 					else
631 						if (outcol == 0)
632 							st = (unsigned char *)"i";
633 						else
634 							st = (unsigned char *)"li";
635 					break;
636 				  case 'a':
637 					if (!strcmp(maps[d].descr, "home"))
638 						st = (unsigned char *)"i";
639 					else
640 						st = (unsigned char *)"a";
641 					break;
642 				  default:
643 					st = (unsigned char *)"i";
644 				}
645 				if(strlen(vmacbuf)  + strlen(st) > BUFSIZE)
646 					error(value(vi_TERSE) ?
647 gettext("Macro too long") : gettext("Macro too long  - maybe recursive?"));
648 				else
649 					/*
650 					 * Macros such as function keys are
651 					 * performed by leaving the insert,
652 					 * replace, or append mode, executing
653 					 * the proper cursor movement commands
654 					 * and returning to the mode we are
655 					 * currently in (commch).
656 					 */
657 					strcat(vmacbuf, st);
658 			}
659 			c = getkey();
660 #ifdef MDEBUG
661 			if (trace)
662 				fprintf(trace,"Success: push(%s), return %c",maps[d].mapto, c);
663 #endif
664 			return(c);	/* first char of map string */
665 			contin:;
666 		}
667 	}
668 #ifdef MDEBUG
669 	if (trace)
670 		fprintf(trace,"Fail: push(%s), return %c", &b[1], c);
671 #endif
672 	macpush(&b[1],0);
673 	return(c);
674 }
675 
676 /*
677  * Push st onto the front of vmacp. This is tricky because we have to
678  * worry about where vmacp was previously pointing. We also have to
679  * check for overflow (which is typically from a recursive macro)
680  * Finally we have to set a flag so the whole thing can be undone.
681  * canundo is 1 iff we want to be able to undo the macro.  This
682  * is false for, for example, pushing back lookahead from fastpeekkey(),
683  * since otherwise two fast escapes can clobber our undo.
684  */
685 void
686 macpush(unsigned char *st, int canundo)
687 {
688 	unsigned char tmpbuf[BUFSIZE];
689 
690 	if (st==0 || *st==0)
691 		return;
692 #ifdef MDEBUG
693 	if (trace)
694 		fprintf(trace, "macpush(%s), canundo=%d\n",st,canundo);
695 #endif
696 	if ((vmacp ? strlen(vmacp) : 0) + strlen(st) > BUFSIZE)
697 		error(value(vi_TERSE) ? gettext("Macro too long") :
698 gettext("Macro too long  - maybe recursive?"));
699 	if (vmacp) {
700 		strcpy(tmpbuf, vmacp);
701 		if (!FIXUNDO)
702 			canundo = 0;	/* can't undo inside a macro anyway */
703 	}
704 	strcpy(vmacbuf, st);
705 	if (vmacp)
706 		strcat(vmacbuf, tmpbuf);
707 	vmacp = vmacbuf;
708 	/* arrange to be able to undo the whole macro */
709 	if (canundo) {
710 #ifdef notdef
711 		otchng = tchng;
712 		vsave();
713 		saveall();
714 		inopen = -1;	/* no need to save since it had to be 1 or -1 before */
715 		vundkind = VMANY;
716 #endif
717 		vch_mac = VC_NOCHANGE;
718 	}
719 }
720 
721 #ifdef UNDOTRACE
722 visdump(s)
723 unsigned char *s;
724 {
725 	int i;
726 
727 	if (!trace) return;
728 
729 	fprintf(trace, "\n%s: basWTOP=%d, basWLINES=%d, WTOP=%d, WBOT=%d, WLINES=%d, WCOLS=%d, WECHO=%d\n",
730 		s, basWTOP, basWLINES, WTOP, WBOT, WLINES, WCOLS, WECHO);
731 	fprintf(trace, "   vcnt=%d, vcline=%d, cursor=%d, wcursor=%d, wdot=%d\n",
732 		vcnt, vcline, cursor-linebuf, wcursor-linebuf, wdot-zero);
733 	for (i=0; i<TUBELINES; i++)
734 		if (vtube[i] && *vtube[i])
735 			fprintf(trace, "%d: '%s'\n", i, vtube[i]);
736 	tvliny();
737 }
738 
739 vudump(s)
740 unsigned char *s;
741 {
742 	line *p;
743 	unsigned char savelb[1024];
744 
745 	if (!trace) return;
746 
747 	fprintf(trace, "\n%s: undkind=%d, vundkind=%d, unddel=%d, undap1=%d, undap2=%d,\n",
748 		s, undkind, vundkind, lineno(unddel), lineno(undap1), lineno(undap2));
749 	fprintf(trace, "  undadot=%d, dot=%d, dol=%d, unddol=%d, truedol=%d\n",
750 		lineno(undadot), lineno(dot), lineno(dol), lineno(unddol), lineno(truedol));
751 	fprintf(trace, "  [\n");
752 	CP(savelb, linebuf);
753 	fprintf(trace, "linebuf = '%s'\n", linebuf);
754 	for (p=zero+1; p<=truedol; p++) {
755 		fprintf(trace, "%o ", *p);
756 		getaline(*p);
757 		fprintf(trace, "'%s'\n", linebuf);
758 	}
759 	fprintf(trace, "]\n");
760 	CP(linebuf, savelb);
761 }
762 #endif
763 
764 /*
765  * Get a count from the keyed input stream.
766  * A zero count is indistinguishable from no count.
767  */
768 int
769 vgetcnt(void)
770 {
771 	int c, cnt;
772 
773 	cnt = 0;
774 	for (;;) {
775 		c = getkey();
776 		if (!isdigit(c))
777 			break;
778 		cnt *= 10, cnt += c - '0';
779 	}
780 	ungetkey(c);
781 	Xhadcnt = 1;
782 	Xcnt = cnt;
783 	return(cnt);
784 }
785 
786 /*
787  * fastpeekkey is just like peekkey but insists the character come in
788  * fast (within 1 second). This will succeed if it is the 2nd char of
789  * a machine generated sequence (such as a function pad from an escape
790  * flavor terminal) but fail for a human hitting escape then waiting.
791  */
792 int
793 fastpeekkey(void)
794 {
795 	void trapalarm();
796 	int c;
797 
798 	/*
799 	 * If the user has set notimeout, we wait forever for a key.
800 	 * If we are in a macro we do too, but since it's already
801 	 * buffered internally it will return immediately.
802 	 * In other cases we force this to die in 1 second.
803 	 * This is pretty reliable (VMUNIX rounds it to .5 - 1.5 secs,
804 	 * but UNIX truncates it to 0 - 1 secs) but due to system delays
805 	 * there are times when arrow keys or very fast typing get counted
806 	 * as separate.  notimeout is provided for people who dislike such
807 	 * nondeterminism.
808 	 */
809 	CATCH
810 		if (value(vi_TIMEOUT) && inopen >= 0) {
811 			signal(SIGALRM, trapalarm);
812 			setalarm();
813 		}
814 		c = peekkey();
815 		cancelalarm();
816 	ONERR
817 		c = 0;
818 	ENDCATCH
819 	/* Should have an alternative method based on select for 4.2BSD */
820 	return(c);
821 }
822 
823 static int ftfd;
824 struct requestbuf {
825 	short time;
826 	short signo;
827 };
828 
829 /*
830  * Arrange for SIGALRM to come in shortly, so we don't
831  * hang very long if the user didn't type anything.  There are
832  * various ways to do this on different systems.
833  */
834 void
835 setalarm(void)
836 {
837 	unsigned char ftname[20];
838 	struct requestbuf rb;
839 
840 #ifdef FTIOCSET
841 	/*
842 	 * Use nonstandard "fast timer" to get better than
843 	 * one second resolution.  We must wait at least
844 	 * 1/15th of a second because some keypads don't
845 	 * transmit faster than this.
846 	 */
847 
848 	/* Open ft psuedo-device - we need our own copy. */
849 	if (ftfd == 0) {
850 		strcpy(ftname, "/dev/ft0");
851 		while (ftfd <= 0 && ftname[7] <= '~') {
852 			ftfd = open(ftname, 0);
853 			if (ftfd <= 0)
854 				ftname[7] ++;
855 		}
856 	}
857 	if (ftfd <= 0) {	/* Couldn't open a /dev/ft? */
858 		alarm(1);
859 	} else {
860 		rb.time = 6;	/* 6 ticks = 100 ms > 67 ms. */
861 		rb.signo = SIGALRM;
862 		ioctl(ftfd, FTIOCSET, &rb);
863 	}
864 #else
865 	/*
866 	 * No special capabilities, so we use alarm, with 1 sec. resolution.
867 	 */
868 	alarm(1);
869 #endif
870 }
871 
872 /*
873  * Get rid of any impending incoming SIGALRM.
874  */
875 void
876 cancelalarm(void)
877 {
878 	struct requestbuf rb;
879 #ifdef FTIOCSET
880 	if (ftfd > 0) {
881 		rb.time = 0;
882 		rb.signo = SIGALRM;
883 		ioctl(ftfd, FTIOCCANCEL, &rb);
884 	}
885 #endif
886 	alarm(0);	/* Have to do this whether or not FTIOCSET */
887 }
888 
889 void trapalarm() {
890 	alarm(0);
891 	longjmp(vreslab,1);
892 }
893