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