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