1991 lines
41 KiB
C
1991 lines
41 KiB
C
/* ed.c: This file contains the main control and user-interface routines
|
|
for the ed line editor. */
|
|
/*-
|
|
* This code is derived from software by Brian W. Kernighan, Bell
|
|
* Laboratories, Murray Hill, New Jersey and P. J. Plauger, Whitesmiths, Ltd.
|
|
*
|
|
* Kernighan/Plauger, "Software Tools in Pascal," (c) 1981 by
|
|
* Addison-Wesley Publishing Company, Inc. Reprinted with permission of
|
|
* the publisher.
|
|
*/
|
|
/*-
|
|
* Copyright (c) 1993 The Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley
|
|
* by Andrew Moore, Talke Studio.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
char copyright1[] =
|
|
"@(#) Kernighan/Plauger, \"Software Tools in Pascal,\" (c) 1981 by\n\
|
|
Addison-Wesley Publishing Company, Inc. Reprinted with permission of\n\
|
|
the publisher.\n";
|
|
char copyright2[] =
|
|
"@(#) Copyright (c) 1993 The Regents of the University of California.\n\
|
|
All rights reserved.\n";
|
|
#endif /* not lint */
|
|
|
|
#ifndef lint
|
|
static char sccsid[] = "@(#)ed.c 5.5 (Berkeley) 3/28/93";
|
|
#endif /* not lint */
|
|
|
|
/*
|
|
* ED -- ED line editor
|
|
* options:
|
|
* -p key use key as the command prompt
|
|
* -s run a script
|
|
* - same as -s (though deprecated)
|
|
* -x use crypt on file i/o
|
|
*
|
|
* Author: Andrew Moore
|
|
* Talke Studio
|
|
* 485 Redwood Avenue, #5
|
|
* Redwood City, CA 94061
|
|
* Email: alm@netcom.com
|
|
*
|
|
* Credits: The buf.c algorithm is attributed to Rodney Ruddock of
|
|
* the University of Guelph, Guelph, Ontario.
|
|
*
|
|
* The cbc.c encryption code is adapted from
|
|
* the bdes program by Matt Bishop of Dartmouth College,
|
|
* Hanover, NH.
|
|
*
|
|
* The Addison-Wesley Publishing Company generously
|
|
* granted permission to distribute this program over
|
|
* Internet.
|
|
*
|
|
* Brian Beattie, Kees Bot and "others" did a C port of
|
|
* Kernighan and Pike's "Software Tools" editor.
|
|
*
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <pwd.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "ed.h"
|
|
|
|
jmp_buf env;
|
|
line_t line0; /* initial node of line queue */
|
|
int modified; /* file-changed status */
|
|
char ibuf[MAXLINE]; /* input buffer */
|
|
char *ibufp; /* input pointer */
|
|
long curln; /* current line number */
|
|
long lastln; /* last line number */
|
|
char fnp[MAXFNAME] = ""; /* output file name */
|
|
char *prompt; /* command-line prompt */
|
|
char *defprompt = "*"; /* default prompt */
|
|
int lineno; /* script line number */
|
|
struct winsize ws; /* window size structure */
|
|
int rows = 22; /* scroll length: ws_row - 2 */
|
|
int cols = 72; /* wrap column: ws_col - 8 */
|
|
|
|
|
|
/* global flags */
|
|
int garrulous = 0; /* if set, print all error messages */
|
|
int verbose = 1; /* if set, print diagnostics */
|
|
int des = 0; /* if set, use crypt for i/o */
|
|
int rtq = 0; /* if set, ready to quit */
|
|
int interactive = 0; /* if set, interactive command mode */
|
|
int mutex = 0; /* if set, signals set "sigflags" */
|
|
int sigflags = 0; /* if set, signals received while mutex set */
|
|
|
|
extern char errmsg[];
|
|
extern int optind;
|
|
extern char *optarg;
|
|
|
|
/* ed: line editor */
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
int c, n;
|
|
long status = 0;
|
|
|
|
while ((c = getopt(argc, argv, "p:sx")) != EOF)
|
|
switch(c) {
|
|
case 'p': /* set prompt */
|
|
prompt = optarg;
|
|
break;
|
|
case 's': /* run script */
|
|
verbose = 0;
|
|
break;
|
|
case 'x': /* use crypt */
|
|
#ifdef DES
|
|
des = getkey();
|
|
#else
|
|
fprintf(stderr, "crypt unavailable\n?\n");
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "usage: ed [-p prompt] [-sx] [-] [file]\n");
|
|
exit(1);
|
|
}
|
|
argv += optind;
|
|
argc -= optind;
|
|
if (argc && **argv == '-') {
|
|
verbose = 0;
|
|
argc--;
|
|
argv++;
|
|
}
|
|
requeue(&line0, &line0);
|
|
if (sbopen() < 0 || argc && doread(0, strcpy(fnp, *argv)) < 0) {
|
|
fprintf(stderr, "%s\n?\n", errmsg);
|
|
if (!isatty(0)) quit(2);
|
|
}
|
|
dowinch(SIGWINCH);
|
|
/* assert: reliable signals! */
|
|
if (isatty(0))
|
|
signal(SIGWINCH, dowinch);
|
|
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
|
|
signal(SIGHUP, onhup);
|
|
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
|
|
if (!(status = setjmp(env)))
|
|
signal(SIGINT, onintr);
|
|
}
|
|
freecmdv();
|
|
interactive = 0;
|
|
for (;;) {
|
|
rtq = (!verbose || status == EMOD); /* ready to quit? */
|
|
if (status >= 0) *errmsg = '\0';
|
|
if (garrulous && *errmsg) fprintf(stderr, "%s\n", errmsg);
|
|
if (prompt) printf("%s", prompt);
|
|
fflush(stdout);
|
|
|
|
if ((n = getline(ibuf, sizeof ibuf)) == 0)
|
|
if (modified && !rtq) {
|
|
fputs("?\n", stderr); /* give warning */
|
|
sprintf(errmsg, "file modified");
|
|
clearerr(stdin);
|
|
status = EMOD;
|
|
continue;
|
|
} else quit(0);
|
|
|
|
if (ibuf[n - 1] != '\n') { /* discard line */
|
|
sprintf(errmsg, "unexpected EOF");
|
|
clearerr(stdin);
|
|
status = ERR;
|
|
continue;
|
|
}
|
|
ibufp = ibuf;
|
|
if ((n = getlist()) >= 0 && (status = ckglob()) != 0) {
|
|
if (status > 0 && (status = doglob()) >= 0) {
|
|
curln = status;
|
|
continue;
|
|
}
|
|
} else if ((status = n) >= 0 && (status = docmd(0)) >= 0) {
|
|
if (!status || status
|
|
&& (status = doprnt(curln, curln, status)) >= 0)
|
|
continue;
|
|
}
|
|
switch (status) {
|
|
case EOF:
|
|
quit(0);
|
|
case EMOD:
|
|
if (rtq) quit(0);
|
|
fputs("?\n", stderr); /* give warning */
|
|
sprintf(errmsg, "file modified");
|
|
break;
|
|
default:
|
|
fputs("?\n", stderr);
|
|
if (!isatty(0)) {
|
|
fprintf(stderr, garrulous ? "script, line %d: %s\n" : "", lineno, errmsg);
|
|
quit(2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
|
|
long line1, line2, nlines;
|
|
|
|
/* getlist: get line numbers from the command buffer until an illegal
|
|
address is seen. return range status */
|
|
getlist()
|
|
{
|
|
long num;
|
|
|
|
nlines = line2 = 0;
|
|
while ((num = getone()) >= 0) {
|
|
line1 = line2;
|
|
line2 = num;
|
|
nlines++;
|
|
if (*ibufp != ',' && *ibufp != ';')
|
|
break;
|
|
else if (*ibufp++ == ';')
|
|
curln = num;
|
|
}
|
|
nlines = min(nlines, 2);
|
|
if (nlines == 0)
|
|
line2 = curln;
|
|
if (nlines <= 1)
|
|
line1 = line2;
|
|
return (num == ERR) ? ERR : nlines;
|
|
}
|
|
|
|
|
|
/* getone: return the next line number in the command buffer */
|
|
long
|
|
getone()
|
|
{
|
|
int c;
|
|
long i, num;
|
|
|
|
if ((num = getnum(1)) < 0)
|
|
return num;
|
|
for (;;) {
|
|
c = isspace(*ibufp);
|
|
skipblanks();
|
|
c = c && isdigit(*ibufp);
|
|
if (!c && *ibufp != '+' && *ibufp != '-' && *ibufp != '^')
|
|
break;
|
|
c = c ? '+' : *ibufp++;
|
|
if ((i = getnum(0)) < 0) {
|
|
sprintf(errmsg, "invalid address");
|
|
return i;
|
|
}
|
|
if (c == '+')
|
|
num += i;
|
|
else num -= i;
|
|
}
|
|
if (num > lastln || num < 0) {
|
|
sprintf(errmsg, "invalid address");
|
|
return ERR;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
long mark['z'-'a'+1];
|
|
|
|
/* getnum: return a relative line number from the command buffer */
|
|
long
|
|
getnum(first)
|
|
int first;
|
|
{
|
|
pattern_t *srchpat;
|
|
char c;
|
|
|
|
skipblanks();
|
|
if (isdigit(*ibufp))
|
|
return strtol(ibufp, &ibufp, 10);
|
|
switch(c = *ibufp) {
|
|
case '.':
|
|
ibufp++;
|
|
return first ? curln : ERR;
|
|
case '$':
|
|
ibufp++;
|
|
return first ? lastln : ERR;
|
|
case '/':
|
|
case '?':
|
|
if ((srchpat = optpat()) == NULL)
|
|
return ERR;
|
|
else if (*ibufp == c)
|
|
ibufp++;
|
|
return first ? find(srchpat, (c == '/') ? 1 : 0) : ERR;
|
|
case '^':
|
|
case '-':
|
|
case '+':
|
|
return first ? curln : 1;
|
|
case '\'':
|
|
ibufp++;
|
|
return (islower(*ibufp) && first) ? mark[*ibufp++ - 'a'] : ERR;
|
|
case '%':
|
|
case ',':
|
|
case ';':
|
|
if (first) {
|
|
ibufp++;
|
|
line2 = (c == ';') ? curln : 1;
|
|
nlines++;
|
|
return lastln;
|
|
}
|
|
return 1;
|
|
default:
|
|
return first ? EOF : 1;
|
|
}
|
|
}
|
|
|
|
|
|
/* gflags */
|
|
#define GLB 001 /* global command */
|
|
#define GPR 002 /* print after command */
|
|
#define GLS 004 /* list after command */
|
|
#define GNP 010 /* enumerate after command */
|
|
#define GSG 020 /* global substitute */
|
|
|
|
|
|
/* VRFYCMD: verify the command suffix in the command buffer */
|
|
#define VRFYCMD() { \
|
|
int done = 0; \
|
|
do { \
|
|
switch(*ibufp) { \
|
|
case 'p': \
|
|
gflag |= GPR, ibufp++; \
|
|
break; \
|
|
case 'l': \
|
|
gflag |= GLS, ibufp++; \
|
|
break; \
|
|
case 'n': \
|
|
gflag |= GNP, ibufp++; \
|
|
break; \
|
|
default: \
|
|
done++; \
|
|
} \
|
|
} while (!done); \
|
|
if (*ibufp++ != '\n') { \
|
|
sprintf(errmsg, "invalid command suffix"); \
|
|
return ERR; \
|
|
} \
|
|
}
|
|
|
|
|
|
int Gflag = 0; /* interactive global command suffix */
|
|
|
|
/* ckglob: mark lines matching a pattern in the command buffer; return
|
|
global status */
|
|
ckglob()
|
|
{
|
|
pattern_t *glbpat;
|
|
char c, delim;
|
|
char *s;
|
|
int nomatch;
|
|
long n;
|
|
line_t *ptr;
|
|
int gflag = 0;
|
|
|
|
if ((c = *ibufp) == 'V' || c == 'G')
|
|
interactive++;
|
|
else if (c != 'g' && c != 'v')
|
|
return 0;
|
|
if (deflt(1, lastln) < 0 || (delim = *++ibufp) == ' ' || delim == '\n'
|
|
|| (glbpat = optpat()) == NULL)
|
|
return ERR;
|
|
if (*ibufp == delim)
|
|
ibufp++;
|
|
if (interactive) {
|
|
VRFYCMD(); /* set gflag */
|
|
Gflag = gflag;
|
|
}
|
|
for (n = 1, ptr = getptr(n); n <= lastln; n++, ptr = ptr->next) {
|
|
if ((s = gettxt(ptr)) == (char *) ERR)
|
|
return ERR;
|
|
ptr->len &= ~ACTV; /* zero ACTV bit */
|
|
if (line1 <= n && n <= line2
|
|
&& (!(nomatch = regexec(glbpat, s, 0, NULL, 0))
|
|
&& (c == 'g' || c == 'G')
|
|
|| nomatch && (c == 'v' || c == 'V')))
|
|
ptr->len |= ACTV;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* doglob: apply command list in the command buffer to the active
|
|
lines in a range; return command status */
|
|
long
|
|
doglob()
|
|
{
|
|
line_t *ptr = NULL;
|
|
long lc;
|
|
int status;
|
|
int n;
|
|
char *cmd = NULL;
|
|
|
|
if (!interactive && (cmd = getcmdv()) == NULL)
|
|
return ERR;
|
|
ureset();
|
|
for (;;) {
|
|
for (lc = 1, ptr = getptr(lc); lc <= lastln; lc++, ptr = ptr->next)
|
|
if (ptr->len & ACTV) /* active line */
|
|
break;
|
|
if (lc > lastln)
|
|
break;
|
|
ptr->len ^= ACTV; /* zero ACTV bit */
|
|
curln = lc;
|
|
if (interactive) {
|
|
/* print curln and get a command in global syntax */
|
|
if (doprnt(curln, curln, 0) < 0) {
|
|
interactive = 0;
|
|
freecmdv();
|
|
return ERR;
|
|
}
|
|
while ((n = getline(ibufp = ibuf, sizeof ibuf))
|
|
&& ibuf[n - 1] != '\n')
|
|
clearerr(stdin);
|
|
if (!n) {
|
|
sprintf(errmsg, "unexpected end of file");
|
|
interactive = 0;
|
|
freecmdv();
|
|
return ERR;
|
|
}
|
|
if (!strcmp(ibuf, "\n"))
|
|
continue;
|
|
if (!strcmp(ibuf, "&\n")) {
|
|
if (cmd == NULL) {
|
|
sprintf(errmsg, "null command");
|
|
interactive = 0;
|
|
freecmdv();
|
|
return ERR;
|
|
}
|
|
} else {
|
|
freecmdv();
|
|
if ((cmd = getcmdv()) == NULL) {
|
|
interactive = 0;
|
|
return ERR;
|
|
}
|
|
}
|
|
}
|
|
ibufp = cmd;
|
|
for (; *ibufp;)
|
|
if ((status = getlist()) < 0
|
|
|| (status = docmd(1)) < 0
|
|
|| (status > 0
|
|
&& (status = doprnt(curln, curln, status)) < 0)) {
|
|
freecmdv();
|
|
interactive = 0;
|
|
return status;
|
|
}
|
|
}
|
|
if (interactive && Gflag && doprnt(curln, curln, Gflag) < 0)
|
|
return ERR;
|
|
freecmdv();
|
|
interactive = 0;
|
|
return curln;
|
|
}
|
|
|
|
|
|
/* GETLINE3: get a legal address from the command buffer */
|
|
#define GETLINE3(num) \
|
|
{ \
|
|
long ol1, ol2; \
|
|
\
|
|
ol1 = line1, ol2 = line2; \
|
|
if (getlist() < 0) \
|
|
return ERR; \
|
|
if (line2 < 0 || lastln < line2) { \
|
|
sprintf(errmsg, "invalid address"); \
|
|
return ERR; \
|
|
} \
|
|
num = line2; \
|
|
line1 = ol1, line2 = ol2; \
|
|
}
|
|
|
|
/* sgflags */
|
|
#define SGG 001 /* complement previous global substitute suffix */
|
|
#define SGP 002 /* complement previous print suffix */
|
|
#define SGR 004 /* use last regex instead of last subpat */
|
|
#define SGF 010 /* newline found */
|
|
|
|
int usw = 0; /* if set, undo last undo */
|
|
int patlock = 0; /* if set, pattern not released by optpat() */
|
|
|
|
/* docmd: execute the next command in command buffer; return print
|
|
request, if any */
|
|
docmd(glob)
|
|
int glob;
|
|
{
|
|
static pattern_t *subpat = NULL;
|
|
static int sgflag = 0;
|
|
static char rhs[MAXLINE];
|
|
static char cmdbuf[MAXLINE];
|
|
|
|
pattern_t *tpat;
|
|
int c = 0;
|
|
char *fptr;
|
|
int gflag = 0;
|
|
long num;
|
|
int changed;
|
|
int subflags = 0;
|
|
char *sp, *tp;
|
|
|
|
skipblanks();
|
|
switch(*ibufp++) {
|
|
case '\n':
|
|
if (deflt(curln + !glob, curln + !glob) < 0
|
|
|| doprnt(line2, line2, 0) < 0)
|
|
return ERR;
|
|
break;
|
|
case '!':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
if (*ibufp == '!') {
|
|
subflags++;
|
|
ibufp++;
|
|
} else {
|
|
cmdbuf[0] = '\0';
|
|
for (;;) {
|
|
/* replace unescaped %s with file name */
|
|
if (sp = strchr(ibufp, '%'))
|
|
for (tp = ibufp; sp && oddesc(tp, sp);)
|
|
sp = strchr((tp = sp) + 1, '%');
|
|
if (sp) {
|
|
subflags++;
|
|
if (strlen(cmdbuf) + strlen(fnp) > sizeof cmdbuf) {
|
|
sprintf(errmsg, "command too long");
|
|
return ERR;
|
|
}
|
|
strncat(cmdbuf, ibufp, sp - ibufp);
|
|
strcat(cmdbuf, fnp);
|
|
ibufp = sp + 1;
|
|
} else {
|
|
sp = strchr(ibufp, '\n');
|
|
if (strlen(cmdbuf) + sp - ibufp + 1 > sizeof cmdbuf) {
|
|
sprintf(errmsg, "command too long");
|
|
return ERR;
|
|
}
|
|
strncat(cmdbuf, ibufp, sp - ibufp + 1);
|
|
ibufp = sp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
VRFYCMD();
|
|
if (subflags) printf("%s", cmdbuf);
|
|
system(cmdbuf);
|
|
if (verbose) printf("!\n");
|
|
break;
|
|
case '=':
|
|
VRFYCMD();
|
|
printf("%d\n", nlines ? line2 : lastln);
|
|
break;
|
|
case 'a':
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (append(line2, glob) < 0)
|
|
return ERR;
|
|
break;
|
|
case 'c':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (del(line1, line2) < 0 || append(curln, glob) < 0)
|
|
return ERR;
|
|
break;
|
|
case 'd':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (del(line1, line2) < 0)
|
|
return ERR;
|
|
if (nextln(curln, lastln) != 0)
|
|
curln = nextln(curln, lastln);
|
|
modified = TRUE;
|
|
break;
|
|
case 'e':
|
|
if (modified && !rtq)
|
|
return EMOD;
|
|
/* fall through */
|
|
case 'E':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
} else if (!isspace(*ibufp)) {
|
|
sprintf(errmsg, "unexpected command suffix");
|
|
return ERR;
|
|
}
|
|
if ((fptr = getfn()) == NULL)
|
|
return ERR;
|
|
VRFYCMD();
|
|
del(1, lastln);
|
|
ureset();
|
|
sbclose();
|
|
if (sbopen() < 0)
|
|
return ERR;
|
|
if (*fptr && *fptr != '!') strcpy(fnp, fptr);
|
|
if (doread(0, *fptr ? fptr : fnp) < 0) {
|
|
if (!garrulous) fprintf(stderr, "%s\n", errmsg);
|
|
return ERR;
|
|
}
|
|
ureset();
|
|
bzero(mark, sizeof mark);
|
|
modified = FALSE;
|
|
break;
|
|
case 'f':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
} else if (!isspace(*ibufp)) {
|
|
sprintf(errmsg, "unexpected command suffix");
|
|
return ERR;
|
|
}
|
|
if ((fptr = getfn()) == NULL)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (*fptr) strcpy(fnp, fptr);
|
|
printf("%s\n", fnp);
|
|
break;
|
|
case 'H':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
if ((garrulous = 1 - garrulous) && *errmsg)
|
|
fprintf(stderr, "%s\n", errmsg);
|
|
break;
|
|
case 'h':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
if (*errmsg) fprintf(stderr, "%s\n", errmsg);
|
|
break;
|
|
case 'i':
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (append(prevln(line2, lastln), glob) < 0)
|
|
return ERR;
|
|
break;
|
|
case 'j':
|
|
if (deflt(curln, curln + 1) < 0)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (join(line1, line2) < 0)
|
|
return ERR;
|
|
break;
|
|
case 'k':
|
|
if (!islower(c = *ibufp++)) {
|
|
sprintf(errmsg, "invalid mark character");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
mark[c-'a'] = line2;
|
|
break;
|
|
case 'l':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
gflag |= GLS;
|
|
VRFYCMD();
|
|
if (doprnt(line1, line2, gflag) < 0)
|
|
return ERR;
|
|
gflag = 0;
|
|
break;
|
|
case 'm':
|
|
/* Clean but inefficient move.
|
|
The inefficiency is outweighed since the requeue
|
|
alternative requires special handling by undo(). */
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
GETLINE3(num);
|
|
if (line1 <= num && num < line2) {
|
|
sprintf(errmsg, "invalid destination");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (transfer(num) < 0)
|
|
return ERR;
|
|
c = (num > line1) ? 0 : line2 - line1 + 1;
|
|
if (del(line1 + c, line2 + c) < 0)
|
|
return ERR;
|
|
curln = num + c;
|
|
modified = TRUE;
|
|
break;
|
|
case 'n':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
gflag |= GNP;
|
|
VRFYCMD();
|
|
if (doprnt(line1, line2, gflag) < 0)
|
|
return ERR;
|
|
gflag = 0;
|
|
break;
|
|
case 'P':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
prompt = prompt ? NULL : optarg ? optarg : defprompt;
|
|
break;
|
|
case 'p':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
gflag |= GPR;
|
|
VRFYCMD();
|
|
if (doprnt(line1, line2, gflag) < 0)
|
|
return ERR;
|
|
gflag = 0;
|
|
break;
|
|
case 'q':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
if (modified)
|
|
return EMOD;
|
|
return (nlines == 0 && !glob) ? EOF : ERR;
|
|
case 'Q':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
return EOF;
|
|
case 'r':
|
|
if (!isspace(*ibufp)) {
|
|
sprintf(errmsg, "unexpected command suffix");
|
|
return ERR;
|
|
}
|
|
if (!nlines) line2 = lastln;
|
|
if ((fptr = getfn()) == NULL)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (*fnp == '\0' && *fptr != '!') strcpy(fnp, fptr);
|
|
if ((num = doread(line2, *fptr ? fptr : fnp)) < 0) {
|
|
if (!garrulous) fprintf(stderr, "%s\n", errmsg);
|
|
return ERR;
|
|
}
|
|
else if (num != lastln)
|
|
modified = TRUE;
|
|
break;
|
|
case 's':
|
|
do {
|
|
switch(*ibufp) {
|
|
case '\n':
|
|
subflags |=SGF;
|
|
break;
|
|
case 'g':
|
|
subflags |= SGG;
|
|
ibufp++;
|
|
break;
|
|
case 'p':
|
|
subflags |= SGP;
|
|
ibufp++;
|
|
break;
|
|
case 'r':
|
|
subflags |= SGR;
|
|
ibufp++;
|
|
break;
|
|
default:
|
|
if (subflags) return ERR;
|
|
}
|
|
} while (subflags && *ibufp != '\n');
|
|
if (!(subflags & SGF))
|
|
sgflag &= 0xff;
|
|
tpat = subpat;
|
|
spl1();
|
|
if ((!subflags || (subflags & SGR))
|
|
&& (tpat = optpat()) == NULL)
|
|
return ERR;
|
|
if (tpat != subpat) {
|
|
if (subpat) {
|
|
regfree(subpat);
|
|
free(subpat);
|
|
}
|
|
subpat = tpat;
|
|
patlock = 1; /* reserve pattern */
|
|
}
|
|
spl0();
|
|
if (!subflags && (sgflag = getrhs(rhs, glob)) < 0)
|
|
return ERR;
|
|
if (glob)
|
|
sgflag |= GLB;
|
|
else
|
|
sgflag &= ~GLB;
|
|
if (subflags & SGG)
|
|
sgflag ^= GSG;
|
|
if (subflags & SGP)
|
|
sgflag ^= GPR, sgflag &= ~GLS;
|
|
do {
|
|
switch(*ibufp) {
|
|
case 'p':
|
|
sgflag |= GPR, ibufp++;
|
|
break;
|
|
case 'l':
|
|
sgflag |= GLS, ibufp++;
|
|
break;
|
|
case 'n':
|
|
sgflag |= GNP, ibufp++;
|
|
break;
|
|
default:
|
|
c++;
|
|
}
|
|
} while (!c);
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if ((changed = subst(subpat, rhs, sgflag)) < 0)
|
|
return ERR;
|
|
else if (changed)
|
|
modified = TRUE;
|
|
break;
|
|
case 't':
|
|
if (deflt(curln, curln) < 0)
|
|
return ERR;
|
|
GETLINE3(num);
|
|
VRFYCMD();
|
|
if (!glob) ureset();
|
|
if (transfer(num) < 0)
|
|
return ERR;
|
|
modified = TRUE;
|
|
break;
|
|
case 'u':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
if (undo() < 0)
|
|
return ERR;
|
|
break;
|
|
case 'W':
|
|
case 'w':
|
|
if (*ibufp == 'q') {
|
|
gflag = EOF;
|
|
ibufp++;
|
|
} else if (!isspace(*ibufp)) {
|
|
sprintf(errmsg, "unexpected command suffix");
|
|
return ERR;
|
|
}
|
|
if ((fptr = getfn()) == NULL)
|
|
return ERR;
|
|
if (nlines == 0 && !lastln)
|
|
line1 = line2 = 0;
|
|
else if (deflt(1, lastln) < 0)
|
|
return ERR;
|
|
VRFYCMD();
|
|
if (*fnp == '\0' && *fptr != '!') strcpy(fnp, fptr);
|
|
if (dowrite(line1, line2, *fptr ? fptr : fnp, c == 'W' ? "a" : "w") < 0) {
|
|
if (!garrulous) fprintf(stderr, "%s\n", errmsg);
|
|
return ERR;
|
|
}
|
|
modified = FALSE;
|
|
break;
|
|
case 'x':
|
|
if (nlines > 0) {
|
|
sprintf(errmsg, "unexpected address");
|
|
return ERR;
|
|
}
|
|
VRFYCMD();
|
|
#ifdef DES
|
|
des = getkey();
|
|
#else
|
|
sprintf(errmsg, "crypt unavailable");
|
|
return ERR;
|
|
#endif
|
|
break;
|
|
case 'z':
|
|
if (deflt(curln + 1, curln + 1) < 0)
|
|
return ERR;
|
|
if (isdigit(*ibufp))
|
|
rows = strtol(ibufp, &ibufp, 10);
|
|
VRFYCMD();
|
|
if (doprnt(line1, min(lastln, line1 + rows - 1), gflag) < 0)
|
|
return ERR;
|
|
gflag = 0;
|
|
break;
|
|
default:
|
|
sprintf(errmsg, "unknown command");
|
|
return ERR;
|
|
}
|
|
return gflag;
|
|
}
|
|
|
|
|
|
/* deflt: return status of line number range check */
|
|
deflt(def1, def2)
|
|
long def1, def2;
|
|
{
|
|
if (nlines == 0) {
|
|
line1 = def1;
|
|
line2 = def2;
|
|
}
|
|
if (line1 > line2 || 1 > line1 || line2 > lastln) {
|
|
sprintf(errmsg, "invalid address");
|
|
return ERR;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* find: return the number of the next line matching a pattern in a
|
|
given direction. wrap around begin/end of line queue if necessary */
|
|
long
|
|
find(pat, dir)
|
|
pattern_t *pat;
|
|
int dir;
|
|
{
|
|
char *s;
|
|
long n = curln;
|
|
|
|
do {
|
|
n = dir ? nextln(n, lastln) : prevln(n, lastln);
|
|
if ((s = gettxt(getptr(n))) == (char *) ERR)
|
|
return ERR;
|
|
else if (s && !regexec(pat, s, 0, NULL, 0))
|
|
return n;
|
|
} while (n != curln);
|
|
sprintf(errmsg, "no match");
|
|
return ERR;
|
|
}
|
|
|
|
|
|
/* getfn: return pointer to file name in the command buffer */
|
|
char *
|
|
getfn()
|
|
{
|
|
static char file[MAXFNAME];
|
|
char *cp = file;
|
|
|
|
if (*ibufp != '\n') {
|
|
skipblanks();
|
|
if (*ibufp == '\n' || *ibufp == '\\' && *(ibufp + 1) == '\n') {
|
|
sprintf(errmsg, "invalid file name");
|
|
return NULL;
|
|
}
|
|
} else if (*fnp == '\0') {
|
|
sprintf(errmsg, "no current file name");
|
|
return NULL;
|
|
}
|
|
while ((*cp = *ibufp) != '\n' && cp - file < sizeof file)
|
|
cp++, ibufp++;
|
|
if (cp - file == sizeof file) {
|
|
sprintf(errmsg, "name too long");
|
|
return NULL;
|
|
}
|
|
*cp = '\0';
|
|
return file;
|
|
}
|
|
|
|
/* getrhs: extract substitution template from the command buffer */
|
|
getrhs(sub, glob)
|
|
char *sub;
|
|
int glob;
|
|
{
|
|
char delim;
|
|
|
|
if ((delim = *ibufp) == '\n') {
|
|
*sub = '\0';
|
|
return GPR;
|
|
} else if (makesub(sub, MAXLINE, glob) == NULL)
|
|
return ERR;
|
|
else if (*ibufp == '\n')
|
|
return GPR;
|
|
else if (*ibufp == delim)
|
|
ibufp++;
|
|
if ('1' <= *ibufp && *ibufp <= '9')
|
|
return (int) strtol(ibufp, &ibufp, 10) << 8;
|
|
else if (*ibufp == 'g') {
|
|
ibufp++;
|
|
return GSG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* makesub: return pointer to substitution template in the command buffer */
|
|
char *
|
|
makesub(sub, subsz, glob)
|
|
char *sub;
|
|
int subsz;
|
|
int glob;
|
|
{
|
|
int n = 0;
|
|
int size = 0;
|
|
char *cp = sub;
|
|
char delim = *ibufp++;
|
|
char c;
|
|
|
|
if (*ibufp == '%' && *(ibufp + 1) == delim) {
|
|
ibufp++;
|
|
return sub;
|
|
}
|
|
for (; *ibufp != delim && size < subsz; size++)
|
|
if ((c = *cp++ = *ibufp++) == '\n' && *ibufp == '\0') {
|
|
cp--, ibufp--;
|
|
break;
|
|
} else if (c != ESCHAR)
|
|
;
|
|
else if (++size == subsz)
|
|
break;
|
|
else if ((*cp++ = *ibufp++) != '\n')
|
|
;
|
|
else if (!glob)
|
|
while ((n = getline(ibufp = ibuf, sizeof ibuf)) == 0
|
|
|| ibuf[n - 1] != '\n')
|
|
clearerr(stdin);
|
|
else
|
|
/*NOTREACHED*/
|
|
;
|
|
if (size >= subsz) {
|
|
sprintf(errmsg, "substitution too long");
|
|
return NULL;
|
|
}
|
|
*cp = '\0';
|
|
return sub;
|
|
}
|
|
|
|
|
|
/* append: insert text from stdin to after line n; stop when either a
|
|
single period is read or EOF; return status */
|
|
append(n, glob)
|
|
long n;
|
|
int glob;
|
|
{
|
|
int l;
|
|
char lin[MAXLINE];
|
|
char *lp = lin;
|
|
undo_t *up = NULL;
|
|
|
|
for (curln = n;;) {
|
|
if (!glob) {
|
|
if ((l = getline(lin, sizeof lin)) == 0
|
|
|| lin[l - 1] != '\n') {
|
|
clearerr(stdin);
|
|
return l ? EOF : 0;
|
|
}
|
|
} else if (*(lp = ibufp) == '\0')
|
|
return 0;
|
|
else
|
|
while (*ibufp++ != '\n')
|
|
;
|
|
if (lp[0] == '.' && lp[1] == '\n') {
|
|
return 0;
|
|
}
|
|
spl1();
|
|
if (puttxt(lp) == (char *) ERR) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
if (up) up->t = getptr(curln);
|
|
else up = getuptr(UADD, curln, curln);
|
|
spl0();
|
|
modified = 1;
|
|
}
|
|
}
|
|
|
|
#ifdef sun
|
|
/* subst: change all text matching a pattern in a range of lines according to
|
|
a substitution template; return status */
|
|
subst(pat, sub, gflag)
|
|
pattern_t *pat;
|
|
char *sub;
|
|
int gflag;
|
|
{
|
|
undo_t *up = NULL;
|
|
char *new;
|
|
line_t *bp, *ep, *np;
|
|
long ocl;
|
|
long nsubs = 0;
|
|
|
|
ep = getptr(curln = line2);
|
|
for (bp = getptr(line1); bp != ep->next; bp = bp->next)
|
|
if ((new = gettxt(bp)) == (char *) ERR)
|
|
return ERR;
|
|
else if ((new = regsub(pat, new, sub, gflag)) == (char *) ERR)
|
|
return ERR;
|
|
else if (!new) {
|
|
/* add copy of bp after current line - this avoids
|
|
overloading the undo structure, since only two
|
|
undo nodes are needed for the whole substitution;
|
|
the cost is high, but the less than if undo is
|
|
overloaded. */
|
|
if ((np = lpdup(bp)) == NULL)
|
|
return ERR;
|
|
spl1();
|
|
lpqueue(np);
|
|
if (up)
|
|
up->t = getptr(curln);
|
|
else up = getuptr(UADD, curln, curln);
|
|
spl0();
|
|
} else {
|
|
spl1();
|
|
do {
|
|
if ((new = puttxt(new)) == (char *) ERR) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
if (up)
|
|
up->t = getptr(curln);
|
|
else up = getuptr(UADD, curln, curln);
|
|
} while (new != NULL);
|
|
spl0();
|
|
nsubs++;
|
|
if ((gflag & (GPR | GLS))
|
|
&& doprnt(curln, curln, gflag) < 0)
|
|
return ERR;
|
|
}
|
|
ocl = curln;
|
|
del(line1, line2);
|
|
curln = ocl - (line2 - line1 + 1);
|
|
if (nsubs == 0 && !(gflag & GLB)) {
|
|
sprintf(errmsg, "no match");
|
|
return ERR;
|
|
}
|
|
return 1;
|
|
}
|
|
#else /* sun */
|
|
|
|
|
|
/* subst: change all text matching a pattern in a range of lines according to
|
|
a substitution template; return status */
|
|
subst(pat, sub, gflag)
|
|
pattern_t *pat;
|
|
char *sub;
|
|
int gflag;
|
|
{
|
|
undo_t *up;
|
|
char *new;
|
|
long lc;
|
|
int nsubs = 0;
|
|
|
|
curln = prevln(line1, lastln);
|
|
for (lc = 0; lc <= line2 - line1; lc++) {
|
|
new = gettxt(getptr(curln = nextln(curln, lastln)));
|
|
if (new == (char *) ERR)
|
|
return ERR;
|
|
else if ((new = regsub(pat, new, sub, gflag)) == (char *) ERR)
|
|
return ERR;
|
|
else if (new) {
|
|
up = NULL;
|
|
del(curln, curln);
|
|
spl1();
|
|
do {
|
|
if ((new = puttxt(new)) == (char *) ERR) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
if (up) up->t = getptr(curln);
|
|
else up = getuptr(UADD, curln, curln);
|
|
} while (new != NULL);
|
|
spl0();
|
|
nsubs++;
|
|
if ((gflag & (GPR | GLS))
|
|
&& doprnt(curln, curln, gflag) < 0)
|
|
return ERR;
|
|
}
|
|
}
|
|
if (nsubs == 0 && !(gflag & GLB)) {
|
|
sprintf(errmsg, "no match");
|
|
return ERR;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif /* sun */
|
|
|
|
|
|
/* regsub: substitute possibly several expressions in a line of text
|
|
matched by a pattern according to a substitution template; return
|
|
pointer to the modified text */
|
|
char *
|
|
regsub(pat, txt, sub, gflag)
|
|
pattern_t *pat;
|
|
char *txt;
|
|
char *sub;
|
|
int gflag;
|
|
{
|
|
static char buf[MAXLINE];
|
|
|
|
char *eob = buf + sizeof buf - 1; /* -1 for '\n' */
|
|
char *new = buf;
|
|
int kth = gflag >> 8; /* substitute kth match only */
|
|
int chngd = 0;
|
|
int matchno = 0;
|
|
int n;
|
|
regmatch_t rm[SE_MAX];
|
|
|
|
if (!regexec(pat, txt, SE_MAX, rm, 0)) {
|
|
do {
|
|
if (!kth || kth == ++matchno) {
|
|
chngd++;
|
|
if ((n = rm[0].rm_so) >= eob - new) {
|
|
sprintf(errmsg, "line too long");
|
|
return (char *) ERR;
|
|
}
|
|
strncpy(new, txt, n);
|
|
if (!(new = subcat(txt, rm, sub, new += n, eob))) {
|
|
sprintf(errmsg, "line too long");
|
|
return (char *) ERR;
|
|
}
|
|
} else {
|
|
if ((n = rm[0].rm_eo) >= eob - new) {
|
|
sprintf(errmsg, "line too long");
|
|
return (char *) ERR;
|
|
}
|
|
strncpy(new, txt, n);
|
|
new += n;
|
|
}
|
|
txt += rm[0].rm_eo;
|
|
} while (*txt && (!chngd || (gflag & GSG) && rm[0].rm_eo)
|
|
&& !regexec(pat, txt, SE_MAX, rm, REG_NOTBOL));
|
|
if ((n = strlen(txt)) >= eob - new) {
|
|
sprintf(errmsg, "line too long");
|
|
return (char *) ERR;
|
|
} else if (n > 0 && !rm[0].rm_eo && (gflag & GSG)) {
|
|
sprintf(errmsg, "infinite loop");
|
|
return (char *) ERR;
|
|
}
|
|
strcpy(new, txt);
|
|
strcpy(new + n, "\n");
|
|
}
|
|
return chngd ? buf : NULL;
|
|
}
|
|
|
|
|
|
/* join: replace a range of lines with the joined text of those lines */
|
|
join(from, to)
|
|
long from;
|
|
long to;
|
|
{
|
|
char buf[MAXLINE] = "";
|
|
char *s;
|
|
int len = 0;
|
|
int size = 0;
|
|
line_t *bp, *ep;
|
|
|
|
if (from == to)
|
|
return 0;
|
|
ep = getptr(nextln(to, lastln));
|
|
for (bp = getptr(from); bp != ep; bp = bp->next, size += len) {
|
|
if ((s = gettxt(bp)) == (char *) ERR)
|
|
return ERR;
|
|
if (size + (len = strlen(s)) >= sizeof buf - 1) {
|
|
sprintf(errmsg, "line too long");
|
|
return ERR;
|
|
}
|
|
strcpy(buf + size, s);
|
|
}
|
|
strcpy(buf + size, "\n");
|
|
del(from, to);
|
|
curln = from - 1;
|
|
spl1();
|
|
if (puttxt(buf) == (char *) ERR) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
getuptr(UADD, curln, curln);
|
|
spl0();
|
|
modified = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* transfer: copy a range of lines; return status */
|
|
transfer(num)
|
|
long num;
|
|
{
|
|
line_t *lp;
|
|
long nl, nt, lc;
|
|
long mid = (num < line2) ? num : line2;
|
|
undo_t *up = NULL;
|
|
|
|
curln = num;
|
|
for (nt = 0, nl = line1; nl <= mid; nl++, nt++) {
|
|
spl1();
|
|
if ((lp = lpdup(getptr(nl))) == NULL) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
lpqueue(lp);
|
|
if (up) up->t = lp;
|
|
else up = getuptr(UADD, curln, curln);
|
|
spl0();
|
|
}
|
|
for (nl += nt, lc = line2 + nt; nl <= lc; nl += 2, lc++) {
|
|
spl1();
|
|
if ((lp = lpdup(getptr(nl))) == NULL) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
lpqueue(lp);
|
|
if (up) up->t = lp;
|
|
else up = getuptr(UADD, curln, curln);
|
|
spl0();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* del: delete a range of lines */
|
|
del(from, to)
|
|
long from, to;
|
|
{
|
|
line_t *before, *after;
|
|
|
|
spl1();
|
|
getuptr(UDEL, from, to);
|
|
after = getptr(nextln(to, lastln));
|
|
before = getptr(prevln(from, lastln)); /* this getptr last! */
|
|
requeue(before, after);
|
|
lastln -= to - from + 1;
|
|
curln = prevln(from, lastln);
|
|
spl0();
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* subcat: modify text according to a substitution template;
|
|
return pointer to modified text */
|
|
char *
|
|
subcat(boln, rm, sub, new, newend)
|
|
char *boln;
|
|
regmatch_t *rm;
|
|
char *sub, *new, *newend;
|
|
{
|
|
char *from = boln + rm[0].rm_so;
|
|
char *to = boln + rm[0].rm_eo;
|
|
|
|
char *cp = new, *cp2, *cp3;
|
|
|
|
for (; *sub && cp < newend ; sub++)
|
|
if (*sub == DITTO)
|
|
for (cp2 = from; cp2 < to;) {
|
|
*cp++ = *cp2++;
|
|
if (cp >= newend)
|
|
break;
|
|
}
|
|
else if (*sub == ESCHAR && '1' <= *++sub && *sub <= '9'
|
|
&& rm[*sub - '0'].rm_so >= 0
|
|
&& rm[*sub - '0'].rm_eo >= 0) {
|
|
cp2 = boln + rm[*sub - '0'].rm_so;
|
|
cp3 = boln + rm[*sub - '0'].rm_eo;
|
|
while (cp2 < cp3) {
|
|
*cp++ = *cp2++;
|
|
if (cp >= newend)
|
|
break;
|
|
}
|
|
} else *cp++ = *sub;
|
|
if (cp >= newend)
|
|
return NULL;
|
|
*cp = '\0';
|
|
return cp;
|
|
}
|
|
|
|
/* doprnt: print a range of lines to stdout */
|
|
doprnt(from, to, gflag)
|
|
long from;
|
|
long to;
|
|
int gflag;
|
|
{
|
|
line_t *bp;
|
|
line_t *ep;
|
|
char *s;
|
|
|
|
ep = getptr(nextln(to, lastln));
|
|
for (bp = getptr(from); bp != ep; bp = bp->next) {
|
|
if ((s = gettxt(bp)) == (char *) ERR)
|
|
return ERR;
|
|
prntln(s, curln = from++, gflag);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* prntln: print text to stdout */
|
|
void
|
|
prntln(s, n, gflag)
|
|
char *s;
|
|
long n;
|
|
int gflag;
|
|
{
|
|
int col = 0;
|
|
|
|
if (gflag & GNP) {
|
|
printf("%ld\t", n);
|
|
col = 8;
|
|
}
|
|
for (; *s; s++) {
|
|
if ((gflag & GLS) && ++col > cols) {
|
|
fputs("\\\n", stdout);
|
|
col = 1;
|
|
}
|
|
if (gflag & GLS) {
|
|
switch (*s) {
|
|
case '\b':
|
|
fputs("\\b", stdout);
|
|
break;
|
|
case '\f':
|
|
fputs("\\f", stdout);
|
|
break;
|
|
case '\n':
|
|
fputs("\\n", stdout);
|
|
break;
|
|
case '\r':
|
|
fputs("\\r", stdout);
|
|
break;
|
|
case '\t':
|
|
fputs("\\t", stdout);
|
|
break;
|
|
case '\v':
|
|
fputs("\\v", stdout);
|
|
break;
|
|
default:
|
|
if (*s < 32 || 126 < *s) {
|
|
putchar('\\');
|
|
putchar((*s >> 6) + '0');
|
|
putchar((*s >> 3) + '0');
|
|
putchar((*s & 07) + '0');
|
|
col += 2;
|
|
} else if (*s == '\\')
|
|
fputs("\\\\", stdout);
|
|
else {
|
|
putchar(*s);
|
|
col--;
|
|
}
|
|
}
|
|
col++;
|
|
} else
|
|
putchar(*s);
|
|
}
|
|
if (gflag & GLS)
|
|
putchar('$');
|
|
putchar('\n');
|
|
}
|
|
|
|
|
|
int nullchar; /* null chars read */
|
|
int newline_added; /* long record split across lines */
|
|
|
|
/* doread: read a text file into the editor buffer; return line count */
|
|
long
|
|
doread(n, fn)
|
|
long n;
|
|
char *fn;
|
|
{
|
|
FILE *fp;
|
|
char buf[MAXLINE];
|
|
unsigned long size = 0;
|
|
undo_t *up = NULL;
|
|
int len;
|
|
line_t *lp = getptr(n);
|
|
|
|
nullchar = newline_added = 0;
|
|
if ((fp = (*fn == '!') ? popen(fn + 1, "r") : desopen(esctos(fn), "r")) == NULL) {
|
|
sprintf(errmsg, "cannot open file");
|
|
return ERR;
|
|
}
|
|
for (curln = n; (len = sgetline(buf, MAXLINE, fp)) > 0; size += len) {
|
|
spl1();
|
|
if (puttxt(buf) == (char *) ERR) {
|
|
spl0();
|
|
return ERR;
|
|
}
|
|
lp = lp->next;
|
|
if (up) up->t = lp;
|
|
else up = getuptr(UADD, curln, curln);
|
|
spl0();
|
|
}
|
|
(*fn == '!') ? pclose(fp) : fclose(fp);
|
|
if (nullchar)
|
|
fputs("null(s) discarded\n", stderr);
|
|
if (newline_added)
|
|
fputs("newline(s) added\n", stderr);
|
|
if (des) size += 8 - size % 8;
|
|
fprintf(stderr, verbose ? "%lu\n" : "", size);
|
|
return curln - n;
|
|
}
|
|
|
|
|
|
/* dowrite: write the text of a range of lines to a file; return line count */
|
|
long
|
|
dowrite(n, m, fn, mode)
|
|
long n;
|
|
long m;
|
|
char *fn;
|
|
char *mode;
|
|
{
|
|
FILE *fp;
|
|
unsigned long size = 0;
|
|
char *s = NULL;
|
|
line_t *lp;
|
|
|
|
if ((fp = ((*fn == '!') ? popen(fn + 1, "w") : desopen(esctos(fn), mode))) == NULL) {
|
|
sprintf(errmsg, "cannot open file");
|
|
return ERR;
|
|
}
|
|
if (n && !des)
|
|
for (lp = getptr(n); n <= m; n++, lp = lp->next) {
|
|
if ((s = gettxt(lp)) == (char *) ERR)
|
|
return ERR;
|
|
else if (fputs(strcat(s, "\n"), fp) < 0) {
|
|
sprintf(errmsg, "cannot write file");
|
|
return ERR;
|
|
}
|
|
size += (lp->len & ~ACTV) + 1; /* +1 '\n' */
|
|
}
|
|
else if (n)
|
|
for (lp = getptr(n); n <= m; n++, lp = lp->next) {
|
|
if ((s = gettxt(lp)) == (char *) ERR)
|
|
return ERR;
|
|
while (*s)
|
|
if (desputc(*s++, fp) < 0) {
|
|
sprintf(errmsg, "cannot write file");
|
|
return ERR;
|
|
}
|
|
if (desputc('\n', fp) < 0) {
|
|
sprintf(errmsg, "cannot write file");
|
|
return ERR;
|
|
}
|
|
size += (lp->len & ~ACTV) + 1; /* +1 '\n' */
|
|
}
|
|
if (des) {
|
|
desputc(EOF, fp); /* flush buffer */
|
|
size += 8 - size % 8; /* adjust DES size */
|
|
}
|
|
(*fn == '!') ? pclose(fp) : fclose(fp);
|
|
fprintf(stderr, verbose ? "%lu\n" : "", size);
|
|
return n ? m - n + 1 : 0;
|
|
}
|
|
|
|
|
|
#define USIZE 100 /* undo stack size */
|
|
undo_t *ustack = NULL; /* undo stack */
|
|
long usize = 0; /* stack size variable */
|
|
long u_p = 0; /* undo stack pointer */
|
|
long ucurln;
|
|
|
|
|
|
/* getuptr: return pointer to intialized undo node */
|
|
undo_t *
|
|
getuptr(type, from, to)
|
|
int type;
|
|
long from;
|
|
long to;
|
|
{
|
|
undo_t *t;
|
|
|
|
#if defined(sun) || defined(NO_REALLOC_NULL)
|
|
if (ustack == NULL
|
|
&& (ustack = (undo_t *) malloc((usize = USIZE) * sizeof(undo_t))) == NULL)
|
|
return NULL;
|
|
#endif
|
|
t = ustack;
|
|
if (u_p < usize
|
|
|| (t = (undo_t *) realloc(ustack, (usize += USIZE) * sizeof(undo_t))) != NULL) {
|
|
ustack = t;
|
|
ustack[u_p].type = type;
|
|
ustack[u_p].t = getptr(to);
|
|
ustack[u_p].h = getptr(from);
|
|
return ustack + u_p++;
|
|
}
|
|
/* out of memory - release undo stack */
|
|
ureset();
|
|
free(ustack);
|
|
ustack = NULL;
|
|
usize = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/* undo: undo last change to the editor buffer */
|
|
undo()
|
|
{
|
|
long n = usw ? 0 : u_p - 1;
|
|
int i = usw ? 1 : -1;
|
|
long j = u_p;
|
|
long ocurln = curln;
|
|
|
|
if (u_p == 0) {
|
|
sprintf(errmsg, "file unmodified");
|
|
return ERR;
|
|
}
|
|
spl1();
|
|
for (; j--; n += i)
|
|
switch(ustack[n].type ^ usw) {
|
|
case UADD:
|
|
getptr(0); /* this getptr last! */
|
|
requeue(ustack[n].h->prev, ustack[n].t->next);
|
|
lastln -= getrange(ustack[n].h, ustack[n].t);
|
|
break;
|
|
case UDEL:
|
|
getptr(0); /* this getptr last! */
|
|
requeue(ustack[n].h->prev, ustack[n].h);
|
|
requeue(ustack[n].t, ustack[n].t->next);
|
|
lastln += getrange(ustack[n].h, ustack[n].t);
|
|
break;
|
|
default:
|
|
/*NOTREACHED*/
|
|
;
|
|
}
|
|
curln = ucurln;
|
|
ucurln = ocurln;
|
|
usw = 1 - usw;
|
|
spl0();
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ureset: clear the undo stack */
|
|
void
|
|
ureset()
|
|
{
|
|
line_t *lp, *ep, *tl;
|
|
|
|
while (u_p--)
|
|
if ((ustack[u_p].type ^ usw) == UDEL) {
|
|
ep = ustack[u_p].t->next;
|
|
for (lp = ustack[u_p].h; lp != ep; lp = tl) {
|
|
tl = lp->next;
|
|
free(lp);
|
|
}
|
|
}
|
|
u_p = usw = 0;
|
|
ucurln = curln;
|
|
}
|
|
|
|
|
|
|
|
/* sgetline: read a line of text up a maximum size from a file; return
|
|
line length */
|
|
sgetline(buf, size, fp)
|
|
char *buf;
|
|
int size;
|
|
FILE *fp;
|
|
{
|
|
int c = EOF;
|
|
char *cp = buf;
|
|
|
|
/* pull des out of loop */
|
|
|
|
if (!des)
|
|
while (cp - buf < size - 1 && (c = getc(fp)) != EOF && c != '\n')
|
|
c && (*cp++ = c) || (nullchar = 1);
|
|
else
|
|
while (cp - buf < size - 1 && (c = desgetc(fp)) != EOF && c != '\n')
|
|
c && (*cp++ = c) || (nullchar = 1);
|
|
if (c == '\n')
|
|
*cp++ = c;
|
|
else if (c == EOF) {
|
|
if (cp - buf) {
|
|
*cp++ = '\n';
|
|
newline_added = 1;
|
|
}
|
|
} else {
|
|
ungetc(c, fp);
|
|
*(cp - 1) = '\n';
|
|
newline_added = 1;
|
|
}
|
|
*cp = '\0';
|
|
return cp - buf;
|
|
}
|
|
|
|
|
|
int sg_ct = 0; /* count for desgetc */
|
|
int sp_ct = 0; /* count for desputc */
|
|
|
|
/* desopen: open a file and initialize DES; return file pointer */
|
|
FILE *
|
|
desopen(fn, mode)
|
|
char *fn;
|
|
char *mode;
|
|
{
|
|
FILE *fp;
|
|
|
|
#ifdef DES
|
|
sg_ct = 0;
|
|
sp_ct = 0;
|
|
cbcinit();
|
|
#endif
|
|
if ((fp = fopen(fn, mode)) != NULL)
|
|
setvbuf(fp, NULL, _IOFBF, BUFSIZ);
|
|
return fp;
|
|
}
|
|
|
|
|
|
/* desgetc: return next char in a file */
|
|
desgetc(fp)
|
|
FILE *fp;
|
|
{
|
|
#ifdef DES
|
|
static char buf[8];
|
|
static int n = 0;
|
|
|
|
if (n >= sg_ct) {
|
|
n = 0;
|
|
sg_ct = cbcdec(buf, fp);
|
|
}
|
|
return (sg_ct > 0) ? buf[n++] : EOF;
|
|
#endif
|
|
}
|
|
|
|
/* desputc: write a char to a file */
|
|
desputc(c, fp)
|
|
char c;
|
|
FILE *fp;
|
|
{
|
|
#ifdef DES
|
|
static char buf[8];
|
|
static int n = 0;
|
|
|
|
if (n == sizeof buf) {
|
|
sp_ct = cbcenc(buf, n, fp);
|
|
n = 0;
|
|
}
|
|
if (c == EOF) {
|
|
sp_ct = cbcenc(buf, n, fp);
|
|
n = 0;
|
|
}
|
|
return (sp_ct >= 0 && c != EOF) ? (buf[n++] = c) : EOF;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* getline: read a line of text up a maximum size from stdin; return
|
|
line length */
|
|
getline(buf, n)
|
|
char *buf;
|
|
int n;
|
|
{
|
|
static char oc = '\0'; /* overflow character */
|
|
|
|
register int i = 0;
|
|
register int oi = 0;
|
|
char c;
|
|
|
|
/* Read one character at a time to avoid i/o contention with shell
|
|
escapes invoked by nonterminal input, e.g.,
|
|
ed - <<EOF
|
|
!cat
|
|
hello, world
|
|
EOF */
|
|
if (n < 2)
|
|
return 0;
|
|
if (oc != '\0') {
|
|
buf[i++] = oc;
|
|
oc = '\0';
|
|
}
|
|
for (;;)
|
|
switch (read(0, &c, 1)) {
|
|
default:
|
|
oi = 0;
|
|
if (i < n - 1) {
|
|
if ((buf[i++] = c) != '\n')
|
|
continue;
|
|
lineno++; /* script line no. */
|
|
buf[i] = '\0';
|
|
return i;
|
|
}
|
|
oc = c;
|
|
buf[i] = '\0';
|
|
return i;
|
|
case 0:
|
|
if (i != oi) {
|
|
oi = i;
|
|
continue;
|
|
}
|
|
/* fall through */
|
|
case -1:
|
|
if (i) buf[i] = '\0';
|
|
return i;
|
|
}
|
|
}
|
|
|
|
|
|
char *gbuf = NULL;
|
|
|
|
#define strip_trailing_esc \
|
|
*(gbuf + l - 2) = '\n', *(gbuf + l - 1) = '\0', l--
|
|
|
|
|
|
/* getcmdv: get a command-list from the command buffer */
|
|
char *
|
|
getcmdv()
|
|
{
|
|
int l, n, m = (sizeof ibuf);
|
|
char *t;
|
|
|
|
if ((gbuf = (char *) malloc(m)) == NULL) {
|
|
sprintf(errmsg, "out of memory");
|
|
return NULL;
|
|
}
|
|
strcpy(gbuf, ibufp);
|
|
if ((l = strlen(gbuf)) < 2 || !oddesc(gbuf, gbuf + l - 1))
|
|
return gbuf;
|
|
strip_trailing_esc;
|
|
for (;;) {
|
|
t = gbuf;
|
|
if ((n = getline(ibuf, m)) == 0 || ibuf[n - 1] != '\n'
|
|
|| (l += n) >= m
|
|
&& (t = realloc(gbuf, m += sizeof ibuf)) == NULL) {
|
|
sprintf(errmsg, "out of memory");
|
|
freecmdv();
|
|
return NULL;
|
|
}
|
|
gbuf = t;
|
|
strcat(gbuf, ibuf);
|
|
if (n < 2 || !oddesc(gbuf, gbuf + l - 1))
|
|
break;
|
|
strip_trailing_esc;
|
|
}
|
|
return gbuf;
|
|
}
|
|
|
|
|
|
/* lpdup: duplicate a line node */
|
|
line_t *
|
|
lpdup(lp)
|
|
line_t *lp;
|
|
{
|
|
line_t *np;
|
|
|
|
if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) {
|
|
sprintf(errmsg, "out of memory");
|
|
return NULL;
|
|
}
|
|
np->seek = lp->seek;
|
|
np->len = (lp->len & ~ACTV); /* zero ACTV bit */
|
|
return np;
|
|
}
|
|
|
|
|
|
/* oddesc: return the parity of escapes preceding a character in a
|
|
string */
|
|
oddesc(s, t)
|
|
char *s;
|
|
char *t;
|
|
{
|
|
return (s == t || *(t - 1) != '\\') ? 0 : !oddesc(s, t - 1);
|
|
}
|
|
|
|
|
|
/* freecmdv: free memory used by a command list */
|
|
void
|
|
freecmdv()
|
|
{
|
|
free(gbuf);
|
|
gbuf = NULL;
|
|
}
|
|
|
|
|
|
/* getrange: return the number of nodes in a list */
|
|
long
|
|
getrange(h, t)
|
|
line_t *h;
|
|
line_t *t;
|
|
{
|
|
long n = 1;
|
|
|
|
for (; h != t; h = h->next)
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
|
|
void
|
|
onhup(signo)
|
|
int signo;
|
|
{
|
|
if (mutex)
|
|
sigflags |= (1 << signo);
|
|
else dohup(signo);
|
|
}
|
|
|
|
|
|
void
|
|
onintr(signo)
|
|
int signo;
|
|
{
|
|
if (mutex)
|
|
sigflags |= (1 << signo);
|
|
else dointr(signo);
|
|
}
|
|
|
|
|
|
char hup[MAXFNAME] = "ed.hup"; /* hup file name */
|
|
|
|
void
|
|
dohup(signo)
|
|
int signo;
|
|
{
|
|
char *s;
|
|
int n;
|
|
|
|
sigflags &= ~(1 << signo);
|
|
if (lastln && dowrite(1, lastln, hup, "w") < 0
|
|
&& (s = getenv("HOME")) != NULL
|
|
&& (n = strlen(s)) + 8 < sizeof hup) { /* "ed.hup" + '/' */
|
|
strcpy(hup, s);
|
|
if (hup[n - 1] != '/')
|
|
hup[n] = '/', hup[n+1] = '\0';
|
|
strcat(hup, "ed.hup");
|
|
dowrite(1, lastln, hup, "w");
|
|
}
|
|
quit(2);
|
|
}
|
|
|
|
|
|
void
|
|
dointr(signo)
|
|
int signo;
|
|
{
|
|
sigflags &= ~(1 << signo);
|
|
fputs("\n?\n", stderr);
|
|
sprintf(errmsg, "interrupt");
|
|
longjmp(env, -1);
|
|
}
|
|
|
|
|
|
void
|
|
dowinch(signo)
|
|
int signo;
|
|
{
|
|
sigflags &= ~(1 << signo);
|
|
if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) {
|
|
if (ws.ws_row > 2) rows = ws.ws_row - 2;
|
|
if (ws.ws_col > 8) cols = ws.ws_col - 8;
|
|
}
|
|
}
|
|
|
|
|
|
/* esctos: return pointer to copy of string stripped of escape characters
|
|
- ignore trailing escape */
|
|
char *
|
|
esctos(s)
|
|
char *s;
|
|
{
|
|
static char file[MAXFNAME];
|
|
char *t = file;
|
|
|
|
while (*t++ = (*s == '\\') ? *++s : *s)
|
|
s++;
|
|
return file;
|
|
}
|
|
|
|
|