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