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