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