1 /*
2  * This code contains changes by
3  *      Gunnar Ritter, Freiburg i. Br., Germany, 2002. All rights reserved.
4  *
5  * Conditions 1, 2, and 4 and the no-warranty notice below apply
6  * to these changes.
7  *
8  *
9  * Copyright (c) 1980, 1993
10  * 	The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  * 	This product includes software developed by the University of
23  * 	California, Berkeley and its contributors.
24  * 4. Neither the name of the University nor the names of its contributors
25  *    may be used to endorse or promote products derived from this software
26  *    without specific prior written permission.
27  *
28  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38  * SUCH DAMAGE.
39  *
40  *
41  * Copyright(C) Caldera International Inc. 2001-2002. All rights reserved.
42  *
43  * Redistribution and use in source and binary forms, with or without
44  * modification, are permitted provided that the following conditions
45  * are met:
46  *   Redistributions of source code and documentation must retain the
47  *    above copyright notice, this list of conditions and the following
48  *    disclaimer.
49  *   Redistributions in binary form must reproduce the above copyright
50  *    notice, this list of conditions and the following disclaimer in the
51  *    documentation and/or other materials provided with the distribution.
52  *   All advertising materials mentioning features or use of this software
53  *    must display the following acknowledgement:
54  *      This product includes software developed or owned by Caldera
55  *      International, Inc.
56  *   Neither the name of Caldera International, Inc. nor the names of
57  *    other contributors may be used to endorse or promote products
58  *    derived from this software without specific prior written permission.
59  *
60  * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
61  * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
62  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
63  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
64  * ARE DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE
65  * LIABLE FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
66  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
67  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
68  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
69  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
70  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
71  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72  */
73 
74 #ifndef	lint
75 #ifdef	DOSCCS
76 static char sccsid[] = "@(#)ex_cmdsub.c	1.29 (gritter) 2/17/05";
77 #endif
78 #endif
79 
80 /* from ex_cmdsub.c	7.7 (Berkeley) 6/7/85 */
81 
82 #include "ex.h"
83 #include "ex_argv.h"
84 #include "ex_temp.h"
85 #include "ex_tty.h"
86 #include "ex_vis.h"
87 
88 /*
89  * Command mode subroutines implementing
90  *	append, args, copy, delete, join, move, put,
91  *	shift, tag, yank, z and undo
92  */
93 
94 bool	endline = 1;
95 line	*tad1;
96 static	int jnoop(void);
97 
98 /*
99  * Append after line a lines returned by function f.
100  * Be careful about intermediate states to avoid scramble
101  * if an interrupt comes in.
102  */
103 int
append(int (* f)(void),line * a)104 append(int (*f)(void), line *a)
105 {
106 	register line *a1, *a2, *rdot;
107 	int nline;
108 
109 	nline = 0;
110 	dot = a;
111 	if(FIXUNDO && !inopen && f!=getsub) {
112 		undap1 = undap2 = dot + 1;
113 		undkind = UNDCHANGE;
114 	}
115 	while ((*f)() == 0) {
116 		if (truedol >= endcore) {
117 			if (morelines() < 0) {
118 				if (FIXUNDO && f == getsub) {
119 					undap1 = addr1;
120 					undap2 = addr2 + 1;
121 				}
122 				error(catgets(catd, 1, 39,
123 				"Out of memory@- too many lines in file"));
124 			}
125 		}
126 		nline++;
127 		a1 = truedol + 1;
128 		a2 = a1 + 1;
129 		dot++;
130 		undap2++;
131 		dol++;
132 		unddol++;
133 		truedol++;
134 		for (rdot = dot; a1 > rdot;)
135 			*--a2 = *--a1;
136 		*rdot = 0;
137 		putmark(rdot);
138 		if (f == gettty) {
139 			dirtcnt++;
140 			TSYNC();
141 		}
142 	}
143 	return (nline);
144 }
145 
146 void
appendnone(void)147 appendnone(void)
148 {
149 
150 	if(FIXUNDO) {
151 		undkind = UNDCHANGE;
152 		undap1 = undap2 = addr1;
153 	}
154 }
155 
156 /*
157  * Print out the argument list, with []'s around the current name.
158  */
159 void
pargs(void)160 pargs(void)
161 {
162 	register char **av = argv0, *as = args0;
163 	register int ac;
164 
165 	for (ac = 0; ac < argc0; ac++) {
166 		if (ac != 0)
167 			putchar(' ' | QUOTE);
168 		if (ac + argc == argc0 - 1)
169 			printf("[");
170 		lprintf("%s", as);
171 		if (ac + argc == argc0 - 1)
172 			printf("]");
173 		as = av ? *++av : strend(as) + 1;
174 	}
175 	noonl();
176 }
177 
178 /*
179  * Delete lines; two cases are if we are really deleting,
180  * more commonly we are just moving lines to the undo save area.
181  */
182 void
delete(int hush)183 delete(int hush)
184 {
185 	register line *a1, *a2;
186 
187 	nonzero();
188 	if(FIXUNDO) {
189 		register shand dsavint;
190 
191 #ifdef TRACE
192 		if (trace)
193 			vudump("before delete");
194 #endif
195 		change();
196 		dsavint = signal(SIGINT, SIG_IGN);
197 		undkind = UNDCHANGE;
198 		a1 = addr1;
199 		squish();
200 		a2 = addr2;
201 		if (a2++ != dol) {
202 			reverse(a1, a2);
203 			reverse(a2, dol + 1);
204 			reverse(a1, dol + 1);
205 		}
206 		dol -= a2 - a1;
207 		unddel = a1 - 1;
208 		if (a1 > dol)
209 			a1 = dol;
210 		dot = a1;
211 		pkill[0] = pkill[1] = 0;
212 		signal(SIGINT, dsavint);
213 #ifdef TRACE
214 		if (trace)
215 			vudump("after delete");
216 #endif
217 	} else {
218 		register line *a3;
219 		register int i;
220 
221 		change();
222 		a1 = addr1;
223 		a2 = addr2 + 1;
224 		a3 = truedol;
225 		i = a2 - a1;
226 		unddol -= i;
227 		undap2 -= i;
228 		dol -= i;
229 		truedol -= i;
230 		do
231 			*a1++ = *a2++;
232 		while (a2 <= a3);
233 		a1 = addr1;
234 		if (a1 > dol)
235 			a1 = dol;
236 		dot = a1;
237 	}
238 	if (!hush)
239 		killed();
240 }
241 
242 void
deletenone(void)243 deletenone(void)
244 {
245 
246 	if(FIXUNDO) {
247 		undkind = UNDCHANGE;
248 		squish();
249 		unddel = addr1;
250 	}
251 }
252 
253 /*
254  * Crush out the undo save area, moving the open/visual
255  * save area down in its place.
256  */
257 void
squish(void)258 squish(void)
259 {
260 	register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
261 
262 	if(FIXUNDO) {
263 		if (inopen == -1)
264 			return;
265 		if (a1 < a2 && a2 < a3)
266 			do
267 				*a1++ = *a2++;
268 			while (a2 < a3);
269 		truedol -= unddol - dol;
270 		unddol = dol;
271 	}
272 }
273 
274 static int	jcount;
275 
276 /*
277  * Join lines.  Special hacks put in spaces, two spaces if
278  * preceding line ends with '.', or no spaces if next line starts with ).
279  */
280 void
join(int c)281 join(int c)
282 {
283 	register line *a1;
284 	register char *cp, *cp1;
285 
286 	cp = genbuf;
287 	*cp = 0;
288 	for (a1 = addr1; a1 <= addr2; a1++) {
289 		getline(*a1);
290 		cp1 = linebuf;
291 		if (a1 != addr1 && c == 0) {
292 			while (*cp1 == ' ' || *cp1 == '\t')
293 				cp1++;
294 			if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
295 				if (*cp1 != ')') {
296 					*cp++ = ' ';
297 					if (cp[-2] == '.')
298 						*cp++ = ' ';
299 				}
300 			}
301 		}
302 		while (*cp++ = *cp1++)
303 			if (cp > &genbuf[LBSIZE-2])
304 				error(catgets(catd, 1, 40,
305 		"Line overflow|Result line of join would be too long"));
306 		cp--;
307 	}
308 	strcLIN(genbuf);
309 	delete(0);
310 	jcount = 1;
311 	if (FIXUNDO)
312 		undap1 = undap2 = addr1;
313 	ignore(append(jnoop, --addr1));
314 	if (FIXUNDO)
315 		vundkind = VMANY;
316 }
317 
318 static int
jnoop(void)319 jnoop(void)
320 {
321 
322 	return(--jcount);
323 }
324 
325 /*
326  * Move and copy lines.  Hard work is done by move1 which
327  * is also called by undo.
328  */
329 
330 void
move1(int cflag,line * addrt)331 move1(int cflag, line *addrt)
332 {
333 	register line *adt, *ad1, *ad2;
334 	int lines;
335 
336 	adt = addrt;
337 	lines = (addr2 - addr1) + 1;
338 	if (cflag) {
339 		tad1 = addr1;
340 		ad1 = dol;
341 		ignore(append(getcopy, ad1++));
342 		ad2 = dol;
343 	} else {
344 		ad2 = addr2;
345 		for (ad1 = addr1; ad1 <= ad2;)
346 			*ad1++ &= ~01;
347 		ad1 = addr1;
348 	}
349 	ad2++;
350 	if (adt < ad1) {
351 		if (adt + 1 == ad1 && !cflag && !inglobal)
352 			error(catgets(catd, 1, 41,
353 					"That move would do nothing!"));
354 		dot = adt + (ad2 - ad1);
355 		if (++adt != ad1) {
356 			reverse(adt, ad1);
357 			reverse(ad1, ad2);
358 			reverse(adt, ad2);
359 		}
360 	} else if (adt >= ad2) {
361 		dot = adt++;
362 		reverse(ad1, ad2);
363 		reverse(ad2, adt);
364 		reverse(ad1, adt);
365 	} else
366 		error(catgets(catd, 1, 42, "Move to a moved line"));
367 	change();
368 	if (!inglobal)
369 		if(FIXUNDO) {
370 			if (cflag) {
371 				undap1 = addrt + 1;
372 				undap2 = undap1 + lines;
373 				deletenone();
374 			} else {
375 				undkind = UNDMOVE;
376 				undap1 = addr1;
377 				undap2 = addr2;
378 				unddel = addrt;
379 				squish();
380 			}
381 		}
382 }
383 
384 void
move(void)385 move(void)
386 {
387 	register line *adt;
388 	bool iscopy = 0;
389 
390 	if (Command[0] == 'm') {
391 		setdot1();
392 		markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
393 	} else {
394 		iscopy++;
395 		setdot();
396 	}
397 	nonzero();
398 	adt = address((char*)0);
399 	if (adt == 0)
400 		serror(catgets(catd, 1, 43,
401 			"%s where?|%s requires a trailing address"), Command);
402 	newline();
403 	move1(iscopy, adt);
404 	killed();
405 }
406 
407 int
getcopy(void)408 getcopy(void)
409 {
410 
411 	if (tad1 > addr2)
412 		return (EOF);
413 	getline(*tad1++);
414 	return (0);
415 }
416 
417 /*
418  * Put lines in the buffer from the undo save area.
419  */
420 int
getput(void)421 getput(void)
422 {
423 
424 	if (tad1 > unddol)
425 		return (EOF);
426 	getline(*tad1++);
427 	tad1++;
428 	return (0);
429 }
430 
431 /*ARGSUSED*/
432 void
put(int unused)433 put(int unused)
434 {
435 	register int cnt;
436 
437 	if (!FIXUNDO)
438 		error(catgets(catd, 1, 44, "Cannot put inside global/macro"));
439 	cnt = unddol - dol;
440 	if (cnt && inopen && pkill[0] && pkill[1]) {
441 		pragged(1);
442 		return;
443 	}
444 	tad1 = dol + 1;
445 	ignore(append(getput, addr2));
446 	undkind = UNDPUT;
447 	notecnt = cnt;
448 	netchange(cnt);
449 }
450 
451 /*
452  * A tricky put, of a group of lines in the middle
453  * of an existing line.  Only from open/visual.
454  * Argument says pkills have meaning, e.g. called from
455  * put; it is 0 on calls from putreg.
456  */
457 void
pragged(int kill)458 pragged(int kill)
459 {
460 	extern char *cursor;
461 	register char *gp = &genbuf[cursor - linebuf];
462 
463 	/*
464 	 * This kind of stuff is TECO's forte.
465 	 * We just grunge along, since it cuts
466 	 * across our line-oriented model of the world
467 	 * almost scrambling our addled brain.
468 	 */
469 	if (!kill)
470 		getDOT();
471 	strcpy(genbuf, linebuf);
472 	getline(*unddol);
473 	if (kill)
474 		*pkill[1] = 0;
475 	strcat(linebuf, gp);
476 	putmark(unddol);
477 	getline(dol[1]);
478 	if (kill)
479 		strcLIN(pkill[0]);
480 	safecp(gp, linebuf, sizeof genbuf - (gp - genbuf), "Line too long");
481 	strcLIN(genbuf);
482 	putmark(dol+1);
483 	undkind = UNDCHANGE;
484 	undap1 = dot;
485 	undap2 = dot + 1;
486 	unddel = dot - 1;
487 	undo(1);
488 }
489 
490 /*
491  * Shift lines, based on c.
492  * If c is neither < nor >, then this is a lisp aligning =.
493  */
494 void
shift(int c,int cnt)495 shift(int c, int cnt)
496 {
497 	register line *addr;
498 	register char *cp = NULL;
499 	char *dp;
500 	register int i;
501 
502 	if(FIXUNDO)
503 		save12(), undkind = UNDCHANGE;
504 	cnt *= value(SHIFTWIDTH);
505 	for (addr = addr1; addr <= addr2; addr++) {
506 		dot = addr;
507 #ifdef LISPCODE
508 		if (c == '=' && addr == addr1 && addr != addr2)
509 			continue;
510 #endif
511 		getDOT();
512 		i = whitecnt(linebuf);
513 		switch (c) {
514 
515 		case '>':
516 			if (linebuf[0] == 0)
517 				continue;
518 			cp = genindent(i + cnt);
519 			break;
520 
521 		case '<':
522 			if (i == 0)
523 				continue;
524 			i -= cnt;
525 			cp = i > 0 ? genindent(i) : genbuf;
526 			break;
527 
528 #ifdef LISPCODE
529 		default:
530 			i = lindent(addr);
531 			getDOT();
532 			cp = genindent(i);
533 			break;
534 #endif
535 		}
536 		if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
537 			error(catgets(catd, 1, 45,
538 		"Line too long|Result line after shift would be too long"));
539 		CP(cp, dp);
540 		strcLIN(genbuf);
541 		putmark(addr);
542 	}
543 	killed();
544 }
545 
546 /*
547  * Find a tag in the tags file.
548  * Most work here is in parsing the tags file itself.
549  */
550 void
tagfind(bool quick)551 tagfind(bool quick)
552 {
553 	char cmdbuf[BUFSIZ];
554 	char filebuf[FNSIZE];
555 	char tagfbuf[128];
556 	register int c, d;
557 	bool samef = 1;
558 	int tfcount = 0;
559 	int omagic;
560 	int owrapscan;
561 	char *fn, *fne;
562 	struct stat sbuf;
563 	char *savefirstpat = NULL;
564 	int	ofailed;
565 #ifdef FASTTAG
566 	int ft_iof;
567 	char ft_iofbuf[MAXBSIZE];
568 	off_t mid;	/* assumed byte offset */
569 	off_t top, bot;	/* length of tag file */
570 #endif
571 
572 	omagic = value(MAGIC);
573 	owrapscan = value(WRAPSCAN);
574 	ofailed = failed;
575 	failed = 1;
576 	if (!skipend()) {
577 		register char *lp = lasttag;
578 
579 		while (!is_white(peekchar()) && !endcmd(peekchar()))
580 			if (lp < &lasttag[sizeof lasttag - 2])
581 				*lp++ = getchar();
582 			else
583 				ignchar();
584 		*lp++ = 0;
585 		if (!endcmd(peekchar()))
586 badtag:
587 			error(catgets(catd, 1, 46,
588 					"Bad tag|Give one tag per line"));
589 	} else if (lasttag[0] == 0)
590 		error(catgets(catd, 1, 47, "No previous tag"));
591 	c = getchar();
592 	if (!endcmd(c))
593 		goto badtag;
594 	if (c == EOF)
595 		ungetchar(c);
596 	clrstats();
597 
598 	/*
599 	 * Loop once for each file in tags "path".
600 	 */
601 	safecp(tagfbuf, svalue(TAGS), sizeof tagfbuf, "Tag too long");
602 	fne = tagfbuf - 1;
603 	while (fne) {
604 		fn = ++fne;
605 		while (*fne && *fne != ' ')
606 			fne++;
607 		if (*fne == 0)
608 			fne = 0;	/* done, quit after this time */
609 		else
610 			*fne = 0;	/* null terminate filename */
611 #ifdef FASTTAG
612 		ft_iof = topen(fn, ft_iofbuf);
613 		if (ft_iof == -1)
614 			continue;
615 		tfcount++;
616 		fstat(ft_iof, &sbuf);
617 		top = sbuf.st_size;
618 		if (top == (off_t) 0 )
619 			top = (off_t) -1;
620 		bot = (off_t) 0;
621 		while (top >= bot) {
622 #else
623 		/*
624 		 * Avoid stdio and scan tag file linearly.
625 		 */
626 		io = open(fn, O_RDONLY);
627 		if (io<0)
628 			continue;
629 		tfcount++;
630 		if (fstat(io, &sbuf) < 0 || sbuf.st_blksize > LBSIZE)
631 			bsize = LBSIZE;
632 		else {
633 			bsize = sbuf.st_blksize;
634 			if (bsize <= 0)
635 				bsize = LBSIZE;
636 		}
637 		while (getfile() == 0) {
638 #endif
639 			/* loop for each tags file entry */
640 			register char *cp = linebuf;
641 			register char *lp = lasttag;
642 			char *oglobp;
643 
644 #ifdef FASTTAG
645 			mid = (top + bot) / 2;
646 			tseek(ft_iof, mid);
647 			if (mid > 0)	/* to get first tag in file to work */
648 				/* scan to next \n */
649 				if(tgets(linebuf, sizeof linebuf, ft_iof)==0)
650 					goto goleft;
651 			/* get the line itself */
652 			if(tgets(linebuf, sizeof linebuf, ft_iof)==0)
653 				goto goleft;
654 #ifdef TDEBUG
655 			printf("tag: %o %o %o %s\n", bot, mid, top, linebuf);
656 #endif
657 #endif
658 			while (*cp && *lp == *cp)
659 				cp++, lp++;
660 			if ((*lp || !is_white(*cp)) && (value(TAGLENGTH)==0 ||
661 			    lp-lasttag < value(TAGLENGTH))) {
662 #ifdef FASTTAG
663 				if (*lp > *cp)
664 					bot = mid + 1;
665 				else
666 goleft:
667 					top = mid - 1;
668 #endif
669 				/* Not this tag.  Try the next */
670 				continue;
671 			}
672 
673 			/*
674 			 * We found the tag.  Decode the line in the file.
675 			 */
676 #ifdef FASTTAG
677 			tclose(ft_iof);
678 #else
679 			close(io);
680 #endif
681 			/* Rest of tag if abbreviated */
682 			while (!is_white(*cp))
683 				cp++;
684 
685 			/* name of file */
686 			while (*cp && is_white(*cp))
687 				cp++;
688 			if (!*cp)
689 badtags:
690 				serror(catgets(catd, 1, 48,
691 					"%s: Bad tags file entry"), lasttag);
692 			lp = filebuf;
693 			while (*cp && *cp != ' ' && *cp != '\t') {
694 				if (lp < &filebuf[sizeof filebuf - 2])
695 					*lp++ = *cp;
696 				cp++;
697 			}
698 			*lp++ = 0;
699 
700 			if (*cp == 0)
701 				goto badtags;
702 			if (dol != zero) {
703 				/*
704 				 * Save current position in 't for ^^ in visual.
705 				 */
706 				names['t'-'a'] = *dot &~ 01;
707 				if (inopen) {
708 					extern char *ncols['z'-'a'+2];
709 					extern char *cursor;
710 
711 					ncols['t'-'a'] = cursor;
712 				}
713 			}
714 			safecp(cmdbuf, cp, sizeof cmdbuf, "command too long");
715 			if (strcmp(filebuf, savedfile) || !edited) {
716 				char cmdbuf2[sizeof filebuf + 10];
717 
718 				savefirstpat = firstpat;
719 				firstpat = NULL;
720 				/* Different file.  Do autowrite & get it. */
721 				if (!quick) {
722 					ckaw();
723 					if (chng && dol > zero)
724 						error(catgets(catd, 1, 49,
725 			"No write@since last change (:tag! overrides)"));
726 				}
727 				oglobp = globp;
728 				strcpy(cmdbuf2, "e! ");
729 				strcat(cmdbuf2, filebuf);
730 				globp = cmdbuf2;
731 				d = peekc; ungetchar(0);
732 				commands(1, 1);
733 				peekc = d;
734 				globp = oglobp;
735 				value(MAGIC) = omagic;
736 				if (tflag > 0)
737 					value(WRAPSCAN) = owrapscan;
738 				samef = 0;
739 				firstpat = savefirstpat;
740 			}
741 
742 			/*
743 			 * Look for pattern in the current file.
744 			 */
745 			oglobp = globp;
746 			globp = cmdbuf;
747 			d = peekc; ungetchar(0);
748 			if (samef)
749 				markpr(dot);
750 			/*
751 			 * BUG: if it isn't found (user edited header
752 			 * line) we get left in nomagic mode.
753 			 */
754 			value(MAGIC) = 0;
755 			if (tflag > 0)
756 				value(WRAPSCAN) = 1;
757 			commands(1, 1);
758 			failed = ofailed;
759 			peekc = d;
760 			globp = oglobp;
761 			value(MAGIC) = omagic;
762 			if (tflag > 0) {
763 				value(WRAPSCAN) = owrapscan;
764 				if (savefirstpat) {
765 					globp = savefirstpat;
766 					tflag = -1;
767 				} else
768 					tflag = 0;
769 			}
770 			return;
771 		}	/* end of "for each tag in file" */
772 
773 		/*
774 		 * No such tag in this file.  Close it and try the next.
775 		 */
776 #ifdef FASTTAG
777 		tclose(ft_iof);
778 #else
779 		close(io);
780 #endif
781 	}	/* end of "for each file in path" */
782 	if (tfcount <= 0)
783 		error(catgets(catd, 1, 50, "No tags file"));
784 	else
785 		serror(catgets(catd, 1, 51,
786 				"%s: No such tag@in tags file"), lasttag);
787 }
788 
789 /*
790  * Save lines from addr1 thru addr2 as though
791  * they had been deleted.
792  */
793 /*ARGSUSED*/
794 void
795 yank(int unused)
796 {
797 
798 	if (!FIXUNDO)
799 		error(catgets(catd, 1, 52, "Can't yank inside global/macro"));
800 	save12();
801 	undkind = UNDNONE;
802 	killcnt(addr2 - addr1 + 1);
803 }
804 
805 /*
806  * z command; print windows of text in the file.
807  *
808  * If this seems unreasonably arcane, the reasons
809  * are historical.  This is one of the first commands
810  * added to the first ex (then called en) and the
811  * number of facilities here were the major advantage
812  * of en over ed since they allowed more use to be
813  * made of fast terminals w/o typing .,.22p all the time.
814  */
815 bool	zhadpr;
816 bool	znoclear;
817 short	zweight;
818 
819 void
820 zop(int hadpr)
821 {
822 	register int c, lines, op;
823 	bool excl;
824 
825 	zhadpr = hadpr;
826 	notempty();
827 	znoclear = 0;
828 	zweight = 0;
829 	excl = exclam();
830 	switch (c = op = getchar()) {
831 
832 	case '^':
833 		zweight = 1;
834 	case '-':
835 	case '+':
836 		while (peekchar() == op) {
837 			ignchar();
838 			zweight++;
839 		}
840 	case '=':
841 	case '.':
842 		c = getchar();
843 		break;
844 
845 	case EOF:
846 		znoclear++;
847 		break;
848 
849 	default:
850 		op = 0;
851 		break;
852 	}
853 	if (isdigit(c)) {
854 		lines = c - '0';
855 		for(;;) {
856 			c = getchar();
857 			if (!isdigit(c))
858 				break;
859 			lines *= 10;
860 			lines += c - '0';
861 		}
862 		if (lines < TLINES)
863 			znoclear++;
864 		value(WINDOW) = lines;
865 		if (op == '=')
866 			lines += 2;
867 	} else
868 		lines = op == EOF ? value(SCROLL) : excl ? TLINES - 1 : 2*value(SCROLL);
869 	if (inopen || c != EOF) {
870 		ungetchar(c);
871 		newline();
872 	}
873 	addr1 = addr2;
874 	if (addr2 == 0 && dot < dol && op == 0)
875 		addr1 = addr2 = dot+1;
876 	setdot();
877 	zop2(lines, op);
878 }
879 
880 static void
881 splitit(void)
882 {
883 	register int l;
884 
885 	for (l = TCOLUMNS > 80 ? 40 : TCOLUMNS / 2; l > 0; l--)
886 		putchar('-');
887 	putnl();
888 }
889 
890 void
891 zop2(register int lines, register int op)
892 {
893 	register line *split;
894 
895 	split = NULL;
896 	switch (op) {
897 
898 	case EOF:
899 		if (addr2 == dol)
900 			error(catgets(catd, 1, 53, "\nAt EOF"));
901 	case '+':
902 		if (addr2 == dol)
903 			error(catgets(catd, 1, 54, "At EOF"));
904 		addr2 += lines * zweight;
905 		if (addr2 > dol)
906 			error(catgets(catd, 1, 55, "Hit BOTTOM"));
907 		addr2++;
908 	default:
909 		addr1 = addr2;
910 		addr2 += lines-1;
911 		dot = addr2;
912 		break;
913 
914 	case '=':
915 	case '.':
916 		znoclear = 0;
917 		lines--;
918 		lines >>= 1;
919 		if (op == '=')
920 			lines--;
921 		addr1 = addr2 - lines;
922 		if (op == '=')
923 			dot = split = addr2;
924 		addr2 += lines;
925 		if (op == '.') {
926 			markDOT();
927 			dot = addr2;
928 		}
929 		break;
930 
931 	case '^':
932 	case '-':
933 		addr2 -= lines * zweight;
934 		if (addr2 < one)
935 			error(catgets(catd, 1, 56, "Hit TOP"));
936 		lines--;
937 		addr1 = addr2 - lines;
938 		dot = addr2;
939 		break;
940 	}
941 	if (addr1 <= zero)
942 		addr1 = one;
943 	if (addr2 > dol)
944 		addr2 = dol;
945 	if (dot > dol)
946 		dot = dol;
947 	if (addr1 > addr2)
948 		return;
949 	if (op == EOF && zhadpr) {
950 		getline(*addr1);
951 		putchar('\r' | QUOTE);
952 		shudclob = 1;
953 	} else if (znoclear == 0 && CL != NOSTR && !inopen) {
954 		flush1();
955 		vclear();
956 	}
957 	if (addr2 - addr1 > 1)
958 		pstart();
959 	if (split) {
960 		plines(addr1, split - 1, 0);
961 		splitit();
962 		plines(split, split, 0);
963 		splitit();
964 		addr1 = split + 1;
965 	}
966 	plines(addr1, addr2, 0);
967 }
968 
969 void
970 plines(line *adr1, register line *adr2, bool movedot)
971 {
972 	register line *addr;
973 
974 	pofix();
975 	for (addr = adr1; addr <= adr2; addr++) {
976 		getline(*addr);
977 		pline(lineno(addr));
978 		if (inopen) {
979 			putchar('\n' | QUOTE);
980 		}
981 		if (movedot)
982 			dot = addr;
983 	}
984 }
985 
986 void
987 pofix(void)
988 {
989 
990 	if (inopen && Outchar != termchar) {
991 		vnfl();
992 		setoutt();
993 	}
994 }
995 
996 /*
997  * Be (almost completely) sure there really
998  * was a change, before claiming to undo.
999  */
1000 void
1001 somechange(void)
1002 {
1003 	register line *ip, *jp;
1004 
1005 	switch (undkind) {
1006 
1007 	case UNDMOVE:
1008 		return;
1009 
1010 	case UNDCHANGE:
1011 		if (undap1 == undap2 && dol == unddol)
1012 			break;
1013 		return;
1014 
1015 	case UNDPUT:
1016 		if (undap1 != undap2)
1017 			return;
1018 		break;
1019 
1020 	case UNDALL:
1021 		if (unddol - dol != lineDOL())
1022 			return;
1023 		for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
1024 			if ((*ip &~ 01) != (*jp &~ 01))
1025 				return;
1026 		break;
1027 
1028 	case UNDNONE:
1029 		error(catgets(catd, 1, 57, "Nothing to undo"));
1030 	}
1031 	error(catgets(catd, 1, 58,
1032 	"Nothing changed|Last undoable command didn't change anything"));
1033 }
1034 
1035 /*
1036  * Dudley doright to the rescue.
1037  * Undo saves the day again.
1038  * A tip of the hatlo hat to Warren Teitleman
1039  * who made undo as useful as do.
1040  *
1041  * Command level undo works easily because
1042  * the editor has a unique temporary file
1043  * index for every line which ever existed.
1044  * We don't have to save large blocks of text,
1045  * only the indices which are small.  We do this
1046  * by moving them to after the last line in the
1047  * line buffer array, and marking down info
1048  * about whence they came.
1049  *
1050  * Undo is its own inverse.
1051  */
1052 void
1053 undo(bool c)
1054 {
1055 	register int i, j;
1056 	register line *jp, *kp;
1057 	line *dolp1, *newdol, *newadot;
1058 
1059 #ifdef TRACE
1060 	if (trace)
1061 		vudump("before undo");
1062 #endif
1063 	if (inglobal && inopen <= 0)
1064 		error(catgets(catd, 1, 59, "Can't undo in global@commands"));
1065 	if (!c)
1066 		somechange();
1067 	pkill[0] = pkill[1] = 0;
1068 	change();
1069 	if (undkind == UNDMOVE) {
1070  		/*
1071 		 * Command to be undone is a move command.
1072 		 * This is handled as a special case by noting that
1073 		 * a move "a,b m c" can be inverted by another move.
1074 		 */
1075 		if ((i = (jp = unddel) - undap2) > 0) {
1076 			/*
1077 			 * when c > b inverse is a+(c-b),c m a-1
1078 			 */
1079 			addr2 = jp;
1080 			addr1 = (jp = undap1) + i;
1081 			unddel = jp-1;
1082 		} else {
1083 			/*
1084 			 * when b > c inverse is  c+1,c+1+(b-a) m b
1085 			 */
1086 			addr1 = ++jp;
1087 			addr2 = jp + ((unddel = undap2) - undap1);
1088 		}
1089 		kp = undap1;
1090 		move1(0, unddel);
1091 		dot = kp;
1092 		Command = "move";
1093 		killed();
1094 	} else {
1095 		int cnt;
1096 
1097 		newadot = dot;
1098 		cnt = lineDOL();
1099 		newdol = dol;
1100 		dolp1 = dol + 1;
1101 		/*
1102 		 * If a mark is pointing to a line between undap1 and
1103 		 * undap2-1, it would be lost (i.e. pointing into the
1104 		 * block between dolp and undol) after the undo. Thus
1105 		 * these marks have to be changed to point to the line
1106 		 * after dolp1 that is restored later during this undo
1107 		 * operation.
1108 		 */
1109 		if (anymarks)
1110 			for (i = 0; &undap1[i] < undap2; i++)
1111 				for (j = 0; j <= 'z'-'a'; j++)
1112 					if (names[j] == (undap1[i] & ~01))
1113 						names[j] = dolp1[i] & ~01;
1114 		/*
1115 		 * Command to be undone is a non-move.
1116 		 * All such commands are treated as a combination of
1117 		 * a delete command and a append command.
1118 		 * We first move the lines appended by the last command
1119 		 * from undap1 to undap2-1 so that they are just before the
1120 		 * saved deleted lines.
1121 		 */
1122 		if ((i = (kp = undap2) - (jp = undap1)) > 0) {
1123 			if (kp != dolp1) {
1124 				reverse(jp, kp);
1125 				reverse(kp, dolp1);
1126 				reverse(jp, dolp1);
1127 			}
1128 			/*
1129 			 * Account for possible backward motion of target
1130 			 * for restoration of saved deleted lines.
1131 			 */
1132 			if (unddel >= jp)
1133 				unddel -= i;
1134 			newdol -= i;
1135 			/*
1136 			 * For the case where no lines are restored, dot
1137 			 * is the line before the first line deleted.
1138 			 */
1139 			dot = jp-1;
1140 		}
1141 		/*
1142 		 * Now put the deleted lines, if any, back where they were.
1143 		 * Basic operation is: dol+1,unddol m unddel
1144 		 */
1145 		if (undkind == UNDPUT) {
1146 			unddel = undap1 - 1;
1147 			squish();
1148 		}
1149 		jp = unddel + 1;
1150 		if ((i = (kp = unddol) - dol) > 0) {
1151 			if (jp != dolp1) {
1152 				reverse(jp, dolp1);
1153 				reverse(dolp1, ++kp);
1154 				reverse(jp, kp);
1155 			}
1156 			/*
1157 			 * Account for possible forward motion of the target
1158 			 * for restoration of the deleted lines.
1159 			 */
1160 			if (undap1 >= jp)
1161 				undap1 += i;
1162 			/*
1163 			 * Dot is the first resurrected line.
1164 			 */
1165 			dot = jp;
1166 			newdol += i;
1167 		}
1168 		/*
1169 		 * Clean up so we are invertible
1170 		 */
1171 		unddel = undap1 - 1;
1172 		undap1 = jp;
1173 		undap2 = jp + i;
1174 		dol = newdol;
1175 		netchHAD(cnt);
1176 		if (undkind == UNDALL) {
1177 			dot = undadot;
1178 			undadot = newadot;
1179 		} else
1180 			undkind = UNDCHANGE;
1181 	}
1182 	/*
1183 	 * Defensive programming - after a munged undadot.
1184 	 * Also handle empty buffer case.
1185 	 */
1186 	if ((dot <= zero || dot > dol) && dot != dol)
1187 		dot = one;
1188 #ifdef TRACE
1189 	if (trace)
1190 		vudump("after undo");
1191 #endif
1192 }
1193 
1194 /*
1195  * Map command:
1196  * map src dest
1197  */
1198 void
1199 mapcmd(int un, int ab)
1200 	/* int un;	/\* true if this is unmap command */
1201 	/*int ab;	/\* true if this is abbr command */
1202 {
1203 	char lhs[100], rhs[100];	/* max sizes resp. */
1204 	register char *p;
1205 	register int c;		/* mjm: char --> int */
1206 	char *dname;
1207 	struct maps *mp;	/* the map structure we are working on */
1208 
1209 	mp = ab ? abbrevs : exclam() ? immacs : arrows;
1210 	if (skipend()) {
1211 		int i;
1212 
1213 		/* print current mapping values */
1214 		if (peekchar() != EOF)
1215 			ignchar();
1216 		if (un)
1217 			error(catgets(catd, 1, 60, "Missing lhs"));
1218 		if (inopen)
1219 			pofix();
1220 		for (i=0; mp[i].mapto; i++)
1221 			if (mp[i].cap) {
1222 				lprintf("%s", mp[i].descr);
1223 				putchar('\t');
1224 				lprintf("%s", mp[i].cap);
1225 				putchar('\t');
1226 				lprintf("%s", mp[i].mapto);
1227 				putNFL();
1228 			}
1229 		return;
1230 	}
1231 
1232 	ignore(skipwh());
1233 	for (p=lhs; ; ) {
1234 		c = getchar();
1235 		if (c == CTRL('v')) {
1236 			c = getchar();
1237 		} else if (!un && any(c, " \t")) {
1238 			/* End of lhs */
1239 			break;
1240 		} else if (endcmd(c) && c!='"') {
1241 			ungetchar(c);
1242 			if (un) {
1243 				newline();
1244 				*p = 0;
1245 				addmac(lhs, NOSTR, NOSTR, mp);
1246 				return;
1247 			} else
1248 				error(catgets(catd, 1, 61, "Missing rhs"));
1249 		}
1250 		*p++ = c;
1251 	}
1252 	*p = 0;
1253 
1254 	if (skipend())
1255 		error(catgets(catd, 1, 62, "Missing rhs"));
1256 	for (p=rhs; ; ) {
1257 		c = getchar();
1258 		if (c == CTRL('v')) {
1259 			c = getchar();
1260 		} else if (endcmd(c) && c!='"') {
1261 			ungetchar(c);
1262 			break;
1263 		}
1264 		*p++ = c;
1265 	}
1266 	*p = 0;
1267 	newline();
1268 	/*
1269 	 * Special hack for function keys: #1 means key f1, etc.
1270 	 * If the terminal doesn't have function keys, we just use #1.
1271 	 */
1272 	if (lhs[0] == '#') {
1273 		char *fnkey;
1274 		char funkey[3];
1275 
1276 		fnkey = fkey(lhs[1] - '0');
1277 		funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0;
1278 		if (fnkey)
1279 			strcpy(lhs, fnkey);
1280 		dname = funkey;
1281 	} else {
1282 		dname = lhs;
1283 	}
1284 	addmac(lhs,rhs,dname,mp);
1285 }
1286 
1287 /*
1288  * Create the integer version of a macro string, for processing in visual
1289  * mode. imapspace cannot overflow because an earlier overflow for mapspace
1290  * would have been detected already.
1291  */
1292 static void
1293 intmac(int **dp, char *cp)
1294 {
1295 	int	c, n;
1296 
1297 	if (imsnext == NULL)
1298 		imsnext = imapspace;
1299 	*dp = imsnext;
1300 	for (;;) {
1301 		nextc(c, cp, n);
1302 		*imsnext++ = c;
1303 		if (c == 0)
1304 			break;
1305 		cp += n;
1306 	}
1307 }
1308 
1309 /*
1310  * Add a macro definition to those that already exist. The sequence of
1311  * chars "src" is mapped into "dest". If src is already mapped into something
1312  * this overrides the mapping. There is no recursion. Unmap is done by
1313  * using NOSTR for dest.  Dname is what to show in listings.  mp is
1314  * the structure to affect (arrows, etc).
1315  */
1316 void
1317 addmac1(register char *src,register char *dest,register char *dname,
1318 		register struct maps *mp, int force)
1319 {
1320 	register int slot, zer;
1321 
1322 #ifdef TRACE
1323 	if (trace)
1324 		fprintf(trace, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src, dest, dname, mp);
1325 #endif
1326 	if (dest && mp==arrows && !force) {
1327 		/* Make sure user doesn't screw himself */
1328 		/*
1329 		 * Prevent tail recursion. We really should be
1330 		 * checking to see if src is a suffix of dest
1331 		 * but this makes mapping involving escapes that
1332 		 * is reasonable mess up.
1333 		 */
1334 		if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
1335 			error(catgets(catd, 1, 63, "No tail recursion"));
1336 		/*
1337 		 * We don't let the user rob himself of ":", and making
1338 		 * multi char words is a bad idea so we don't allow it.
1339 		 * Note that if user sets mapinput and maps all of return,
1340 		 * linefeed, and escape, he can screw himself. This is
1341 		 * so weird I don't bother to check for it.
1342 		 */
1343 		if (isalpha(src[0]&0377) && src[1] || any(src[0],":"))
1344 			error(catgets(catd, 1, 64,
1345 						"Too dangerous to map that"));
1346 	}
1347 	else if (dest) {
1348 		/* check for tail recursion in input mode: fussier */
1349 		if (eq(src, dest+strlen(dest)-strlen(src)))
1350 			error(catgets(catd, 1, 65, "No tail recursion"));
1351 	}
1352 	/*
1353 	 * If the src were null it would cause the dest to
1354 	 * be mapped always forever. This is not good.
1355 	 */
1356 	if (!force && (src == NOSTR || src[0] == 0))
1357 		error(catgets(catd, 1, 66, "Missing lhs"));
1358 
1359 	/* see if we already have a def for src */
1360 	zer = -1;
1361 	for (slot=0; mp[slot].mapto; slot++) {
1362 		if (mp[slot].cap) {
1363 			if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto))
1364 				break;	/* if so, reuse slot */
1365 		} else {
1366 			zer = slot;	/* remember an empty slot */
1367 		}
1368 	}
1369 
1370 	if (dest == NOSTR) {
1371 		/* unmap */
1372 		if (mp[slot].cap) {
1373 			mp[slot].cap = NOSTR;
1374 			mp[slot].descr = NOSTR;
1375 		} else {
1376 			error(catgets(catd, 1, 67,
1377 				"Not mapped|That macro wasn't mapped"));
1378 		}
1379 		return;
1380 	}
1381 
1382 	/* reuse empty slot, if we found one and src isn't already defined */
1383 	if (zer >= 0 && mp[slot].mapto == 0)
1384 		slot = zer;
1385 
1386 	/* if not, append to end */
1387 	if (slot >= MAXNOMACS)
1388 		error(catgets(catd, 1, 68, "Too many macros"));
1389 	if (msnext == 0)	/* first time */
1390 		msnext = mapspace;
1391 	/* Check is a bit conservative, we charge for dname even if reusing src */
1392 	if (msnext - mapspace + strlen(dest) + (src ? strlen(src) : 0) + strlen(dname) + 3 > MAXCHARMACS)
1393 		error(catgets(catd, 1, 69, "Too much macro text"));
1394 	if (src) {
1395 		CP(msnext, src);
1396 		mp[slot].cap = msnext;
1397 		msnext += strlen(src) + 1;	/* plus 1 for null on the end */
1398 		intmac(&mp[slot].icap, src);
1399 	} else
1400 		mp[slot].cap = NULL;
1401 	CP(msnext, dest);
1402 	mp[slot].mapto = msnext;
1403 	msnext += strlen(dest) + 1;
1404 	if (dname) {
1405 		CP(msnext, dname);
1406 		mp[slot].descr = msnext;
1407 		msnext += strlen(dname) + 1;
1408 	} else {
1409 		/* default descr to string user enters */
1410 		mp[slot].descr = src;
1411 	}
1412 }
1413 
1414 /*
1415  * Implements macros from command mode. c is the buffer to
1416  * get the macro from.
1417  */
1418 void
1419 cmdmac(char c)
1420 {
1421 	char macbuf[BUFSIZ];
1422 	line *ad, *a1, *a2;
1423 	char *oglobp;
1424 	short pk;
1425 	bool oinglobal;
1426 
1427 	lastmac = c;
1428 	oglobp = globp;
1429 	oinglobal = inglobal;
1430 	pk = peekc; peekc = 0;
1431 	if (inglobal < 2)
1432 		inglobal = 1;
1433 	regbuf(c, macbuf, sizeof(macbuf));
1434 	a1 = addr1; a2 = addr2;
1435 	for (ad=a1; ad<=a2; ad++) {
1436 		globp = macbuf;
1437 		dot = ad;
1438 		commands(1,1);
1439 	}
1440 	globp = oglobp;
1441 	inglobal = oinglobal;
1442 	peekc = pk;
1443 }
1444