# define INCLUDE_CTYPE
# include "ed.h"
# include "edcmd.h"
# include "fileio.h"
/*
* This file defines the command subroutines for edcmd.c
*/
extern char *skipst P((char*));
extern char *pattern P((char*, int, char*));
extern void cb_count P((cmdbuf*));
extern void not_in_global P((cmdbuf*));
extern void cb_do P((cmdbuf*, Int));
extern void cb_buf P((cmdbuf*, block));
extern void add P((cmdbuf*, Int, block, Int));
extern block delete P((cmdbuf*, Int, Int));
extern void change P((cmdbuf*, Int, Int, block));
extern void startblock P((cmdbuf*));
extern void addblock P((cmdbuf*, char*));
extern void endblock P((cmdbuf*));
/*
* NAME: find()
* DESCRIPTION: scan a line for a pattern. If the pattern is found, longjump
* out.
*/
static void find(ptr, text)
char *ptr, *text;
{
register cmdbuf *cb;
cb = (cmdbuf *) ptr;
if (rx_exec(cb->regexp, text, 0, cb->ignorecase) > 0) {
longjmp(cb->env, TRUE);
}
cb->lineno++;
}
/*
* NAME: cmdbuf->search()
* DESCRIPTION: search a range of lines for the occurance of a pattern. When
* found, jump out immediately.
*/
Int cb_search(cb, first, last, reverse)
cmdbuf *cb;
Int first, last;
int reverse;
{
if (setjmp(cb->env)) {
/* found */
return (reverse) ? last - cb->lineno : first + cb->lineno;
}
cb->lineno = 0;
cb->ignorecase = IGNORECASE(cb->vars);
eb_range(cb->edbuf, first, last, find, (char *) cb, reverse);
/* not found */
return 0;
}
/*
* NAME: println()
* DESCRIPTION: output a line of text. The format is decided by flags.
* Non-ascii characters (eight bit set) have no special processing.
*/
static void println(ptr, text)
char *ptr;
register char *text;
{
char buffer[2 * MAX_LINE_SIZE + 14]; /* all ^x + number + list */
register cmdbuf *cb;
register char *p;
cb = (cmdbuf *) ptr;
if (cb->flags & CB_NUMBER) {
sprintf(buffer, "%6ld ", (long) cb->lineno++);
p = buffer + 8;
} else {
p = buffer;
}
while (*text != '\0') {
if ((*text & 0x7f) < ' ') {
/* control character */
if (*text == HT && !(cb->flags & CB_LIST)) {
*p++ = HT;
} else {
*p++ = '^'; *p++ = (*text & 0x9f) + '@';
}
} else if (*text == 0x7f) {
/* DEL */
*p++ = '^'; *p++ = '?';
} else {
/* normal character */
*p++ = *text;
}
text++;
}
if (cb->flags & CB_LIST) {
*p++ = '$';
}
*p = '\0';
output("%s\012", buffer); /* LF */
}
/*
* NAME: cmdbuf->print()
* DESCRIPTION: print a range of lines, according to the format specified in
* the flags. Afterwards, the current line is set to the last line
* printed.
*/
int cb_print(cb)
register cmdbuf *cb;
{
register char *p;
/* handle flags right now */
p = cb->cmd;
for (;;) {
switch (*p++) {
case '-':
case '+':
case 'p':
/* ignore */
continue;
case 'l':
cb->flags |= CB_LIST;
continue;
case '#':
cb->flags |= CB_NUMBER;
continue;
}
cb->cmd = --p;
break;
}
cb->lineno = cb->first;
eb_range(cb->edbuf, cb->first, cb->last, println, (char *) cb, FALSE);
cb->this = cb->last;
return 0;
}
/*
* NAME: cmdbuf->list()
* DESCRIPTION: output a range of lines in a hopefully unambiguous format
*/
int cb_list(cb)
cmdbuf *cb;
{
cb->flags |= CB_LIST;
return cb_print(cb);
}
/*
* NAME: cmdbuf->number()
* DESCRIPTION: output a range of lines preceded by line numbers
*/
int cb_number(cb)
cmdbuf *cb;
{
cb->flags |= CB_NUMBER;
return cb_print(cb);
}
/*
* NAME: cmdbuf->page()
* DESCRIPTION: show a page of lines
*/
int cb_page(cb)
register cmdbuf *cb;
{
register Int offset, window;
if (cb->edbuf->lines == 0) {
error("No lines in buffer");
}
window = WINDOW(cb->vars);
switch (*(cb->cmd)++) {
default: /* next line */
cb->cmd--;
cb->this++;
case '+': /* top */
offset = 0;
break;
case '-': /* bottom */
offset = 1 - window;
break;
case '.': /* middle */
offset = 1 - (window + 1) / 2;
break;
}
/* set first */
if (cb->first < 0) {
cb->first = cb->this;
}
cb->first += offset;
if (cb->first <= 0) {
cb->first = 1;
} else if (cb->first > cb->edbuf->lines) {
cb->first = cb->edbuf->lines;
}
/* set last */
cb->last = cb->first + window - 1;
if (cb->last < cb->first) {
cb->last = cb->first;
} else if (cb->last > cb->edbuf->lines) {
cb->last = cb->edbuf->lines;
}
return cb_print(cb);
}
/*
* NAME: cmdbuf->assign()
* DESCRIPTION: show the specified line number
*/
int cb_assign(cb)
register cmdbuf *cb;
{
output("%ld\012",
(long) (cb->first < 0) ? cb->edbuf->lines : cb->first); /* LF */
return 0;
}
/*
* NAME: cmdbuf->mark()
* DESCRIPTION: set a mark in the range [a-z] to line number
*/
int cb_mark(cb)
register cmdbuf *cb;
{
if (!islower(cb->cmd[0])) {
error("Mark must specify a letter");
}
cb->mark[*(cb->cmd)++ - 'a'] = cb->first;
return 0;
}
/*
* NAME: cmdbuf->append()
* DESCRIPTION: append a block of lines, read from user, to edit buffer
*/
int cb_append(cb)
register cmdbuf *cb;
{
not_in_global(cb);
cb_do(cb, cb->first);
startblock(cb);
cb->flags |= CB_INSERT;
return 0;
}
/*
* NAME: cmdbuf->insert()
* DESCRIPTION: insert a block of lines in the edit buffer
*/
int cb_insert(cb)
cmdbuf *cb;
{
not_in_global(cb);
if (cb->first > 0) {
cb->first--;
}
return cb_append(cb);
}
/*
* NAME: cmdbuf->change()
* DESCRIPTION: change a subrange of lines in the edit buffer
*/
int cb_change(cb)
cmdbuf *cb;
{
register Int *m;
not_in_global(cb);
cb_do(cb, cb->first);
/* erase marks of changed lines */
for (m = cb->mark; m < &cb->mark[26]; m++) {
if (*m >= cb->first && *m <= cb->last) {
*m = 0;
}
}
startblock(cb);
cb->flags |= CB_INSERT | CB_CHANGE;
return 0;
}
/*
* NAME: cmdbuf->delete()
* DESCRIPTION: delete a subrange of lines in the edit buffer
*/
int cb_delete(cb)
register cmdbuf *cb;
{
cb_do(cb, cb->first);
cb_buf(cb, delete(cb, cb->first, cb->last));
cb->edit++;
return RET_FLAGS;
}
/*
* NAME: cmdbuf->copy()
* DESCRIPTION: copy a subrange of lines in the edit buffer
*/
int cb_copy(cb)
register cmdbuf *cb;
{
cb_do(cb, cb->a_addr);
add(cb, cb->a_addr, eb_yank(cb->edbuf, cb->first, cb->last),
cb->last - cb->first + 1);
cb->edit++;
return RET_FLAGS;
}
/*
* NAME: cmdbuf->move()
* DESCRIPTION: move a subrange of lines in the edit buffer
*/
int cb_move(cb)
register cmdbuf *cb;
{
Int mark[26];
register Int offset, *m1, *m2;
if (cb->a_addr >= cb->first - 1 && cb->a_addr <= cb->last) {
error("Move to moved line");
}
cb_do(cb, cb->first);
memset(mark, '\0', sizeof(mark));
if (cb->a_addr < cb->last) {
offset = cb->a_addr + 1 - cb->first;
} else {
offset = cb->a_addr - cb->last;
cb->a_addr -= cb->last - cb->first + 1;
}
/* make a copy of the marks of the lines to move */
for (m1 = mark, m2 = cb->mark; m1 < &mark[26]; m1++, m2++) {
if (*m2 >= cb->first && *m2 <= cb->last) {
*m1 = *m2;
} else {
*m1 = 0;
}
}
add(cb, cb->a_addr, delete(cb, cb->first, cb->last),
cb->last - cb->first + 1);
/* copy back adjusted marks of moved lines */
for (m1 = mark, m2 = cb->mark; m1 < &mark[26]; m1++, m2++) {
if (*m1 != 0) {
*m2 = *m1 + offset;
}
}
cb->edit++;
return RET_FLAGS;
}
/*
* NAME: cmdbuf->put()
* DESCRIPTION: put a block in the edit buffer
*/
int cb_put(cb)
register cmdbuf *cb;
{
register block b;
if (isalpha(cb->a_buffer)) {
/* 'a' and 'A' both refer to buffer 'a' */
b = cb->zbuf[tolower(cb->a_buffer) - 'a'];
} else {
b = cb->buf;
}
if (b == (block) 0) {
error("Nothing in buffer");
}
cb_do(cb, cb->first);
add(cb, cb->first, b, bk_size(cb->edbuf->lb, b));
cb->edit++;
return RET_FLAGS;
}
/*
* NAME: cmdbuf->yank()
* DESCRIPTION: yank a block of lines from the edit buffer
*/
int cb_yank(cb)
register cmdbuf *cb;
{
cb_buf(cb, eb_yank(cb->edbuf, cb->first, cb->last));
return 0;
}
/*
* NAME: shift()
* DESCRIPTION: shift a line left or right
*/
static void shift(ptr, text)
char *ptr;
register char *text;
{
register cmdbuf *cb;
register int idx;
cb = (cmdbuf *) ptr;
/* first determine the number of leading spaces */
idx = 0;
while (*text == ' ' || *text == HT) {
if (*text++ == ' ') {
idx++;
} else {
idx = (idx + 8) & ~7;
}
}
if (*text == '\0') {
/* don't shift lines with ws only */
addblock(cb, text);
cb->lineno++;
} else {
idx += cb->shift;
if (idx < MAX_LINE_SIZE) {
char buffer[MAX_LINE_SIZE];
register char *p;
p = buffer;
/* fill with leading ws */
while (idx >= 8) {
*p++ = HT;
idx -= 8;
}
while (idx > 0) {
*p++ = ' ';
--idx;
}
if (p - buffer + strlen(text) < MAX_LINE_SIZE) {
strcpy(p, text);
addblock(cb, buffer);
cb->lineno++;
return;
}
}
/* Error: line too long. Finish block of lines already shifted. */
cb->last = cb->lineno;
endblock(cb);
error("Result of shift would be too long");
}
}
/*
* NAME: cmdbuf->shift()
* DESCRIPTION: shift a range of lines left or right
*/
static int cb_shift(cb)
register cmdbuf *cb;
{
cb_do(cb, cb->first);
startblock(cb);
cb->lineno = cb->first - 1;
cb->flags |= CB_CHANGE;
eb_range(cb->edbuf, cb->first, cb->last, shift, (char *) cb, FALSE);
endblock(cb);
return RET_FLAGS;
}
/*
* NAME: cmdbuf->lshift()
* DESCRIPTION: shift a range of lines to the left
*/
int cb_lshift(cb)
register cmdbuf *cb;
{
cb->shift = -SHIFTWIDTH(cb->vars);
return cb_shift(cb);
}
/*
* NAME: cmdbuf->rshift()
* DESCRIPTION: shift a range of lines to the right
*/
int cb_rshift(cb)
register cmdbuf *cb;
{
cb->shift = SHIFTWIDTH(cb->vars);
return cb_shift(cb);
}
# define STACKSZ 1024 /* size of indent stack */
/* token definitions in indent */
# define SEMICOLON 0
# define LBRACKET 1
# define RBRACKET 2
# define LOPERATOR 3
# define ROPERATOR 4
# define LHOOK 5
# define RHOOK 6
# define TOKEN 7
# define ELSE 8
# define IF 9
# define FOR 10 /* WHILE, RLIMIT */
# define DO 11
# define EOT 12
/*
* NAME: noshift()
* DESCRIPTION: add this line to the current block without shifting it
*/
static void noshift(cb, text)
cmdbuf *cb;
char *text;
{
addblock(cb, text);
cb->lineno++;
}
/*
* NAME: indent()
* DESCRIPTION: Parse and indent a line of text. This isn't perfect, as
* keywords could be defined as macros, comments are very hard to
* handle properly, (, [ and ({ will match any of ), ] and }),
* and last but not least everyone has his own taste of
* indentation.
*/
static void indent(ptr, text)
char *ptr, *text;
{
static char f[] = { 7, 1, 7, 1, 2, 1, 6, 4, 2, 6, 7, 2, 0, };
static char g[] = { 2, 2, 1, 7, 1, 5, 1, 3, 6, 2, 2, 2, 0, };
char ident[MAX_LINE_SIZE];
char line[MAX_LINE_SIZE];
register cmdbuf *cb;
register char *p, *sp;
register int *ip, idx;
register int top, token;
char *start;
bool do_indent;
cb = (cmdbuf *) ptr;
do_indent = FALSE;
idx = 0;
p = text = strcpy(line, text);
/* process status vars */
if (cb->quote != '\0') {
cb->shift = 0; /* in case a comment starts on this line */
noshift(cb, p);
} else if ((cb->flags & CB_PPCONTROL) || *p == '#') {
noshift(cb, p);
while (*p != '\0') {
if (*p == '\\' && *++p == '\0') {
cb->flags |= CB_PPCONTROL;
return;
}
p++;
}
cb->flags &= ~CB_PPCONTROL;
return;
} else {
/* count leading ws */
while (*p == ' ' || *p == HT) {
if (*p++ == ' ') {
idx++;
} else {
idx = (idx + 8) & ~7;
}
}
if (*p == '\0') {
noshift(cb, p);
return;
} else if (cb->flags & CB_COMMENT) {
shift(cb, text); /* use previous shift */
} else {
do_indent = TRUE;
}
}
/* process this line */
start = p;
while (*p != '\0') {
/* lexical scanning: find the next token */
ident[0] = '\0';
if (cb->flags & CB_COMMENT) {
/* comment */
while (*p != '*') {
if (*p == '\0') {
return;
}
p++;
}
while (*p == '*') {
p++;
}
if (*p == '/') {
cb->flags &= ~CB_COMMENT;
p++;
}
continue;
} else if (cb->quote != '\0') {
/* string or character constant */
for (;;) {
if (*p == cb->quote) {
cb->quote = '\0';
p++;
break;
} else if (*p == '\0') {
cb->last = cb->lineno;
endblock(cb);
error("Unterminated string");
} else if (*p == '\\' && *++p == '\0') {
break;
}
p++;
}
token = TOKEN;
} else {
switch (*p++) {
case ' ': /* white space */
case HT:
continue;
case '\'': /* start of string */
case '"':
cb->quote = p[-1];
continue;
case '/':
if (*p == '*') { /* start of comment */
cb->flags |= CB_COMMENT;
if (do_indent) {
/* this line hasn't been indented yet */
cb->shift = cb->ind[0] - idx;
shift(cb, text);
do_indent = FALSE;
} else {
register char *q;
register int idx2;
/*
* find how much the comment has shifted, so the same
* shift can be used if the comment continues on the
* next line
*/
idx2 = cb->ind[0];
for (q = start; q < p - 1;) {
if (*q++ == HT) {
idx = (idx + 8) & ~7;
idx2 = (idx2 + 8) & ~7;
} else {
idx++;
idx2++;
}
}
cb->shift = idx2 - idx;
}
p++;
continue;
}
token = TOKEN;
break;
case '{':
token = LBRACKET;
break;
case '(':
if (cb->flags & CB_JSKEYWORD) {
/*
* LOPERATOR & ROPERATOR are a kludge. The operator
* precedence parser that is used could not work if
* parenthesis after keywords was not treated specially.
*/
token = LOPERATOR;
break;
}
if (*p == '{') {
p++; /* ({ is one token */
}
case '[':
token = LHOOK;
break;
case '}':
if (*p != ')') {
token = RBRACKET;
break;
}
p++;
/* }) is one token; fall through */
case ')':
case ']':
token = RHOOK;
break;
case ';':
token = SEMICOLON;
break;
default:
if (isalpha(*--p) || *p == '_') {
register char *q;
/* Identifier. See if it's a keyword. */
q = ident;
do {
*q++ = *p++;
} while (isalnum(*p) || *p == '_');
*q = '\0';
if (strcmp(ident, "if") == 0) token = IF;
else if (strcmp(ident, "else") == 0) token = ELSE;
else if (strcmp(ident, "for") == 0 ||
strcmp(ident, "while") == 0 ||
strcmp(ident, "rlimits") == 0) token = FOR;
else if (strcmp(ident, "do") == 0) token = DO;
else /* not a keyword */ token = TOKEN;
} else {
/* anything else is a "token" */
p++;
token = TOKEN;
}
break;
}
}
/* parse */
sp = cb->stack;
ip = cb->ind;
for (;;) {
top = *sp;
if (top == LOPERATOR && token == RHOOK) {
/* ) after LOPERATOR is ROPERATOR */
token = ROPERATOR;
}
if (f[top] <= g[token]) { /* shift the token on the stack */
register int i;
if (sp == cb->stackbot) {
/* out of stack. Finish already indented block. */
cb->last = cb->lineno;
endblock(cb);
error("Nesting too deep");
}
/* handle indentation */
i = *ip;
/* if needed, reduce indentation prior to shift */
if ((token == LBRACKET &&
(*sp == ROPERATOR || *sp == ELSE || *sp == DO)) ||
token == RBRACKET ||
(token == IF && *sp == ELSE)) {
/* back up */
i -= SHIFTWIDTH(cb->vars);
}
/* shift the current line, if appropriate */
if (do_indent) {
cb->shift = i - idx;
if (i > 0 && token != RHOOK &&
(*sp == LOPERATOR || *sp == LHOOK)) {
/* half indent after ( [ ({ (HACK!) */
cb->shift += SHIFTWIDTH(cb->vars) / 2;
} else if (token == TOKEN && *sp == LBRACKET &&
(strcmp(ident, "case") == 0 ||
strcmp(ident, "default") == 0)) {
/* back up if this is a switch label */
cb->shift -= SHIFTWIDTH(cb->vars);
}
shift(cb, text);
do_indent = FALSE;
}
/* change indentation after current token */
if (token == LBRACKET || token == ROPERATOR || token == ELSE ||
token == DO) {
/* add indentation */
i += SHIFTWIDTH(cb->vars);
} else if (token == SEMICOLON &&
(*sp == ROPERATOR || *sp == ELSE)) {
/* in case it is followed by a comment */
i -= SHIFTWIDTH(cb->vars);
}
*--sp = token;
*--ip = i;
break;
}
/* reduce handle */
do {
top = *sp++;
ip++;
} while (f[*sp] >= g[top]);
}
cb->stack = sp;
cb->ind = ip;
if (token >= IF) { /* but not ELSE */
cb->flags |= CB_JSKEYWORD;
} else {
cb->flags &= ~CB_JSKEYWORD;
}
}
}
/*
* NAME: cmdbuf->indent()
* DESCRIPTION: indent a range of lines
*/
int cb_indent(cb)
register cmdbuf *cb;
{
char s[STACKSZ];
int i[STACKSZ];
/* setup stacks */
cb->stackbot = s;
cb->stack = s + STACKSZ - 1;
cb->stack[0] = EOT;
cb->ind = i + STACKSZ - 1;
cb->ind[0] = 0;
cb->quote = '\0';
cb_do(cb, cb->first);
startblock(cb);
cb->lineno = cb->first - 1;
cb->flags |= CB_CHANGE;
cb->flags &= ~(CB_PPCONTROL | CB_COMMENT | CB_JSKEYWORD);
eb_range(cb->edbuf, cb->first, cb->last, indent, (char *) cb, FALSE);
endblock(cb);
return 0;
}
/*
* NAME: join()
* DESCRIPTION: join a string to the one already in the join buffer
*/
static void join(ptr, text)
char *ptr;
register char *text;
{
register cmdbuf *cb;
register char *p;
cb = (cmdbuf *) ptr;
p = cb->buffer + cb->buflen;
if (cb->buflen != 0 && !(cb->flags & CB_EXCL)) {
/* do special processing */
text = skipst(text);
if (*text != '\0' && *text != ')' && p[-1] != ' ' && p[-1] != HT) {
if (p[-1] == '.') {
*p++ = ' ';
}
*p++ = ' ';
}
cb->buflen = p - cb->buffer;
}
cb->buflen += strlen(text);
if (cb->buflen >= MAX_LINE_SIZE) {
error("Result of join would be too long");
}
strcpy(p, text);
}
/*
* NAME: cmdbuf->join()
* DESCRIPTION: join a range of lines in the edit buffer
*/
int cb_join(cb)
register cmdbuf *cb;
{
char buf[MAX_LINE_SIZE + 1];
register Int *m;
if (cb->edbuf->lines == 0) {
error("No lines in buffer");
}
if (cb->first < 0) {
cb->first = cb->this;
}
if (cb->last < 0) {
cb->last = (cb->first == cb->edbuf->lines) ? cb->first : cb->first + 1;
}
cb_do(cb, cb->first);
cb->this = cb->othis = cb->first;
buf[0] = '\0';
cb->buffer = buf;
cb->buflen = 0;
eb_range(cb->edbuf, cb->first, cb->last, join, (char *) cb, FALSE);
/* erase marks for joined lines */
for (m = cb->mark; m < &cb->mark[26]; m++) {
if (*m > cb->first && *m <= cb->last) {
*m = 0;
}
}
cb->flags |= CB_CHANGE;
startblock(cb);
addblock(cb, buf);
endblock(cb);
return RET_FLAGS;
}
/*
* NAME: sub()
* DESCRIPTION: add a string to the current substitute buffer
*/
static void sub(cb, text, size)
register cmdbuf *cb;
char *text;
unsigned int size;
{
register char *p, *q;
register unsigned int i;
i = size;
if (cb->buflen + i >= MAX_LINE_SIZE) {
if (cb->flags & CB_CURRENTBLK) {
/* finish already processed block */
endblock(cb);
}
cb->this = cb->othis = cb->lineno;
error("Line overflow in substitute");
}
p = cb->buffer + cb->buflen;
q = text;
if (cb->flags & CB_TLOWER) { /* lowercase one letter */
*p++ = tolower(*q);
q++;
cb->flags &= ~CB_TLOWER;
--i;
} else if (cb->flags & CB_TUPPER) { /* uppercase one letter */
*p++ = toupper(*q);
q++;
cb->flags &= ~CB_TUPPER;
--i;
}
if (cb->flags & CB_LOWER) { /* lowercase string */
while (i > 0) {
*p++ = tolower(*q);
q++;
--i;
}
} else if (cb->flags & CB_UPPER) { /* uppercase string */
while (i > 0) {
*p++ = toupper(*q);
q++;
--i;
}
} else if (i > 0) { /* don't change case */
memcpy(p, q, i);
}
cb->buflen += size;
}
/*
* NAME: subst()
* DESCRIPTION: do substitutions in a line. If something is substituted on line
* N, and the next substitution happens on line N + 2, line N + 1
* is joined in the new block also.
*/
static void subst(ptr, text)
char *ptr;
register char *text;
{
char line[MAX_LINE_SIZE];
register cmdbuf *cb;
register int idx, size;
register char *p;
register Int *k, *l;
Int newlines;
bool found;
cb = (cmdbuf *) ptr;
found = FALSE;
newlines = 0;
idx = 0;
/*
* Because the write buffer might be flushed, and the text would
* not remain in memory, use a local copy.
*/
text = strcpy(line, text);
while (rx_exec(cb->regexp, text, idx, IGNORECASE(cb->vars)) > 0) {
if (cb->flags & CB_SKIPPED) {
/*
* add the previous line, in which nothing was substituted, to
* the block. Has to be done here, before the contents of the buffer
* are changed.
*/
addblock(cb, cb->buffer);
cb->flags &= ~CB_SKIPPED;
/*
* check if there were newlines in the last substitution. If there
* are, marks on the previous line (without substitutions) will
* also have to be changed.
*/
if (cb->offset > 0) {
for (k = cb->mark, l = cb->moffset; k < &cb->mark[26]; k++, l++)
{
if (*k == cb->lineno - 1 && *l == 0) {
*l = *k + cb->offset;
}
}
}
}
found = TRUE;
cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
size = cb->regexp->start - text - idx;
if (size > 0) {
/* copy first unchanged part of line to buffer */
sub(cb, text + idx, size);
}
p = cb->replace;
while (*p != '\0') {
switch (*p) {
case '&':
/* insert matching string */
sub(cb, cb->regexp->start, cb->regexp->size);
break;
case '\\': /* special substitute characters */
switch (*++p) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* insert subexpression between \( \) */
if (cb->regexp->se[*p - '1'].start != (char*) NULL) {
sub(cb, cb->regexp->se[*p - '1'].start,
cb->regexp->se[*p - '1'].size);
break;
}
/* if no subexpression, fall though */
default:
sub(cb, p, 1); /* ignore preceding backslash */
break;
case 'n':
cb->buffer[cb->buflen++] = '\0';
newlines++; /* insert newline */
break;
case 'U':
/* convert string to uppercase */
cb->flags |= CB_UPPER;
cb->flags &= ~(CB_LOWER | CB_TUPPER | CB_TLOWER);
break;
case 'L':
/* convert string to lowercase */
cb->flags |= CB_LOWER;
cb->flags &= ~(CB_UPPER | CB_TUPPER | CB_TLOWER);
break;
case 'e':
case 'E':
/* end case conversion */
cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
break;
case 'u':
/* convert char to uppercase */
cb->flags |= CB_TUPPER;
cb->flags &= ~CB_TLOWER;
break;
case 'l':
/* convert char to lowercase */
cb->flags &= ~CB_TUPPER;
cb->flags |= CB_TLOWER;
break;
case '\0': /* sigh */
continue;
}
break;
default: /* normal char */
sub(cb, p, 1);
break;
}
p++;
}
idx = cb->regexp->start + cb->regexp->size - text;
if (!(cb->flags & CB_GLOBSUBST) || text[idx] == '\0' ||
(cb->regexp->size == 0 && text[++idx] == '\0')) {
break;
}
}
if (found) {
if (text[idx] != '\0') {
/* concatenate unchanged part of line after found pattern */
cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
sub(cb, text + idx, strlen(text + idx));
}
if (!(cb->flags & CB_CURRENTBLK)) {
/* start a new block of lines with substitutions in them */
cb->flags |= CB_CHANGE;
cb->first = cb->lineno;
startblock(cb);
cb->flags |= CB_CURRENTBLK;
}
/* add this changed line to block */
cb->buffer[cb->buflen] = '\0';
if (newlines == 0) {
addblock(cb, cb->buffer);
} else {
/*
* There were newlines in the substituted string. Add all
* lines to the current block, and save the marks in range.
*/
p = cb->buffer;
do {
addblock(cb, p);
p += strlen(p) + 1;
} while (p <= cb->buffer + cb->buflen);
for (k = cb->mark, l = cb->moffset; k < &cb->mark[26]; k++, l++) {
if (*k == cb->lineno && *l == 0) {
*l = *k + cb->offset;
}
}
cb->offset += newlines;
}
cb->buflen = 0;
cb->last = cb->lineno;
} else {
if (cb->flags & CB_SKIPPED) {
/* two lines without substitutions now. Finish previous block. */
endblock(cb);
cb->lineno += cb->offset;
cb->offset = 0;
cb->flags &= ~(CB_CURRENTBLK | CB_SKIPPED);
} else if (cb->flags & CB_CURRENTBLK) {
/*
* no substitution on this line, but there was one on the previous
* line. mark this line as skipped, so it can still be added to
* the block of changed lines if the next line has substitutions.
*/
strcpy(cb->buffer, text);
cb->flags |= CB_SKIPPED;
}
}
cb->lineno++;
}
/*
* NAME: cmdbuf->substitute()
* DESCRIPTION: do substitutions on a range of lines
*/
int cb_subst(cb)
register cmdbuf *cb;
{
char buf[MAX_LINE_SIZE], delim;
Int m[26];
Int edit;
register char *p;
register Int *k, *l;
delim = cb->cmd[0];
if (delim == '\0' || strchr("0123456789gpl#-+", delim) != (char*) NULL) {
/* no search pattern & replace string specified */
if (cb->search[0] == '\0') {
error("No previous substitute to repeat");
}
} else if (!isalpha(delim)) {
register char *q;
/* get search pattern */
p = pattern(cb->cmd + 1, delim, cb->search);
/* get replace string */
q = cb->replace;
while (*p != '\0') {
if (*p == delim) {
p++;
break;
}
if (q == cb->replace + STRINGSZ - 1) {
cb->search[0] = '\0';
error("Replace string too large");
}
if ((*q++ = *p++) == '\\' && *p != '\0') {
*q++ = *p++;
}
}
*q = '\0';
cb->cmd = p;
} else {
/* cause error */
cb->search[0] = '\0';
}
if (cb->search[0] == '\0') {
error("Missing regular expression for substitute");
}
/* compile regexp */
p = rx_comp(cb->regexp, cb->search);
if (p != (char *) NULL) {
error(p);
}
cb_count(cb); /* get count */
/* handle global flag */
if (cb->cmd[0] == 'g') {
cb->flags |= CB_GLOBSUBST;
cb->cmd++;
} else {
cb->flags &= ~CB_GLOBSUBST;
}
/* make a blank mark table */
cb->moffset = m;
for (l = m; l < &m[26]; ) {
*l++ = 0;
}
cb->offset = 0;
/* do substitutions */
cb_do(cb, cb->first);
cb->lineno = cb->first;
edit = cb->edit;
cb->buffer = buf;
cb->buflen = 0;
cb->flags &= ~(CB_CURRENTBLK | CB_SKIPPED);
eb_range(cb->edbuf, cb->first, cb->last, subst, (char *) cb, FALSE);
if (cb->flags & CB_CURRENTBLK) {
/* finish current block, if needed */
endblock(cb);
}
cb->othis = cb->uthis;
if (edit == cb->edit) {
error("Substitute pattern match failed");
}
/* some marks may have been messed up. fix them */
for (l = m, k = cb->mark; l < &m[26]; l++, k++) {
if (*l != 0) {
*k = *l;
}
}
return RET_FLAGS;
}
/*
* NAME: getfname()
* DESCRIPTION: copy a string to another buffer, unless it has length 0 or
* is too long
*/
static bool getfname(cb, buffer)
register cmdbuf *cb;
char *buffer;
{
register char *p, *q;
/* find the end of the filename */
p = strchr(cb->cmd, ' ');
q = strchr(cb->cmd, HT);
if (q != (char *) NULL && (p == (char *) NULL || p > q)) {
p = q;
}
q = strchr(cb->cmd, '|');
if (q != (char *) NULL && (p == (char *) NULL || p > q)) {
p = q;
}
if (p == (char *) NULL) {
p = strchr(cb->cmd, '\0');
}
/* checks */
if (p == cb->cmd) {
return FALSE;
}
if (p - cb->cmd >= STRINGSZ) {
error("Filename too long");
}
/* copy */
memcpy(buffer, cb->cmd, p - cb->cmd);
buffer[p - cb->cmd] = '\0';
cb->cmd = p;
return TRUE;
}
/*
* NAME: cmdbuf->file()
* DESCRIPTION: get/set the file name & current line, etc.
*/
int cb_file(cb)
register cmdbuf *cb;
{
not_in_global(cb);
if (getfname(cb, cb->fname)) {
/* file name is changed: mark the file as "not edited" */
cb->flags |= CB_NOIMAGE;
}
/* give statistics */
if (cb->fname[0] == '\0') {
output("No file");
} else {
output("\"%s\"", cb->fname);
}
if (cb->flags & CB_NOIMAGE) {
output(" [Not edited]");
}
if (cb->edit > 0) {
output(" [Modified]");
}
output(" line %ld of %ld --%d%%--\012", /* LF */
(long) cb->this, (long) cb->edbuf->lines,
(cb->edbuf->lines == 0) ? 0 :
(int) ((100 * cb->this) / cb->edbuf->lines));
return 0;
}
/*
* NAME: io->show()
* DESCRIPTION: show statistics on the file just read/written
*/
static void io_show(iob)
register io *iob;
{
output("%ld lines, %ld characters", (long) iob->lines,
(long) (iob->chars + iob->zero - iob->split - iob->ill));
if (iob->zero > 0) {
output(" [%ld zero]", (long) iob->zero);
}
if (iob->split > 0) {
output(" [%ld split]", (long) iob->split);
}
if (iob->ill) {
output(" [incomplete last line]");
}
output("\012"); /* LF */
}
/*
* NAME: cmdbuf->read()
* DESCRIPTION: insert a file in the current edit buffer
*/
int cb_read(cb)
register cmdbuf *cb;
{
char buffer[STRINGSZ];
io iob;
not_in_global(cb);
if (!getfname(cb, buffer)) {
if (cb->fname[0] == '\0') {
error("No current filename");
}
/* read current file, by default. I don't know why, but ex has it
that way. */
strcpy(buffer, cb->fname);
}
cb_do(cb, cb->first);
output("\"%s\" ", buffer);
if (!io_load(cb->edbuf, buffer, cb->first, &iob)) {
error("is unreadable");
}
io_show(&iob);
cb->edit++;
cb->this = cb->first + iob.lines;
return 0;
}
/*
* NAME: cmdbuf->edit()
* DESCRIPTION: edit a new file
*/
int cb_edit(cb)
register cmdbuf *cb;
{
io iob;
not_in_global(cb);
if (cb->edit > 0 && !(cb->flags & CB_EXCL)) {
error("No write since last change (edit! overrides)");
}
getfname(cb, cb->fname);
if (cb->fname[0] == '\0') {
error("No current filename");
}
eb_clear(cb->edbuf);
cb->flags &= ~CB_NOIMAGE;
cb->edit = 0;
cb->first = cb->this = 0;
memset(cb->mark, '\0', sizeof(cb->mark));
cb->buf = 0;
memset(cb->zbuf, '\0', sizeof(cb->zbuf));
cb->undo = (block) -1; /* not 0! */
output("\"%s\" ", cb->fname);
if (!io_load(cb->edbuf, cb->fname, cb->first, &iob)) {
error("is unreadable");
}
io_show(&iob);
if (iob.zero > 0 || iob.split > 0 || iob.ill) {
/* the editbuffer in memory is not a perfect image of the file read */
cb->flags |= CB_NOIMAGE;
}
cb->this = iob.lines;
return 0;
}
/*
* NAME: cmdbuf->quit()
* DESCRIPTION: quit editing
*/
int cb_quit(cb)
cmdbuf *cb;
{
not_in_global(cb);
if (cb->edit > 0 && !(cb->flags & CB_EXCL)) {
error("No write since last change (quit! overrides)");
}
return RET_QUIT;
}
/*
* NAME: cmdbuf->write()
* DESCRIPTION: write a range of lines to a file
*/
int cb_write(cb)
register cmdbuf *cb;
{
char buffer[STRINGSZ];
bool append;
io iob;
not_in_global(cb);
if (strncmp(cb->cmd, ">>", 2) == 0) {
append = TRUE;
cb->cmd = skipst(cb->cmd + 2);
} else {
append = FALSE;
}
/* check if write can be done */
if (!getfname(cb, buffer)) {
if (cb->fname[0] == '\0') {
error("No current filename");
}
strcpy(buffer, cb->fname);
}
if (strcmp(buffer, cb->fname) == 0) {
if (cb->first == 1 && cb->last == cb->edbuf->lines) {
if ((cb->flags & (CB_NOIMAGE|CB_EXCL)) == CB_NOIMAGE) {
error("File is changed (use w! to override)");
}
} else if (!(cb->flags & CB_EXCL)) {
error("Use w! to write partial buffer");
}
}
output("\"%s\" ", buffer);
if (!io_save(cb->edbuf, buffer, cb->first, cb->last, append, &iob)) {
error("write failed");
}
io_show(&iob);
if (cb->first == 1 && cb->last == cb->edbuf->lines) {
/* file is now perfect image of editbuffer in memory */
cb->flags &= ~CB_NOIMAGE;
cb->edit = 0;
}
return 0;
}
/*
* NAME: cmdbuf->wq()
* DESCRIPTION: write a range of lines to a file and quit
*/
int cb_wq(cb)
cmdbuf *cb;
{
cb->first = 1;
cb->last = cb->edbuf->lines;
cb_write(cb);
return cb_quit(cb);
}
/*
* NAME: cmdbuf->xit()
* DESCRIPTION: write to the current file if modified, and quit
*/
int cb_xit(cb)
register cmdbuf *cb;
{
if (cb->edit > 0) {
cb->flags |= CB_EXCL;
return cb_wq(cb);
} else {
not_in_global(cb);
return RET_QUIT;
}
}
/*
* NAME: cmdbuf->set()
* DESCRIPTION: get/set variable(s)
*/
int cb_set(cb)
register cmdbuf *cb;
{
char buffer[STRINGSZ];
register char *p, *q;
not_in_global(cb);
p = cb->cmd;
if (strlen(p) >= STRINGSZ) {
p[STRINGSZ - 1] = '\0'; /* must fit in the buffer */
}
if (*p == '\0') {
/* no arguments */
va_show(cb->vars);
} else {
do {
/* copy argument */
q = buffer;
while (*p != '\0' && *p != ' ' && *p != HT) {
*q++ = *p++;
}
*q = '\0';
/* let va_set() process it */
va_set(cb->vars, buffer);
p = skipst(p);
} while (*p != '\0');
cb->cmd = p;
}
return 0;
}