1: /* Copyright (c) 1979 Regents of the University of California */
2: #include "ex.h"
3: #include "ex_argv.h"
4: #include "ex_temp.h"
5: #include "ex_tty.h"
6: #include "ex_vis.h"
7:
8: /*
9: * Command mode subroutines implementing
10: * append, args, copy, delete, join, move, put,
11: * shift, tag, yank, z and undo
12: */
13:
14: bool endline = 1;
15: line *tad1;
16: static jnoop();
17:
18: /*
19: * Append after line a lines returned by function f.
20: * Be careful about intermediate states to avoid scramble
21: * if an interrupt comes in.
22: */
23: append(f, a)
24: int (*f)();
25: line *a;
26: {
27: register line *a1, *a2, *rdot;
28: int nline;
29:
30: nline = 0;
31: dot = a;
32: /*
33: * This is probably a bug, since it's different than the other tests
34: * in appendnone, delete, and deletenone. It is known to fail for
35: * the command :g/foo/r xxx (where there is one foo and the file
36: * xxx exists) and you try to undo it. I'm leaving it in for now
37: * because I'm afraid if I change it I'll break something.
38: */
39: if (!inglobal && !inopen && f != getsub) {
40: undap1 = undap2 = dot + 1;
41: undkind = UNDCHANGE;
42: }
43: while ((*f)() == 0) {
44: if (truedol >= endcore) {
45: if (morelines() < 0) {
46: if (!inglobal && f == getsub) {
47: undap1 = addr1;
48: undap2 = addr2 + 1;
49: }
50: error("Out of memory@- too many lines in file");
51: }
52: }
53: nline++;
54: a1 = truedol + 1;
55: a2 = a1 + 1;
56: dot++;
57: undap2++;
58: dol++;
59: unddol++;
60: truedol++;
61: for (rdot = dot; a1 > rdot;)
62: *--a2 = *--a1;
63: *rdot = 0;
64: putmark(rdot);
65: if (f == gettty) {
66: dirtcnt++;
67: TSYNC();
68: }
69: }
70: return (nline);
71: }
72:
73: appendnone()
74: {
75:
76: if (!inglobal || inopen > 0) {
77: undkind = UNDCHANGE;
78: undap1 = undap2 = addr1;
79: }
80: }
81:
82: /*
83: * Print out the argument list, with []'s around the current name.
84: */
85: pargs()
86: {
87: register char **av = argv0, *as = args0;
88: register int ac;
89:
90: for (ac = 0; ac < argc0; ac++) {
91: if (ac != 0)
92: putchar(' ');
93: if (ac + argc == argc0 - 1)
94: printf("[");
95: lprintf("%s", as);
96: if (ac + argc == argc0 - 1)
97: printf("]");
98: as = av ? *++av : strend(as) + 1;
99: }
100: noonl();
101: }
102:
103: /*
104: * Delete lines; two cases are if we are really deleting,
105: * more commonly we are just moving lines to the undo save area.
106: */
107: delete(hush)
108: bool hush;
109: {
110: register line *a1, *a2;
111:
112: nonzero();
113: if (!inglobal || inopen > 0) {
114: register int (*dsavint)();
115:
116: change();
117: dsavint = signal(SIGINT, SIG_IGN);
118: undkind = UNDCHANGE;
119: a1 = addr1;
120: squish();
121: a2 = addr2;
122: if (a2++ != dol) {
123: reverse(a1, a2);
124: reverse(a2, dol + 1);
125: reverse(a1, dol + 1);
126: }
127: dol -= a2 - a1;
128: unddel = a1 - 1;
129: if (a1 > dol)
130: a1 = dol;
131: dot = a1;
132: pkill[0] = pkill[1] = 0;
133: signal(SIGINT, dsavint);
134: } else {
135: register line *a3;
136: register int i;
137:
138: change();
139: a1 = addr1;
140: a2 = addr2 + 1;
141: a3 = truedol;
142: i = a2 - a1;
143: unddol -= i;
144: undap2 -= i;
145: dol -= i;
146: truedol -= i;
147: do
148: *a1++ = *a2++;
149: while (a2 <= a3);
150: a1 = addr1;
151: if (a1 > dol)
152: a1 = dol;
153: dot = a1;
154: }
155: if (!hush)
156: killed();
157: }
158:
159: deletenone()
160: {
161:
162: if (!inglobal || inopen > 0) {
163: undkind = UNDCHANGE;
164: squish();
165: unddel = addr1;
166: }
167: }
168:
169: /*
170: * Crush out the undo save area, moving the open/visual
171: * save area down in its place.
172: */
173: squish()
174: {
175: register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
176:
177: if (a1 < a2 && a2 < a3)
178: do
179: *a1++ = *a2++;
180: while (a2 < a3);
181: truedol -= unddol - dol;
182: unddol = dol;
183: }
184:
185: /*
186: * Join lines. Special hacks put in spaces, two spaces if
187: * preceding line ends with '.', or no spaces if next line starts with ).
188: */
189: static int jcount, jnoop();
190:
191: join(c)
192: int c;
193: {
194: register line *a1;
195: register char *cp, *cp1;
196:
197: cp = genbuf;
198: *cp = 0;
199: for (a1 = addr1; a1 <= addr2; a1++) {
200: getline(*a1);
201: cp1 = linebuf;
202: if (a1 != addr1 && c == 0) {
203: while (*cp1 == ' ' || *cp1 == '\t')
204: cp1++;
205: if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
206: if (*cp1 != ')') {
207: *cp++ = ' ';
208: if (cp[-2] == '.')
209: *cp++ = ' ';
210: }
211: }
212: }
213: while (*cp++ = *cp1++)
214: if (cp > &genbuf[LBSIZE-2])
215: error("Line overflow|Result line of join would be too long");
216: cp--;
217: }
218: strcLIN(genbuf);
219: delete(0);
220: jcount = 1;
221: if (FIXUNDO)
222: undap1 = undap2 = addr1;
223: ignore(append(jnoop, --addr1));
224: if (FIXUNDO)
225: vundkind = VMANY;
226: }
227:
228: static
229: jnoop()
230: {
231:
232: return(--jcount);
233: }
234:
235: /*
236: * Move and copy lines. Hard work is done by move1 which
237: * is also called by undo.
238: */
239: int getcopy();
240:
241: move()
242: {
243: register line *adt;
244: bool iscopy = 0;
245:
246: if (Command[0] == 'm') {
247: setdot1();
248: markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
249: } else {
250: iscopy++;
251: setdot();
252: }
253: nonzero();
254: adt = address(0);
255: if (adt == 0)
256: serror("%s where?|%s requires a trailing address", Command);
257: newline();
258: move1(iscopy, adt);
259: killed();
260: }
261:
262: move1(cflag, addrt)
263: int cflag;
264: line *addrt;
265: {
266: register line *adt, *ad1, *ad2;
267: int lines;
268:
269: adt = addrt;
270: lines = (addr2 - addr1) + 1;
271: if (cflag) {
272: tad1 = addr1;
273: ad1 = dol;
274: ignore(append(getcopy, ad1++));
275: ad2 = dol;
276: } else {
277: ad2 = addr2;
278: for (ad1 = addr1; ad1 <= ad2;)
279: *ad1++ &= ~01;
280: ad1 = addr1;
281: }
282: ad2++;
283: if (adt < ad1) {
284: if (adt + 1 == ad1 && !cflag && !inglobal)
285: error("That move would do nothing!");
286: dot = adt + (ad2 - ad1);
287: if (++adt != ad1) {
288: reverse(adt, ad1);
289: reverse(ad1, ad2);
290: reverse(adt, ad2);
291: }
292: } else if (adt >= ad2) {
293: dot = adt++;
294: reverse(ad1, ad2);
295: reverse(ad2, adt);
296: reverse(ad1, adt);
297: } else
298: error("Move to a moved line");
299: change();
300: if (!inglobal)
301: if (cflag) {
302: undap1 = addrt + 1;
303: undap2 = undap1 + lines;
304: deletenone();
305: } else {
306: undkind = UNDMOVE;
307: undap1 = addr1;
308: undap2 = addr2;
309: unddel = addrt;
310: squish();
311: }
312: }
313:
314: getcopy()
315: {
316:
317: if (tad1 > addr2)
318: return (EOF);
319: getline(*tad1++);
320: return (0);
321: }
322:
323: /*
324: * Put lines in the buffer from the undo save area.
325: */
326: getput()
327: {
328:
329: if (tad1 > unddol)
330: return (EOF);
331: getline(*tad1++);
332: tad1++;
333: return (0);
334: }
335:
336: put()
337: {
338: register int cnt;
339:
340: cnt = unddol - dol;
341: if (cnt && inopen && pkill[0] && pkill[1]) {
342: pragged(1);
343: return;
344: }
345: tad1 = dol + 1;
346: ignore(append(getput, addr2));
347: undkind = UNDPUT;
348: notecnt = cnt;
349: netchange(cnt);
350: }
351:
352: /*
353: * A tricky put, of a group of lines in the middle
354: * of an existing line. Only from open/visual.
355: * Argument says pkills have meaning, e.g. called from
356: * put; it is 0 on calls from putreg.
357: */
358: pragged(kill)
359: bool kill;
360: {
361: extern char *cursor;
362: register char *gp = &genbuf[cursor - linebuf];
363:
364: /*
365: * This kind of stuff is TECO's forte.
366: * We just grunge along, since it cuts
367: * across our line-oriented model of the world
368: * almost scrambling our addled brain.
369: */
370: if (!kill)
371: getDOT();
372: strcpy(genbuf, linebuf);
373: getline(*unddol);
374: if (kill)
375: *pkill[1] = 0;
376: strcat(linebuf, gp);
377: putmark(unddol);
378: getline(dol[1]);
379: if (kill)
380: strcLIN(pkill[0]);
381: strcpy(gp, linebuf);
382: strcLIN(genbuf);
383: putmark(dol+1);
384: undkind = UNDCHANGE;
385: undap1 = dot;
386: undap2 = dot + 1;
387: unddel = dot - 1;
388: undo(1);
389: }
390:
391: /*
392: * Shift lines, based on c.
393: * If c is neither < nor >, then this is a lisp aligning =.
394: */
395: shift(c, cnt)
396: int c;
397: int cnt;
398: {
399: register line *addr;
400: register char *cp;
401: char *dp;
402: register int i;
403:
404: if (!inglobal)
405: save12(), undkind = UNDCHANGE;
406: cnt *= value(SHIFTWIDTH);
407: for (addr = addr1; addr <= addr2; addr++) {
408: dot = addr;
409: #ifdef LISPCODE
410: if (c == '=' && addr == addr1 && addr != addr2)
411: continue;
412: #endif
413: getDOT();
414: i = whitecnt(linebuf);
415: switch (c) {
416:
417: case '>':
418: if (linebuf[0] == 0)
419: continue;
420: cp = genindent(i + cnt);
421: break;
422:
423: case '<':
424: if (i == 0)
425: continue;
426: i -= cnt;
427: cp = i > 0 ? genindent(i) : genbuf;
428: break;
429:
430: #ifdef LISPCODE
431: default:
432: i = lindent(addr);
433: getDOT();
434: cp = genindent(i);
435: break;
436: #endif
437: }
438: if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
439: error("Line too long|Result line after shift would be too long");
440: CP(cp, dp);
441: strcLIN(genbuf);
442: putmark(addr);
443: }
444: killed();
445: }
446:
447: #ifdef TAGSCODE
448: /*
449: * Find a tag in the tags file.
450: * Most work here is in parsing the tags file itself.
451: */
452: tagfind(quick)
453: bool quick;
454: {
455: char cmdbuf[BUFSIZ];
456: char filebuf[FNSIZE];
457: register int c, d;
458: bool samef = 1;
459: short omagic;
460:
461: omagic = value(MAGIC);
462: if (!skipend()) {
463: register char *lp = lasttag;
464:
465: while (!iswhite(peekchar()) && !endcmd(peekchar()))
466: if (lp < &lasttag[sizeof lasttag - 2])
467: *lp++ = getchar();
468: else
469: ignchar();
470: *lp++ = 0;
471: if (!endcmd(peekchar()))
472: badtag:
473: error("Bad tag|Give one tag per line");
474: } else if (lasttag[0] == 0)
475: error("No previous tag");
476: c = getchar();
477: if (!endcmd(c))
478: goto badtag;
479: if (c == EOF)
480: ungetchar(c);
481: clrstats();
482: io = open("tags", 0);
483: if (io < 0)
484: error("No tags file");
485: while (getfile() == 0) {
486: register char *cp = linebuf;
487: register char *lp = lasttag;
488: char *oglobp;
489:
490: while (*cp && *lp == *cp)
491: cp++, lp++;
492: if ((*lp || !iswhite(*cp)) && (value(TAGLENGTH)==0 || lp-lasttag < value(TAGLENGTH)))
493: continue;
494: close(io);
495:
496: /* Rest of tag if abbreviated */
497: while (*cp && !iswhite(*cp))
498: cp++;
499:
500: while (*cp && iswhite(*cp))
501: cp++;
502: if (!*cp)
503: badtags:
504: serror("%s: Bad tags file entry", lasttag);
505: lp = filebuf;
506: while (*cp && *cp != ' ' && *cp != '\t') {
507: if (lp < &filebuf[sizeof filebuf - 2])
508: *lp++ = *cp;
509: cp++;
510: }
511: *lp++ = 0;
512: if (*cp == 0)
513: goto badtags;
514: if (dol != zero) {
515: /*
516: * Save current position in 't for ^^ in visual.
517: */
518: names['t'-'a'] = *dot &~ 01;
519: if (inopen) {
520: extern char *ncols['z'-'a'+2];
521: extern char *cursor;
522:
523: ncols['t'-'a'] = cursor;
524: }
525: }
526: strcpy(cmdbuf, cp);
527: if (strcmp(filebuf, savedfile) || !edited) {
528: char cmdbuf2[sizeof filebuf + 10];
529:
530: if (!quick) {
531: ckaw();
532: if (chng && dol > zero)
533: error("No write@since last change (:tag! overrides)");
534: }
535: oglobp = globp;
536: strcpy(cmdbuf2, "e! ");
537: strcat(cmdbuf2, filebuf);
538: globp = cmdbuf2;
539: d = peekc; ungetchar(0);
540: commands(1, 1);
541: peekc = d;
542: globp = oglobp;
543: samef = 0;
544: }
545: oglobp = globp;
546: globp = cmdbuf;
547: d = peekc; ungetchar(0);
548: if (samef)
549: markpr(dot);
550: value(MAGIC) = 0; /* force nomagic tags */
551: commands(1, 1);
552: peekc = d;
553: globp = oglobp;
554: value(MAGIC) = omagic;
555: return;
556: }
557: serror("%s: No such tag@in tags file", lasttag);
558: }
559: #endif
560:
561: /*
562: * Save lines from addr1 thru addr2 as though
563: * they had been deleted.
564: */
565: yank()
566: {
567:
568: save12();
569: undkind = UNDNONE;
570: killcnt(addr2 - addr1 + 1);
571: }
572:
573: /*
574: * z command; print windows of text in the file.
575: *
576: * If this seems unreasonably arcane, the reasons
577: * are historical. This is one of the first commands
578: * added to the first ex (then called en) and the
579: * number of facilities here were the major advantage
580: * of en over ed since they allowed more use to be
581: * made of fast terminals w/o typing .,.22p all the time.
582: */
583: bool zhadpr;
584: bool znoclear;
585: short zweight;
586:
587: zop(hadpr)
588: int hadpr;
589: {
590: register int c, lines, op;
591: bool excl;
592:
593: zhadpr = hadpr;
594: notempty();
595: znoclear = 0;
596: zweight = 0;
597: excl = exclam();
598: #ifdef ZCMD
599: switch (c = op = getchar()) {
600:
601: case '^':
602: zweight = 1;
603: case '-':
604: case '+':
605: while (peekchar() == op) {
606: ignchar();
607: zweight++;
608: }
609: case '=':
610: case '.':
611: c = getchar();
612: break;
613:
614: case EOF:
615: znoclear++;
616: break;
617:
618: default:
619: #endif
620: op = 0;
621: #ifdef ZCMD
622: break;
623: }
624: #endif
625: if (isdigit(c)) {
626: lines = c - '0';
627: for(;;) {
628: c = getchar();
629: if (!isdigit(c))
630: break;
631: lines *= 10;
632: lines += c - '0';
633: }
634: if (lines < LINES)
635: znoclear++;
636: value(WINDOW) = lines;
637: if (op == '=')
638: lines += 2;
639: } else
640: lines = op == EOF ? value(SCROLL) : excl ? LINES - 1 : 2*value(SCROLL);
641: if (inopen || c != EOF) {
642: ungetchar(c);
643: newline();
644: }
645: addr1 = addr2;
646: if (addr2 == 0 && dot < dol && op == 0)
647: addr1 = addr2 = dot+1;
648: setdot();
649: zop2(lines, op);
650: }
651:
652: zop2(lines, op)
653: register int lines;
654: register int op;
655: {
656: register line *split;
657:
658: split = NULL;
659: #ifdef ZCMD
660: switch (op) {
661:
662: case EOF:
663: if (addr2 == dol)
664: error("\nAt EOF");
665: case '+':
666: if (addr2 == dol)
667: error("At EOF");
668: addr2 += lines * zweight;
669: if (addr2 > dol)
670: error("Hit BOTTOM");
671: addr2++;
672: default:
673: #endif
674: addr1 = addr2;
675: addr2 += lines-1;
676: dot = addr2;
677: #ifdef ZCMD
678: break;
679:
680: case '=':
681: case '.':
682: znoclear = 0;
683: lines--;
684: lines >>= 1;
685: if (op == '=')
686: lines--;
687: addr1 = addr2 - lines;
688: if (op == '=')
689: dot = split = addr2;
690: addr2 += lines;
691: if (op == '.') {
692: markDOT();
693: dot = addr2;
694: }
695: break;
696:
697: case '^':
698: case '-':
699: addr2 -= lines * zweight;
700: if (addr2 < one)
701: error("Hit TOP");
702: lines--;
703: addr1 = addr2 - lines;
704: dot = addr2;
705: break;
706: }
707: #endif
708: if (addr1 <= zero)
709: addr1 = one;
710: if (addr2 > dol)
711: addr2 = dol;
712: if (dot > dol)
713: dot = dol;
714: if (addr1 > addr2)
715: return;
716: if (op == EOF && zhadpr) {
717: getline(*addr1);
718: putchar('\r' | QUOTE);
719: shudclob = 1;
720: } else if (znoclear == 0 && CL != NOSTR && !inopen) {
721: flush1();
722: vclear();
723: }
724: if (addr2 - addr1 > 1)
725: pstart();
726: if (split) {
727: plines(addr1, split - 1, 0);
728: splitit();
729: plines(split, split, 0);
730: splitit();
731: addr1 = split + 1;
732: }
733: plines(addr1, addr2, 0);
734: }
735:
736: static
737: splitit()
738: {
739: register int l;
740:
741: for (l = COLUMNS > 80 ? 40 : COLUMNS / 2; l > 0; l--)
742: putchar('-');
743: putnl();
744: }
745:
746: plines(adr1, adr2, movedot)
747: line *adr1;
748: register line *adr2;
749: bool movedot;
750: {
751: register line *addr;
752:
753: pofix();
754: for (addr = adr1; addr <= adr2; addr++) {
755: getline(*addr);
756: pline(lineno(addr));
757: if (inopen)
758: putchar('\n' | QUOTE);
759: if (movedot)
760: dot = addr;
761: }
762: }
763:
764: pofix()
765: {
766:
767: if (inopen && Outchar != termchar) {
768: vnfl();
769: setoutt();
770: }
771: }
772:
773: /*
774: * Dudley doright to the rescue.
775: * Undo saves the day again.
776: * A tip of the hatlo hat to Warren Teitleman
777: * who made undo as useful as do.
778: *
779: * Command level undo works easily because
780: * the editor has a unique temporary file
781: * index for every line which ever existed.
782: * We don't have to save large blocks of text,
783: * only the indices which are small. We do this
784: * by moving them to after the last line in the
785: * line buffer array, and marking down info
786: * about whence they came.
787: *
788: * Undo is its own inverse.
789: */
790: undo(c)
791: bool c;
792: {
793: register int i;
794: register line *jp, *kp;
795: line *dolp1, *newdol, *newadot;
796:
797: if (inglobal && inopen <= 0)
798: error("Can't undo in global@commands");
799: if (!c)
800: somechange();
801: pkill[0] = pkill[1] = 0;
802: change();
803: if (undkind == UNDMOVE) {
804: /*
805: * Command to be undone is a move command.
806: * This is handled as a special case by noting that
807: * a move "a,b m c" can be inverted by another move.
808: */
809: if ((i = (jp = unddel) - undap2) > 0) {
810: /*
811: * when c > b inverse is a+(c-b),c m a-1
812: */
813: addr2 = jp;
814: addr1 = (jp = undap1) + i;
815: unddel = jp-1;
816: } else {
817: /*
818: * when b > c inverse is c+1,c+1+(b-a) m b
819: */
820: addr1 = ++jp;
821: addr2 = jp + ((unddel = undap2) - undap1);
822: }
823: kp = undap1;
824: move1(0, unddel);
825: dot = kp;
826: Command = "move";
827: killed();
828: } else {
829: int cnt;
830:
831: newadot = dot;
832: cnt = lineDOL();
833: newdol = dol;
834: dolp1 = dol + 1;
835: /*
836: * Command to be undone is a non-move.
837: * All such commands are treated as a combination of
838: * a delete command and a append command.
839: * We first move the lines appended by the last command
840: * from undap1 to undap2-1 so that they are just before the
841: * saved deleted lines.
842: */
843: if ((i = (kp = undap2) - (jp = undap1)) > 0) {
844: if (kp != dolp1) {
845: reverse(jp, kp);
846: reverse(kp, dolp1);
847: reverse(jp, dolp1);
848: }
849: /*
850: * Account for possible backward motion of target
851: * for restoration of saved deleted lines.
852: */
853: if (unddel >= jp)
854: unddel -= i;
855: newdol -= i;
856: /*
857: * For the case where no lines are restored, dot
858: * is the line before the first line deleted.
859: */
860: dot = jp-1;
861: }
862: /*
863: * Now put the deleted lines, if any, back where they were.
864: * Basic operation is: dol+1,unddol m unddel
865: */
866: if (undkind == UNDPUT) {
867: unddel = undap1 - 1;
868: squish();
869: }
870: jp = unddel + 1;
871: if ((i = (kp = unddol) - dol) > 0) {
872: if (jp != dolp1) {
873: reverse(jp, dolp1);
874: reverse(dolp1, ++kp);
875: reverse(jp, kp);
876: }
877: /*
878: * Account for possible forward motion of the target
879: * for restoration of the deleted lines.
880: */
881: if (undap1 >= jp)
882: undap1 += i;
883: /*
884: * Dot is the first resurrected line.
885: */
886: dot = jp;
887: newdol += i;
888: }
889: /*
890: * Clean up so we are invertible
891: */
892: unddel = undap1 - 1;
893: undap1 = jp;
894: undap2 = jp + i;
895: dol = newdol;
896: netchHAD(cnt);
897: if (undkind == UNDALL) {
898: dot = undadot;
899: undadot = newadot;
900: }
901: undkind = UNDCHANGE;
902: }
903: if (dot == zero && dot != dol)
904: dot = one;
905: }
906:
907: /*
908: * Be (almost completely) sure there really
909: * was a change, before claiming to undo.
910: */
911: somechange()
912: {
913: register line *ip, *jp;
914:
915: switch (undkind) {
916:
917: case UNDMOVE:
918: return;
919:
920: case UNDCHANGE:
921: if (undap1 == undap2 && dol == unddol)
922: break;
923: return;
924:
925: case UNDPUT:
926: if (undap1 != undap2)
927: return;
928: break;
929:
930: case UNDALL:
931: if (unddol - dol != lineDOL())
932: return;
933: for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
934: if ((*ip &~ 01) != (*jp &~ 01))
935: return;
936: break;
937:
938: case UNDNONE:
939: error("Nothing to undo");
940: }
941: error("Nothing changed|Last undoable command didn't change anything");
942: }