674 lines
12 KiB
C
674 lines
12 KiB
C
/* cmd2.c */
|
|
|
|
/* Author:
|
|
* Steve Kirkendall
|
|
* 16820 SW Tallac Way
|
|
* Beaverton, OR 97006
|
|
* kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda
|
|
*/
|
|
|
|
|
|
/* This file contains some of the commands - mostly ones that change text */
|
|
|
|
#include "vi.h"
|
|
#include "regexp.h"
|
|
|
|
|
|
void cmd_substitute(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra; /* rest of the command line */
|
|
{
|
|
char *line; /* a line from the file */
|
|
regexp *re; /* the compiled search expression */
|
|
char *subst; /* the substitution string */
|
|
char *opt; /* substitution options */
|
|
int optp; /* boolean option: print when done? */
|
|
int optg; /* boolean option: substitute globally in line? */
|
|
long l; /* a line number */
|
|
char *s, *d; /* used during subtitutions */
|
|
long chline; /* # of lines changed */
|
|
long chsub; /* # of substitutions made */
|
|
|
|
|
|
/* make sure we got a search pattern */
|
|
if (*extra != '/' && *extra != '?')
|
|
{
|
|
msg("Usage: s/regular expression/new text/");
|
|
return;
|
|
}
|
|
|
|
/* parse & compile the search pattern */
|
|
subst = parseptrn(extra);
|
|
re = regcomp(extra + 1);
|
|
if (!re)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* parse the substitution string & find the option string */
|
|
for (opt = subst; *opt && (*opt != *extra || opt[-1] == '\\'); opt++)
|
|
{
|
|
}
|
|
if (*opt)
|
|
{
|
|
*opt++ = '\0';
|
|
}
|
|
|
|
/* analyse the option string */
|
|
optp = optg = 0;
|
|
while (*opt)
|
|
{
|
|
switch (*opt++)
|
|
{
|
|
case 'p': optp = 1; break;
|
|
case 'g': optg = 1; break;
|
|
case ' ':
|
|
case '\t': break;
|
|
default:
|
|
msg("Subst options are p and g -- not %c", opt[-1]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ChangeText
|
|
{
|
|
/* reset the change counters */
|
|
chline = chsub = 0L;
|
|
|
|
/* for each selected line */
|
|
for (l = markline(frommark); l <= markline(tomark); l++)
|
|
{
|
|
/* fetch the line */
|
|
line = fetchline(l);
|
|
|
|
/* if it contains the search pattern... */
|
|
if (regexec(re, line, TRUE))
|
|
{
|
|
/* increment the line change counter */
|
|
chline++;
|
|
|
|
/* initialize the pointers */
|
|
s = line;
|
|
d = tmpblk.c;
|
|
|
|
/* do once or globally ... */
|
|
do
|
|
{
|
|
/* increment the substitution change counter */
|
|
chsub++;
|
|
|
|
/* this may be the first line to redraw */
|
|
redrawrange(l, l + 1L, l + 1L);
|
|
|
|
/* copy stuff from before the match */
|
|
while (s < re->startp[0])
|
|
{
|
|
*d++ = *s++;
|
|
}
|
|
|
|
/* subtitute for the matched part */
|
|
regsub(re, subst, d);
|
|
s = re->endp[0];
|
|
d += strlen(d);
|
|
|
|
} while (optg && regexec(re, s, FALSE));
|
|
|
|
/* copy stuff from after the match */
|
|
while (*d++ = *s++) /* yes, ASSIGNMENT! */
|
|
{
|
|
}
|
|
|
|
/* replace the old version of the line with the new */
|
|
changeline(l, tmpblk.c);
|
|
|
|
/* if supposed to print it, do so */
|
|
if (optp)
|
|
{
|
|
addstr(tmpblk.c);
|
|
addch('\n');
|
|
exrefresh();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* report what happened */
|
|
if (chsub == 0)
|
|
{
|
|
msg("Substitution failed");
|
|
}
|
|
|
|
/* tweak for redrawing */
|
|
if (chline > 1 || redrawafter && redrawafter != markline(cursor))
|
|
{
|
|
mustredraw = TRUE;
|
|
}
|
|
|
|
/* free the regexp */
|
|
free(re);
|
|
|
|
/* Reporting */
|
|
if (chline >= *o_report)
|
|
{
|
|
msg("%ld substitutions on %ld lines", chsub, chline);
|
|
}
|
|
rptlines = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
void cmd_delete(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
MARK curs2; /* al altered form of the cursor */
|
|
|
|
/* choose your cut buffer */
|
|
if (*extra == '"')
|
|
{
|
|
extra++;
|
|
}
|
|
if (*extra)
|
|
{
|
|
cutname(*extra);
|
|
}
|
|
|
|
/* make sure we're talking about whole lines here */
|
|
frommark = frommark & ~(BLKSIZE - 1);
|
|
tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
|
|
|
|
/* yank the lines */
|
|
cut(frommark, tomark);
|
|
|
|
/* if CMD_DELETE then delete the lines */
|
|
if (cmd != CMD_YANK)
|
|
{
|
|
curs2 = cursor;
|
|
ChangeText
|
|
{
|
|
/* delete the lines */
|
|
delete(frommark, tomark);
|
|
}
|
|
if (curs2 > tomark)
|
|
{
|
|
cursor = curs2 - tomark + frommark;
|
|
}
|
|
else if (curs2 > frommark)
|
|
{
|
|
cursor = frommark;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void cmd_append(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
long l; /* line counter */
|
|
|
|
ChangeText
|
|
{
|
|
/* if we're doing a change, delete the old version */
|
|
if (cmd == CMD_CHANGE)
|
|
{
|
|
/* delete 'em */
|
|
cmd_delete(frommark, tomark, cmd, bang, extra);
|
|
}
|
|
|
|
/* new lines start at the frommark line, or after it */
|
|
l = markline(frommark);
|
|
if (cmd == CMD_APPEND)
|
|
{
|
|
l++;
|
|
}
|
|
|
|
/* get lines until no more lines, or "." line, and insert them */
|
|
while (vgets('\0', tmpblk.c, BLKSIZE) >= 0)
|
|
{
|
|
addch('\n');
|
|
if (!strcmp(tmpblk.c, "."))
|
|
{
|
|
break;
|
|
}
|
|
|
|
addline(l, tmpblk.c);
|
|
l++;
|
|
}
|
|
}
|
|
|
|
/* on the odd chance that we're calling this from vi mode ... */
|
|
redraw(MARK_UNSET, FALSE);
|
|
}
|
|
|
|
|
|
void cmd_put(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
/* choose your cut buffer */
|
|
if (*extra == '"')
|
|
{
|
|
extra++;
|
|
}
|
|
if (*extra)
|
|
{
|
|
cutname(*extra);
|
|
}
|
|
|
|
/* paste it */
|
|
ChangeText
|
|
{
|
|
cursor = paste(frommark, !bang, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void cmd_join(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
long l;
|
|
char *scan;
|
|
int len; /* length of the new line */
|
|
|
|
/* if only one line is specified, assume the following one joins too */
|
|
if (markline(frommark) == nlines)
|
|
{
|
|
msg("Nothing to join with this line");
|
|
return;
|
|
}
|
|
if (markline(frommark) == markline(tomark))
|
|
{
|
|
tomark = movedown(tomark, 1L);
|
|
}
|
|
|
|
/* get the first line */
|
|
l = markline(frommark);
|
|
strcpy(tmpblk.c, fetchline(l++));
|
|
len = strlen(tmpblk.c);
|
|
|
|
/* build the longer line */
|
|
while (l <= markline(tomark))
|
|
{
|
|
/* get the next line */
|
|
scan = fetchline(l++);
|
|
|
|
/* remove any leading whitespace */
|
|
while (*scan == '\t' || *scan == ' ')
|
|
{
|
|
scan++;
|
|
}
|
|
|
|
/* see if the line will fit */
|
|
if (strlen(scan) + len + 1 > BLKSIZE)
|
|
{
|
|
msg("Can't join -- the resulting line would be too long");
|
|
return;
|
|
}
|
|
|
|
/* catenate it, with a space in between */
|
|
tmpblk.c[len++] = ' ';
|
|
strcpy(tmpblk.c + len, scan);
|
|
len += strlen(scan);
|
|
}
|
|
|
|
/* make the change */
|
|
ChangeText
|
|
{
|
|
frommark &= ~(BLKSIZE - 1);
|
|
tomark &= ~(BLKSIZE - 1);
|
|
tomark += BLKSIZE;
|
|
delete(frommark, tomark);
|
|
addline(markline(frommark), tmpblk.c);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void cmd_shift(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
long l; /* line number counter */
|
|
int oldidx; /* number of chars previously used for indent */
|
|
int newidx; /* number of chars in the new indent string */
|
|
int oldcol; /* previous indent amount */
|
|
int newcol; /* new indent amount */
|
|
char *text; /* pointer to the old line's text */
|
|
|
|
/* figure out how much of the screen we must redraw (for vi mode) */
|
|
if (markline(frommark) != markline(tomark))
|
|
{
|
|
mustredraw = TRUE;
|
|
redrawrange(markline(frommark), markline(tomark) + 1L, markline(tomark) + 1L);
|
|
}
|
|
|
|
ChangeText
|
|
{
|
|
/* for each line to shift... */
|
|
for (l = markline(frommark); l <= markline(tomark); l++)
|
|
{
|
|
/* get the line - ignore empty lines */
|
|
text = fetchline(l);
|
|
if (!*text)
|
|
continue;
|
|
|
|
/* calc oldidx and oldcol */
|
|
for (oldidx = 0, oldcol = 0;
|
|
text[oldidx] == ' ' || text[oldidx] == '\t';
|
|
oldidx++)
|
|
{
|
|
if (text[oldidx] == ' ')
|
|
{
|
|
oldcol += 1;
|
|
}
|
|
else
|
|
{
|
|
oldcol += *o_tabstop - (oldcol % *o_tabstop);
|
|
}
|
|
}
|
|
|
|
/* calc newcol */
|
|
if (cmd == CMD_SHIFTR)
|
|
{
|
|
newcol = oldcol + (*o_shiftwidth & 0xff);
|
|
}
|
|
else
|
|
{
|
|
newcol = oldcol - (*o_shiftwidth & 0xff);
|
|
if (newcol < 0)
|
|
newcol = 0;
|
|
}
|
|
|
|
/* if no change, then skip to next line */
|
|
if (oldcol == newcol)
|
|
continue;
|
|
|
|
/* build a new indent string */
|
|
newidx = 0;
|
|
while (newcol >= *o_tabstop)
|
|
{
|
|
tmpblk.c[newidx++] = '\t';
|
|
newcol -= *o_tabstop;
|
|
}
|
|
while (newcol > 0)
|
|
{
|
|
tmpblk.c[newidx++] = ' ';
|
|
newcol--;
|
|
}
|
|
tmpblk.c[newidx] = '\0';
|
|
|
|
/* change the old indent string into the new */
|
|
change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c);
|
|
}
|
|
}
|
|
|
|
/* Reporting... */
|
|
rptlines = markline(tomark) - markline(frommark) + 1L;
|
|
if (cmd == CMD_SHIFTR)
|
|
{
|
|
rptlabel = ">ed";
|
|
}
|
|
else
|
|
{
|
|
rptlabel = "<ed";
|
|
}
|
|
}
|
|
|
|
|
|
void cmd_read(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
long l; /* line number counter - where new lines go */
|
|
int fd, rc; /* used while reading from the file */
|
|
char *scan; /* used for finding newlines */
|
|
char *line; /* points to the start of a line */
|
|
int prevrc; /* used to detect abnormal EOF */
|
|
|
|
/* first line goes after the selected line */
|
|
l = markline(frommark) + 1;
|
|
|
|
/* open the file */
|
|
fd = open(extra, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
msg("Can't open \"%s\"", extra);
|
|
return;
|
|
}
|
|
|
|
/* get blocks from the file, and add each line in the block */
|
|
ChangeText
|
|
{
|
|
/* NOTE! lint worried needlessly about the order of evaluation
|
|
* of the 'rc' expressions in the test clause of this for(;;){}
|
|
*/
|
|
for (prevrc = rc = 0;
|
|
(rc += read(fd, tmpblk.c + rc, BLKSIZE - rc)) > 0;
|
|
prevrc = rc)
|
|
{
|
|
/* if we couldn't read anything, we damn well better have \n */
|
|
if (prevrc == rc)
|
|
{
|
|
if (rc == BLKSIZE)
|
|
{
|
|
rc--;
|
|
}
|
|
if (tmpblk.c[rc - 1] != '\n' || rc <= 0)
|
|
{
|
|
tmpblk.c[rc++] = '\n';
|
|
}
|
|
}
|
|
|
|
/* for each complete line in this block, add it */
|
|
for (line = scan = tmpblk.c; rc > 0; rc--, scan++)
|
|
{
|
|
if (*scan == '\n')
|
|
{
|
|
*scan = '\0';
|
|
addline(l, line);
|
|
l++;
|
|
line = scan + 1;
|
|
}
|
|
else if (!*scan)
|
|
{
|
|
/* protect against NUL chars in file */
|
|
*scan = 0x80;
|
|
}
|
|
}
|
|
|
|
/* any extra chars are shifted to the start of the buffer */
|
|
rc = scan - line;
|
|
for (scan = tmpblk.c; scan < tmpblk.c + rc; )
|
|
{
|
|
*scan++ = *line++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* close the file */
|
|
close(fd);
|
|
|
|
/* Reporting... */
|
|
rptlines = l - markline(frommark) - 1L;
|
|
rptlabel = "read";
|
|
}
|
|
|
|
|
|
void cmd_list(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
long l; /* line number counter */
|
|
register char *scan; /* used for moving through the line */
|
|
|
|
for (l = markline(frommark); l <= markline(tomark); l++)
|
|
{
|
|
/* list the line */
|
|
scan = fetchline(l);
|
|
|
|
while (*scan)
|
|
{
|
|
/* if the char is non-printable, write it as \000 */
|
|
if (*scan < ' ' || *scan > '~')
|
|
{
|
|
/* build the \000 form & write it */
|
|
addch('\\');
|
|
addch('0' + ((*scan >> 6) & 3));
|
|
addch('0' + ((*scan >> 3) & 7));
|
|
addch('0' + (*scan & 7));
|
|
}
|
|
else
|
|
{
|
|
addch(*scan);
|
|
}
|
|
scan++;
|
|
}
|
|
|
|
/* write a $ and a \n */
|
|
addstr("$\n");
|
|
exrefresh();
|
|
}
|
|
}
|
|
|
|
|
|
void cmd_undo(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
undo();
|
|
}
|
|
|
|
|
|
/* print the selected lines */
|
|
void cmd_print(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
register char *scan;
|
|
register long l;
|
|
|
|
for (l = markline(frommark); l <= markline(tomark); l++)
|
|
{
|
|
/* get the next line */
|
|
scan = fetchline(l);
|
|
addstr(scan);
|
|
addch('\n');
|
|
exrefresh();
|
|
}
|
|
}
|
|
|
|
|
|
/* move or copy selected lines */
|
|
void cmd_move(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
MARK destmark;
|
|
|
|
/* parse the destination linespec. No defaults. Line 0 is okay */
|
|
destmark = cursor;
|
|
if (!strcmp(extra, "0"))
|
|
{
|
|
destmark = 0L;
|
|
}
|
|
else if (linespec(extra, &destmark) == extra || !destmark)
|
|
{
|
|
msg("invalid destination address");
|
|
return;
|
|
}
|
|
|
|
/* flesh the marks out to encompass whole lines */
|
|
frommark &= ~(BLKSIZE - 1);
|
|
tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
|
|
destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE;
|
|
|
|
/* make sure the destination is valid */
|
|
if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark)
|
|
{
|
|
msg("invalid destination address");
|
|
}
|
|
|
|
/* Do it */
|
|
ChangeText
|
|
{
|
|
/* save the text to a cut buffer */
|
|
cutname('\0');
|
|
cut(frommark, tomark);
|
|
|
|
/* if we're not copying, delete the old text & adjust destmark */
|
|
if (cmd != CMD_COPY)
|
|
{
|
|
delete(frommark, tomark);
|
|
if (destmark >= frommark)
|
|
{
|
|
destmark -= (tomark - frommark);
|
|
}
|
|
}
|
|
|
|
/* add the new text */
|
|
paste(destmark, FALSE, FALSE);
|
|
}
|
|
|
|
/* move the cursor to the last line of the moved text */
|
|
cursor = destmark + (tomark - frommark);
|
|
|
|
/* Reporting... */
|
|
rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" );
|
|
}
|
|
|
|
|
|
|
|
/* execute EX commands from a file */
|
|
void cmd_source(frommark, tomark, cmd, bang, extra)
|
|
MARK frommark;
|
|
MARK tomark;
|
|
CMD cmd;
|
|
int bang;
|
|
char *extra;
|
|
{
|
|
/* must have a filename */
|
|
if (!*extra)
|
|
{
|
|
msg("\"source\" requires a filename");
|
|
return;
|
|
}
|
|
|
|
doexrc(extra);
|
|
}
|