sbase/ed.c

1468 lines
23 KiB
C
Raw Normal View History

2015-12-14 07:09:34 -05:00
/* 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 "util.h"
2015-12-15 09:45:16 -05:00
2015-12-14 07:09:34 -05:00
#define REGEXSIZE 100
#define LINESIZE 80
#define NUMLINES 32
#define CACHESIZ 4096
2018-03-04 10:08:57 -05:00
#define AFTER 0
#define BEFORE 1
2015-12-14 07:09:34 -05:00
typedef struct {
char *str;
size_t cap;
size_t siz;
} String;
2015-12-14 07:09:34 -05:00
struct hline {
off_t seek;
char global;
int next, prev;
};
struct undo {
int curln, lastln;
2015-12-14 07:09:34 -05:00
size_t nr, cap;
struct link {
int to1, from1;
int to2, from2;
} *vec;
};
2015-12-14 07:13:23 -05:00
static char *prompt = "*";
static regex_t *pattern;
static regmatch_t matchs[10];
2018-03-04 08:06:24 -05:00
static String lastre;
2015-12-14 07:09:34 -05:00
2015-12-14 07:13:23 -05:00
static int optverbose, optprompt, exstatus, optdiag = 1;
static int marks['z' - 'a'];
static int nlines, line1, line2;
static int curln, lastln, ocurln, olastln;
2015-12-14 07:13:23 -05:00
static jmp_buf savesp;
static char *lasterr;
static size_t idxsize, lastidx;
static struct hline *zero;
2018-03-04 08:06:24 -05:00
static String text;
2015-12-14 07:13:23 -05:00
static char savfname[FILENAME_MAX];
static char tmpname[FILENAME_MAX];
static int scratch;
static int pflag, modflag, uflag, gflag;
static size_t csize;
2018-03-04 08:01:21 -05:00
static String cmdline;
2015-12-14 07:13:23 -05:00
static char *ocmdline;
static int repidx;
static char *rhs;
static char *lastmatch;
static struct undo udata;
static int newcmd;
int eol, bol;
2015-12-14 07:09:34 -05:00
static void
discard(void)
2015-12-14 07:09:34 -05:00
{
int c;
2018-03-04 08:01:21 -05:00
if (repidx >= 0)
return;
/* discard until the end of the line */
if (cmdline.siz > 0 && cmdline.str[cmdline.siz-1] == '\n')
return;
while ((c = getchar()) != '\n' && c != EOF)
;
}
static void undo(void);
static void
error(char *msg)
{
2015-12-14 07:09:34 -05:00
exstatus = 1;
lasterr = msg;
puts("?");
2015-12-14 07:09:34 -05:00
if (optverbose)
puts(msg);
if (!newcmd)
undo();
discard();
curln = ocurln;
2015-12-14 07:09:34 -05:00
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, String *s)
{
size_t cap = s->cap, siz = s->siz;
char *t = s->str;
if (siz >= cap &&
(cap > SIZE_MAX - LINESIZE ||
(t = realloc(t, cap += LINESIZE)) == NULL))
error("out of memory");
t[siz++] = c;
s->siz = siz;
s->cap = cap;
s->str = t;
return t;
}
2015-12-14 07:09:34 -05:00
static int
input(void)
{
int c;
if (repidx >= 0)
return ocmdline[repidx++];
if ((c = getchar()) != EOF)
addchar(c, &cmdline);
2015-12-14 07:09:34 -05:00
return c;
}
static int
back(int c)
{
if (repidx > 0) {
--repidx;
} else {
ungetc(c, stdin);
if (c != EOF)
2018-03-04 08:01:21 -05:00
--cmdline.siz;
2015-12-14 07:09:34 -05:00
}
return c;
}
static int
makeline(char *s, int *off)
{
struct hline *lp;
size_t len;
char c, *begin = s;
if (lastidx >= idxsize) {
lp = NULL;
if (idxsize <= SIZE_MAX - NUMLINES)
2019-12-31 16:41:38 -05:00
lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp));
if (!lp)
2015-12-14 07:09:34 -05:00
error("out of memory");
idxsize += NUMLINES;
zero = lp;
}
lp = zero + lastidx;
if (!s) {
lp->seek = -1;
len = 0;
} else {
while ((c = *s++) != '\n')
/* nothing */;
len = s - begin;
if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
write(scratch, begin, len) < 0) {
error("input/output error");
}
}
2015-12-14 07:09:34 -05:00
if (off)
*off = len;
++lastidx;
return lp - zero;
}
static int
getindex(int line)
{
struct hline *lp;
int n;
if (line == -1)
line = 0;
for (n = 0, lp = zero; n != line; n++)
2015-12-14 07:09:34 -05:00
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);
2018-03-04 08:06:24 -05:00
text.siz = 0;
off = lp->seek;
if (off == (off_t) -1)
return addchar('\0', &text);
2015-12-14 07:09:34 -05:00
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;
addchar(*p, &text);
2015-12-14 07:09:34 -05:00
}
if (csize && p == buf + csize)
goto repeat;
addchar('\n', &text);
addchar('\0', &text);
2018-03-04 08:06:24 -05:00
return text.str;
2015-12-14 07:09:34 -05:00
}
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;
modflag = 0;
2015-12-14 07:09:34 -05:00
}
static void
newundo(int from1, int from2)
2015-12-14 07:09:34 -05:00
{
struct link *p;
if (newcmd) {
clearundo();
udata.curln = ocurln;
udata.lastln = olastln;
2015-12-14 07:09:34 -05:00
}
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;
}
2015-12-14 07:09:34 -05:00
/*
* relink: to1 <- from1
* from2 -> to2
*/
static void
relink(int to1, int from1, int from2, int to2)
{
newundo(from1, from2);
2015-12-14 07:09:34 -05:00
zero[from1].next = to1;
zero[from2].prev = to2;
modflag = 1;
}
static void
undo(void)
{
struct link *p;
if (udata.nr == 0)
2015-12-31 14:35:26 -05:00
return;
for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
--udata.nr;
2015-12-14 07:09:34 -05:00
zero[p->from1].next = p->to1;
zero[p->from2].prev = p->to2;
}
free(udata.vec);
udata.vec = NULL;
udata.cap = 0;
curln = udata.curln;
lastln = udata.lastln;
2015-12-14 07:09:34 -05:00
}
static void
2018-03-04 10:08:57 -05:00
inject(char *s, int where)
2015-12-14 07:09:34 -05:00
{
int off, k, begin, end;
2018-03-04 10:08:57 -05:00
if (where == BEFORE) {
2016-10-09 19:12:46 -04:00
begin = getindex(curln-1);
end = getindex(nextln(curln-1));
} else {
begin = getindex(curln);
end = getindex(nextln(curln));
}
2015-12-14 07:09:34 -05:00
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(void)
2015-12-14 07:09:34 -05:00
{
if (scratch)
close(scratch);
remove(tmpname);
free(zero);
zero = NULL;
scratch = csize = idxsize = lastidx = curln = lastln = 0;
modflag = lastln = curln = 0;
2015-12-14 07:09:34 -05:00
}
static void
setscratch(void)
2015-12-14 07:09:34 -05:00
{
int r, k;
char *dir;
2015-12-14 07:09:34 -05:00
clearbuf();
clearundo();
if ((dir = getenv("TMPDIR")) == NULL)
dir = "/tmp";
r = snprintf(tmpname, sizeof(tmpname), "%s/%s",
dir, "ed.XXXXXX");
if (r < 0 || (size_t)r >= sizeof(tmpname))
error("scratch filename too long");
if ((scratch = mkstemp(tmpname)) < 0)
error("failed to create scratch file");
if ((k = makeline(NULL, NULL)))
2015-12-14 07:09:34 -05:00
error("input/output error in scratch file");
relink(k, k, k, k);
clearundo();
}
static void
compile(int delim)
{
int n, ret, c,bracket;
static char buf[BUFSIZ];
if (!isgraph(delim))
error("invalid pattern delimiter");
2018-03-04 08:06:24 -05:00
eol = bol = bracket = lastre.siz = 0;
2015-12-14 07:09:34 -05:00
for (n = 0;; ++n) {
if ((c = input()) == delim && !bracket)
break;
if (c == '^') {
bol = 1;
} else if (c == '$') {
eol = 1;
} else if (c == '\n' || c == EOF) {
2015-12-14 07:09:34 -05:00
back(c);
break;
}
if (c == '\\') {
addchar(c, &lastre);
2015-12-14 07:09:34 -05:00
c = input();
} else if (c == '[') {
bracket = 1;
} else if (c == ']') {
bracket = 0;
}
addchar(c, &lastre);
2015-12-14 07:09:34 -05:00
}
if (n == 0) {
if (!pattern)
error("no previous pattern");
return;
}
addchar('\0', &lastre);
2015-12-14 07:09:34 -05:00
if (pattern)
regfree(pattern);
if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
error("out of memory");
2018-03-04 08:06:24 -05:00
if ((ret = regcomp(pattern, lastre.str, REG_NEWLINE))) {
2015-12-14 07:09:34 -05:00
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)
{
regoff_t off = matchs[0].rm_eo;
if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) {
lastmatch += off;
return 1;
}
return 0;
2015-12-14 07:09:34 -05:00
}
static int
search(int way)
{
int i;
i = curln;
do {
i = (way == '?') ? prevln(i) : nextln(i);
if (i > 0 && match(i))
2015-12-14 07:09:34 -05:00
return i;
} while (i != curln);
error("invalid address");
return -1; /* not reached */
2015-12-14 07:09:34 -05:00
}
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 */
2015-12-14 07:09:34 -05:00
}
static int
linenum(int *line)
{
int ln, c;
skipblank();
switch (c = input()) {
case '.':
ln = curln;
break;
case '\'':
skipblank();
if (!islower(c = input()))
2015-12-14 07:09:34 -05:00
error("invalid mark character");
if (!(ln = marks[c - 'a']))
2015-12-14 07:09:34 -05:00
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 */
2015-12-14 07:09:34 -05:00
}
static void
getlst(void)
2015-12-14 07:09:34 -05:00
{
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(const char *fname, int trunc)
2015-12-14 07:09:34 -05:00
{
FILE *fp;
int i, line;
2015-12-14 07:09:34 -05:00
if (!(fp = fopen(fname, (trunc) ? "w" : "a")))
error("input/output error");
line = curln;
2015-12-14 07:09:34 -05:00
for (i = line1; i <= line2; ++i)
fputs(gettxt(i), fp);
curln = line2;
if (fclose(fp))
error("input/output error");
strcpy(savfname, fname);
2015-12-14 07:09:34 -05:00
modflag = 0;
curln = line;
2015-12-14 07:09:34 -05:00
}
static void
doread(const char *fname)
2015-12-14 07:09:34 -05:00
{
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")) == NULL)
error("cannot open input file");
2015-12-14 07:09:34 -05:00
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';
}
2018-03-04 10:08:57 -05:00
inject(s, AFTER);
2015-12-14 07:09:34 -05:00
}
if (optdiag)
printf("%zu\n", cnt);
2015-12-14 07:09:34 -05:00
aux = fp;
fp = NULL;
if (fclose(aux))
error("input/output error");
}
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)
puts(lasterr);
2015-12-14 07:09:34 -05:00
}
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(char comm)
2015-12-14 07:09:34 -05:00
{
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';
if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
strcpy(savfname, fname);
2015-12-14 07:09:34 -05:00
return fname;
}
return NULL; /* not reached */
2015-12-14 07:09:34 -05:00
}
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;
2018-03-04 10:08:57 -05:00
inject(s, AFTER);
2015-12-14 07:09:34 -05:00
}
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))
2015-12-14 07:09:34 -05:00
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;
2018-03-04 08:06:24 -05:00
static String s;
2015-12-14 07:09:34 -05:00
2018-03-04 08:06:24 -05:00
free(s.str);
s.siz = s.cap = 0;
for (i = line1;; i = nextln(i)) {
2015-12-14 07:09:34 -05:00
for (t = gettxt(i); (c = *t) != '\n'; ++t)
addchar(*t, &s);
if (i == line2)
break;
2015-12-14 07:09:34 -05:00
}
addchar('\n', &s);
addchar('\0', &s);
2015-12-14 07:09:34 -05:00
delete(line1, line2);
2018-03-04 10:08:57 -05:00
inject(s.str, BEFORE);
2018-03-04 08:06:24 -05:00
free(s.str);
2015-12-14 07:09:34 -05:00
}
static void
scroll(int num)
{
2018-03-04 11:10:17 -05:00
int max, ln, cnt;
2015-12-14 07:09:34 -05:00
if (!line1 || line1 == lastln)
error("incorrect address");
2018-03-04 11:10:17 -05:00
ln = line1;
max = line1 + num;
if (max > lastln)
max = lastln;
for (cnt = line1; cnt < max; cnt++) {
fputs(gettxt(ln), stdout);
ln = nextln(ln);
}
curln = ln;
2015-12-14 07:09:34 -05:00
}
static void
copy(int where)
{
2018-03-04 10:08:57 -05:00
if (!line1)
2015-12-14 07:09:34 -05:00
error("incorrect address");
curln = where;
while (line1 <= line2) {
inject(gettxt(line1), AFTER);
if (line2 >= curln)
line2 = nextln(line2);
line1 = nextln(line1);
if (line1 >= curln)
line1 = nextln(line1);
}
2015-12-14 07:09:34 -05:00
}
static void
quit(void)
{
clearbuf();
exit(exstatus);
}
static void
execsh(void)
{
2018-03-04 08:06:24 -05:00
static String cmd;
char *p;
int c, repl = 0;
2015-12-14 07:09:34 -05:00
skipblank();
if ((c = input()) != '!') {
back(c);
2018-03-04 08:06:24 -05:00
cmd.siz = 0;
} else if (cmd.siz) {
--cmd.siz;
2015-12-14 07:09:34 -05:00
repl = 1;
} else {
error("no previous command");
}
while ((c = input()) != EOF && c != '\n') {
2018-03-04 08:06:24 -05:00
if (c == '%' && (cmd.siz == 0 || cmd.str[cmd.siz - 1] != '\\')) {
2015-12-14 07:09:34 -05:00
if (savfname[0] == '\0')
error("no current filename");
repl = 1;
for (p = savfname; *p; ++p)
addchar(*p, &cmd);
2015-12-14 07:09:34 -05:00
} else {
addchar(c, &cmd);
2015-12-14 07:09:34 -05:00
}
}
addchar('\0', &cmd);
2015-12-14 07:09:34 -05:00
if (repl)
2018-03-04 08:06:24 -05:00
puts(cmd.str);
system(cmd.str);
if (optdiag)
puts("!");
2015-12-14 07:09:34 -05:00
}
static void
getrhs(int delim)
{
int c;
static String s;
2015-12-14 07:09:34 -05:00
free(s.str);
s.str = NULL;
s.siz = s.cap = 0;
while ((c = input()) != '\n' && c != EOF && c != delim)
addchar(c, &s);
addchar('\0', &s);
2015-12-14 07:09:34 -05:00
if (c == EOF)
error("invalid pattern delimiter");
if (c == '\n') {
pflag = 'p';
back(c);
}
if (!strcmp("%", s.str)) {
free(s.str);
2015-12-14 07:09:34 -05:00
if (!rhs)
error("no previous substitution");
} else {
free(rhs);
rhs = s.str;
2015-12-14 07:09:34 -05:00
}
s.str = NULL;
2015-12-14 07:09:34 -05:00
}
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
2018-03-04 08:06:24 -05:00
addpre(String *s)
2015-12-14 07:09:34 -05:00
{
char *p;
for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
addchar(*p, s);
2015-12-14 07:09:34 -05:00
}
static void
2018-03-04 08:06:24 -05:00
addpost(String *s)
2015-12-14 07:09:34 -05:00
{
char c, *p;
for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
addchar(c, s);
addchar('\0', s);
2015-12-14 07:09:34 -05:00
}
static int
2018-03-04 08:06:24 -05:00
addsub(String *s, int nth, int nmatch)
2015-12-14 07:09:34 -05:00
{
char *end, *q, *p, c;
int sub;
if (nth != nmatch && nth != -1) {
q = lastmatch + matchs[0].rm_so;
end = lastmatch + matchs[0].rm_eo;
while (q < end)
addchar(*q++, s);
return 0;
}
for (p = rhs; (c = *p); ++p) {
2015-12-14 07:09:34 -05:00
switch (c) {
case '&':
sub = 0;
goto copy_match;
case '\\':
if ((c = *++p) == '\0')
return 1;
2015-12-14 07:09:34 -05:00
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)
addchar(*q++, s);
2015-12-14 07:09:34 -05:00
break;
default:
copy_char:
addchar(c, s);
2015-12-14 07:09:34 -05:00
break;
}
}
return 1;
2015-12-14 07:09:34 -05:00
}
static void
subline(int num, int nth)
{
int i, m, changed;
2018-03-04 08:06:24 -05:00
static String s;
2015-12-14 07:09:34 -05:00
2018-03-04 08:06:24 -05:00
i = changed = s.siz = 0;
for (m = match(num); m; m = rematch(num)) {
2018-03-04 08:06:24 -05:00
addpre(&s);
changed |= addsub(&s, nth, ++i);
if (eol || bol)
break;
2015-12-14 07:09:34 -05:00
}
if (!changed)
return;
2018-03-04 08:06:24 -05:00
addpost(&s);
2015-12-14 07:09:34 -05:00
delete(num, num);
curln = prevln(num);
2018-03-04 10:08:57 -05:00
inject(s.str, AFTER);
2015-12-14 07:09:34 -05:00
}
static void
subst(int nth)
{
int i;
for (i = line1; i <= line2; ++i)
subline(i, nth);
}
static void
docmd(void)
{
char cmd;
2015-12-14 07:09:34 -05:00
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:
2018-03-04 08:01:21 -05:00
if (cmdline.siz == 0)
2015-12-14 07:09:34 -05:00
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(cmd), trunc);
2015-12-14 07:09:34 -05:00
break;
case 'r':
if (nlines > 1)
goto bad_address;
deflines(lastln, lastln);
doread(getfname(cmd));
2015-12-14 07:09:34 -05:00
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);
2015-12-31 14:35:26 -05:00
if (udata.nr == 0)
error("nothing to undo");
2015-12-14 07:09:34 -05:00
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)
line1++;
2015-12-14 07:09:34 -05:00
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 != line2 && curln != 0)
join();
2015-12-14 07:09:34 -05:00
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 - 'a'] = line1;
2015-12-14 07:09:34 -05:00
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 (back(input()) != '\n')
getfname(cmd);
2015-12-14 07:09:34 -05:00
else
puts(savfname);
chkprint(0);
2015-12-14 07:09:34 -05:00
break;
case 'E':
modflag = 0;
case 'e':
if (nlines > 0)
goto unexpected;
if (modflag)
goto modified;
getfname(cmd);
setscratch();
2015-12-14 07:09:34 -05:00
deflines(curln, curln);
doread(savfname);
clearundo();
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);
addchar('\0', &cmdline);
2018-03-04 08:01:21 -05:00
if ((ocmdline = strdup(cmdline.str)) == NULL)
2015-12-14 07:09:34 -05:00
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 cnt, ln, k;
2015-12-14 07:09:34 -05:00
skipblank();
2018-03-04 08:01:21 -05:00
cmdline.siz = 0;
2015-12-14 07:09:34 -05:00
gflag = 1;
if (uflag)
chkprint(0);
ln = line1;
for (cnt = 0; cnt < lastln; ) {
k = getindex(ln);
if (zero[k].global) {
zero[k].global = 0;
curln = ln;
nlines = 0;
if (uflag) {
line1 = line2 = ln;
pflag = 0;
doprint();
}
getlst();
docmd();
} else {
cnt++;
ln = nextln(ln);
2015-12-14 07:09:34 -05:00
}
}
2015-12-16 15:17:52 -05:00
discard(); /* cover the case of not matching anything */
2015-12-14 07:09:34 -05:00
}
static void
usage(void)
{
eprintf("usage: %s [-s] [-p] [file]\n", argv0);
2015-12-14 07:09:34 -05:00
}
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();
}
static void
edit(void)
{
for (;;) {
newcmd = 1;
ocurln = curln;
olastln = lastln;
2018-03-04 08:01:21 -05:00
cmdline.siz = 0;
repidx = -1;
if (optprompt) {
fputs(prompt, stdout);
fflush(stdout);
}
getlst();
chkglobal() ? doglobal() : docmd();
}
}
static void
init(char *fname)
{
size_t len;
setscratch();
if (!fname)
return;
if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
error("incorrect filename");
memcpy(savfname, fname, len);
doread(fname);
clearundo();
}
2015-12-14 07:09:34 -05:00
int
main(int argc, char *argv[])
{
2015-12-15 09:45:16 -05:00
ARGBEGIN {
case 'p':
prompt = EARGF(usage());
optprompt = 1;
break;
case 's':
optdiag = 0;
break;
default:
usage();
} ARGEND
2015-12-14 07:09:34 -05:00
2015-12-15 09:45:16 -05:00
if (argc > 1)
usage();
2015-12-14 07:09:34 -05:00
if (!setjmp(savesp)) {
signal(SIGINT, sigintr);
signal(SIGHUP, sighup);
signal(SIGQUIT, SIG_IGN);
init(*argv);
}
edit();
2015-12-14 07:09:34 -05:00
/* not reached */
return 0;
}