xref: /original-bsd/usr.bin/ex/ex_vget.c (revision f238860a)
1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 static char *sccsid = "@(#)ex_vget.c	6.8 (Berkeley) 06/07/85";
9 #endif not lint
10 
11 #include "ex.h"
12 #include "ex_tty.h"
13 #include "ex_vis.h"
14 
15 /*
16  * Input routines for open/visual.
17  * We handle upper case only terminals in visual and reading from the
18  * echo area here as well as notification on large changes
19  * which appears in the echo area.
20  */
21 
22 /*
23  * Return the key.
24  */
25 ungetkey(c)
26 	int c;		/* mjm: char --> int */
27 {
28 
29 	if (Peekkey != ATTN)
30 		Peekkey = c;
31 }
32 
33 /*
34  * Return a keystroke, but never a ^@.
35  */
36 getkey()
37 {
38 	register int c;		/* mjm: char --> int */
39 
40 	do {
41 		c = getbr();
42 		if (c==0)
43 			beep();
44 	} while (c == 0);
45 	return (c);
46 }
47 
48 /*
49  * Tell whether next keystroke would be a ^@.
50  */
51 peekbr()
52 {
53 
54 	Peekkey = getbr();
55 	return (Peekkey == 0);
56 }
57 
58 short	precbksl;
59 jmp_buf	readbuf;
60 int	doingread = 0;
61 
62 /*
63  * Get a keystroke, including a ^@.
64  * If an key was returned with ungetkey, that
65  * comes back first.  Next comes unread input (e.g.
66  * from repeating commands with .), and finally new
67  * keystrokes.
68  *
69  * The hard work here is in mapping of \ escaped
70  * characters on upper case only terminals.
71  */
72 getbr()
73 {
74 	char ch;
75 	register int c, d;
76 	register char *colp;
77 	int cnt;
78 #define BEEHIVE
79 #ifdef BEEHIVE
80 	static char Peek2key;
81 #endif
82 	extern short slevel, ttyindes;
83 
84 getATTN:
85 	if (Peekkey) {
86 		c = Peekkey;
87 		Peekkey = 0;
88 		return (c);
89 	}
90 #ifdef BEEHIVE
91 	if (Peek2key) {
92 		c = Peek2key;
93 		Peek2key = 0;
94 		return (c);
95 	}
96 #endif
97 	if (vglobp) {
98 		if (*vglobp)
99 			return (lastvgk = *vglobp++);
100 		lastvgk = 0;
101 		return (ESCAPE);
102 	}
103 	if (vmacp) {
104 		if (*vmacp)
105 			return(*vmacp++);
106 		/* End of a macro or set of nested macros */
107 		vmacp = 0;
108 		if (inopen == -1)	/* don't screw up undo for esc esc */
109 			vundkind = VMANY;
110 		inopen = 1;	/* restore old setting now that macro done */
111 		vch_mac = VC_NOTINMAC;
112 	}
113 	flusho();
114 again:
115 	if (setjmp(readbuf))
116 		goto getATTN;
117 	doingread = 1;
118 	c = read(slevel == 0 ? 0 : ttyindes, &ch, 1);
119 	doingread = 0;
120 	if (c != 1) {
121 		if (errno == EINTR)
122 			goto getATTN;
123 		error("Input read error");
124 	}
125 	c = ch & TRIM;
126 #ifdef BEEHIVE
127 	if (XB && slevel==0 && c == ESCAPE) {
128 		if (read(0, &Peek2key, 1) != 1)
129 			goto getATTN;
130 		Peek2key &= TRIM;
131 		switch (Peek2key) {
132 		case 'C':	/* SPOW mode sometimes sends \EC for space */
133 			c = ' ';
134 			Peek2key = 0;
135 			break;
136 		case 'q':	/* f2 -> ^C */
137 			c = CTRL(c);
138 			Peek2key = 0;
139 			break;
140 		case 'p':	/* f1 -> esc */
141 			Peek2key = 0;
142 			break;
143 		}
144 	}
145 #endif
146 
147 #ifdef UCVISUAL
148 	/*
149 	 * The algorithm here is that of the UNIX kernel.
150 	 * See the description in the programmers manual.
151 	 */
152 	if (UPPERCASE) {
153 		if (isupper(c))
154 			c = tolower(c);
155 		if (c == '\\') {
156 			if (precbksl < 2)
157 				precbksl++;
158 			if (precbksl == 1)
159 				goto again;
160 		} else if (precbksl) {
161 			d = 0;
162 			if (islower(c))
163 				d = toupper(c);
164 			else {
165 				colp = "({)}!|^~'~";
166 				while (d = *colp++)
167 					if (d == c) {
168 						d = *colp++;
169 						break;
170 					} else
171 						colp++;
172 			}
173 			if (precbksl == 2) {
174 				if (!d) {
175 					Peekkey = c;
176 					precbksl = 0;
177 					c = '\\';
178 				}
179 			} else if (d)
180 				c = d;
181 			else {
182 				Peekkey = c;
183 				precbksl = 0;
184 				c = '\\';
185 			}
186 		}
187 		if (c != '\\')
188 			precbksl = 0;
189 	}
190 #endif
191 #ifdef TRACE
192 	if (trace) {
193 		if (!techoin) {
194 			tfixnl();
195 			techoin = 1;
196 			fprintf(trace, "*** Input: ");
197 		}
198 		tracec(c);
199 	}
200 #endif
201 	lastvgk = 0;
202 	return (c);
203 }
204 
205 /*
206  * Get a key, but if a delete, quit or attention
207  * is typed return 0 so we will abort a partial command.
208  */
209 getesc()
210 {
211 	register int c;
212 
213 	c = getkey();
214 	switch (c) {
215 
216 	case CTRL(v):
217 	case CTRL(q):
218 		c = getkey();
219 		return (c);
220 
221 	case ATTN:
222 	case QUIT:
223 		ungetkey(c);
224 		return (0);
225 
226 	case ESCAPE:
227 		return (0);
228 	}
229 	return (c);
230 }
231 
232 /*
233  * Peek at the next keystroke.
234  */
235 peekkey()
236 {
237 
238 	Peekkey = getkey();
239 	return (Peekkey);
240 }
241 
242 /*
243  * Read a line from the echo area, with single character prompt c.
244  * A return value of 1 means the user blewit or blewit away.
245  */
246 readecho(c)
247 	char c;
248 {
249 	register char *sc = cursor;
250 	register int (*OP)();
251 	bool waste;
252 	register int OPeek;
253 
254 	if (WBOT == WECHO)
255 		vclean();
256 	else
257 		vclrech(0);
258 	splitw++;
259 	vgoto(WECHO, 0);
260 	putchar(c);
261 	vclreol();
262 	vgoto(WECHO, 1);
263 	cursor = linebuf; linebuf[0] = 0; genbuf[0] = c;
264 	if (peekbr()) {
265 		if (!INS[0] || (INS[0] & (QUOTE|TRIM)) == OVERBUF)
266 			goto blewit;
267 		vglobp = INS;
268 	}
269 	OP = Pline; Pline = normline;
270 	ignore(vgetline(0, genbuf + 1, &waste, c));
271 	if (Outchar == termchar)
272 		putchar('\n');
273 	vscrap();
274 	Pline = OP;
275 	if (Peekkey != ATTN && Peekkey != QUIT && Peekkey != CTRL(h)) {
276 		cursor = sc;
277 		vclreol();
278 		return (0);
279 	}
280 blewit:
281 	OPeek = Peekkey==CTRL(h) ? 0 : Peekkey; Peekkey = 0;
282 	splitw = 0;
283 	vclean();
284 	vshow(dot, NOLINE);
285 	vnline(sc);
286 	Peekkey = OPeek;
287 	return (1);
288 }
289 
290 /*
291  * A complete command has been defined for
292  * the purposes of repeat, so copy it from
293  * the working to the previous command buffer.
294  */
295 setLAST()
296 {
297 
298 	if (vglobp || vmacp)
299 		return;
300 	lastreg = vreg;
301 	lasthad = Xhadcnt;
302 	lastcnt = Xcnt;
303 	*lastcp = 0;
304 	CP(lastcmd, workcmd);
305 }
306 
307 /*
308  * Gather up some more text from an insert.
309  * If the insertion buffer oveflows, then destroy
310  * the repeatability of the insert.
311  */
312 addtext(cp)
313 	char *cp;
314 {
315 
316 	if (vglobp)
317 		return;
318 	addto(INS, cp);
319 	if ((INS[0] & (QUOTE|TRIM)) == OVERBUF)
320 		lastcmd[0] = 0;
321 }
322 
323 setDEL()
324 {
325 
326 	setBUF(DEL);
327 }
328 
329 /*
330  * Put text from cursor upto wcursor in BUF.
331  */
332 setBUF(BUF)
333 	register char *BUF;
334 {
335 	register int c;
336 	register char *wp = wcursor;
337 
338 	c = *wp;
339 	*wp = 0;
340 	BUF[0] = 0;
341 	addto(BUF, cursor);
342 	*wp = c;
343 }
344 
345 addto(buf, str)
346 	register char *buf, *str;
347 {
348 
349 	if ((buf[0] & (QUOTE|TRIM)) == OVERBUF)
350 		return;
351 	if (strlen(buf) + strlen(str) + 1 >= VBSIZE) {
352 		buf[0] = OVERBUF;
353 		return;
354 	}
355 	ignore(strcat(buf, str));
356 }
357 
358 /*
359  * Note a change affecting a lot of lines, or non-visible
360  * lines.  If the parameter must is set, then we only want
361  * to do this for open modes now; return and save for later
362  * notification in visual.
363  */
364 noteit(must)
365 	bool must;
366 {
367 	register int sdl = destline, sdc = destcol;
368 
369 	if (notecnt < 2 || !must && state == VISUAL)
370 		return (0);
371 	splitw++;
372 	if (WBOT == WECHO)
373 		vmoveitup(1, 1);
374 	vigoto(WECHO, 0);
375 	printf("%d %sline", notecnt, notesgn);
376 	if (notecnt > 1)
377 		putchar('s');
378 	if (*notenam) {
379 		printf(" %s", notenam);
380 		if (*(strend(notenam) - 1) != 'e')
381 			putchar('e');
382 		putchar('d');
383 	}
384 	vclreol();
385 	notecnt = 0;
386 	if (state != VISUAL)
387 		vcnt = vcline = 0;
388 	splitw = 0;
389 	if (state == ONEOPEN || state == CRTOPEN)
390 		vup1();
391 	destline = sdl; destcol = sdc;
392 	return (1);
393 }
394 
395 /*
396  * Rrrrringgggggg.
397  * If possible, use flash (VB).
398  */
399 beep()
400 {
401 
402 	if (VB)
403 		vputp(VB, 0);
404 	else
405 		vputc(CTRL(g));
406 }
407 
408 /*
409  * Map the command input character c,
410  * for keypads and labelled keys which do cursor
411  * motions.  I.e. on an adm3a we might map ^K to ^P.
412  * DM1520 for example has a lot of mappable characters.
413  */
414 
415 map(c,maps)
416 	register int c;
417 	register struct maps *maps;
418 {
419 	register int d;
420 	register char *p, *q;
421 	char b[10];	/* Assumption: no keypad sends string longer than 10 */
422 
423 	/*
424 	 * Mapping for special keys on the terminal only.
425 	 * BUG: if there's a long sequence and it matches
426 	 * some chars and then misses, we lose some chars.
427 	 *
428 	 * For this to work, some conditions must be met.
429 	 * 1) Keypad sends SHORT (2 or 3 char) strings
430 	 * 2) All strings sent are same length & similar
431 	 * 3) The user is unlikely to type the first few chars of
432 	 *    one of these strings very fast.
433 	 * Note: some code has been fixed up since the above was laid out,
434 	 * so conditions 1 & 2 are probably not required anymore.
435 	 * However, this hasn't been tested with any first char
436 	 * that means anything else except escape.
437 	 */
438 #ifdef MDEBUG
439 	if (trace)
440 		fprintf(trace,"map(%c): ",c);
441 #endif
442 	/*
443 	 * If c==0, the char came from getesc typing escape.  Pass it through
444 	 * unchanged.  0 messes up the following code anyway.
445 	 */
446 	if (c==0)
447 		return(0);
448 
449 	b[0] = c;
450 	b[1] = 0;
451 	for (d=0; maps[d].mapto; d++) {
452 #ifdef MDEBUG
453 		if (trace)
454 			fprintf(trace,"\ntry '%s', ",maps[d].cap);
455 #endif
456 		if (p = maps[d].cap) {
457 			for (q=b; *p; p++, q++) {
458 #ifdef MDEBUG
459 				if (trace)
460 					fprintf(trace,"q->b[%d], ",q-b);
461 #endif
462 				if (*q==0) {
463 					/*
464 					 * Is there another char waiting?
465 					 *
466 					 * This test is oversimplified, but
467 					 * should work mostly. It handles the
468 					 * case where we get an ESCAPE that
469 					 * wasn't part of a keypad string.
470 					 */
471 					if ((c=='#' ? peekkey() : fastpeekkey()) == 0) {
472 #ifdef MDEBUG
473 						if (trace)
474 							fprintf(trace,"fpk=0: will return '%c'",c);
475 #endif
476 						/*
477 						 * Nothing waiting.  Push back
478 						 * what we peeked at & return
479 						 * failure (c).
480 						 *
481 						 * We want to be able to undo
482 						 * commands, but it's nonsense
483 						 * to undo part of an insertion
484 						 * so if in input mode don't.
485 						 */
486 #ifdef MDEBUG
487 						if (trace)
488 							fprintf(trace, "Call macpush, b %d %d %d\n", b[0], b[1], b[2]);
489 #endif
490 						macpush(&b[1],maps == arrows);
491 #ifdef MDEBUG
492 						if (trace)
493 							fprintf(trace, "return %d\n", c);
494 #endif
495 						return(c);
496 					}
497 					*q = getkey();
498 					q[1] = 0;
499 				}
500 				if (*p != *q)
501 					goto contin;
502 			}
503 			macpush(maps[d].mapto,maps == arrows);
504 			c = getkey();
505 #ifdef MDEBUG
506 			if (trace)
507 				fprintf(trace,"Success: push(%s), return %c",maps[d].mapto, c);
508 #endif
509 			return(c);	/* first char of map string */
510 			contin:;
511 		}
512 	}
513 #ifdef MDEBUG
514 	if (trace)
515 		fprintf(trace,"Fail: push(%s), return %c", &b[1], c);
516 #endif
517 	macpush(&b[1],0);
518 	return(c);
519 }
520 
521 /*
522  * Push st onto the front of vmacp. This is tricky because we have to
523  * worry about where vmacp was previously pointing. We also have to
524  * check for overflow (which is typically from a recursive macro)
525  * Finally we have to set a flag so the whole thing can be undone.
526  * canundo is 1 iff we want to be able to undo the macro.  This
527  * is false for, for example, pushing back lookahead from fastpeekkey(),
528  * since otherwise two fast escapes can clobber our undo.
529  */
530 macpush(st, canundo)
531 char *st;
532 int canundo;
533 {
534 	char tmpbuf[BUFSIZ];
535 
536 	if (st==0 || *st==0)
537 		return;
538 #ifdef MDEBUG
539 	if (trace)
540 		fprintf(trace, "macpush(%s), canundo=%d\n",st,canundo);
541 #endif
542 	if ((vmacp ? strlen(vmacp) : 0) + strlen(st) > BUFSIZ)
543 		error("Macro too long@ - maybe recursive?");
544 	if (vmacp) {
545 		strcpy(tmpbuf, vmacp);
546 		if (!FIXUNDO)
547 			canundo = 0;	/* can't undo inside a macro anyway */
548 	}
549 	strcpy(vmacbuf, st);
550 	if (vmacp)
551 		strcat(vmacbuf, tmpbuf);
552 	vmacp = vmacbuf;
553 	/* arrange to be able to undo the whole macro */
554 	if (canundo) {
555 #ifdef notdef
556 		otchng = tchng;
557 		vsave();
558 		saveall();
559 		inopen = -1;	/* no need to save since it had to be 1 or -1 before */
560 		vundkind = VMANY;
561 #endif
562 		vch_mac = VC_NOCHANGE;
563 	}
564 }
565 
566 #ifdef TRACE
567 visdump(s)
568 char *s;
569 {
570 	register int i;
571 
572 	if (!trace) return;
573 
574 	fprintf(trace, "\n%s: basWTOP=%d, basWLINES=%d, WTOP=%d, WBOT=%d, WLINES=%d, WCOLS=%d, WECHO=%d\n",
575 		s, basWTOP, basWLINES, WTOP, WBOT, WLINES, WCOLS, WECHO);
576 	fprintf(trace, "   vcnt=%d, vcline=%d, cursor=%d, wcursor=%d, wdot=%d\n",
577 		vcnt, vcline, cursor-linebuf, wcursor-linebuf, wdot-zero);
578 	for (i=0; i<TUBELINES; i++)
579 		if (vtube[i] && *vtube[i])
580 			fprintf(trace, "%d: '%s'\n", i, vtube[i]);
581 	tvliny();
582 }
583 
584 vudump(s)
585 char *s;
586 {
587 	register line *p;
588 	char savelb[1024];
589 
590 	if (!trace) return;
591 
592 	fprintf(trace, "\n%s: undkind=%d, vundkind=%d, unddel=%d, undap1=%d, undap2=%d,\n",
593 		s, undkind, vundkind, lineno(unddel), lineno(undap1), lineno(undap2));
594 	fprintf(trace, "  undadot=%d, dot=%d, dol=%d, unddol=%d, truedol=%d\n",
595 		lineno(undadot), lineno(dot), lineno(dol), lineno(unddol), lineno(truedol));
596 	fprintf(trace, "  [\n");
597 	CP(savelb, linebuf);
598 	fprintf(trace, "linebuf = '%s'\n", linebuf);
599 	for (p=zero+1; p<=truedol; p++) {
600 		fprintf(trace, "%o ", *p);
601 		getline(*p);
602 		fprintf(trace, "'%s'\n", linebuf);
603 	}
604 	fprintf(trace, "]\n");
605 	CP(linebuf, savelb);
606 }
607 #endif
608 
609 /*
610  * Get a count from the keyed input stream.
611  * A zero count is indistinguishable from no count.
612  */
613 vgetcnt()
614 {
615 	register int c, cnt;
616 
617 	cnt = 0;
618 	for (;;) {
619 		c = getkey();
620 		if (!isdigit(c))
621 			break;
622 		cnt *= 10, cnt += c - '0';
623 	}
624 	ungetkey(c);
625 	Xhadcnt = 1;
626 	Xcnt = cnt;
627 	return(cnt);
628 }
629 
630 /*
631  * fastpeekkey is just like peekkey but insists the character come in
632  * fast (within 1 second). This will succeed if it is the 2nd char of
633  * a machine generated sequence (such as a function pad from an escape
634  * flavor terminal) but fail for a human hitting escape then waiting.
635  */
636 fastpeekkey()
637 {
638 	int trapalarm();
639 	int (*Oint)();
640 	register int c;
641 
642 	/*
643 	 * If the user has set notimeout, we wait forever for a key.
644 	 * If we are in a macro we do too, but since it's already
645 	 * buffered internally it will return immediately.
646 	 * In other cases we force this to die in 1 second.
647 	 * This is pretty reliable (VMUNIX rounds it to .5 - 1.5 secs,
648 	 * but UNIX truncates it to 0 - 1 secs) but due to system delays
649 	 * there are times when arrow keys or very fast typing get counted
650 	 * as separate.  notimeout is provided for people who dislike such
651 	 * nondeterminism.
652 	 */
653 #ifdef MDEBUG
654 	if (trace)
655 		fprintf(trace,"\nfastpeekkey: ",c);
656 #endif
657 	Oint = signal(SIGINT, trapalarm);
658 	if (value(TIMEOUT) && inopen >= 0) {
659 		signal(SIGALRM, trapalarm);
660 #ifdef MDEBUG
661 		alarm(10);
662 		if (trace)
663 			fprintf(trace, "set alarm ");
664 #else
665 		alarm(1);
666 #endif
667 	}
668 	CATCH
669 		c = peekkey();
670 #ifdef MDEBUG
671 	if (trace)
672 		fprintf(trace,"[OK]",c);
673 #endif
674 		alarm(0);
675 	ONERR
676 		c = 0;
677 #ifdef MDEBUG
678 	if (trace)
679 		fprintf(trace,"[TIMEOUT]",c);
680 #endif
681 	ENDCATCH
682 #ifdef MDEBUG
683 	if (trace)
684 		fprintf(trace,"[fpk:%o]",c);
685 #endif
686 	signal(SIGINT,Oint);
687 	return(c);
688 }
689 
690 trapalarm() {
691 	alarm(0);
692 	if (vcatch)
693 		longjmp(vreslab,1);
694 }
695