3e130cee66
getrhs() must remove \ excepts in the case of & and \d (where d is a digit), because in this case are sequences understood by addsub(), so addsub() must be able to see them.
1390 lines
22 KiB
C
1390 lines
22 KiB
C
/* 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>
|
|
|
|
#include "arg.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;
|
|
};
|
|
|
|
static char *prompt = "*";
|
|
static regex_t *pattern;
|
|
static regmatch_t matchs[10];
|
|
static char *lastre;
|
|
|
|
static int optverbose, optprompt, exstatus, optdiag = 1;
|
|
static int marks['z' - 'a'];
|
|
static int nlines, line1, line2;
|
|
static int curln, lastln, ocurln;
|
|
static jmp_buf savesp;
|
|
static char *lasterr;
|
|
static size_t idxsize, lastidx;
|
|
static struct hline *zero;
|
|
static char *text;
|
|
static char savfname[FILENAME_MAX];
|
|
static char tmpname[FILENAME_MAX];
|
|
static size_t sizetxt, memtxt;
|
|
static int scratch;
|
|
static int pflag, modflag, uflag, gflag;
|
|
static size_t csize;
|
|
static char *cmdline;
|
|
static char *ocmdline;
|
|
static size_t cmdsiz, cmdcap;
|
|
static int repidx;
|
|
static char *rhs;
|
|
static char *lastmatch;
|
|
static struct undo udata;
|
|
static int newcmd;
|
|
|
|
|
|
|
|
static void
|
|
discard(void)
|
|
{
|
|
int c;
|
|
|
|
/* discard until end of line */
|
|
if (repidx < 0 &&
|
|
((cmdsiz > 0 && cmdline[cmdsiz-1] != '\n') || cmdsiz == 0)) {
|
|
while ((c = getchar()) != '\n' && c != EOF)
|
|
/* nothing */;
|
|
}
|
|
}
|
|
|
|
static void undo(void);
|
|
|
|
static void
|
|
error(char *msg)
|
|
{
|
|
exstatus = 1;
|
|
lasterr = msg;
|
|
fputs("?\n", stderr);
|
|
|
|
if (optverbose)
|
|
fprintf(stderr, "%s\n", msg);
|
|
if (!newcmd)
|
|
undo();
|
|
|
|
discard();
|
|
curln = ocurln;
|
|
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)
|
|
{
|
|
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");
|
|
return -1; /* not reached */
|
|
}
|
|
|
|
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");
|
|
return -1; /* not reached */
|
|
}
|
|
|
|
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");
|
|
return -1; /* not reached */
|
|
}
|
|
|
|
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, line;
|
|
|
|
if (!(fp = fopen(fname, (trunc) ? "w" : "a")))
|
|
error("input/output error");
|
|
|
|
line = curln;
|
|
for (i = line1; i <= line2; ++i)
|
|
fputs(gettxt(i), fp);
|
|
|
|
curln = line2;
|
|
if (fclose(fp))
|
|
error("input/output error");
|
|
strcpy(savfname, fname);
|
|
modflag = 0;
|
|
curln = line;
|
|
}
|
|
|
|
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;
|
|
}
|
|
return NULL; /* not reached */
|
|
}
|
|
|
|
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 siz, cap;
|
|
static char *s;
|
|
|
|
free(s);
|
|
s = NULL;
|
|
siz = cap = 0;
|
|
while ((c = input()) != '\n' && c != EOF && c != delim) {
|
|
if (c == '\\') {
|
|
if ((c = input()) == '&' || isdigit(c))
|
|
s = addchar(c, s, &siz, &cap);
|
|
}
|
|
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;
|
|
strcpy(savfname, getfname());
|
|
setscratch();
|
|
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();
|
|
}
|
|
discard(); /* cover the case of not matchingc anything */
|
|
}
|
|
|
|
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[])
|
|
{
|
|
ARGBEGIN {
|
|
case 'p':
|
|
prompt = EARGF(usage());
|
|
optprompt = 1;
|
|
break;
|
|
case 's':
|
|
optdiag = 0;
|
|
break;
|
|
default:
|
|
usage();
|
|
} ARGEND
|
|
|
|
if (argc > 1)
|
|
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;
|
|
}
|