1375 lines
25 KiB
C
1375 lines
25 KiB
C
/*
|
|
** edit.c
|
|
**
|
|
** editor main routine, plus other routines used a lot.
|
|
**
|
|
** This file is part of mse, under GPLv3.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <sys/utsname.h> /* stuff to find out who we are */
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef LOG_USAGE
|
|
#include <syslog.h>
|
|
#endif
|
|
|
|
#include "se.h"
|
|
#include "docmd1.h"
|
|
#include "docmd2.h"
|
|
#include "extern.h"
|
|
#include "edit.h"
|
|
#include "main.h"
|
|
#include "misc.h"
|
|
#include "pat.h"
|
|
#include "scratch.h"
|
|
#include "screen.h"
|
|
#include "term.h"
|
|
|
|
static char Savknm = DEFAULTNAME; /* saved mark name for < and > */
|
|
|
|
/* edit --- main routine for screen editor */
|
|
|
|
void edit (int argc, char *argv[])
|
|
{
|
|
int cursav, status, len, cursor;
|
|
char lin[MAXLINE], term;
|
|
|
|
watch (); /* display time of day */
|
|
|
|
#ifdef LOG_USAGE
|
|
log_usage (); /* log who used the program */
|
|
#endif
|
|
|
|
serc (); /* execute commands in ./.serc or $HOME/.serc */
|
|
|
|
status = OK;
|
|
|
|
while (status == OK && Argno < argc)
|
|
{
|
|
memset (lin, EOS, MAXLINE);
|
|
strncpy (lin, argv[Argno], MAXLINE-1);
|
|
loadstr (lin, Argno, POOPCOL, Ncols);
|
|
if (lin[0] == '-')
|
|
{
|
|
len = strlen (lin) + 1;
|
|
lin[len - 1] = '\n';
|
|
lin[len] = EOS;
|
|
len = 0;
|
|
status = doopt (lin, &len);
|
|
}
|
|
else
|
|
{
|
|
dfltsopt (lin);
|
|
status = doread (Lastln, lin, NO);
|
|
}
|
|
Argno++;
|
|
}
|
|
|
|
if (status == ERR)
|
|
{
|
|
if (Errcode == EHANGUP)
|
|
hangup ();
|
|
print_verbose_err_msg ();
|
|
}
|
|
else
|
|
Curln = min (1, Lastln);
|
|
|
|
Buffer_changed = NO;
|
|
First_affected = 1; /* maintained by updscreen & commands */
|
|
updscreen ();
|
|
|
|
if (status != ERR) /* leave offending file name or option */
|
|
{
|
|
lin[0] = EOS;
|
|
}
|
|
cursor = 0;
|
|
|
|
/* main command loop */
|
|
do {
|
|
intrpt (); /* discard pending breaks (interrupts) */
|
|
if (Lost_lines > GARB_THRESHOLD
|
|
&& (Lastln + Limcnt) / Lost_lines <= GARB_FACTOR)
|
|
garbage_collect ();
|
|
|
|
Cmdrow = Botrow + 1; /* reset the command line location */
|
|
prompt ("cmd>");
|
|
getcmd (lin, 0, &cursor, &term);
|
|
remark (""); /* clear out any error messages */
|
|
|
|
while (term == CURSOR_UP || term == CURSOR_DOWN
|
|
|| term == CURSOR_SAME)
|
|
{
|
|
switch (term) {
|
|
case CURSOR_UP:
|
|
if (Curln > 1)
|
|
Curln--;
|
|
else
|
|
Curln = Lastln;
|
|
break;
|
|
|
|
case CURSOR_DOWN:
|
|
if (Curln < Lastln)
|
|
Curln++;
|
|
else
|
|
Curln = min (1, Lastln);
|
|
break;
|
|
}
|
|
adjust_window (Curln, Curln);
|
|
updscreen ();
|
|
getcmd (lin, 0, &cursor, &term);
|
|
}
|
|
|
|
prompt (""); /* remove prompt */
|
|
|
|
cursav = Curln; /* remember it in case of an error */
|
|
Errcode = EEGARB; /* default error code for garbage at end */
|
|
|
|
len = 0;
|
|
if (getlst (lin, &len, &status) == OK)
|
|
{
|
|
if (ckglob (lin, &len, &status) == OK)
|
|
doglob (lin, &len, &cursav, &status);
|
|
else if (status != ERR)
|
|
docmd (lin, len, NO, &status);
|
|
}
|
|
if (status == ERR)
|
|
{
|
|
if (Errcode == EHANGUP)
|
|
hangup ();
|
|
print_verbose_err_msg ();
|
|
Curln = min (cursav, Lastln);
|
|
}
|
|
else if (term != FUNNY)
|
|
{
|
|
cursor = 0;
|
|
lin[0] = EOS;
|
|
}
|
|
|
|
adjust_window (Curln, Curln);
|
|
updscreen ();
|
|
|
|
} while (status != EOF);
|
|
|
|
clrscreen ();
|
|
clrbuf ();
|
|
tflush ();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* getlst --- collect line numbers (if any) at lin[*i], increment i */
|
|
|
|
int getlst (char lin[], int *i, int *status)
|
|
{
|
|
int num;
|
|
|
|
Line2 = 0;
|
|
for (Nlines = 0; getone (lin, i, &num, status) == OK; )
|
|
{
|
|
Line1 = Line2;
|
|
Line2 = num;
|
|
Nlines++;
|
|
if (lin[*i] != ',' && lin[*i] != ';')
|
|
break;
|
|
if (lin[*i] == ';')
|
|
Curln = num;
|
|
(*i)++;
|
|
}
|
|
|
|
if (Nlines > 2)
|
|
Nlines = 2;
|
|
|
|
if (Nlines <= 1)
|
|
Line1 = Line2;
|
|
|
|
if (Line1 > Line2)
|
|
{
|
|
*status = ERR;
|
|
Errcode = EBACKWARD;
|
|
}
|
|
|
|
if (*status != ERR)
|
|
*status = OK;
|
|
|
|
return (*status);
|
|
}
|
|
|
|
|
|
/* getnum --- convert one term to line number */
|
|
|
|
int getnum (char lin[], int *i, int *pnum, int *status)
|
|
{
|
|
int j, ret;
|
|
int k;
|
|
|
|
ret = OK;
|
|
SKIPBL (lin, *i);
|
|
if (lin[*i] >= Rel_a && lin[*i] <= Rel_z && Absnos == NO)
|
|
*pnum = Topln - Toprow + lin[*i] - Rel_a;
|
|
else if (lin[*i] == CURLINE)
|
|
*pnum = Curln;
|
|
else if (lin[*i] == PREVLN || lin[*i] == PREVLN2)
|
|
*pnum = Curln - 1;
|
|
else if (lin[*i] == LASTLINE)
|
|
*pnum = Lastln;
|
|
else if (lin[*i] == SCAN || lin[*i] == BACKSCAN)
|
|
{
|
|
int missing_delim = YES;
|
|
|
|
/* see if trailing delim supplied, since command can follow pattern */
|
|
for (k = *i + 1; lin[k] != EOS; k++)
|
|
if (lin[k] == ESCAPE)
|
|
k++; /* skip esc, loop will skip escaped char */
|
|
else if (lin[k] == lin[*i])
|
|
{
|
|
missing_delim = NO;
|
|
break;
|
|
}
|
|
/* else
|
|
continue */
|
|
|
|
if (missing_delim == YES)
|
|
{
|
|
for (; lin[k] != EOS; k++)
|
|
;
|
|
k--; /* k now at newline */
|
|
|
|
/* supply trailing delim */
|
|
lin[k] = lin[*i];
|
|
lin[++k] = '\n';
|
|
lin[++k] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
|
|
if (optpat (lin, i) == ERR)
|
|
ret = ERR;
|
|
else if (lin[*i] == SCAN)
|
|
ret = ptscan (FORWARD, pnum);
|
|
else
|
|
ret = ptscan (BACKWARD, pnum);
|
|
}
|
|
else if (lin[*i] == SEARCH || lin[*i] == BACKSEARCH)
|
|
{
|
|
j = *i;
|
|
(*i)++;
|
|
if (getkn (lin, i, &Savknm, Savknm) == ERR)
|
|
ret = ERR;
|
|
else if (lin[j] == SEARCH)
|
|
ret = knscan (FORWARD, pnum);
|
|
else
|
|
ret = knscan (BACKWARD, pnum);
|
|
(*i)--;
|
|
}
|
|
else if (isdigit (lin[*i]))
|
|
{
|
|
*pnum = ctoi (lin, i);
|
|
(*i)--;
|
|
}
|
|
else if (lin[*i] == TOPLINE)
|
|
*pnum = Topln;
|
|
else
|
|
ret = EOF;
|
|
|
|
if (ret == OK)
|
|
(*i)++;
|
|
*status = ret;
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/* getone --- evaluate one line number expression */
|
|
|
|
int getone (char lin[], int *i, int *num, int *status)
|
|
{
|
|
int pnum, ret;
|
|
char porm; /* "plus or minus" (sic) */
|
|
|
|
ret = EOF; /* assume we won't find anything for now */
|
|
*num = 0;
|
|
|
|
if (getnum (lin, i, num, status) == OK) /* first term */
|
|
{
|
|
ret = OK; /* to indicate we've seen something */
|
|
do { /* + or - terms */
|
|
porm = EOS;
|
|
SKIPBL (lin, *i);
|
|
if (lin[*i] == '-' || lin[*i] == '+')
|
|
{
|
|
porm = lin[*i];
|
|
(*i)++;
|
|
}
|
|
if (getnum (lin, i, &pnum, status) == OK)
|
|
if (porm == '-')
|
|
*num -= pnum;
|
|
else
|
|
*num += pnum;
|
|
if (*status == EOF && porm != EOS) /* trailing + or - */
|
|
*status = ERR;
|
|
} while (*status == OK);
|
|
}
|
|
|
|
if (*num < 0 || *num > Lastln) /* make sure number is in range */
|
|
{
|
|
*status = ERR;
|
|
Errcode = EORANGE;
|
|
}
|
|
|
|
if (*status == ERR)
|
|
ret = ERR;
|
|
else
|
|
*status = ret;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static int special_casing = NO;
|
|
|
|
/* ckglob --- if global prefix, mark lines to be affected */
|
|
|
|
int ckglob (char lin[], int *i, int *status)
|
|
{
|
|
int line, tmp;
|
|
int usepat, usemark;
|
|
LINEDESC *k;
|
|
|
|
*status = OK;
|
|
usepat = EOF;
|
|
usemark = EOF;
|
|
|
|
if ( /* g/^/m0 or g/$/m0 -- special case the pathological */
|
|
/* cases in order to save time */
|
|
(lin[*i] == GLOBAL || lin[*i] == UCGLOBAL)
|
|
&& (lin[*i + 1] == lin[*i + 3])
|
|
&& (lin[*i + 2] == '^' || lin[*i + 2] == '$')
|
|
&& (lin[*i + 4] == MOVECOM || lin[*i + 4] == UCMOVECOM)
|
|
&& (lin[*i + 5] == '0' && lin[*i + 6] == '\n') )
|
|
{
|
|
special_casing = YES;
|
|
remark ("GLOB");
|
|
return (OK);
|
|
}
|
|
|
|
if (lin[*i] == GMARK || lin[*i] == XMARK) /* global markname prefix? */
|
|
{
|
|
if (lin[*i] == GMARK) /* tag lines with the specified markname */
|
|
usemark = YES;
|
|
else /* tag lines without the specified markname */
|
|
usemark = NO;
|
|
(*i)++;
|
|
*status = getkn (lin, i, &Savknm, Savknm);
|
|
}
|
|
|
|
if (*status == OK) /* check for a pattern prefix too */
|
|
{
|
|
if (lin[*i] == GLOBAL || lin[*i] == UCGLOBAL)
|
|
usepat = YES;
|
|
|
|
if (lin[*i] == EXCLUDE || lin[*i] == UCEXCLUDE)
|
|
usepat = NO;
|
|
|
|
if (usepat != EOF)
|
|
{
|
|
(*i)++;
|
|
if (optpat (lin, i) == ERR)
|
|
*status = ERR;
|
|
else
|
|
(*i)++;
|
|
}
|
|
}
|
|
|
|
if (*status == OK && usepat == EOF && usemark == EOF)
|
|
*status = EOF;
|
|
else if (*status == OK)
|
|
defalt (1, Lastln);
|
|
|
|
if (*status == OK) /* no errors so far, safe to proceed */
|
|
{
|
|
remark ("GLOB");
|
|
|
|
k = Line0; /* unmark all lines preceeding range */
|
|
for (line = 0; line < Line1; line++)
|
|
{
|
|
k -> Globmark = NO;
|
|
k = NEXTLINE(k);
|
|
}
|
|
|
|
for (; line <= Line2; line++) /* mark lines in range */
|
|
{
|
|
if (intrpt ())
|
|
{
|
|
*status = ERR;
|
|
return (*status);
|
|
}
|
|
tmp = NO;
|
|
if (((usemark == EOF
|
|
|| usemark == YES) && k -> Markname == Savknm)
|
|
|| (usemark == NO && k -> Markname != Savknm))
|
|
{
|
|
if (usepat == EOF) /* no global pattern to look for */
|
|
tmp = YES;
|
|
else /* there is also a pattern to look for */
|
|
{
|
|
gtxt (k);
|
|
if (match (Txt, Pat) == usepat)
|
|
tmp = YES;
|
|
}
|
|
}
|
|
|
|
k -> Globmark = tmp;
|
|
|
|
k = NEXTLINE(k);
|
|
}
|
|
|
|
/* mark remaining lines */
|
|
for (; line <= Lastln; line++)
|
|
{
|
|
k -> Globmark = NO;
|
|
k = NEXTLINE (k);
|
|
}
|
|
|
|
remark ("");
|
|
}
|
|
|
|
return (*status);
|
|
}
|
|
|
|
|
|
/* doglob --- do command at lin[i] on all marked lines */
|
|
|
|
int doglob (char lin[], int *i, int *cursav, int *status)
|
|
{
|
|
int istart, line;
|
|
LINEDESC *k;
|
|
|
|
if (special_casing)
|
|
{
|
|
/*
|
|
remark ("Warp 7, Captain!");
|
|
*/
|
|
/* not on the screen too long anyway */
|
|
reverse (1, Lastln);
|
|
Curln = Lastln;
|
|
special_casing = NO;
|
|
Buffer_changed = YES;
|
|
First_affected = min (1, First_affected);
|
|
remark ("");
|
|
adjust_window (Curln, Curln);
|
|
updscreen ();
|
|
return (OK);
|
|
}
|
|
|
|
*status = OK;
|
|
istart = *i;
|
|
k = Line0;
|
|
line = 0;
|
|
|
|
do {
|
|
line++;
|
|
k = NEXTLINE(k);
|
|
if (k -> Globmark == YES) /* line is marked */
|
|
{
|
|
k -> Globmark = NO; /* unmark the line */
|
|
Curln = line;
|
|
*cursav = Curln; /* remember where we are */
|
|
*i = istart;
|
|
if (getlst (lin, i, status) == OK)
|
|
docmd (lin, *i, YES, status);
|
|
line = 0; /* lines may have been moved */
|
|
k = Line0;
|
|
}
|
|
if (intrpt ())
|
|
*status = ERR;
|
|
} while (line <= Lastln && *status == OK);
|
|
|
|
return (*status);
|
|
}
|
|
|
|
|
|
/* ckchar --- look for ch or altch on lin at i, set flag if found */
|
|
|
|
int ckchar (char ch, char altch, char lin[], int *i, int *flag, int *status)
|
|
{
|
|
|
|
if (lin[*i] == ch || lin[*i] == altch)
|
|
{
|
|
(*i)++;
|
|
*flag = YES;
|
|
}
|
|
else
|
|
*flag = NO;
|
|
|
|
*status = OK;
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* ckp --- check for "p" after command */
|
|
|
|
int ckp (char lin[], int i, int *pflag, int *status)
|
|
{
|
|
|
|
if (lin[i] == PRINT || lin[i] == UCPRINT)
|
|
{
|
|
i++;
|
|
*pflag = YES;
|
|
}
|
|
else
|
|
*pflag = NO;
|
|
|
|
if (lin[i] == '\n')
|
|
*status = OK;
|
|
else
|
|
*status = ERR;
|
|
|
|
return (*status);
|
|
}
|
|
|
|
|
|
/* ckupd --- make sure it is ok to destroy the buffer */
|
|
|
|
int ckupd (char lin[], int *i, char cmd, int *status)
|
|
{
|
|
int flag;
|
|
|
|
*status = ckchar (ANYWAY, ANYWAY, lin, i, &flag, status);
|
|
if (flag == NO && Buffer_changed == YES && Probation != cmd)
|
|
{
|
|
*status = ERR;
|
|
Errcode = ESTUPID;
|
|
Probation = cmd; /* if same command is repeated, */
|
|
} /* we'll keep quiet */
|
|
|
|
return (*status);
|
|
}
|
|
|
|
|
|
/* defalt --- set defaulted line numbers */
|
|
|
|
void defalt (int def1, int def2)
|
|
{
|
|
|
|
if (Nlines == 0) /* no line numbers supplied, use defaults */
|
|
{
|
|
Line1 = def1;
|
|
Line2 = def2;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* getfn --- get file name from lin[i]... */
|
|
|
|
int getfn (char lin[], int i, char filename[], size_t filenamesize)
|
|
{
|
|
int j, k, ret;
|
|
|
|
ret = ERR;
|
|
if (lin[i + 1] == ' ')
|
|
{
|
|
j = i + 2; /* get new file name */
|
|
SKIPBL (lin, j);
|
|
for (k = 0; lin[j] != NEWLINE; k++, j++)
|
|
{
|
|
filename[k] = lin[j];
|
|
}
|
|
filename[k] = EOS;
|
|
if (k > 0)
|
|
{
|
|
ret = OK;
|
|
}
|
|
}
|
|
else if (lin[i + 1] == '\n' && Savfil[0] != EOS)
|
|
{
|
|
memset (filename, EOS, filenamesize-1);
|
|
strncpy (filename, Savfil, filenamesize); /* or old name */
|
|
ret = OK;
|
|
}
|
|
else
|
|
{
|
|
if (lin[i + 1] == '\n')
|
|
{
|
|
Errcode = ENOFN;
|
|
}
|
|
else
|
|
{
|
|
Errcode = EFILEN;
|
|
}
|
|
}
|
|
|
|
if (ret == OK && Savfil[1] == EOS)
|
|
{
|
|
memset (Savfil, EOS, MAXLINE);
|
|
strncpy (Savfil, filename, MAXLINE-1); /* save if no old one */
|
|
mesg (Savfil, FILE_MSG);
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/* getkn --- get mark name from lin[i], increment i */
|
|
|
|
int getkn (char lin[], int *i, char *kname, char dfltnm)
|
|
{
|
|
|
|
if (lin[*i] == '\n' || lin[*i] == EOS)
|
|
{
|
|
*kname = dfltnm;
|
|
return (EOF);
|
|
}
|
|
|
|
*kname = lin[*i];
|
|
(*i)++;
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* getrange --- get 'from' range for tlit command */
|
|
|
|
int getrange (char array[], int *k, char set[], int size, int *allbut)
|
|
{
|
|
int i, j;
|
|
|
|
Errcode = EBADLIST; /* preset error code */
|
|
|
|
i = *k + 1;
|
|
if (array[i] == NOTINCCL) /* check for negated character class */
|
|
{
|
|
*allbut = YES;
|
|
i++;
|
|
}
|
|
else
|
|
*allbut = NO;
|
|
|
|
j = 0;
|
|
filset (array[*k], array, &i, set, &j, size);
|
|
if (array[i] != array[*k])
|
|
{
|
|
set[0] = EOS;
|
|
return (ERR);
|
|
}
|
|
if (set[0] == EOS)
|
|
{
|
|
Errcode = ENOLIST;
|
|
return (ERR);
|
|
}
|
|
if (j > 0 && addset (EOS, set, &j, size) == NO)
|
|
{
|
|
set[0] = EOS;
|
|
return (ERR);
|
|
}
|
|
|
|
*k = i;
|
|
Errcode = EEGARB;
|
|
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* getrhs --- get substitution string for 's' command */
|
|
|
|
int getrhs (char lin[], int *i, char sub[], size_t subsize, int *gflag)
|
|
{
|
|
static char Subs[MAXPAT] = ""; /* saved replacement pattern */
|
|
int j;
|
|
/* saved replacement pattern char */
|
|
|
|
|
|
Errcode = EBADSUB;
|
|
|
|
if (lin[*i] == EOS) /* missing the middle delimeter */
|
|
return (ERR);
|
|
|
|
if (lin[*i + 1] == '%' && (lin[*i + 2] == lin[*i]
|
|
|| lin[*i + 2] == '\n'))
|
|
{
|
|
/*
|
|
* s//%/ --- should mean do the same thing as I did last time, even
|
|
* s//&/ --- if I deleted something. So we comment out these lines.
|
|
*
|
|
if (Subs[0] == EOS)
|
|
{
|
|
Errcode = ENOSUB;
|
|
return (ERR);
|
|
}
|
|
*/
|
|
memset (sub, EOS, subsize);
|
|
strncpy (sub, Subs, subsize-1);
|
|
*i += 2;
|
|
if (lin[*i] == '\n')
|
|
{
|
|
/* fix it up for pattern matching routines */
|
|
lin[*i] = lin[*i - 2];
|
|
lin[*i + 1] = '\n';
|
|
lin[*i + 2] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else /* not using saved substitution pattern */
|
|
{
|
|
if (lin[*i + 1] == '\n')
|
|
{
|
|
/* missing the trailing delimiter */
|
|
/* pattern was empty */
|
|
lin[*i + 1] = lin[*i]; /* supply missing delimiter */
|
|
lin[*i + 2] = '\n';
|
|
lin[*i + 3] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
/* return (ERR); this is the original action */
|
|
}
|
|
else
|
|
{
|
|
/* stuff in pattern, check end of line */
|
|
for (j = *i; lin[j] != EOS; j++)
|
|
;
|
|
j -= 2; /* j now points to char before '\n' */
|
|
|
|
if (lin[j] == 'p' || lin[j] == 'P')
|
|
{
|
|
--j;
|
|
if (lin[j] == GLOBAL || lin[j] == UCGLOBAL)
|
|
{
|
|
if (j >= *i + 1 && lin[j-1] == lin[*i]
|
|
&& (lin[j-2] != ESCAPE
|
|
|| lin[j-3] == ESCAPE))
|
|
; /* leave alone */
|
|
else
|
|
{
|
|
/* \<delim>gp\n is pattern */
|
|
/* supply trailing delim */
|
|
j += 2; /* j at \n */
|
|
lin[j] = lin[*i];
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else if (j >= *i + 1 && lin[j] == lin[*i] &&
|
|
(lin[j-1] != ESCAPE
|
|
|| lin[j-2] == ESCAPE))
|
|
; /* leave alone */
|
|
else
|
|
{
|
|
/* \<delim>p\n is pattern */
|
|
/* supply trailing delim */
|
|
j += 2;
|
|
lin[j] = lin[*i];
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else if (lin[j] == GLOBAL || lin[j] == UCGLOBAL)
|
|
{
|
|
--j;
|
|
if (j >= *i + 1 && lin[j] == lin[*i] &&
|
|
(lin[j-1] != ESCAPE
|
|
|| lin[j-2] == ESCAPE))
|
|
; /* leave alone */
|
|
else
|
|
{
|
|
/* \<delim>g\n is pattern */
|
|
/* supply trailing delim */
|
|
j += 2; /* j at \n */
|
|
lin[j] = lin[*i];
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else if ((lin[j] != lin[*i]) ||
|
|
(lin[j] == lin[*i] &&
|
|
lin[j-1] == ESCAPE && lin[j-2] != ESCAPE))
|
|
{
|
|
/* simply missing trailing delimeter */
|
|
/* supply it */
|
|
j++; /* j at \n */
|
|
lin[j] = lin[*i];
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
/* else
|
|
unescaped delim is there,
|
|
leave well enough alone */
|
|
}
|
|
|
|
if ((*i = maksub (lin, *i + 1, lin[*i], sub)) == ERR)
|
|
{
|
|
return (ERR);
|
|
}
|
|
|
|
memset (Subs, EOS, MAXPAT);
|
|
strncpy (Subs, sub, MAXPAT-1); /* save pattern for later */
|
|
}
|
|
|
|
if (lin[*i + 1] == GLOBAL || lin[*i + 1] == UCGLOBAL)
|
|
{
|
|
(*i)++;
|
|
*gflag = YES;
|
|
}
|
|
else
|
|
{
|
|
*gflag = NO;
|
|
}
|
|
|
|
Errcode = EEGARB; /* the default */
|
|
|
|
return (OK);
|
|
|
|
}
|
|
|
|
|
|
/* getstr --- get string from lin at i, copy to dst, bump i */
|
|
|
|
/*
|
|
** NOTE: this routine only called for doing the join command.
|
|
** therefore, don't do anything else with it.
|
|
*/
|
|
|
|
int getstr (char lin[], int *i, char dst[], int maxdst)
|
|
{
|
|
char delim;
|
|
int j, k, d;
|
|
|
|
j = *i;
|
|
Errcode = EBADSTR;
|
|
|
|
delim = lin[j];
|
|
|
|
if (delim == '\n')
|
|
{
|
|
lin[j] = '/';
|
|
lin[++j] = ' '; /* join with a single blank */
|
|
lin[++j] = '/';
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
j = *i;
|
|
delim = lin[j];
|
|
Peekc = SKIP_RIGHT;
|
|
/* now fall thru */
|
|
|
|
/* return (ERR); old way */
|
|
}
|
|
else if ((delim == 'p' || delim == 'P') && lin[j + 1] == '\n') /* jp */
|
|
{
|
|
lin[j] = '/';
|
|
lin[++j] = ' '; /* join with a single blank */
|
|
lin[++j] = '/';
|
|
lin[++j] = delim; /* 'p' or 'P' */
|
|
lin[++j] = '\n';
|
|
lin[++j] = EOS;
|
|
j = *i;
|
|
delim = lin[j];
|
|
Peekc = SKIP_RIGHT;
|
|
/* now fall thru */
|
|
}
|
|
|
|
if (lin[j + 1] == '\n') /* command was 'j/' */
|
|
{
|
|
dst[0] = EOS;
|
|
Errcode = ENOERR;
|
|
return (OK);
|
|
/* return (ERR); old way */
|
|
}
|
|
|
|
/*
|
|
* otherwise, stuff there in the string, try to allow for
|
|
* a missing final delimiter.
|
|
*/
|
|
|
|
for (k = j + 1; lin[k] != '\n'; k++)
|
|
; /* find end */
|
|
|
|
k--; /* now points to char before newline */
|
|
|
|
if (lin[k] == 'p' || lin[k] == 'P')
|
|
{
|
|
k--;
|
|
if (lin[k] == delim &&
|
|
(lin[k-1] != ESCAPE || lin[k-2] == ESCAPE))
|
|
; /* it's fine, leave it alone */
|
|
else
|
|
{
|
|
/* ESCAPE delim p NEWLINE is the join string */
|
|
/* supply trailing delimiter. */
|
|
k += 2;
|
|
lin[k] = delim;
|
|
lin[++k] = '\n';
|
|
lin[++k] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else if (lin[k] != delim || (lin[k-1] == ESCAPE && lin[k-2] != ESCAPE))
|
|
{
|
|
/* no delim and no p, or last char is escaped delim */
|
|
k++;
|
|
lin[k] = delim;
|
|
lin[++k] = '\n';
|
|
lin[++k] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
/* else
|
|
delim is there
|
|
leave well enough alone */
|
|
|
|
/* code to actually do the join */
|
|
|
|
for (k = j + 1; lin[k] != delim; k++) /* find end */
|
|
{
|
|
if (lin[k] == '\n' || lin[k] == EOS)
|
|
if (delim == ' ')
|
|
break;
|
|
else
|
|
return (ERR);
|
|
esc (lin, &k);
|
|
}
|
|
if (k - j > maxdst)
|
|
return (ERR);
|
|
|
|
for (d = 0, j++; j < k; d++, j++)
|
|
dst[d] = esc (lin, &j);
|
|
dst[d] = EOS;
|
|
|
|
*i = j;
|
|
Errcode = EEGARB; /* the default */
|
|
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* getwrd --- get next word from line at i; increment i */
|
|
|
|
int getwrd (char line[], int *i, char word[], int size)
|
|
{
|
|
int j;
|
|
|
|
SKIPBL (line, *i);
|
|
j = 0;
|
|
while (line[*i] != ' ' && line[*i] != '\n' && line[*i] != EOS)
|
|
{
|
|
if (j < size - 1)
|
|
{
|
|
word[j] = line[*i];
|
|
j++;
|
|
}
|
|
(*i)++;
|
|
}
|
|
word[j] = EOS;
|
|
|
|
return (j);
|
|
}
|
|
|
|
|
|
/* knscan --- scan for a line with a given mark name */
|
|
|
|
int knscan (int way, int *num)
|
|
{
|
|
LINEDESC *k;
|
|
|
|
*num = Curln;
|
|
k = getind (*num);
|
|
do {
|
|
bump (num, &k, way);
|
|
if (k -> Markname == Savknm)
|
|
return (OK);
|
|
} while (*num != Curln && ! intrpt ());
|
|
|
|
if (Errcode == EEGARB)
|
|
Errcode = EKNOTFND;
|
|
return (ERR);
|
|
|
|
}
|
|
|
|
|
|
/* makset --- make set from array[k] in set */
|
|
|
|
int makset (char array[], int *k, char set[], size_t size)
|
|
{
|
|
static char Tset[MAXPAT] = ""; /* saved translit dest range */
|
|
int i, j;
|
|
int l;
|
|
|
|
Errcode = EBADLIST;
|
|
|
|
/*
|
|
* try to allow missing delimiter for translit command.
|
|
*/
|
|
|
|
if (array[*k] == EOS)
|
|
return (ERR);
|
|
|
|
if (array[*k + 1] == '%' && (array[*k + 2] == array[*k]
|
|
|| array[*k + 2] == '\n'))
|
|
{
|
|
memset (set, EOS, size);
|
|
strncpy (set, Tset, size-1);
|
|
*k += 2;
|
|
if (array[*k] == '\n')
|
|
{
|
|
/* fix it up for rest of the routines */
|
|
array[*k] = array[*k - 2];
|
|
array[*k+ 1] = '\n';
|
|
array[*k+ 2] = EOS;
|
|
}
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
|
|
for (l = *k; array[l] != EOS; l++)
|
|
;
|
|
l -= 2; /* l now points to char before '\n' */
|
|
|
|
if (l == *k) /* "y/.../\n" */
|
|
{
|
|
array[*k + 1] = array[*k]; /* add delimiter */
|
|
array[*k + 2] = '\n';
|
|
array[*k + 3] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
else if (array[l] == 'p' || array[l] == 'P')
|
|
{
|
|
--l;
|
|
if (l >= *k + 1 && array[l] == array[*k] &&
|
|
(array[l-1] != ESCAPE || array[l-2] == ESCAPE))
|
|
; /* leave alone */
|
|
else
|
|
{
|
|
/* \<delim>p\n is set */
|
|
/* supply trailing delim */
|
|
l += 2;
|
|
array[l] = array[*k];
|
|
array[++l] = '\n';
|
|
array[++l] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
}
|
|
else if (array[l] != array[*k] /* no delim, and no p */
|
|
|| (array[l-1] == ESCAPE /* or last char is escaped delim */
|
|
&& array[l-2] != ESCAPE))
|
|
{
|
|
/* simply missing trailing delimeter */
|
|
/* supply it */
|
|
l++; /* l now at \n */
|
|
array[l] = array[*k];
|
|
array[++l] = '\n';
|
|
array[++l] = EOS;
|
|
Peekc = SKIP_RIGHT;
|
|
}
|
|
/* else
|
|
delim is there,
|
|
leave well enough alone */
|
|
|
|
j = 0;
|
|
i = *k + 1;
|
|
filset (array[*k], array, &i, set, &j, size);
|
|
|
|
if (array[i] != array[*k])
|
|
return (ERR);
|
|
|
|
if (addset (EOS, set, &j, size) == NO)
|
|
return (ERR);
|
|
|
|
memset (Tset, EOS, MAXPAT);
|
|
strncpy (Tset, set, MAXPAT-1); /* save for later */
|
|
*k = i;
|
|
|
|
}
|
|
|
|
Errcode = EEGARB;
|
|
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* optpat --- make pattern specified at lin[i] */
|
|
|
|
int optpat (char lin[], int *i)
|
|
{
|
|
if (lin[*i] == EOS)
|
|
*i = ERR;
|
|
else if (lin[*i + 1] == EOS)
|
|
*i = ERR;
|
|
else if (lin[*i + 1] == lin[*i]) /* repeated delimiter */
|
|
(*i)++; /* leave existing pattern alone */
|
|
else
|
|
*i = makpat (lin, *i + 1, lin[*i], Pat);
|
|
|
|
if (Pat [0] == EOS)
|
|
{
|
|
Errcode = ENOPAT;
|
|
return (ERR);
|
|
}
|
|
if (*i == ERR)
|
|
{
|
|
Pat[0] = EOS;
|
|
Errcode = EBADPAT;
|
|
return (ERR);
|
|
}
|
|
return (OK);
|
|
}
|
|
|
|
|
|
/* ptscan --- scan for next occurrence of pattern */
|
|
|
|
int ptscan (int way, int *num)
|
|
{
|
|
LINEDESC *k;
|
|
|
|
*num = Curln;
|
|
k = getind (*num);
|
|
do {
|
|
bump (num, &k, way);
|
|
gtxt (k);
|
|
if (match (Txt, Pat) == YES)
|
|
return (OK);
|
|
} while (*num != Curln && ! intrpt ());
|
|
|
|
if (Errcode == EEGARB)
|
|
Errcode = EPNOTFND;
|
|
|
|
return (ERR);
|
|
}
|
|
|
|
|
|
/* settab --- set tab stops */
|
|
|
|
int settab (char str[])
|
|
{
|
|
int i, j, n, maxstop, last, inc, ret;
|
|
int ctoi ();
|
|
|
|
for (i = 0; i < MAXLINE; i++) /* clear all tab stops */
|
|
Tabstops[i] = NO;
|
|
|
|
ret = OK;
|
|
maxstop = 0;
|
|
last = 1;
|
|
|
|
i = 0;
|
|
SKIPBL (str, i);
|
|
while (str[i] != EOS && str[i] != '\n')
|
|
{
|
|
if (str[i] == '+') /* increment */
|
|
{
|
|
i++;
|
|
inc = YES;
|
|
}
|
|
else
|
|
inc = NO;
|
|
|
|
n = ctoi (str, &i);
|
|
|
|
if (n <= 0 || n >= MAXLINE)
|
|
{
|
|
ret = ERR;
|
|
Errcode = ENONSENSE;
|
|
break;
|
|
}
|
|
|
|
if (str[i] != ' ' && str[i] != '+' &&
|
|
str[i] != '\n' && str[i] != EOS)
|
|
{
|
|
ret = ERR;
|
|
Errcode = EBADTABS;
|
|
break;
|
|
}
|
|
|
|
if (inc == YES)
|
|
{
|
|
for (j = last + n; j < MAXLINE; j += n)
|
|
{
|
|
Tabstops[j - 1] = YES;
|
|
maxstop = max (j, maxstop);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Tabstops[n - 1] = YES;
|
|
last = n;
|
|
maxstop = max (n, maxstop);
|
|
}
|
|
SKIPBL (str, i);
|
|
} /* while ... */
|
|
|
|
if (ret == ERR)
|
|
maxstop = 0;
|
|
|
|
if (maxstop == 0) /* no tab stops specified, use defaults */
|
|
{
|
|
for (i = 4; i < MAXLINE - 1; i += 4)
|
|
Tabstops[i] = YES;
|
|
maxstop = i - 4 + 1;
|
|
}
|
|
|
|
Tabstops[0] = YES; /* always set to YES */
|
|
|
|
for (i = maxstop; i < MAXLINE; i++)
|
|
Tabstops[i] = YES;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/* serc_safe --- check if the file permissions and ownership are safe */
|
|
|
|
/*
|
|
* err on the side of caution and only exec ~/.serc and ./serc files
|
|
* that we own and cannot be written by others.
|
|
*/
|
|
|
|
int serc_safe (char *path)
|
|
{
|
|
int rc;
|
|
uid_t our_euid;
|
|
struct stat sbuf;
|
|
|
|
rc = stat (path, &sbuf);
|
|
if (rc != 0)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
our_euid = geteuid ();
|
|
|
|
/* don't exec .serc files that aren't ours */
|
|
if (sbuf.st_uid != our_euid)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
/* don't .serc files that others can write to */
|
|
if ((sbuf.st_mode & S_IWGRP) || (sbuf.st_mode & S_IWOTH))
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
/* serc --- read in ./.serc or $HOME/.serc and execute the commands in it. */
|
|
|
|
/*
|
|
* note that se's special control characters are NOT processed,
|
|
* and therefore should NOT be used in one's .serc file.
|
|
*/
|
|
|
|
void serc (void)
|
|
{
|
|
char file[MAXLINE];
|
|
char lin[MAXLINE];
|
|
char *homeserc;
|
|
FILE *fp;
|
|
int status = ENOERR;
|
|
int len, cursav, i;
|
|
char *serc_files[] = {file, "./.serc", NULL};
|
|
|
|
homeserc = expand_env ("$HOME/.serc");
|
|
|
|
memset (file, EOS, MAXLINE);
|
|
strncpy (file, homeserc, MAXLINE-1);
|
|
|
|
fp = NULL;
|
|
|
|
for (i = 0; serc_files[i]; i++)
|
|
{
|
|
if ((serc_safe (serc_files[i]) == YES) &&
|
|
((fp = fopen (serc_files[i], "r")) != NULL))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fp == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (fgets (lin, MAXLINE, fp) != NULL && status != EOF /*??*/)
|
|
{
|
|
if (lin[0] == '#' || lin[0] == '\n')
|
|
{
|
|
continue; /* comment in .serc file */
|
|
}
|
|
|
|
/* most of this code stolen from edit() */
|
|
len = 0;
|
|
cursav = Curln;
|
|
if (getlst (lin, &len, &status) == OK)
|
|
{
|
|
if (ckglob (lin, &len, &status) == OK)
|
|
{
|
|
doglob (lin, &len, &cursav, &status);
|
|
}
|
|
else if (status != ERR)
|
|
{
|
|
docmd (lin, len, NO, &status);
|
|
}
|
|
}
|
|
|
|
if (status == ERR)
|
|
{
|
|
if (Errcode == EHANGUP)
|
|
{
|
|
hangup ();
|
|
}
|
|
Curln = min (cursav, Lastln);
|
|
}
|
|
}
|
|
fclose (fp);
|
|
}
|
|
|
|
#ifdef LOG_USAGE
|
|
|
|
/* log -- log se usage */
|
|
|
|
|
|
void log_usage (void)
|
|
{
|
|
char logname[MAXLINE], tod[26]; /* tod => time of day */
|
|
long clock;
|
|
|
|
/* get the login name */
|
|
memset (logname, EOS, MAXLINE);
|
|
strncpy (logname, getlogin (), MAXLINE-1);
|
|
|
|
time (&clock);
|
|
|
|
memset (tod, EOS, 26);
|
|
strncpy (tod, ctime (&clock), 26-1); /* see the manual on ctime(3C) */
|
|
tod[24] = EOS; /* delete the '\n' at the end */
|
|
|
|
openlog (PACKAGE, 0, LOG_USER);
|
|
syslog (LOG_INFO, "%s was invoked by %s on %s.", PACKAGE, logname, tod);
|
|
closelog ();
|
|
|
|
}
|
|
#endif
|
|
|
|
/* sysname --- return a string telling us who we are */
|
|
|
|
char *sysname (void)
|
|
{
|
|
int i, j, k;
|
|
char c;
|
|
static char buf[MAXLINE] = "";
|
|
FILE *fp;
|
|
static char unknown[] = "unknown";
|
|
|
|
static struct utsname whoarewe;
|
|
|
|
uname (& whoarewe);
|
|
return (whoarewe.nodename);
|
|
}
|