xref: /illumos-gate/usr/src/cmd/vi/port/ex_cmds.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
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  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #include "ex.h"
35 #include "ex_argv.h"
36 #include "ex_temp.h"
37 #include "ex_tty.h"
38 #include "ex_vis.h"
39 
40 bool	pflag, nflag;
41 int	poffset;
42 
43 #define	nochng()	lchng = chng
44 
45 
46 /*
47  * Main loop for command mode command decoding.
48  * A few commands are executed here, but main function
49  * is to strip command addresses, do a little address oriented
50  * processing and call command routines to do the real work.
51  */
52 extern unsigned char *Version;
53 void
54 commands(noprompt, exitoneof)
55 	bool noprompt, exitoneof;
56 {
57 	line *addr;
58 	int c;
59 	int lchng;
60 	int given;
61 	int seensemi;
62 	int cnt;
63 	bool hadpr;
64 	bool gotfile;
65 #ifdef XPG4
66 	int d;
67 #endif /* XPG4 */
68 	unsigned char *vgetpass();
69 
70 	resetflav();
71 	nochng();
72 	for (;;) {
73 		if (!firstpat)
74 			laste = 0;
75 		/*
76 		 * If dot at last command
77 		 * ended up at zero, advance to one if there is a such.
78 		 */
79 		if (dot <= zero) {
80 			dot = zero;
81 			if (dol > zero)
82 				dot = one;
83 		}
84 		shudclob = 0;
85 
86 		/*
87 		 * If autoprint or trailing print flags,
88 		 * print the line at the specified offset
89 		 * before the next command.
90 		 */
91 		if ((pflag || lchng != chng && value(vi_AUTOPRINT) &&
92 		    !inglobal && !inopen && endline) || poffset != 0) {
93 			pflag = 0;
94 			nochng();
95 			if (dol != zero) {
96 				addr1 = addr2 = dot + poffset;
97 				poffset = 0;
98 				if (addr1 < one || addr1 > dol)
99 					error(value(vi_TERSE) ?
100 					    gettext("Offset out-of-bounds") :
101 					    gettext("Offset after command "
102 						"too large"));
103 				dot = addr1;
104 				setdot1();
105 
106 				goto print;
107 			}
108 		}
109 		nochng();
110 
111 		/*
112 		 * Print prompt if appropriate.
113 		 * If not in global flush output first to prevent
114 		 * going into pfast mode unreasonably.
115 		 */
116 		if (inglobal == 0) {
117 			flush();
118 			if (!hush && value(vi_PROMPT) && !globp &&
119 			    !noprompt && endline) {
120 				putchar(':');
121 				hadpr = 1;
122 			}
123 			TSYNC();
124 		}
125 
126 		/*
127 		 * Gobble up the address.
128 		 * Degenerate addresses yield ".".
129 		 */
130 		addr2 = 0;
131 		given = seensemi = 0;
132 		do {
133 			addr1 = addr2;
134 			addr = address(0);
135 			c = getcd();
136 			if (addr == 0) {
137 				if (c == ',' || c == ';')
138 					addr = dot;
139 				else if (addr1 != 0) {
140 					addr2 = dot;
141 					break;
142 				} else
143 					break;
144 			}
145 			addr2 = addr;
146 			given++;
147 			if (c == ';') {
148 				c = ',';
149 				dot = addr;
150 				seensemi = 1;
151 			}
152 		} while (c == ',');
153 
154 		if (c == '%') {
155 			/* %: same as 1,$ */
156 			addr1 = one;
157 			addr2 = dol;
158 			given = 2;
159 			c = getchar();
160 		}
161 		if (addr1 == 0)
162 			addr1 = addr2;
163 
164 		/*
165 		 * eat multiple colons
166 		 */
167 		while (c == ':')
168 			c = getchar();
169 		/*
170 		 * Set command name for special character commands.
171 		 */
172 		tailspec(c);
173 
174 		/*
175 		 * If called via : escape from open or visual, limit
176 		 * the set of available commands here to save work below.
177 		 */
178 		if (inopen) {
179 			if (c == '\n' || c == '\r' ||
180 			    c == CTRL('d') || c == EOF) {
181 				if (addr2)
182 					dot = addr2;
183 				if (c == EOF)
184 					return;
185 				continue;
186 			}
187 			if (any(c, "o"))
188 notinvis:
189 				tailprim(Command, 1, 1);
190 		}
191 		switch (c) {
192 
193 		case 'a':
194 
195 			switch (peekchar()) {
196 			case 'b':
197 /* abbreviate */
198 				tail("abbreviate");
199 				setnoaddr();
200 				mapcmd(0, 1);
201 				anyabbrs = 1;
202 				continue;
203 			case 'r':
204 /* args */
205 				tail("args");
206 				setnoaddr();
207 				eol();
208 				pargs();
209 				continue;
210 			}
211 
212 /* append */
213 			if (inopen)
214 				goto notinvis;
215 			tail("append");
216 			setdot();
217 			aiflag = exclam();
218 			donewline();
219 			vmacchng(0);
220 			deletenone();
221 			setin(addr2);
222 			inappend = 1;
223 			(void) append(gettty, addr2);
224 			inappend = 0;
225 			nochng();
226 			continue;
227 
228 		case 'c':
229 			switch (peekchar()) {
230 
231 /* copy */
232 			case 'o':
233 				tail("copy");
234 				vmacchng(0);
235 				vi_move();
236 				continue;
237 
238 /* crypt */
239 			case 'r':
240 				tail("crypt");
241 				crflag = -1;
242 			ent_crypt:
243 				setnoaddr();
244 				xflag = 1;
245 				if (permflag)
246 					(void) crypt_close(perm);
247 				permflag = 1;
248 				if ((kflag = run_setkey(perm,
249 				    (key = vgetpass(
250 					gettext("Enter key:"))))) == -1) {
251 					xflag = 0;
252 					kflag = 0;
253 					crflag = 0;
254 					smerror(gettext("Encryption facility "
255 						    "not available\n"));
256 				}
257 				if (kflag == 0)
258 					crflag = 0;
259 				continue;
260 
261 /* cd */
262 			case 'd':
263 				tail("cd");
264 				goto changdir;
265 
266 /* chdir */
267 			case 'h':
268 				ignchar();
269 				if (peekchar() == 'd') {
270 					unsigned char *p;
271 					tail2of("chdir");
272 changdir:
273 					if (savedfile[0] == '/' ||
274 					    !value(vi_WARN))
275 						(void) exclam();
276 					else
277 						(void) quickly();
278 					if (skipend()) {
279 						p = (unsigned char *)
280 						    getenv("HOME");
281 						if (p == NULL)
282 							error(gettext(
283 								"Home directory"
284 								    /*CSTYLED*/
285 								    " unknown"));
286 					} else
287 						getone(), p = file;
288 					eol();
289 					if (chdir((char *)p) < 0)
290 						filioerr(p);
291 					if (savedfile[0] != '/')
292 						edited = 0;
293 					continue;
294 				}
295 				if (inopen)
296 					tailprim((unsigned char *)"change",
297 					    2, 1);
298 				tail2of("change");
299 				break;
300 
301 			default:
302 				if (inopen)
303 					goto notinvis;
304 				tail("change");
305 				break;
306 			}
307 /* change */
308 			aiflag = exclam();
309 #ifdef XPG4ONLY
310 			setcount2();
311 			donewline();
312 #else /* XPG6 and Solaris */
313 			setCNL();
314 #endif /* XPG4ONLY */
315 			vmacchng(0);
316 			setin(addr1);
317 			(void) delete(0);
318 			inappend = 1;
319 			if (append(gettty, addr1 - 1) == 0) {
320 #ifdef XPG4
321 				/*
322 				 * P2003.2/D9:5.10.7.2.4, p. 646,
323 				 * assertion 214(A). If nothing changed,
324 				 * set dot to the line preceding the lines
325 				 * to be changed.
326 				 */
327 				dot = addr1 - 1;
328 #else /* XPG4 */
329 				dot = addr1;
330 #endif /* XPG4 */
331 				if (dot > dol)
332 					dot = dol;
333 			}
334 			inappend = 0;
335 			nochng();
336 			continue;
337 
338 /* delete */
339 		case 'd':
340 			/*
341 			 * Caution: dp and dl have special meaning already.
342 			 */
343 			tail("delete");
344 			c = cmdreg();
345 #ifdef XPG4ONLY
346 			setcount2();
347 			donewline();
348 #else /* XPG6 and Solaris */
349 			setCNL();
350 #endif /* XPG4ONLY */
351 			vmacchng(0);
352 			if (c)
353 				(void) YANKreg(c);
354 			(void) delete(0);
355 			appendnone();
356 			continue;
357 
358 /* edit */
359 /* ex */
360 		case 'e':
361 			if (crflag == 2 || crflag == -2)
362 				crflag = -1;
363 			tail(peekchar() == 'x' ? "ex" : "edit");
364 editcmd:
365 			if (!exclam() && chng)
366 				c = 'E';
367 			gotfile = 0;
368 			if (c == 'E') {
369 				if (inopen && !value(vi_AUTOWRITE)) {
370 					filename(c);
371 					gotfile = 1;
372 				}
373 				ungetchar(lastchar());
374 				if (!exclam()) {
375 					ckaw();
376 					if (chng && dol > zero) {
377 						xchng = 0;
378 						error(value(vi_TERSE) ?
379 						    gettext("No write") :
380 						    gettext("No write since "
381 							"last change (:%s! "
382 							"overrides)"),
383 						    Command);
384 					}
385 				}
386 
387 			}
388 			if (gotfile == 0)
389 				filename(c);
390 			setnoaddr();
391 doecmd:
392 			init();
393 			addr2 = zero;
394 			laste++;
395 			sync();
396 			rop(c);
397 			nochng();
398 			continue;
399 
400 /* file */
401 		case 'f':
402 			tail("file");
403 			setnoaddr();
404 			filename(c);
405 			noonl();
406 /*
407  *			synctmp();
408  */
409 			continue;
410 
411 /* global */
412 		case 'g':
413 			tail("global");
414 			global(!exclam());
415 			nochng();
416 			continue;
417 
418 /* insert */
419 		case 'i':
420 			if (inopen)
421 				goto notinvis;
422 			tail("insert");
423 			setdot();
424 			nonzero();
425 			aiflag = exclam();
426 			donewline();
427 			vmacchng(0);
428 			deletenone();
429 			setin(addr2);
430 			inappend = 1;
431 			(void) append(gettty, addr2 - 1);
432 			inappend = 0;
433 			if (dot == zero && dol > zero)
434 				dot = one;
435 			nochng();
436 			continue;
437 
438 /* join */
439 		case 'j':
440 			tail("join");
441 			c = exclam();
442 			setcount();
443 			nonzero();
444 			donewline();
445 			vmacchng(0);
446 #ifdef XPG4ONLY
447 			/*
448 			 * if no count was specified, addr1 == addr2. if only
449 			 * 1 range arg was specified, inc addr2 to allow
450 			 * joining of the next line.
451 			 */
452 			if (given < 2 && (addr1 == addr2) && (addr2 != dol))
453 				addr2++;
454 
455 #else /* XPG6 and Solaris */
456 			if (given < 2 && addr2 != dol)
457 				addr2++;
458 #endif /* XPG4ONLY */
459 			(void) join(c);
460 			continue;
461 
462 /* k */
463 		case 'k':
464 casek:
465 			pastwh();
466 			c = getchar();
467 			if (endcmd(c))
468 				serror((vi_TERSE) ?
469 				    (unsigned char *)gettext("Mark what?") :
470 				    (unsigned char *)
471 				    gettext("%s requires following "
472 				    "letter"), Command);
473 			donewline();
474 			if (!islower(c))
475 				error((vi_TERSE) ? gettext("Bad mark") :
476 					gettext("Mark must specify a letter"));
477 			setdot();
478 			nonzero();
479 			names[c - 'a'] = *addr2 &~ 01;
480 			anymarks = 1;
481 			continue;
482 
483 /* list */
484 		case 'l':
485 			tail("list");
486 #ifdef XPG4ONLY
487 			setcount2();
488 			donewline();
489 #else /* XPG6 and Solaris */
490 			setCNL();
491 #endif /* XPG4ONLY */
492 			(void) setlist(1);
493 			pflag = 0;
494 			goto print;
495 
496 		case 'm':
497 			if (peekchar() == 'a') {
498 				ignchar();
499 				if (peekchar() == 'p') {
500 /* map */
501 					tail2of("map");
502 					setnoaddr();
503 					mapcmd(0, 0);
504 					continue;
505 				}
506 /* mark */
507 				tail2of("mark");
508 				goto casek;
509 			}
510 /* move */
511 			tail("move");
512 			vmacchng(0);
513 			vi_move();
514 			continue;
515 
516 		case 'n':
517 			if (peekchar() == 'u') {
518 				tail("number");
519 				goto numberit;
520 			}
521 /* next */
522 			tail("next");
523 			setnoaddr();
524 			if (!exclam()) {
525 				ckaw();
526 				if (chng && dol > zero) {
527 					xchng = 0;
528 					error(value(vi_TERSE) ?
529 					    gettext("No write") :
530 					    gettext("No write since last "
531 						"change (:%s! overrides)"),
532 					    Command);
533 				}
534 			}
535 
536 			if (getargs())
537 				makargs();
538 			next();
539 			c = 'e';
540 			filename(c);
541 			goto doecmd;
542 
543 /* open */
544 		case 'o':
545 			tail("open");
546 			oop();
547 			pflag = 0;
548 			nochng();
549 			continue;
550 
551 		case 'p':
552 		case 'P':
553 			switch (peekchar()) {
554 #ifdef TAG_STACK
555 /* pop */
556 			case 'o':
557 				tail("pop");
558 				poptag(exclam());
559 				if (!inopen)
560 					lchng = chng - 1;
561 				else
562 					nochng();
563 				continue;
564 #endif
565 
566 /* put */
567 			case 'u':
568 				tail("put");
569 				setdot();
570 				c = cmdreg();
571 				eol();
572 				vmacchng(0);
573 				if (c)
574 					(void) putreg(c);
575 				else
576 					(void) put();
577 				continue;
578 
579 			case 'r':
580 				ignchar();
581 				if (peekchar() == 'e') {
582 /* preserve */
583 					tail2of("preserve");
584 					eol();
585 					if (preserve() == 0)
586 						error(gettext(
587 						    "Preserve failed!"));
588 					else {
589 #ifdef XPG4
590 						/*
591 						 * error() incs errcnt. this is
592 						 * misleading here; and a
593 						 * violation of POSIX. so call
594 						 * noerror() instead.
595 						 * this is for assertion ex:222.
596 						 */
597 						noerror(
598 						    gettext("File preserved."));
599 
600 #else /* XPG4 */
601 						error(
602 						    gettext("File preserved."));
603 #endif /* XPG4 */
604 					}
605 				}
606 				tail2of("print");
607 				break;
608 
609 			default:
610 				tail("print");
611 				break;
612 			}
613 /* print */
614 			setCNL();
615 			pflag = 0;
616 print:
617 			nonzero();
618 			if (clear_screen && span() > lines) {
619 				flush1();
620 				vclear();
621 			}
622 			/*
623 			 * poffset is nonzero if trailing + or - flags
624 			 * were given, and in that case we need to
625 			 * adjust dot before printing a line.
626 			 */
627 			if (poffset == 0)
628 				plines(addr1, addr2, 1);
629 			else
630 				dot = addr2;
631 			continue;
632 
633 /* quit */
634 		case 'q':
635 			tail("quit");
636 			setnoaddr();
637 			c = quickly();
638 			eol();
639 			if (!c)
640 quit:
641 				if (nomore())
642 					continue;
643 			if (inopen) {
644 				vgoto(WECHO, 0);
645 				if (!ateopr())
646 					vnfl();
647 				else {
648 					tostop();
649 				}
650 				flush();
651 				setty(normf);
652 				ixlatctl(1);
653 			}
654 			cleanup(1);
655 			exit(errcnt);
656 
657 		case 'r':
658 			if (peekchar() == 'e') {
659 				ignchar();
660 				switch (peekchar()) {
661 
662 /* rewind */
663 				case 'w':
664 					tail2of("rewind");
665 					setnoaddr();
666 					if (!exclam()) {
667 						ckaw();
668 						if (chng && dol > zero)
669 							error((vi_TERSE) ?
670 							    /*CSTYLED*/
671 							    gettext("No write") :
672 							    gettext("No write "
673 								"since last "
674 								"change (:rewi"
675 								/*CSTYLED*/
676 								"nd! overrides)"));
677 					}
678 					eol();
679 					erewind();
680 					next();
681 					c = 'e';
682 					ungetchar(lastchar());
683 					filename(c);
684 					goto doecmd;
685 
686 /* recover */
687 				case 'c':
688 					tail2of("recover");
689 					setnoaddr();
690 					c = 'e';
691 					if (!exclam() && chng)
692 						c = 'E';
693 					filename(c);
694 					if (c == 'E') {
695 						ungetchar(lastchar());
696 						(void) quickly();
697 					}
698 					init();
699 					addr2 = zero;
700 					laste++;
701 					sync();
702 					recover();
703 					rop2();
704 					revocer();
705 					if (status == 0)
706 						rop3(c);
707 					if (dol != zero)
708 						change();
709 					nochng();
710 					continue;
711 				}
712 				tail2of("read");
713 			} else
714 				tail("read");
715 /* read */
716 			if (crflag == 2 || crflag == -2)
717 			/* restore crflag for new input text */
718 				crflag = -1;
719 			if (savedfile[0] == 0 && dol == zero)
720 				c = 'e';
721 			pastwh();
722 			vmacchng(0);
723 			if (peekchar() == '!') {
724 				setdot();
725 				ignchar();
726 				unix0(0, 1);
727 				(void) vi_filter(0);
728 				continue;
729 			}
730 			filename(c);
731 			rop(c);
732 			nochng();
733 			if (inopen && endline && addr1 > zero && addr1 < dol)
734 				dot = addr1 + 1;
735 			continue;
736 
737 		case 's':
738 			switch (peekchar()) {
739 			/*
740 			 * Caution: 2nd char cannot be c, g, or r
741 			 * because these have meaning to substitute.
742 			 */
743 
744 /* set */
745 			case 'e':
746 				tail("set");
747 				setnoaddr();
748 				set();
749 				continue;
750 
751 /* shell */
752 			case 'h':
753 				tail("shell");
754 				setNAEOL();
755 				vnfl();
756 				putpad((unsigned char *)exit_ca_mode);
757 				flush();
758 				resetterm();
759 				unixwt(1, unixex("-i", (char *)0, 0, 0));
760 				vcontin(0);
761 				continue;
762 
763 /* source */
764 			case 'o':
765 #ifdef notdef
766 				if (inopen)
767 					goto notinvis;
768 #endif
769 				tail("source");
770 				setnoaddr();
771 				getone();
772 				eol();
773 				source(file, 0);
774 				continue;
775 #ifdef SIGTSTP
776 /* stop, suspend */
777 			case 't':
778 				tail("stop");
779 				goto suspend;
780 			case 'u':
781 #ifdef XPG4
782 				/*
783 				 * for POSIX, "su" with no other distinguishing
784 				 * characteristics, maps to "s". Re. P1003.D11,
785 				 * 5.10.7.3.
786 				 *
787 				 * so, unless the "su" is followed by a "s" or
788 				 * a "!", we assume that the user means "s".
789 				 */
790 				switch (d = peekchar()) {
791 				case 's':
792 				case '!':
793 #endif /* XPG4 */
794 					tail("suspend");
795 suspend:
796 					c = exclam();
797 					eol();
798 					if (!c)
799 						ckaw();
800 					onsusp(0);
801 					continue;
802 #ifdef XPG4
803 				}
804 #endif /* XPG4 */
805 #endif
806 
807 			}
808 			/* fall into ... */
809 
810 /* & */
811 /* ~ */
812 /* substitute */
813 		case '&':
814 		case '~':
815 			Command = (unsigned char *)"substitute";
816 			if (c == 's')
817 				tail(Command);
818 			vmacchng(0);
819 			if (!substitute(c))
820 				pflag = 0;
821 			continue;
822 
823 /* t */
824 		case 't':
825 			if (peekchar() == 'a') {
826 				tagflg = 1; /* :tag command */
827 				tail("tag");
828 				tagfind(exclam());
829 				tagflg = 0;
830 				if (!inopen)
831 					lchng = chng - 1;
832 				else
833 					nochng();
834 				continue;
835 			}
836 			tail("t");
837 			vmacchng(0);
838 			vi_move();
839 			continue;
840 
841 		case 'u':
842 			if (peekchar() == 'n') {
843 				ignchar();
844 				switch (peekchar()) {
845 /* unmap */
846 				case 'm':
847 					tail2of("unmap");
848 					setnoaddr();
849 					mapcmd(1, 0);
850 					continue;
851 /* unabbreviate */
852 				case 'a':
853 					tail2of("unabbreviate");
854 					setnoaddr();
855 					mapcmd(1, 1);
856 					anyabbrs = 1;
857 					continue;
858 				}
859 /* undo */
860 				tail2of("undo");
861 			} else
862 				tail("undo");
863 			setnoaddr();
864 			markDOT();
865 			c = exclam();
866 			donewline();
867 			undo(c);
868 			continue;
869 
870 		case 'v':
871 			switch (peekchar()) {
872 
873 			case 'e':
874 /* version */
875 				tail("version");
876 				setNAEOL();
877 				viprintf("%s", Version);
878 				noonl();
879 				continue;
880 
881 /* visual */
882 			case 'i':
883 				tail("visual");
884 				if (inopen) {
885 					c = 'e';
886 					goto editcmd;
887 				}
888 				vop();
889 				pflag = 0;
890 				nochng();
891 				continue;
892 			}
893 /* v */
894 			tail("v");
895 			global(0);
896 			nochng();
897 			continue;
898 
899 /* write */
900 		case 'w':
901 			c = peekchar();
902 			tail(c == 'q' ? "wq" : "write");
903 wq:
904 			if (skipwh() && peekchar() == '!') {
905 				pofix();
906 				ignchar();
907 				setall();
908 				unix0(0, 1);
909 				(void) vi_filter(1);
910 			} else {
911 				setall();
912 				if (c == 'q')
913 					write_quit = 1;
914 				else
915 					write_quit = 0;
916 				wop(1);
917 				nochng();
918 			}
919 			if (c == 'q')
920 				goto quit;
921 			continue;
922 /* X: crypt */
923 		case 'X':
924 			crflag = -1; /* determine if file is encrypted */
925 			goto ent_crypt;
926 
927 		case 'C':
928 			crflag = 1;  /* assume files read in are encrypted */
929 			goto ent_crypt;
930 
931 /* xit */
932 		case 'x':
933 			tail("xit");
934 			if (!chng)
935 				goto quit;
936 			c = 'q';
937 			goto wq;
938 
939 /* yank */
940 		case 'y':
941 			tail("yank");
942 			c = cmdreg();
943 #ifdef XPG4ONLY
944 			setcount2();
945 #else /* XPG6 and Solaris */
946 			setcount();
947 #endif /* XPG4ONLY */
948 			eol();
949 			vmacchng(0);
950 			if (c)
951 				(void) YANKreg(c);
952 			else
953 				(void) yank();
954 			continue;
955 
956 /* z */
957 		case 'z':
958 			zop(0);
959 			pflag = 0;
960 			continue;
961 
962 /* * */
963 /* @ */
964 		case '*':
965 		case '@':
966 			c = getchar();
967 			if (c == '\n' || c == '\r')
968 				ungetchar(c);
969 			if (any(c, "@*\n\r"))
970 				c = lastmac;
971 			if (isupper(c))
972 				c = tolower(c);
973 			if (!islower(c))
974 				error(gettext("Bad register"));
975 			donewline();
976 			setdot();
977 			cmdmac(c);
978 			continue;
979 
980 /* | */
981 		case '|':
982 			endline = 0;
983 			goto caseline;
984 
985 /* \n */
986 		case '\n':
987 			endline = 1;
988 caseline:
989 			notempty();
990 			if (addr2 == 0) {
991 				if (cursor_up != NOSTR && c == '\n' &&
992 				    !inglobal)
993 					c = CTRL('k');
994 				if (inglobal)
995 					addr1 = addr2 = dot;
996 				else {
997 					if (dot == dol)
998 						error((vi_TERSE) ?
999 						    gettext("At EOF") :
1000 						    gettext("At end-of-file"));
1001 					addr1 = addr2 = dot + 1;
1002 				}
1003 			}
1004 			setdot();
1005 			nonzero();
1006 			if (seensemi)
1007 				addr1 = addr2;
1008 			getline(*addr1);
1009 			if (c == CTRL('k')) {
1010 				flush1();
1011 				destline--;
1012 				if (hadpr)
1013 					shudclob = 1;
1014 			}
1015 			plines(addr1, addr2, 1);
1016 			continue;
1017 
1018 /* " */
1019 		case '"':
1020 			comment();
1021 			continue;
1022 
1023 /* # */
1024 		case '#':
1025 numberit:
1026 			setCNL();
1027 			(void) setnumb(1);
1028 			pflag = 0;
1029 			goto print;
1030 
1031 /* = */
1032 		case '=':
1033 			donewline();
1034 			setall();
1035 			if (inglobal == 2)
1036 				pofix();
1037 			viprintf("%d", lineno(addr2));
1038 			noonl();
1039 			continue;
1040 
1041 /* ! */
1042 		case '!':
1043 			if (addr2 != 0) {
1044 				vmacchng(0);
1045 				unix0(0, 1);
1046 				setdot();
1047 				(void) vi_filter(2);
1048 			} else {
1049 				unix0(1, 1);
1050 				pofix();
1051 				putpad((unsigned char *)exit_ca_mode);
1052 				flush();
1053 				resetterm();
1054 				if (!tagflg) {
1055 					unixwt(1, unixex("-c", uxb, 0, 0));
1056 				} else {
1057 					error(gettext("Invalid tags file:"
1058 					    " contains shell escape"));
1059 				}
1060 				vclrech(1);	/* vcontin(0); */
1061 				nochng();
1062 			}
1063 			continue;
1064 
1065 /* < */
1066 /* > */
1067 		case '<':
1068 		case '>':
1069 			for (cnt = 1; peekchar() == c; cnt++)
1070 				ignchar();
1071 			setCNL();
1072 			vmacchng(0);
1073 			shift(c, cnt);
1074 			continue;
1075 
1076 /* ^D */
1077 /* EOF */
1078 		case CTRL('d'):
1079 		case EOF:
1080 			if (exitoneof) {
1081 				if (addr2 != 0)
1082 					dot = addr2;
1083 				return;
1084 			}
1085 			if (!isatty(0)) {
1086 				if (intty)
1087 					/*
1088 					 * Chtty sys call at UCB may cause a
1089 					 * input which was a tty to suddenly be
1090 					 * turned into /dev/null.
1091 					 */
1092 					onhup(0);
1093 				return;
1094 			}
1095 			if (addr2 != 0) {
1096 				setlastchar('\n');
1097 				putnl();
1098 			}
1099 			if (dol == zero) {
1100 				if (addr2 == 0)
1101 					putnl();
1102 				notempty();
1103 			}
1104 			ungetchar(EOF);
1105 			zop(hadpr);
1106 			continue;
1107 		default:
1108 			if (!isalpha(c) || !isascii(c))
1109 				break;
1110 			ungetchar(c);
1111 			tailprim((unsigned char *)"", 0, 0);
1112 		}
1113 		ungetchar(c);
1114 		{
1115 			int length;
1116 			char multic[MULTI_BYTE_MAX];
1117 			wchar_t wchar;
1118 			length = _mbftowc(multic, &wchar, getchar, &peekc);
1119 			if (length < 0)
1120 				length = -length;
1121 			multic[length] = '\0';
1122 			error((vi_TERSE) ? gettext("What?") :
1123 				gettext("Unknown command character '%s'"),
1124 			    multic);
1125 		}
1126 	}
1127 }
1128