commit 1de650edf5827037422190dca7c75a6a073be172
parent b533625aac2522828e22cdfabb4d9a902c709860
Author: Roberto E. Vargas Caballero <k0ga@shike2.com>
Date: Mon, 14 Dec 2015 12:09:34 +0000
Add ed(1) - the standard text editor
Diffstat:
M | Makefile | | | 1 | + |
M | README | | | 1 | + |
M | TODO | | | 2 | +- |
A | ed.1 | | | 9 | +++++++++ |
A | ed.c | | | 1377 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 1389 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -92,6 +92,7 @@ BIN =\
dirname\
du\
echo\
+ ed\
env\
expand\
expr\
diff --git a/README b/README
@@ -30,6 +30,7 @@ The following tools are implemented:
=*|o dirname .
=*|o du .
=*|o echo .
+ o ed .
=*|o env .
#*|o expand .
#*|o expr .
diff --git a/TODO b/TODO
@@ -7,7 +7,7 @@ at
awk
bc
diff
-ed
+ed manpage
install
patch
pathchk
diff --git a/ed.1 b/ed.1
@@ -0,0 +1,9 @@
+.Dd 2015-12-14
+.Dt ED 1
+.Os sbase
+.Sh NAME
+.Nm ed
+.Nd text editor
+.Sh SYNOPSIS
+.Nm
+is the standard text editor.
diff --git a/ed.c b/ed.c
@@ -0,0 +1,1377 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <unistd.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define REGEXSIZE 100
+#define LINESIZE 80
+#define NUMLINES 32
+#define CACHESIZ 4096
+
+struct hline {
+ off_t seek;
+ char global;
+ int next, prev;
+};
+
+struct undo {
+ int curln;
+ size_t nr, cap;
+ struct link {
+ int to1, from1;
+ int to2, from2;
+ } *vec;
+};
+
+char *prompt = "*";
+regex_t *pattern;
+regmatch_t matchs[10];
+char *lastre;
+
+int optverbose, optprompt, exstatus, optdiag = 1;
+int marks['z' - 'a'];
+int nlines, line1, line2;
+int curln, lastln, ocurln;
+jmp_buf savesp;
+char *lasterr;
+size_t idxsize, lastidx;
+struct hline *zero;
+char *text;
+char savfname[FILENAME_MAX];
+char tmpname[FILENAME_MAX];
+size_t sizetxt, memtxt;
+int scratch;
+int pflag, modflag, uflag, gflag;
+size_t csize;
+char *cmdline;
+char *ocmdline;
+size_t cmdsiz, cmdcap;
+int repidx;
+char *rhs;
+char *lastmatch;
+struct undo udata;
+int newcmd;
+
+static void
+error(char *msg)
+{
+ int c;
+
+ exstatus = 1;
+ lasterr = msg;
+ fputs("?\n", stderr);
+
+ if (optverbose)
+ fprintf(stderr, "%s\n", msg);
+
+ /* discard until end of line */
+ if (repidx < 0 && cmdsiz > 0 && cmdline[cmdsiz-1] != '\n') {
+ while ((c = getchar()) != '\n' && c != EOF)
+ /* nothing */;
+ }
+
+ longjmp(savesp, 1);
+}
+
+static int
+nextln(int line)
+{
+ ++line;
+ return (line > lastln) ? 0 : line;
+}
+
+static int
+prevln(int line)
+{
+ --line;
+ return (line < 0) ? lastln : line;
+}
+
+static char *
+addchar(char c, char *t, size_t *capacity, size_t *size)
+{
+ size_t cap = *capacity, siz = *size;
+
+ if (siz >= cap &&
+ (cap > SIZE_MAX - LINESIZE ||
+ (t = realloc(t, cap += LINESIZE)) == NULL))
+ error("out of memory");
+ t[siz++] = c;
+ *size = siz;
+ *capacity = cap;
+ return t;
+}
+
+static int
+input(void)
+{
+ int c;
+
+ if (repidx >= 0)
+ return ocmdline[repidx++];
+
+ if ((c = getchar()) != EOF)
+ cmdline = addchar(c, cmdline, &cmdcap, &cmdsiz);
+ return c;
+}
+
+static int
+back(int c)
+{
+ if (repidx > 0) {
+ --repidx;
+ } else {
+ ungetc(c, stdin);
+ if (c != EOF)
+ --cmdsiz;
+ }
+ return c;
+}
+
+static int
+makeline(char *s, int *off)
+{
+ struct hline *lp;
+ size_t len;
+ char c, *begin = s;
+
+ if (lastidx >= idxsize) {
+ if (idxsize > SIZE_MAX - NUMLINES ||
+ !(lp = realloc(zero, (idxsize + NUMLINES) * sizeof(*lp))))
+ error("out of memory");
+ idxsize += NUMLINES;
+ zero = lp;
+ }
+ lp = zero + lastidx;
+
+ while ((c = *s) && *s != '\n')
+ ++s;
+ if (c == '\n')
+ ++s;
+ len = s - begin;
+ if (off)
+ *off = len;
+
+ if (len > 0)
+ if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
+ write(scratch, begin, len) < 0) {
+ error("input/output error");
+ }
+
+ ++lastidx;
+ return lp - zero;
+}
+
+static int
+getindex(int line)
+{
+ struct hline *lp;
+ int n;
+
+ for (n = 0, lp = zero; n != line; ++n)
+ lp = zero + lp->next;
+
+ return lp - zero;
+}
+
+static char *
+gettxt(int line)
+{
+ static char buf[CACHESIZ];
+ static off_t lasto;
+ struct hline *lp;
+ off_t off, block;
+ ssize_t n;
+ char *p;
+
+ lp = zero + getindex(line);
+ off = lp->seek;
+ sizetxt = 0;
+
+repeat:
+ if (!csize || off < lasto || off - lasto >= csize) {
+ block = off & ~(CACHESIZ-1);
+ if (lseek(scratch, block, SEEK_SET) < 0 ||
+ (n = read(scratch, buf, CACHESIZ)) < 0) {
+ error("input/output error");
+ }
+ csize = n;
+ lasto = block;
+ }
+ for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) {
+ ++off;
+ text = addchar(*p, text, &memtxt, &sizetxt);
+ }
+ if (csize && p == buf + csize)
+ goto repeat;
+
+ text = addchar('\n', text, &memtxt, &sizetxt);
+ text = addchar('\0', text, &memtxt, &sizetxt);
+ return text;
+}
+
+static void
+setglobal(int i, int v)
+{
+ zero[getindex(i)].global = v;
+}
+
+static void
+clearundo(void)
+{
+ free(udata.vec);
+ udata.vec = NULL;
+ newcmd = udata.nr = udata.cap = 0;
+}
+
+static void
+relink(int to1, int from1, int from2, int to2)
+{
+ struct link *p;
+
+ if (newcmd) {
+ clearundo();
+ udata.curln = ocurln;
+ }
+ if (udata.nr >= udata.cap) {
+ size_t siz = (udata.cap + 10) * sizeof(struct link);
+ if ((p = realloc(udata.vec, siz)) == NULL)
+ error("out of memory");
+ udata.vec = p;
+ udata.cap = udata.cap + 10;
+ }
+ p = &udata.vec[udata.nr++];
+ p->from1 = from1;
+ p->to1 = zero[from1].next;
+ p->from2 = from2;
+ p->to2 = zero[from2].prev;
+
+ zero[from1].next = to1;
+ zero[from2].prev = to2;
+ modflag = 1;
+}
+
+static void
+undo(void)
+{
+ int i;
+ struct link *p;
+
+ if (udata.nr == 0)
+ error("nothing to undo");
+ for (p = &udata.vec[udata.nr-1]; udata.nr--; --p) {
+ zero[p->from1].next = p->to1;
+ zero[p->from2].prev = p->to2;
+ }
+ free(udata.vec);
+ udata.vec = NULL;
+ udata.cap = 0;
+ curln = udata.curln;
+}
+
+static void
+inject(char *s)
+{
+ int off, k, begin, end;
+
+ begin = getindex(curln);
+ end = getindex(nextln(curln));
+
+ while (*s) {
+ k = makeline(s, &off);
+ s += off;
+ relink(k, begin, k, begin);
+ relink(end, k, end, k);
+ ++lastln;
+ ++curln;
+ begin = k;
+ }
+}
+
+static void
+clearbuf()
+{
+ if (scratch)
+ close(scratch);
+ remove(tmpname);
+ free(zero);
+ zero = NULL;
+ scratch = csize = idxsize = lastidx = curln = lastln = 0;
+ lastln = curln = 0;
+}
+
+static void
+setscratch()
+{
+ int k;
+
+ clearbuf();
+ clearundo();
+ strcpy(tmpname, "ed.XXXXXX");
+ if ((scratch = mkstemp(tmpname)) < 0) {
+ /* try /tmp if cwd is not writable */
+ strcpy(tmpname, "/tmp/ed.XXXXXX");
+ if ((scratch = mkstemp(tmpname)) < 0)
+ error("failed to create scratch file");
+ }
+ if ((k = makeline("", NULL)))
+ error("input/output error in scratch file");
+ relink(k, k, k, k);
+ clearundo();
+ modflag = 0;
+}
+
+static void
+compile(int delim)
+{
+ int n, ret, c,bracket;
+ static size_t siz, cap;
+ static char buf[BUFSIZ];
+
+ if (!isgraph(delim))
+ error("invalid pattern delimiter");
+
+ bracket = siz = 0;
+ for (n = 0;; ++n) {
+ if ((c = input()) == delim && !bracket)
+ break;
+ if (c == '\n' || c == EOF) {
+ back(c);
+ break;
+ }
+
+ if (c == '\\') {
+ lastre = addchar(c, lastre, &cap, &siz);
+ c = input();
+ } else if (c == '[') {
+ bracket = 1;
+ } else if (c == ']') {
+ bracket = 0;
+ }
+ lastre = addchar(c, lastre, &cap, &siz);
+ }
+ if (n == 0) {
+ if (!pattern)
+ error("no previous pattern");
+ return;
+ }
+ lastre = addchar('\0', lastre, &cap, &siz);
+
+ if (pattern)
+ regfree(pattern);
+ if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
+ error("out of memory");
+ if ((ret = regcomp(pattern, lastre, REG_NEWLINE))) {
+ regerror(ret, pattern, buf, sizeof(buf));
+ error(buf);
+ }
+}
+
+static int
+match(int num)
+{
+ lastmatch = gettxt(num);
+ return !regexec(pattern, lastmatch, 10, matchs, 0);
+}
+
+static int
+rematch(int num)
+{
+ lastmatch += matchs[0].rm_eo;
+ return !regexec(pattern, lastmatch, 10, matchs, 0);
+}
+
+static int
+search(int way)
+{
+ int i;
+
+ i = curln;
+ do {
+ i = (way == '?') ? prevln(i) : nextln(i);
+ if (match(i))
+ return i;
+ } while (i != curln);
+
+ error("invalid address");
+}
+
+static void
+skipblank(void)
+{
+ char c;
+
+ while ((c = input()) == ' ' || c == '\t')
+ /* nothing */;
+ back(c);
+}
+
+static int
+getnum(void)
+{
+ int ln, n, c;
+
+ for (ln = 0; isdigit(c = input()); ln += n) {
+ if (ln > INT_MAX/10)
+ goto invalid;
+ n = c - '0';
+ ln *= 10;
+ if (INT_MAX - ln < n)
+ goto invalid;
+ }
+ back(c);
+ return ln;
+
+invalid:
+ error("invalid address");
+}
+
+static int
+linenum(int *line)
+{
+ int ln, c;
+
+ skipblank();
+
+ switch (c = input()) {
+ case '.':
+ ln = curln;
+ break;
+ case '\'':
+ skipblank();
+ if (!isalpha(c = input()))
+ error("invalid mark character");
+ if (!(ln = marks[c]))
+ error("invalid address");
+ break;
+ case '$':
+ ln = lastln;
+ break;
+ case '?':
+ case '/':
+ compile(c);
+ ln = search(c);
+ break;
+ case '^':
+ case '-':
+ case '+':
+ ln = curln;
+ back(c);
+ break;
+ default:
+ back(c);
+ if (isdigit(c))
+ ln = getnum();
+ else
+ return 0;
+ break;
+ }
+ *line = ln;
+ return 1;
+}
+
+static int
+address(int *line)
+{
+ int ln, sign, c, num;
+
+ if (!linenum(&ln))
+ return 0;
+
+ for (;;) {
+ skipblank();
+ if ((c = input()) != '+' && c != '-' && c != '^')
+ break;
+ sign = c == '+' ? 1 : -1;
+ num = isdigit(back(input())) ? getnum() : 1;
+ num *= sign;
+ if (INT_MAX - ln < num)
+ goto invalid;
+ ln += num;
+ }
+ back(c);
+
+ if (ln < 0 || ln > lastln)
+ error("invalid address");
+ *line = ln;
+ return 1;
+
+invalid:
+ error("invalid address");
+}
+
+static void
+getlst()
+{
+ int ln, c;
+
+ if ((c = input()) == ',') {
+ line1 = 1;
+ line2 = lastln;
+ nlines = lastln;
+ return;
+ } else if (c == ';') {
+ line1 = curln;
+ line2 = lastln;
+ nlines = lastln - curln + 1;
+ return;
+ }
+ back(c);
+ line2 = curln;
+ for (nlines = 0; address(&ln); ) {
+ line1 = line2;
+ line2 = ln;
+ ++nlines;
+
+ skipblank();
+ if ((c = input()) != ',' && c != ';') {
+ back(c);
+ break;
+ }
+ if (c == ';')
+ curln = line2;
+ }
+ if (nlines > 2)
+ nlines = 2;
+ else if (nlines <= 1)
+ line1 = line2;
+}
+
+static void
+deflines(int def1, int def2)
+{
+ if (!nlines) {
+ line1 = def1;
+ line2 = def2;
+ }
+ if (line1 > line2 || line1 < 0 || line2 > lastln)
+ error("invalid address");
+}
+
+static void
+dowrite(char *fname, int trunc)
+{
+ FILE *fp;
+ int i;
+
+ if (!(fp = fopen(fname, (trunc) ? "w" : "a")))
+ error("input/output error");
+
+ for (i = line1; i <= line2; ++i)
+ fputs(gettxt(i), fp);
+
+ curln = line2;
+ if (fclose(fp))
+ error("input/output error");
+ strcpy(savfname, fname);
+ modflag = 0;
+}
+
+static void
+doread(char *fname)
+{
+ size_t cnt;
+ ssize_t n;
+ char *p;
+ FILE *aux;
+ static size_t len;
+ static char *s;
+ static FILE *fp;
+
+ if (fp)
+ fclose(fp);
+ if (!(fp = fopen(fname, "r")))
+ error("input/output error");
+
+ curln = line2;
+ for (cnt = 0; (n = getline(&s, &len, fp)) > 0; cnt += (size_t)n) {
+ if (s[n-1] != '\n') {
+ if (len == SIZE_MAX || !(p = realloc(s, ++len)))
+ error("out of memory");
+ s = p;
+ s[n-1] = '\n';
+ s[n] = '\0';
+ }
+ inject(s);
+ }
+ printf("%zu\n", cnt);
+
+ aux = fp;
+ fp = NULL;
+ if (fclose(aux))
+ error("input/output error");
+
+ if (savfname[0] == '\0') {
+ modflag = 0;
+ clearundo();
+ strcpy(savfname, fname);
+ }
+}
+
+static void
+doprint(void)
+{
+ int i, c;
+ char *s, *str;
+
+ if (line1 <= 0 || line2 > lastln)
+ error("incorrect address");
+ for (i = line1; i <= line2; ++i) {
+ if (pflag == 'n')
+ printf("%d\t", i);
+ for (s = gettxt(i); (c = *s) != '\n'; ++s) {
+ if (pflag != 'l')
+ goto print_char;
+ switch (c) {
+ case '$':
+ str = "\\$";
+ goto print_str;
+ case '\t':
+ str = "\\t";
+ goto print_str;
+ case '\b':
+ str = "\\b";
+ goto print_str;
+ case '\\':
+ str = "\\\\";
+ goto print_str;
+ default:
+ if (!isprint(c)) {
+ printf("\\x%x", 0xFF & c);
+ break;
+ }
+ print_char:
+ putchar(c);
+ break;
+ print_str:
+ fputs(str, stdout);
+ break;
+ }
+ }
+ if (pflag == 'l')
+ fputs("$", stdout);
+ putc('\n', stdout);
+ }
+ curln = i - 1;
+}
+
+static void
+dohelp(void)
+{
+ if (lasterr)
+ fprintf(stderr, "%s\n", lasterr);
+}
+
+static void
+chkprint(int flag)
+{
+ char c;
+
+ if (flag) {
+ if ((c = input()) == 'p' || c == 'l' || c == 'n')
+ pflag = c;
+ else
+ back(c);
+ }
+ if (input() != '\n')
+ error("invalid command suffix");
+}
+
+static char *
+getfname(void)
+{
+ int c;
+ char *bp;
+ static char fname[FILENAME_MAX];
+
+ skipblank();
+ for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
+ if ((c = input()) == EOF || c == '\n')
+ break;
+ }
+ if (bp == fname) {
+ if (savfname[0] == '\0')
+ error("no current filename");
+ return savfname;
+ } else if (bp == &fname[FILENAME_MAX]) {
+ error("file name too long");
+ } else {
+ *bp = '\0';
+ return fname;
+ }
+}
+
+static void
+append(int num)
+{
+ char *s = NULL;
+ size_t len = 0;
+
+ curln = num;
+ while (getline(&s, &len, stdin) > 0) {
+ if (*s == '.' && s[1] == '\n')
+ break;
+ inject(s);
+ }
+ free(s);
+}
+
+static void
+delete(int from, int to)
+{
+ int lto, lfrom;
+
+ if (!from)
+ error("incorrect address");
+
+ lfrom = getindex(prevln(from));
+ lto = getindex(nextln(to));
+ lastln -= to - from + 1;
+ curln = (from > lastln) ? lastln : from;;
+ relink(lto, lfrom, lto, lfrom);
+}
+
+static void
+move(int where)
+{
+ int before, after, lto, lfrom;
+
+ if (!line1 || where >= line1 && where <= line2)
+ error("incorrect address");
+
+ before = getindex(prevln(line1));
+ after = getindex(nextln(line2));
+ lfrom = getindex(line1);
+ lto = getindex(line2);
+ relink(after, before, after, before);
+
+ if (where < line1) {
+ curln = where + line1 - line2 + 1;
+ } else {
+ curln = where;
+ where -= line1 - line2 + 1;
+ }
+ before = getindex(where);
+ after = getindex(nextln(where));
+ relink(lfrom, before, lfrom, before);
+ relink(after, lto, after, lto);
+}
+
+static void
+join(void)
+{
+ int i;
+ char *t, c;
+ size_t len = 0, cap = 0;
+ static char *s;
+
+ free(s);
+ for (s = NULL, i = line1; i <= line2; i = nextln(i)) {
+ for (t = gettxt(i); (c = *t) != '\n'; ++t)
+ s = addchar(*t, s, &cap, &len);
+ }
+
+ s = addchar('\n', s, &cap, &len);
+ s = addchar('\0', s, &cap, &len);
+ delete(line1, line2);
+ inject(s);
+ free(s);
+}
+
+static void
+scroll(int num)
+{
+ int i;
+
+ if (!line1 || line1 == lastln)
+ error("incorrect address");
+
+ for (i = line1; i <= line1 + num && i <= lastln; ++i)
+ fputs(gettxt(i), stdout);
+ curln = i;
+}
+
+static void
+copy(int where)
+{
+ int i;
+
+ if (!line1 || where >= line1 && where <= line2)
+ error("incorrect address");
+ curln = where;
+
+ for (i = line1; i <= line2; ++i)
+ inject(gettxt(i));
+}
+
+static void
+quit(void)
+{
+ clearbuf();
+ exit(exstatus);
+}
+
+static void
+execsh(void)
+{
+ static char *cmd;
+ static size_t siz, cap;
+ char c, *p;
+ int repl = 0;
+
+ skipblank();
+ if ((c = input()) != '!') {
+ back(c);
+ siz = 0;
+ } else if (cmd) {
+ --siz;
+ repl = 1;
+ } else {
+ error("no previous command");
+ }
+
+ while ((c = input()) != EOF && c != '\n') {
+ if (c == '%' && (siz == 0 || cmd[siz - 1] != '\\')) {
+ if (savfname[0] == '\0')
+ error("no current filename");
+ repl = 1;
+ for (p = savfname; *p; ++p)
+ cmd = addchar(*p, cmd, &cap, &siz);
+ } else {
+ cmd = addchar(c, cmd, &cap, &siz);
+ }
+ }
+ cmd = addchar('\0', cmd, &cap, &siz);
+
+ if (repl)
+ puts(cmd);
+ system(cmd);
+ puts("!");
+}
+
+static void
+getrhs(int delim)
+{
+ int c;
+ size_t n, siz, cap;
+ static char *s;
+
+ free(s);
+ s = NULL;
+ siz = cap = 0;
+ while ((c = input()) != '\n' && c != EOF && c != delim) {
+ if (c == '\\') {
+ if (isdigit(c = input())) {
+ back(c);
+ c = '\\';
+ }
+ }
+ s = addchar(c, s, &siz, &cap);
+ }
+ s = addchar('\0', s, &siz, &cap);
+ if (c == EOF)
+ error("invalid pattern delimiter");
+ if (c == '\n') {
+ pflag = 'p';
+ back(c);
+ }
+
+ if (!strcmp("%", s)) {
+ free(s);
+ if (!rhs)
+ error("no previous substitution");
+ } else {
+ free(rhs);
+ rhs = s;
+ }
+ s = NULL;
+}
+
+static int
+getnth(void)
+{
+ int c;
+
+ if ((c = input()) == 'g') {
+ return -1;
+ } else if (isdigit(c)) {
+ if (c == '0')
+ return -1;
+ return c - '0';
+ } else {
+ back(c);
+ return 1;
+ }
+}
+
+static void
+addpre(char **s, size_t *cap, size_t *siz)
+{
+ char *p;
+
+ for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
+ *s = addchar(*p, *s, cap, siz);
+}
+
+static void
+addpost(char **s, size_t *cap, size_t *siz)
+{
+ char c, *p;
+
+ for (p = lastmatch + matchs[0].rm_eo; c = *p; ++p)
+ *s = addchar(c, *s, cap, siz);
+ *s = addchar('\0', *s, cap, siz);
+}
+
+static void
+addsub(char **s, size_t *cap, size_t *siz)
+{
+ char *end, *q, *p, c;
+ int sub;
+
+ for (p = rhs; c = *p; ++p) {
+ switch (c) {
+ case '&':
+ sub = 0;
+ goto copy_match;
+ case '\\':
+ if ((c = *++p) == '\0')
+ return;
+ if (!isdigit(c))
+ goto copy_char;
+ sub = c - '0';
+ copy_match:
+ q = lastmatch + matchs[sub].rm_so;
+ end = lastmatch + matchs[sub].rm_eo;
+ while (q < end)
+ *s = addchar(*q++, *s, cap, siz);
+ break;
+ default:
+ copy_char:
+ *s = addchar(c, *s, cap, siz);
+ break;
+ }
+ }
+}
+
+static void
+subline(int num, int nth)
+{
+ int m, changed;
+ static char *s;
+ static size_t siz, cap;
+
+ siz = 0;
+ for (m = match(num); m; m = rematch(num)) {
+ if (--nth > 0)
+ continue;
+ changed = 1;
+ addpre(&s, &cap, &siz);
+ addsub(&s, &cap, &siz);
+ if (nth == 0)
+ break;
+ }
+ if (!changed)
+ return;
+ addpost(&s, &cap, &siz);
+ delete(num, num);
+ curln = prevln(num);
+ inject(s);
+}
+
+static void
+subst(int nth)
+{
+ int i;
+
+ for (i = line1; i <= line2; ++i)
+ subline(i, nth);
+}
+
+static void
+docmd(void)
+{
+ char *s, cmd;
+ int rep = 0, c, line3, num, trunc;
+
+repeat:
+ skipblank();
+ cmd = input();
+ trunc = pflag = 0;
+ switch (cmd) {
+ case '&':
+ skipblank();
+ chkprint(0);
+ if (!ocmdline)
+ error("no previous command");
+ rep = 1;
+ repidx = 0;
+ getlst();
+ goto repeat;
+ case '!':
+ execsh();
+ break;
+ case EOF:
+ if (cmdsiz == 0)
+ quit();
+ case '\n':
+ if (gflag && uflag)
+ return;
+ num = gflag ? curln : curln+1;
+ deflines(num, num);
+ pflag = 'p';
+ goto print;
+ case 'l':
+ case 'n':
+ case 'p':
+ back(cmd);
+ chkprint(1);
+ deflines(curln, curln);
+ goto print;
+ case 'g':
+ case 'G':
+ case 'v':
+ case 'V':
+ error("cannot nest global commands");
+ case 'H':
+ if (nlines > 0)
+ goto unexpected;
+ chkprint(0);
+ optverbose ^= 1;
+ break;
+ case 'h':
+ if (nlines > 0)
+ goto unexpected;
+ chkprint(0);
+ dohelp();
+ break;
+ case 'w':
+ trunc = 1;
+ case 'W':
+ deflines(nextln(0), lastln);
+ dowrite(getfname(), trunc);
+ break;
+ case 'r':
+ if (nlines > 1)
+ goto bad_address;
+ deflines(lastln, lastln);
+ doread(getfname());
+ break;
+ case 'd':
+ chkprint(1);
+ deflines(curln, curln);
+ delete(line1, line2);
+ break;
+ case '=':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(lastln, lastln);
+ printf("%d\n", line1);
+ break;
+ case 'u':
+ if (nlines > 0)
+ goto bad_address;
+ chkprint(1);
+ undo();
+ break;
+ case 's':
+ deflines(curln, curln);
+ c = input();
+ compile(c);
+ getrhs(c);
+ num = getnth();
+ chkprint(1);
+ subst(num);
+ break;
+ case 'i':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(curln, curln);
+ if (!line1)
+ goto bad_address;
+ append(prevln(line1));
+ break;
+ case 'a':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(curln, curln);
+ append(line1);
+ break;
+ case 'm':
+ deflines(curln, curln);
+ if (!address(&line3))
+ line3 = curln;
+ chkprint(1);
+ move(line3);
+ break;
+ case 't':
+ deflines(curln, curln);
+ if (!address(&line3))
+ line3 = curln;
+ chkprint(1);
+ copy(line3);
+ break;
+ case 'c':
+ chkprint(1);
+ deflines(curln, curln);
+ delete(line1, line2);
+ append(prevln(line1));
+ break;
+ case 'j':
+ chkprint(1);
+ deflines(curln, curln+1);
+ if (!line1)
+ goto bad_address;
+ join();
+ break;
+ case 'z':
+ if (nlines > 1)
+ goto bad_address;
+ if (isdigit(back(input())))
+ num = getnum();
+ else
+ num = 24;
+ chkprint(1);
+ scroll(num);
+ break;
+ case 'k':
+ if (nlines > 1)
+ goto bad_address;
+ if (!islower(c = input()))
+ error("invalid mark character");
+ chkprint(1);
+ deflines(curln, curln);
+ marks[c] = line1;
+ break;
+ case 'P':
+ if (nlines > 0)
+ goto unexpected;
+ chkprint(1);
+ optprompt ^= 1;
+ break;
+ case 'Q':
+ modflag = 0;
+ case 'q':
+ if (nlines > 0)
+ goto unexpected;
+ if (modflag)
+ goto modified;
+ quit();
+ break;
+ case 'f':
+ if (nlines > 0)
+ goto unexpected;
+ if (!strcmp(s = getfname(), savfname))
+ puts(savfname);
+ else
+ strcpy(savfname, s);
+ break;
+ case 'E':
+ modflag = 0;
+ case 'e':
+ if (nlines > 0)
+ goto unexpected;
+ if (modflag)
+ goto modified;
+ setscratch();
+ strcpy(savfname, getfname());
+ deflines(curln, curln);
+ doread(savfname);
+ clearundo();
+ modflag = 0;
+ break;
+ default:
+ error("unknown command");
+ bad_address:
+ error("invalid address");
+ modified:
+ modflag = 0;
+ error("warning: file modified");
+ unexpected:
+ error("unexpected address");
+ }
+
+ if (!pflag)
+ goto save_last_cmd;
+
+ line1 = line2 = curln;
+print:
+ doprint();
+
+save_last_cmd:
+ if (!uflag)
+ repidx = 0;
+ if (rep)
+ return;
+ free(ocmdline);
+ cmdline = addchar('\0', cmdline, &cmdcap, &cmdsiz);
+ if ((ocmdline = strdup(cmdline)) == NULL)
+ error("out of memory");
+}
+
+static int
+chkglobal(void)
+{
+ int delim, c, dir, i, v;
+
+ uflag = 1;
+ gflag = 0;
+ skipblank();
+
+ switch (c = input()) {
+ case 'g':
+ uflag = 0;
+ case 'G':
+ dir = 1;
+ break;
+ case 'v':
+ uflag = 0;
+ case 'V':
+ dir = 0;
+ break;
+ default:
+ back(c);
+ return 0;
+ }
+ gflag = 1;
+ deflines(nextln(0), lastln);
+ delim = input();
+ compile(delim);
+
+ for (i = 1; i <= lastln; ++i) {
+ if (i >= line1 && i <= line2)
+ v = match(i) == dir;
+ else
+ v = 0;
+ setglobal(i, v);
+ }
+
+ return 1;
+}
+
+static void
+doglobal(void)
+{
+ int i, k;
+
+ skipblank();
+ cmdsiz = 0;
+ gflag = 1;
+ if (uflag)
+ chkprint(0);
+
+ for (i = 1; i <= lastln; i++) {
+ k = getindex(i);
+ if (!zero[k].global)
+ continue;
+ curln = i;
+ nlines = 0;
+ if (uflag) {
+ line1 = line2 = i;
+ pflag = 0;
+ doprint();
+ }
+ docmd();
+ }
+}
+
+static void
+usage(void)
+{
+ fputs("ed [-s][-p][file]\n", stderr);
+ exit(1);
+}
+
+static void
+sigintr(int n)
+{
+ signal(SIGINT, sigintr);
+ error("interrupt");
+}
+
+static void
+sighup(int dummy)
+{
+ int n;
+ char *home = getenv("HOME"), fname[FILENAME_MAX];
+
+ if (modflag) {
+ line1 = nextln(0);
+ line2 = lastln;
+ if (!setjmp(savesp)) {
+ dowrite("ed.hup", 1);
+ } else if (home && !setjmp(savesp)) {
+ n = snprintf(fname,
+ sizeof(fname), "%s/%s", home, "ed.hup");
+ if (n < sizeof(fname) && n > 0)
+ dowrite(fname, 1);
+ }
+ }
+ exstatus = 1;
+ quit();
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *p;
+
+ while (*++argv) {
+ if (argv[0][0] != '-')
+ break;
+ for (p = argv[0] + 1; *p; ++p) {
+ switch (*p) {
+ case 's':
+ optdiag = 0;
+ break;
+ case 'p':
+ if (!*++argv)
+ usage();
+ prompt = *argv;
+ optprompt = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ }
+
+ signal(SIGINT, sigintr);
+ signal(SIGHUP, sighup);
+ signal(SIGQUIT, SIG_IGN);
+ if (!setjmp(savesp)) {
+ setscratch();
+ if (*argv) {
+ if (strlen(*argv) >= FILENAME_MAX)
+ error("file name too long");
+ doread(*argv);
+ }
+ }
+
+ for (;;) {
+ newcmd = 1;
+ ocurln = curln;
+ cmdsiz = 0;
+ repidx = -1;
+ if (optprompt)
+ fputs(prompt, stdout);
+ getlst();
+ chkglobal() ? doglobal() : docmd();
+ }
+
+ /* not reached */
+ return 0;
+}