1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/stat.h>
5#include <fcntl.h>
6#include <regex.h>
7#include <unistd.h>
8
9#include <ctype.h>
10#include <limits.h>
11#include <setjmp.h>
12#include <signal.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18#include "util.h"
19
20#define REGEXSIZE 100
21#define LINESIZE 80
22#define NUMLINES 32
23#define CACHESIZ 4096
24#define AFTER 0
25#define BEFORE 1
26
27typedef struct {
28 char *str;
29 size_t cap;
30 size_t siz;
31} String;
32
33struct hline {
34 off_t seek;
35 char global;
36 int next, prev;
37};
38
39struct undo {
40 int curln, lastln;
41 size_t nr, cap;
42 struct link {
43 int to1, from1;
44 int to2, from2;
45 } *vec;
46};
47
48static char *prompt = "*";
49static regex_t *pattern;
50static regmatch_t matchs[10];
51static String lastre;
52
53static int optverbose, optprompt, exstatus, optdiag = 1;
54static int marks['z' - 'a' + 1];
55static int nlines, line1, line2;
56static int curln, lastln, ocurln, olastln;
57static jmp_buf savesp;
58static char *lasterr;
59static size_t idxsize, lastidx;
60static struct hline *zero;
61static String text;
62static char savfname[FILENAME_MAX];
63static char tmpname[FILENAME_MAX];
64static int scratch;
65static int pflag, modflag, uflag, gflag;
66static size_t csize;
67static String cmdline;
68static char *ocmdline;
69static int inputidx;
70static char *rhs;
71static char *lastmatch;
72static struct undo udata;
73static int newcmd;
74
75static sig_atomic_t intr, hup;
76
77static void undo(void);
78
79static void
80error(char *msg)
81{
82 exstatus = 1;
83 lasterr = msg;
84 puts("?");
85
86 if (optverbose)
87 puts(msg);
88 if (!newcmd)
89 undo();
90
91 curln = ocurln;
92 longjmp(savesp, 1);
93}
94
95static int
96nextln(int line)
97{
98 ++line;
99 return (line > lastln) ? 0 : line;
100}
101
102static int
103prevln(int line)
104{
105 --line;
106 return (line < 0) ? lastln : line;
107}
108
109static String *
110copystring(String *s, char *from)
111{
112 size_t len;
113 char *t;
114
115 if ((t = strdup(from)) == NULL)
116 error("out of memory");
117 len = strlen(t);
118
119 free(s->str);
120 s->str = t;
121 s->siz = len;
122 s->cap = len;
123
124 return s;
125}
126
127static String *
128string(String *s)
129{
130 free(s->str);
131 s->str = NULL;
132 s->siz = 0;
133 s->cap = 0;
134
135 return s;
136}
137
138static char *
139addchar(char c, String *s)
140{
141 size_t cap = s->cap, siz = s->siz;
142 char *t = s->str;
143
144 if (siz >= cap &&
145 (cap > SIZE_MAX - LINESIZE ||
146 (t = realloc(t, cap += LINESIZE)) == NULL))
147 error("out of memory");
148 t[siz++] = c;
149 s->siz = siz;
150 s->cap = cap;
151 s->str = t;
152 return t;
153}
154
155static void chksignals(void);
156
157static int
158input(void)
159{
160 int ch;
161
162 chksignals();
163
164 ch = cmdline.str[inputidx];
165 if (ch != '\0')
166 inputidx++;
167 return ch;
168}
169
170static int
171back(int c)
172{
173 if (c == '\0')
174 return c;
175 return cmdline.str[--inputidx] = c;
176}
177
178static int
179makeline(char *s, int *off)
180{
181 struct hline *lp;
182 size_t len;
183 char *begin = s;
184 int c;
185
186 if (lastidx >= idxsize) {
187 lp = NULL;
188 if (idxsize <= SIZE_MAX - NUMLINES)
189 lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp));
190 if (!lp)
191 error("out of memory");
192 idxsize += NUMLINES;
193 zero = lp;
194 }
195 lp = zero + lastidx;
196 lp->global = 0;
197
198 if (!s) {
199 lp->seek = -1;
200 len = 0;
201 } else {
202 while ((c = *s++) && c != '\n')
203 ;
204 len = s - begin;
205 if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
206 write(scratch, begin, len) < 0) {
207 error("input/output error");
208 }
209 }
210 if (off)
211 *off = len;
212 ++lastidx;
213 return lp - zero;
214}
215
216static int
217getindex(int line)
218{
219 struct hline *lp;
220 int n;
221
222 if (line == -1)
223 line = 0;
224 for (n = 0, lp = zero; n != line; n++)
225 lp = zero + lp->next;
226
227 return lp - zero;
228}
229
230static char *
231gettxt(int line)
232{
233 static char buf[CACHESIZ];
234 static off_t lasto;
235 struct hline *lp;
236 off_t off, block;
237 ssize_t n;
238 char *p;
239
240 lp = zero + getindex(line);
241 text.siz = 0;
242 off = lp->seek;
243
244 if (off == (off_t) -1)
245 return addchar('\0', &text);
246
247repeat:
248 chksignals();
249 if (!csize || off < lasto || (size_t)(off - lasto) >= csize) {
250 block = off & ~(CACHESIZ-1);
251 if (lseek(scratch, block, SEEK_SET) < 0 ||
252 (n = read(scratch, buf, CACHESIZ)) < 0) {
253 error("input/output error");
254 }
255 csize = n;
256 lasto = block;
257 }
258 for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) {
259 ++off;
260 addchar(*p, &text);
261 }
262 if (csize == CACHESIZ && p == buf + csize)
263 goto repeat;
264
265 addchar('\n', &text);
266 addchar('\0', &text);
267 return text.str;
268}
269
270static void
271setglobal(int i, int v)
272{
273 zero[getindex(i)].global = v;
274}
275
276static void
277clearundo(void)
278{
279 free(udata.vec);
280 udata.vec = NULL;
281 newcmd = udata.nr = udata.cap = 0;
282 modflag = 0;
283}
284
285static void
286newundo(int from1, int from2)
287{
288 struct link *p;
289
290 if (newcmd) {
291 clearundo();
292 udata.curln = ocurln;
293 udata.lastln = olastln;
294 }
295 if (udata.nr >= udata.cap) {
296 size_t siz = (udata.cap + 10) * sizeof(struct link);
297 if ((p = realloc(udata.vec, siz)) == NULL)
298 error("out of memory");
299 udata.vec = p;
300 udata.cap = udata.cap + 10;
301 }
302 p = &udata.vec[udata.nr++];
303 p->from1 = from1;
304 p->to1 = zero[from1].next;
305 p->from2 = from2;
306 p->to2 = zero[from2].prev;
307}
308
309/*
310 * relink: to1 <- from1
311 * from2 -> to2
312 */
313static void
314relink(int to1, int from1, int from2, int to2)
315{
316 newundo(from1, from2);
317 zero[from1].next = to1;
318 zero[from2].prev = to2;
319 modflag = 1;
320}
321
322static void
323undo(void)
324{
325 struct link *p;
326
327 if (udata.nr == 0)
328 return;
329 for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
330 --udata.nr;
331 zero[p->from1].next = p->to1;
332 zero[p->from2].prev = p->to2;
333 }
334 free(udata.vec);
335 udata.vec = NULL;
336 udata.cap = 0;
337 curln = udata.curln;
338 lastln = udata.lastln;
339}
340
341static void
342inject(char *s, int where)
343{
344 int off, k, begin, end;
345
346 if (where == BEFORE) {
347 begin = getindex(curln-1);
348 end = getindex(nextln(curln-1));
349 } else {
350 begin = getindex(curln);
351 end = getindex(nextln(curln));
352 }
353 while (*s) {
354 k = makeline(s, &off);
355 s += off;
356 relink(k, begin, k, begin);
357 relink(end, k, end, k);
358 ++lastln;
359 ++curln;
360 begin = k;
361 }
362}
363
364static void
365clearbuf(void)
366{
367 if (scratch)
368 close(scratch);
369 remove(tmpname);
370 free(zero);
371 zero = NULL;
372 scratch = csize = idxsize = lastidx = curln = lastln = 0;
373 modflag = lastln = curln = 0;
374}
375
376static void
377setscratch(void)
378{
379 int r, k;
380 char *dir;
381
382 clearbuf();
383 clearundo();
384 if ((dir = getenv("TMPDIR")) == NULL)
385 dir = "/tmp";
386 r = snprintf(tmpname, sizeof(tmpname), "%s/%s",
387 dir, "ed.XXXXXX");
388 if (r < 0 || (size_t)r >= sizeof(tmpname))
389 error("scratch filename too long");
390 if ((scratch = mkstemp(tmpname)) < 0)
391 error("failed to create scratch file");
392 if ((k = makeline(NULL, NULL)))
393 error("input/output error in scratch file");
394 relink(k, k, k, k);
395 clearundo();
396}
397
398static void
399compile(int delim)
400{
401 int n, ret, c,bracket;
402 static char buf[BUFSIZ];
403
404 if (!isgraph(delim))
405 error("invalid pattern delimiter");
406
407 bracket = lastre.siz = 0;
408 for (n = 0;; ++n) {
409 c = input();
410 if ((c == delim && !bracket) || c == '\0') {
411 break;
412 } else if (c == '\\') {
413 addchar(c, &lastre);
414 c = input();
415 } else if (c == '[') {
416 bracket = 1;
417 } else if (c == ']') {
418 bracket = 0;
419 }
420 addchar(c, &lastre);
421 }
422 if (n == 0) {
423 if (!pattern)
424 error("no previous pattern");
425 return;
426 }
427 addchar('\0', &lastre);
428
429 if (pattern)
430 regfree(pattern);
431 if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
432 error("out of memory");
433 if ((ret = regcomp(pattern, lastre.str, 0))) {
434 regerror(ret, pattern, buf, sizeof(buf));
435 error(buf);
436 }
437}
438
439static int
440match(int num)
441{
442 int r;
443
444 lastmatch = gettxt(num);
445 text.str[text.siz - 2] = '\0';
446 r = !regexec(pattern, lastmatch, 10, matchs, 0);
447 text.str[text.siz - 2] = '\n';
448
449 return r;
450}
451
452static int
453rematch(int num)
454{
455 (void)num;
456 regoff_t off = matchs[0].rm_eo;
457 regmatch_t *m;
458 int r;
459
460 text.str[text.siz - 2] = '\0';
461 r = !regexec(pattern, lastmatch + off, 10, matchs, REG_NOTBOL);
462 text.str[text.siz - 2] = '\n';
463
464 if (!r)
465 return 0;
466
467 if (matchs[0].rm_eo > 0) {
468 lastmatch += off;
469 return 1;
470 }
471
472 /* Zero width match was found at the end of the input, done */
473 if (lastmatch[off] == '\n') {
474 lastmatch += off;
475 return 0;
476 }
477
478 /* Zero width match at the current posiion, find the next one */
479 text.str[text.siz - 2] = '\0';
480 r = !regexec(pattern, lastmatch + off + 1, 10, matchs, REG_NOTBOL);
481 text.str[text.siz - 2] = '\n';
482
483 if (!r)
484 return 0;
485
486 /* Re-adjust matches to account for +1 in regexec */
487 for (m = matchs; m < &matchs[10]; m++) {
488 m->rm_so += 1;
489 m->rm_eo += 1;
490 }
491 lastmatch += off;
492
493 return 1;
494}
495
496static int
497search(int way)
498{
499 int i;
500
501 i = curln;
502 do {
503 chksignals();
504
505 i = (way == '?') ? prevln(i) : nextln(i);
506 if (i > 0 && match(i))
507 return i;
508 } while (i != curln);
509
510 error("invalid address");
511 return -1; /* not reached */
512}
513
514static void
515skipblank(void)
516{
517 char c;
518
519 while ((c = input()) == ' ' || c == '\t')
520 ;
521 back(c);
522}
523
524static void
525ensureblank(void)
526{
527 char c;
528
529 switch ((c = input())) {
530 case ' ':
531 case '\t':
532 skipblank();
533 /* fallthrough */
534 case '\0':
535 back(c);
536 break;
537 default:
538 error("unknown command");
539 }
540}
541
542static int
543getnum(void)
544{
545 int ln, n, c;
546
547 for (ln = 0; isdigit(c = input()); ln += n) {
548 if (ln > INT_MAX/10)
549 goto invalid;
550 n = c - '0';
551 ln *= 10;
552 if (INT_MAX - ln < n)
553 goto invalid;
554 }
555 back(c);
556 return ln;
557
558invalid:
559 error("invalid address");
560 return -1; /* not reached */
561}
562
563static int
564linenum(int *line)
565{
566 int ln, c;
567
568 skipblank();
569
570 switch (c = input()) {
571 case '.':
572 ln = curln;
573 break;
574 case '\'':
575 skipblank();
576 if (!islower(c = input()))
577 error("invalid mark character");
578 if (!(ln = marks[c - 'a']))
579 error("invalid address");
580 break;
581 case '$':
582 ln = lastln;
583 break;
584 case '?':
585 case '/':
586 compile(c);
587 ln = search(c);
588 break;
589 case '^':
590 case '-':
591 case '+':
592 ln = curln;
593 back(c);
594 break;
595 default:
596 back(c);
597 if (isdigit(c))
598 ln = getnum();
599 else
600 return 0;
601 break;
602 }
603 *line = ln;
604 return 1;
605}
606
607static int
608address(int *line)
609{
610 int ln, sign, c, num;
611
612 if (!linenum(&ln))
613 return 0;
614
615 for (;;) {
616 skipblank();
617 if ((c = input()) != '+' && c != '-' && c != '^')
618 break;
619 sign = c == '+' ? 1 : -1;
620 num = isdigit(back(input())) ? getnum() : 1;
621 num *= sign;
622 if (INT_MAX - ln < num)
623 goto invalid;
624 ln += num;
625 }
626 back(c);
627
628 if (ln < 0 || ln > lastln)
629 error("invalid address");
630 *line = ln;
631 return 1;
632
633invalid:
634 error("invalid address");
635 return -1; /* not reached */
636}
637
638static void
639getlst(void)
640{
641 int ln, c;
642
643 if ((c = input()) == ',') {
644 line1 = 1;
645 line2 = lastln;
646 nlines = lastln;
647 return;
648 } else if (c == ';') {
649 line1 = curln;
650 line2 = lastln;
651 nlines = lastln - curln + 1;
652 return;
653 }
654 back(c);
655 line2 = curln;
656 for (nlines = 0; address(&ln); ) {
657 line1 = line2;
658 line2 = ln;
659 ++nlines;
660
661 skipblank();
662 if ((c = input()) != ',' && c != ';') {
663 back(c);
664 break;
665 }
666 if (c == ';')
667 curln = line2;
668 }
669 if (nlines > 2)
670 nlines = 2;
671 else if (nlines <= 1)
672 line1 = line2;
673}
674
675static void
676deflines(int def1, int def2)
677{
678 if (!nlines) {
679 line1 = def1;
680 line2 = def2;
681 }
682 if (line1 > line2 || line1 < 0 || line2 > lastln)
683 error("invalid address");
684}
685
686static void
687quit(void)
688{
689 clearbuf();
690 exit(exstatus);
691}
692
693static void
694setinput(char *s)
695{
696 copystring(&cmdline, s);
697 inputidx = 0;
698}
699
700static void
701getinput(void)
702{
703 int ch;
704
705 string(&cmdline);
706
707 while ((ch = getchar()) != '\n' && ch != EOF) {
708 if (ch == '\\') {
709 if ((ch = getchar()) == EOF)
710 break;
711 if (ch != '\n')
712 addchar('\\', &cmdline);
713 }
714 addchar(ch, &cmdline);
715 }
716
717 addchar('\0', &cmdline);
718 inputidx = 0;
719
720 if (ch == EOF) {
721 chksignals();
722 if (ferror(stdin)) {
723 exstatus = 1;
724 fputs("ed: error reading input\n", stderr);
725 }
726 quit();
727 }
728}
729
730static int
731moreinput(void)
732{
733 if (!uflag)
734 return cmdline.str[inputidx] != '\0';
735
736 getinput();
737 return 1;
738}
739
740static void dowrite(const char *, int);
741
742static void
743dump(void)
744{
745 char *home;
746
747 if (modflag)
748 return;
749
750 line1 = nextln(0);
751 line2 = lastln;
752
753 if (!setjmp(savesp)) {
754 dowrite("ed.hup", 1);
755 return;
756 }
757
758 home = getenv("HOME");
759 if (!home || chdir(home) < 0)
760 return;
761
762 if (!setjmp(savesp))
763 dowrite("ed.hup", 1);
764}
765
766static void
767chksignals(void)
768{
769 if (hup) {
770 exstatus = 1;
771 dump();
772 quit();
773 }
774
775 if (intr) {
776 intr = 0;
777 newcmd = 1;
778 clearerr(stdin);
779 error("Interrupt");
780 }
781}
782
783static const char *
784expandcmd(void)
785{
786 static String cmd;
787 char *p;
788 int c, repl = 0;
789
790 skipblank();
791 if ((c = input()) != '!') {
792 back(c);
793 string(&cmd);
794 } else if (cmd.siz) {
795 --cmd.siz;
796 repl = 1;
797 } else {
798 error("no previous command");
799 }
800
801 while ((c = input()) != '\0') {
802 switch (c) {
803 case '%':
804 if (savfname[0] == '\0')
805 error("no current filename");
806 repl = 1;
807 for (p = savfname; *p; ++p)
808 addchar(*p, &cmd);
809 break;
810 case '\\':
811 c = input();
812 if (c != '%') {
813 back(c);
814 c = '\\';
815 }
816 /* fallthrough */
817 default:
818 addchar(c, &cmd);
819 }
820 }
821 addchar('\0', &cmd);
822
823 if (repl)
824 puts(cmd.str);
825
826 return cmd.str;
827}
828
829static void
830dowrite(const char *fname, int trunc)
831{
832 size_t bytecount = 0;
833 int i, r, line;
834 FILE *aux;
835 static int sh;
836 static FILE *fp;
837 char *mode;
838
839 if (fp) {
840 sh ? pclose(fp) : fclose(fp);
841 fp = NULL;
842 }
843
844 if (fname[0] == '!') {
845 sh = 1;
846 if((fp = popen(expandcmd(), "w")) == NULL)
847 error("bad exec");
848 } else {
849 sh = 0;
850 mode = (trunc) ? "w" : "a";
851 if ((fp = fopen(fname, mode)) == NULL)
852 error("cannot open input file");
853 }
854
855 line = curln;
856 for (i = line1; i <= line2; ++i) {
857 chksignals();
858
859 gettxt(i);
860 bytecount += text.siz - 1;
861 fwrite(text.str, 1, text.siz - 1, fp);
862 }
863
864 curln = line2;
865
866 aux = fp;
867 fp = NULL;
868 r = sh ? pclose(aux) : fclose(aux);
869 if (r)
870 error("input/output error");
871 strcpy(savfname, fname);
872 if (!sh)
873 modflag = 0;
874 curln = line;
875 if (optdiag)
876 printf("%zu\n", bytecount);
877}
878
879static void
880doread(const char *fname)
881{
882 int r;
883 size_t cnt;
884 ssize_t len;
885 char *p;
886 FILE *aux;
887 static size_t n;
888 static int sh;
889 static char *s;
890 static FILE *fp;
891
892 if (fp) {
893 sh ? pclose(fp) : fclose(fp);
894 fp = NULL;
895 }
896
897 if(fname[0] == '!') {
898 sh = 1;
899 if((fp = popen(expandcmd(), "r")) == NULL)
900 error("bad exec");
901 } else if ((fp = fopen(fname, "r")) == NULL) {
902 error("cannot open input file");
903 }
904
905 curln = line2;
906 for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) {
907 chksignals();
908 if (s[len-1] != '\n') {
909 if ((size_t)len + 1 >= n) {
910 if (n == SIZE_MAX || !(p = realloc(s, ++n)))
911 error("out of memory");
912 s = p;
913 }
914 s[len] = '\n';
915 s[len+1] = '\0';
916 }
917 inject(s, AFTER);
918 }
919 if (optdiag)
920 printf("%zu\n", cnt);
921
922 aux = fp;
923 fp = NULL;
924 r = sh ? pclose(aux) : fclose(aux);
925 if (r)
926 error("input/output error");
927}
928
929static void
930doprint(void)
931{
932 int i, c;
933 char *s, *str;
934
935 if (line1 <= 0 || line2 > lastln)
936 error("incorrect address");
937 for (i = line1; i <= line2; ++i) {
938 chksignals();
939 if (pflag == 'n')
940 printf("%d\t", i);
941 for (s = gettxt(i); (c = *s) != '\n'; ++s) {
942 if (pflag != 'l')
943 goto print_char;
944 switch (c) {
945 case '$':
946 str = "\\$";
947 goto print_str;
948 case '\t':
949 str = "\\t";
950 goto print_str;
951 case '\b':
952 str = "\\b";
953 goto print_str;
954 case '\\':
955 str = "\\\\";
956 goto print_str;
957 default:
958 if (!isprint(c)) {
959 printf("\\x%x", 0xFF & c);
960 break;
961 }
962 print_char:
963 putchar(c);
964 break;
965 print_str:
966 fputs(str, stdout);
967 break;
968 }
969 }
970 if (pflag == 'l')
971 fputs("$", stdout);
972 putc('\n', stdout);
973 }
974 curln = i - 1;
975}
976
977static void
978dohelp(void)
979{
980 if (lasterr)
981 puts(lasterr);
982}
983
984static void
985chkprint(int flag)
986{
987 int c;
988
989 if (flag) {
990 if ((c = input()) == 'p' || c == 'l' || c == 'n')
991 pflag = c;
992 else
993 back(c);
994 }
995 if ((c = input()) != '\0' && c != '\n')
996 error("invalid command suffix");
997}
998
999static char *
1000getfname(int comm)
1001{
1002 int c;
1003 char *bp;
1004 static char fname[FILENAME_MAX];
1005
1006 skipblank();
1007 if ((c = input()) == '!') {
1008 return strcpy(fname, "!");
1009 }
1010 back(c);
1011 for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
1012 if ((c = input()) == '\0')
1013 break;
1014 }
1015 if (bp == fname) {
1016 if (savfname[0] == '\0')
1017 error("no current filename");
1018 return savfname;
1019 }
1020 if (bp == &fname[FILENAME_MAX])
1021 error("file name too long");
1022 *bp = '\0';
1023
1024 if (fname[0] == '!')
1025 return fname;
1026 if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
1027 strcpy(savfname, fname);
1028 return fname;
1029}
1030
1031static void
1032append(int num)
1033{
1034 int ch;
1035 static String line;
1036
1037 curln = num;
1038 while (moreinput()) {
1039 string(&line);
1040 while ((ch = input()) != '\n' && ch != '\0')
1041 addchar(ch, &line);
1042 addchar('\n', &line);
1043 addchar('\0', &line);
1044
1045 if (!strcmp(line.str, ".\n") || !strcmp(line.str, "."))
1046 break;
1047 inject(line.str, AFTER);
1048 }
1049}
1050
1051static void
1052delete(int from, int to)
1053{
1054 int lto, lfrom;
1055
1056 if (!from)
1057 error("incorrect address");
1058
1059 lfrom = getindex(prevln(from));
1060 lto = getindex(nextln(to));
1061 lastln -= to - from + 1;
1062 curln = (from > lastln) ? lastln : from;;
1063 relink(lto, lfrom, lto, lfrom);
1064}
1065
1066static void
1067move(int where)
1068{
1069 int before, after, lto, lfrom;
1070
1071 if (!line1 || (where >= line1 && where <= line2))
1072 error("incorrect address");
1073
1074 before = getindex(prevln(line1));
1075 after = getindex(nextln(line2));
1076 lfrom = getindex(line1);
1077 lto = getindex(line2);
1078 relink(after, before, after, before);
1079
1080 if (where < line1) {
1081 curln = where + line1 - line2 + 1;
1082 } else {
1083 curln = where;
1084 where -= line1 - line2 + 1;
1085 }
1086 before = getindex(where);
1087 after = getindex(nextln(where));
1088 relink(lfrom, before, lfrom, before);
1089 relink(after, lto, after, lto);
1090}
1091
1092static void
1093join(void)
1094{
1095 int i;
1096 char *t, c;
1097 static String s;
1098
1099 string(&s);
1100 for (i = line1;; i = nextln(i)) {
1101 chksignals();
1102 for (t = gettxt(i); (c = *t) != '\n'; ++t)
1103 addchar(*t, &s);
1104 if (i == line2)
1105 break;
1106 }
1107
1108 addchar('\n', &s);
1109 addchar('\0', &s);
1110 delete(line1, line2);
1111 inject(s.str, BEFORE);
1112}
1113
1114static void
1115scroll(int num)
1116{
1117 int max, ln, cnt;
1118
1119 if (!line1 || line1 == lastln)
1120 error("incorrect address");
1121
1122 ln = line1;
1123 max = line1 + num;
1124 if (max > lastln)
1125 max = lastln;
1126 for (cnt = line1; cnt < max; cnt++) {
1127 chksignals();
1128 fputs(gettxt(ln), stdout);
1129 ln = nextln(ln);
1130 }
1131 curln = ln;
1132}
1133
1134static void
1135copy(int where)
1136{
1137
1138 if (!line1)
1139 error("incorrect address");
1140 curln = where;
1141
1142 while (line1 <= line2) {
1143 chksignals();
1144 inject(gettxt(line1), AFTER);
1145 if (line2 >= curln)
1146 line2 = nextln(line2);
1147 line1 = nextln(line1);
1148 if (line1 >= curln)
1149 line1 = nextln(line1);
1150 }
1151}
1152
1153static void
1154execsh(void)
1155{
1156 system(expandcmd());
1157 if (optdiag)
1158 puts("!");
1159}
1160
1161static void
1162getrhs(int delim)
1163{
1164 int c;
1165 static String s;
1166
1167 string(&s);
1168 while ((c = input()) != '\0' && c != delim)
1169 addchar(c, &s);
1170 addchar('\0', &s);
1171 if (c == '\0') {
1172 pflag = 'p';
1173 back(c);
1174 }
1175
1176 if (!strcmp("%", s.str)) {
1177 if (!rhs)
1178 error("no previous substitution");
1179 free(s.str);
1180 } else {
1181 free(rhs);
1182 rhs = s.str;
1183 }
1184 s.str = NULL;
1185}
1186
1187static int
1188getnth(void)
1189{
1190 int c;
1191
1192 if ((c = input()) == 'g') {
1193 return -1;
1194 } else if (isdigit(c)) {
1195 if (c == '0')
1196 return -1;
1197 return c - '0';
1198 } else {
1199 back(c);
1200 return 1;
1201 }
1202}
1203
1204static void
1205addpre(String *s)
1206{
1207 char *p;
1208
1209 for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
1210 addchar(*p, s);
1211}
1212
1213static void
1214addpost(String *s)
1215{
1216 char c, *p;
1217
1218 for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
1219 addchar(c, s);
1220 addchar('\0', s);
1221}
1222
1223static int
1224addsub(String *s, int nth, int nmatch)
1225{
1226 char *end, *q, *p, c;
1227 int sub;
1228
1229 if (nth != nmatch && nth != -1) {
1230 q = lastmatch + matchs[0].rm_so;
1231 end = lastmatch + matchs[0].rm_eo;
1232 while (q < end)
1233 addchar(*q++, s);
1234 return 0;
1235 }
1236
1237 for (p = rhs; (c = *p); ++p) {
1238 switch (c) {
1239 case '&':
1240 sub = 0;
1241 goto copy_match;
1242 case '\\':
1243 if ((c = *++p) == '\0')
1244 return 1;
1245 if (!isdigit(c))
1246 goto copy_char;
1247 sub = c - '0';
1248 copy_match:
1249 q = lastmatch + matchs[sub].rm_so;
1250 end = lastmatch + matchs[sub].rm_eo;
1251 while (q < end)
1252 addchar(*q++, s);
1253 break;
1254 default:
1255 copy_char:
1256 addchar(c, s);
1257 break;
1258 }
1259 }
1260 return 1;
1261}
1262
1263static void
1264subline(int num, int nth)
1265{
1266 int i, m, changed;
1267 static String s;
1268
1269 string(&s);
1270 i = changed = 0;
1271 for (m = match(num); m; m = (nth < 0 || i < nth) && rematch(num)) {
1272 chksignals();
1273 addpre(&s);
1274 changed |= addsub(&s, nth, ++i);
1275 }
1276 if (!changed)
1277 return;
1278 addpost(&s);
1279 delete(num, num);
1280 curln = prevln(num);
1281 inject(s.str, AFTER);
1282}
1283
1284static void
1285subst(int nth)
1286{
1287 int i, line, next;
1288
1289 line = line1;
1290 for (i = 0; i < line2 - line1 + 1; i++) {
1291 chksignals();
1292
1293 next = getindex(nextln(line));
1294 subline(line, nth);
1295
1296 /*
1297 * The substitution command can add lines, so
1298 * we have to skip lines until we find the
1299 * index that we saved before the substitution
1300 */
1301 do
1302 line = nextln(line);
1303 while (getindex(line) != next);
1304 }
1305}
1306
1307static void
1308docmd(void)
1309{
1310 char *var;
1311 int cmd, c, line3, num, trunc;
1312
1313repeat:
1314 skipblank();
1315 cmd = input();
1316 trunc = pflag = 0;
1317 switch (cmd) {
1318 case '&':
1319 skipblank();
1320 chkprint(0);
1321 if (!ocmdline)
1322 error("no previous command");
1323 setinput(ocmdline);
1324 getlst();
1325 goto repeat;
1326 case '!':
1327 execsh();
1328 break;
1329 case '\0':
1330 num = gflag ? curln : curln+1;
1331 deflines(num, num);
1332 line1 = line2;
1333 pflag = 'p';
1334 goto print;
1335 case 'l':
1336 case 'n':
1337 // ?man -p: preserve file attributes
1338 case 'p':
1339 back(cmd);
1340 chkprint(1);
1341 deflines(curln, curln);
1342 goto print;
1343 case 'g':
1344 case 'G':
1345 case 'v':
1346 case 'V':
1347 error("cannot nest global commands");
1348 break;
1349 case 'H':
1350 if (nlines > 0)
1351 goto unexpected;
1352 chkprint(0);
1353 optverbose ^= 1;
1354 break;
1355 case 'h':
1356 if (nlines > 0)
1357 goto unexpected;
1358 chkprint(0);
1359 dohelp();
1360 break;
1361 case 'w':
1362 trunc = 1;
1363 /* fallthrough */
1364 case 'W':
1365 ensureblank();
1366 deflines(nextln(0), lastln);
1367 dowrite(getfname(cmd), trunc);
1368 break;
1369 case 'r':
1370 ensureblank();
1371 if (nlines > 1)
1372 goto bad_address;
1373 deflines(lastln, lastln);
1374 doread(getfname(cmd));
1375 break;
1376 case 'd':
1377 chkprint(1);
1378 deflines(curln, curln);
1379 delete(line1, line2);
1380 break;
1381 case '=':
1382 if (nlines > 1)
1383 goto bad_address;
1384 chkprint(1);
1385 deflines(lastln, lastln);
1386 printf("%d\n", line1);
1387 break;
1388 case 'u':
1389 if (nlines > 0)
1390 goto bad_address;
1391 chkprint(1);
1392 if (udata.nr == 0)
1393 error("nothing to undo");
1394 undo();
1395 break;
1396 // ?man -s: silent mode or print summary
1397 case 's':
1398 deflines(curln, curln);
1399 c = input();
1400 compile(c);
1401 getrhs(c);
1402 num = getnth();
1403 chkprint(1);
1404 subst(num);
1405 break;
1406 case 'i':
1407 if (nlines > 1)
1408 goto bad_address;
1409 chkprint(1);
1410 deflines(curln, curln);
1411 if (!line1)
1412 line1++;
1413 append(prevln(line1));
1414 break;
1415 case 'a':
1416 if (nlines > 1)
1417 goto bad_address;
1418 chkprint(1);
1419 deflines(curln, curln);
1420 append(line1);
1421 break;
1422 case 'm':
1423 deflines(curln, curln);
1424 if (!address(&line3))
1425 line3 = curln;
1426 chkprint(1);
1427 move(line3);
1428 break;
1429 case 't':
1430 deflines(curln, curln);
1431 if (!address(&line3))
1432 line3 = curln;
1433 chkprint(1);
1434 copy(line3);
1435 break;
1436 case 'c':
1437 chkprint(1);
1438 deflines(curln, curln);
1439 delete(line1, line2);
1440 append(prevln(line1));
1441 break;
1442 case 'j':
1443 chkprint(1);
1444 deflines(curln, curln+1);
1445 if (line1 != line2 && curln != 0)
1446 join();
1447 break;
1448 case 'z':
1449 if (nlines > 1)
1450 goto bad_address;
1451
1452 num = 0;
1453 if (isdigit(back(input())))
1454 num = getnum();
1455 else if ((var = getenv("LINES")) != NULL)
1456 num = atoi(var) - 1;
1457 if (num <= 0)
1458 num = 23;
1459 chkprint(1);
1460 deflines(curln, curln);
1461 scroll(num);
1462 break;
1463 case 'k':
1464 if (nlines > 1)
1465 goto bad_address;
1466 if (!islower(c = input()))
1467 error("invalid mark character");
1468 chkprint(1);
1469 deflines(curln, curln);
1470 marks[c - 'a'] = line1;
1471 break;
1472 case 'P':
1473 if (nlines > 0)
1474 goto unexpected;
1475 chkprint(1);
1476 optprompt ^= 1;
1477 break;
1478 case 'x':
1479 trunc = 1;
1480 /* fallthrough */
1481 case 'X':
1482 ensureblank();
1483 if (nlines > 0)
1484 goto unexpected;
1485 exstatus = 0;
1486 deflines(nextln(0), lastln);
1487 dowrite(getfname(cmd), trunc);
1488 /* fallthrough */
1489 case 'Q':
1490 case 'q':
1491 if (nlines > 0)
1492 goto unexpected;
1493 if (cmd != 'Q' && modflag)
1494 goto modified;
1495 modflag = 0;
1496 quit();
1497 break;
1498 case 'f':
1499 ensureblank();
1500 if (nlines > 0)
1501 goto unexpected;
1502 if (back(input()) != '\0')
1503 getfname(cmd);
1504 else
1505 puts(savfname);
1506 chkprint(0);
1507 break;
1508 case 'E':
1509 case 'e':
1510 ensureblank();
1511 if (nlines > 0)
1512 goto unexpected;
1513 if (cmd == 'e' && modflag)
1514 goto modified;
1515 setscratch();
1516 deflines(curln, curln);
1517 doread(getfname(cmd));
1518 clearundo();
1519 modflag = 0;
1520 break;
1521 default:
1522 error("unknown command");
1523 bad_address:
1524 error("invalid address");
1525 modified:
1526 modflag = 0;
1527 error("warning: file modified");
1528 unexpected:
1529 error("unexpected address");
1530 }
1531
1532 if (!pflag)
1533 return;
1534 line1 = line2 = curln;
1535
1536print:
1537 doprint();
1538}
1539
1540static int
1541chkglobal(void)
1542{
1543 int delim, c, dir, i, v;
1544
1545 uflag = 1;
1546 gflag = 0;
1547 skipblank();
1548
1549 switch (c = input()) {
1550 case 'g':
1551 uflag = 0;
1552 /* fallthrough */
1553 case 'G':
1554 dir = 1;
1555 break;
1556 case 'v':
1557 uflag = 0;
1558 /* fallthrough */
1559 case 'V':
1560 dir = 0;
1561 break;
1562 default:
1563 back(c);
1564 return 0;
1565 }
1566 gflag = 1;
1567 deflines(nextln(0), lastln);
1568 delim = input();
1569 compile(delim);
1570
1571 for (i = 1; i <= lastln; ++i) {
1572 chksignals();
1573 if (i >= line1 && i <= line2)
1574 v = match(i) == dir;
1575 else
1576 v = 0;
1577 setglobal(i, v);
1578 }
1579
1580 return 1;
1581}
1582
1583static void
1584savecmd(void)
1585{
1586 int ch;
1587
1588 skipblank();
1589 ch = input();
1590 if (ch != '&') {
1591 ocmdline = strdup(cmdline.str);
1592 if (ocmdline == NULL)
1593 error("out of memory");
1594 }
1595 back(ch);
1596}
1597
1598static void
1599doglobal(void)
1600{
1601 int cnt, ln, k, idx, c;
1602
1603 skipblank();
1604 gflag = 1;
1605 if (uflag)
1606 chkprint(0);
1607
1608 ln = line1;
1609 for (cnt = 0; cnt < lastln; ) {
1610 chksignals();
1611 k = getindex(ln);
1612 if (zero[k].global) {
1613 zero[k].global = 0;
1614 curln = ln;
1615 nlines = 0;
1616
1617 if (!uflag) {
1618 idx = inputidx;
1619 getlst();
1620 for (;;) {
1621 docmd();
1622 if (!(c = input()))
1623 break;
1624 back(c);
1625 }
1626 inputidx = idx;
1627 continue;
1628 }
1629
1630 line1 = line2 = ln;
1631 pflag = 0;
1632 doprint();
1633
1634 for (;;) {
1635 getinput();
1636 if (strcmp(cmdline.str, "") == 0)
1637 break;
1638 savecmd();
1639 getlst();
1640 docmd();
1641 }
1642
1643 } else {
1644 cnt++;
1645 ln = nextln(ln);
1646 }
1647 }
1648}
1649
1650static void
1651usage(void)
1652{
1653 eprintf("usage: %s [-s] [-p] [file]\n", argv0);
1654}
1655
1656static void
1657sigintr(int n)
1658{
1659 (void)n;
1660 intr = 1;
1661}
1662
1663static void
1664sighup(int dummy)
1665{
1666 (void)dummy;
1667 hup = 1;
1668}
1669
1670static void
1671edit(void)
1672{
1673 for (;;) {
1674 newcmd = 1;
1675 ocurln = curln;
1676 olastln = lastln;
1677 if (optprompt) {
1678 fputs(prompt, stdout);
1679 fflush(stdout);
1680 }
1681
1682 getinput();
1683 getlst();
1684 chkglobal() ? doglobal() : docmd();
1685 }
1686}
1687
1688static void
1689init(char *fname)
1690{
1691 size_t len;
1692
1693 setscratch();
1694 if (!fname)
1695 return;
1696 if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
1697 error("incorrect filename");
1698 memcpy(savfname, fname, len);
1699 doread(fname);
1700 clearundo();
1701}
1702
1703// ?man ed: line editor
1704// ?man arguments: file
1705// ?man simple text line editor
1706int
1707main(int argc, char *argv[])
1708{
1709 ARGBEGIN {
1710 // ?man -p:str: preserve file attributes
1711 case 'p':
1712 prompt = EARGF(usage());
1713 optprompt = 1;
1714 break;
1715 // ?man -s: silent mode or print summary
1716 case 's':
1717 optdiag = 0;
1718 break;
1719 default:
1720 usage();
1721 } ARGEND
1722
1723 if (argc > 1)
1724 usage();
1725
1726 if (!setjmp(savesp)) {
1727 sigaction(SIGINT,
1728 &(struct sigaction) {.sa_handler = sigintr},
1729 NULL);
1730 sigaction(SIGHUP,
1731 &(struct sigaction) {.sa_handler = sighup},
1732 NULL);
1733 sigaction(SIGQUIT,
1734 &(struct sigaction) {.sa_handler = SIG_IGN},
1735 NULL);
1736 init(*argv);
1737 }
1738 edit();
1739
1740 /* not reached */
1741 return 0;
1742}