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