Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

809 lines (695 sloc) 19.741 kB
/* exedit.c */
/* Copyright 1995 by Steve Kirkendall */
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_exedit[] = "$Id: exedit.c,v 2.71 2004/03/19 16:28:59 steve Exp $";
#endif
/* This command implements the :insert, :append, and :change commands */
RESULT ex_append(xinf)
EXINFO *xinf;
{
MARK where;
assert(xinf->command == EX_APPEND || xinf->command == EX_CHANGE
|| xinf->command == EX_INSERT);
/* different behavior, depending on the command... */
if (xinf->command == EX_CHANGE)
{
cutyank('\0', xinf->fromaddr, xinf->toaddr, 'L', 'd');
where = markdup(xinf->fromaddr);
}
else if (xinf->command == EX_APPEND)
{
where = markdup(xinf->toaddr);
}
else
{
where = markdup(xinf->fromaddr);
}
/* Was the new text given as an argument to the command? */
if (xinf->rhs)
{
/* yes, insert the new text at the appropriate place */
bufreplace(where, where, xinf->rhs, (long)CHARlen(xinf->rhs));
}
xinf->newcurs = where;
return RESULT_COMPLETE;
}
/* This function implements the :delete and :yank command */
RESULT ex_delete(xinf)
EXINFO *xinf;
{
/* check the cut buffer name */
if (xinf->cutbuf && !elvalnum(xinf->cutbuf) &&
xinf->cutbuf != '^' && xinf->cutbuf != '<' && xinf->cutbuf != '>')
{
msg(MSG_ERROR, "bad cut buffer");
return RESULT_ERROR;
}
/* do the command */
cutyank(xinf->cutbuf, xinf->fromaddr, xinf->toaddr, (CHAR)'L', (ELVBOOL)(xinf->command == EX_DELETE) ? 'd' : 'y');
return RESULT_COMPLETE;
}
RESULT ex_global(xinf)
EXINFO *xinf;
{
CHAR *cp;
long lenln, endln;
RESULT ret = RESULT_COMPLETE;
MARK cursor;
MARK orig;
MARKBUF thisln;
long origlines;
assert(xinf->command == EX_GLOBAL || xinf->command == EX_VGLOBAL);
/* Default command is p, which should NOT be required */
if (!xinf->rhs)
xinf->rhs = CHARdup(toCHAR("p"));
/* ":g!" is like ":v" */
if (xinf->bang)
xinf->command = EX_VGLOBAL;
/* remember the cursor's original position. Inside the following loop,
* we'll force the cursor onto each matching line; when we're done,
* we'll move the cursor back and return the final position so the
* experform() function can move the cursor there in the conventional
* way -- that will be important if we switch buffers.
*/
if (xinf->window->state->acton)
cursor = xinf->window->state->acton->cursor;
else
cursor = xinf->window->cursor;
orig = markdup(cursor);
if (markbuffer(cursor) != markbuffer(xinf->fromaddr))
{
marksetbuffer(cursor, markbuffer(xinf->fromaddr));
marksetoffset(cursor, markoffset(xinf->fromaddr));
}
/* remember the number of lines before any changes... */
origlines = o_buflines(markbuffer(xinf->fromaddr));
#ifdef FEATURE_AUTOCMD
/* we want to trigger Edit events for change, even though we only save
* an undo version before the first change.
*/
markbuffer(cursor)->eachedit = ElvTrue;
#endif
/* for each line... */
(void)scanalloc(&cp, xinf->fromaddr);
ret = RESULT_COMPLETE;
while (cp && markoffset(xinf->fromaddr) < markoffset(xinf->toaddr))
{
/* find the end of this line */
for (lenln = 0; cp && *cp != '\n'; lenln++)
(void)scannext(&cp);
if (cp)
{
(void)scannext(&cp);
lenln++;
}
/* move "fromaddr" to end of this line (start of next line) */
thisln = *xinf->fromaddr;
endln = cp ? markoffset(scanmark(&cp)) : markoffset(xinf->toaddr);
marksetoffset(xinf->fromaddr, endln);
/* is this a selected line? */
if ((regexec(xinf->re, &thisln, ElvTrue) ? EX_GLOBAL : EX_VGLOBAL)
== xinf->command)
{
/* move the cursor to the matching line */
marksetoffset(cursor, endln - lenln);
/* free the scan pointer -- can't make changes
* while scanning.
*/
scanfree(&cp);
/* execute the command */
ret = exstring(xinf->window, xinf->rhs, NULL);
/* reallocate the scan pointer */
(void)scanalloc(&cp, xinf->fromaddr);
/* if the ex command failed, then exit */
if (ret != RESULT_COMPLETE)
break;
}
/* if user wants to abort operation, then exit */
if (guipoll(ElvFalse))
break;
}
scanfree(&cp);
#ifdef FEATURE_AUTOCMD
/* Revert to normal behavior of 1 Edit per "undo" version */
markbuffer(cursor)->eachedit = ElvTrue;
#endif
/* report any change in the number of lines */
if (o_report != 0)
{
origlines -= o_buflines(markbuffer(cursor));
if (origlines >= o_report)
msg(MSG_INFO, "[d]$1 fewer lines", origlines);
else if (-origlines >= o_report)
msg(MSG_INFO, "[d]$1 more lines", -origlines);
}
/* move the cursor back to its original position, and then return
* the final position so the cursor will be moved there in a graceful
* way.
*/
xinf->newcurs = markdup(cursor);
if (markbuffer(cursor) != markbuffer(orig))
marksetbuffer(cursor, markbuffer(orig));
marksetoffset(cursor, markoffset(orig));
markfree(orig);
return ret;
}
RESULT ex_join(xinf)
EXINFO *xinf;
{
CHAR prevchar; /* character before newline */
CHAR *cp; /* used while scanning for newlines */
long njoined; /* number of lines joined */
long newlines; /* number of newlines to be clobbered */
long nspaces; /* number of spaces to insert */
MARK start, end; /* region around a newline */
long offset; /* position of last change */
CHAR *endlist; /* string of sentence ending punctuation */
static CHAR spaces[3] = {' ', ' ', ' '};
/* initialize "offset" just to silence a compiler warning */
offset = 0;
/* initialize endlist from options (if set) or literals */
endlist = (o_sentenceend ? o_sentenceend : toCHAR(".!?"));
/* We're going to be replacing newlines with blanks. The number of
* newlines we want to replace is equal to the number lines affected
* minus one... except that if the user requested a "join" of a single
* line then we should assume we're supposed to join two lines.
*/
newlines = xinf->to - xinf->from;
if (newlines == 0)
newlines = 1;
if (xinf->from + newlines > o_buflines(markbuffer(xinf->fromaddr)))
{
msg(MSG_ERROR, "nothing to join with this line");
return RESULT_ERROR;;
}
njoined = newlines + 1;
/* scan the text for newlines */
prevchar = ' ';
for (scanalloc(&cp, xinf->fromaddr); cp && newlines > 0; scannext(&cp))
{
/* if newline, then clobber it */
if (*cp == '\n')
{
start = markdup(scanmark(&cp));
offset = markoffset(start);
if (scannext(&cp))
{
/* figure out how many spaces to insert */
if (xinf->bang || *cp == ')' || elvspace(prevchar))
nspaces = 0;
else if (CHARchr(toCHAR(endlist), prevchar))
nspaces = o_sentencegap;
else
nspaces = 1;
/* skip any leading whitespace in next line */
while (!xinf->bang && cp && (*cp == ' ' || *cp == '\t'))
{
scannext(&cp);
}
/* Find the end mark */
end = (cp ? markdup(scanmark(&cp))
: markalloc(markbuffer(start), o_bufchars(markbuffer(start))));
/* free the scan context during the change;
* can't mix scanning & updates.
*/
scanfree(&cp);
/* replace the newline (and trailing whitespace)
* with a given number of spaces.
*/
bufreplace(start, end, spaces, nspaces);
marksetoffset(end, offset + nspaces - 1);
/* NOTE: the "- 1" is to compensate for
* the scannext() at the top of the loop
*/
/* resume scanning */
scanalloc(&cp, end);
markfree(end);
if (nspaces > 0)
{
prevchar = ' ';
}
}
markfree(start);
/* All that to clobber one newline! */
newlines--;
}
else if ((*cp != '"' && *cp != ')') || elvspace(prevchar))
{
/* remember the character. When we hit a newline,
* the previous character will be checked to determine
* how many spaces to insert. Note that we ignore
* quote and parenthesis characters except after a
* blank.
*/
prevchar = *cp;
}
}
scanfree(&cp);
/* Choose a new cursor position: at the start of last joint */
xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), offset);
/* Report it */
if (o_report != 0 && njoined >= o_report)
{
msg(MSG_INFO, "[d]$1 lines joined", njoined);
}
return RESULT_COMPLETE;
}
RESULT ex_move(xinf)
EXINFO *xinf;
{
long oldfrom, oldto, olddest, len;
/* detect errors */
oldfrom = markoffset(xinf->fromaddr);
oldto = markoffset(xinf->toaddr);
olddest = markoffset(xinf->destaddr);
if (markbuffer(xinf->fromaddr) == markbuffer(xinf->destaddr)
&& oldfrom < olddest
&& olddest < oldto)
{
msg(MSG_ERROR, "destination can't be inside source");
return RESULT_ERROR;
}
/* copy the text. Adjust the offsets explicitly */
bufpaste(xinf->destaddr, xinf->fromaddr, xinf->toaddr);
len = oldto - oldfrom;
if (olddest <= oldfrom)
{
oldfrom += len;
oldto += len;
}
/* leave the cursor on the last line of the destination */
xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), olddest+len-1);
marksetoffset(xinf->newcurs, markoffset(
(*dmnormal.move)(xinf->window, xinf->newcurs, 0L, 0L, ElvFalse)));
/* If moving (not copying) then delete source */
if (xinf->command == EX_MOVE)
{
marksetoffset(xinf->fromaddr, oldfrom);
marksetoffset(xinf->toaddr, oldto);
bufreplace(xinf->fromaddr, xinf->toaddr, NULL, 0);
}
return RESULT_COMPLETE;
}
RESULT ex_print(xinf)
EXINFO *xinf;
{
long last; /* offset of start of last line */
PFLAG pflag; /* how to print */
/* generate a pflag from the command name and any supplied pflag */
switch (xinf->pflag)
{
case PF_NONE:
case PF_PRINT:
pflag = (xinf->command == EX_NUMBER || o_number(xinf->window))
? ((xinf->command == EX_LIST || o_list(xinf->window)) ? PF_NUMLIST : PF_NUMBER)
: ((xinf->command == EX_LIST || o_list(xinf->window)) ? PF_LIST : PF_PRINT);
break;
case PF_LIST:
pflag = (xinf->command == EX_NUMBER || o_number(xinf->window)) ? PF_NUMLIST : PF_LIST;
break;
case PF_NUMBER:
pflag = (xinf->command == EX_LIST || o_list(xinf->window))? PF_NUMLIST : PF_NUMBER;
break;
default:
pflag = PF_NUMLIST;
break;
}
/* print the lines */
last = exprintlines(xinf->window, xinf->fromaddr, xinf->to - xinf->from + 1, pflag);
/* disable autoprinting for this command */
xinf->pflag = PF_NONE;
/* leave the cursor at the start of the last line */
xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), last);
return RESULT_COMPLETE;
}
RESULT ex_put(xinf)
EXINFO *xinf;
{
MARK newcurs;
newcurs = cutput(xinf->cutbuf, xinf->window, xinf->fromaddr, (ELVBOOL)(xinf->from > 0), ElvFalse, ElvFalse);
if (newcurs)
{
xinf->newcurs = markdup(newcurs);
return RESULT_COMPLETE;
}
return RESULT_ERROR;
}
RESULT ex_read(xinf)
EXINFO *xinf;
{
long offset;
if (!xinf->rhs && xinf->nfiles != 1)
{
msg(MSG_ERROR, "filename required");
return RESULT_ERROR;
}
/* remember where we started inserting */
offset = markoffset(xinf->toaddr);
xinf->newcurs = markdup(xinf->toaddr);
/* read in the text */
if (!bufread(xinf->toaddr, xinf->rhs ? tochar8(xinf->rhs) : xinf->file[0]))
{
return RESULT_ERROR;
}
/* Choose a place to leave the cursor. If reading due to visual <:>
* command, this should be the start of the first line read; else it
* should be the start of the last line read.
*/
if (xinf->window)
{
if (xinf->window->state->flags & ELVIS_1LINE)
{
marksetoffset(xinf->newcurs, offset);
}
else
{
marksetoffset(xinf->newcurs, markoffset(
(*xinf->window->md->move)(xinf->window, xinf->newcurs, -1, 0, ElvFalse)));
}
}
return RESULT_COMPLETE;
}
/* This function implements the :< and :> commands. It is also used to do
* the real work for the visual <<> and <>> operators.
*/
RESULT ex_shift(xinf)
EXINFO *xinf;
{
long ws; /* amount of whitespace currently */
CHAR *cp; /* used for scanning through a line's whitespace */
long line; /* used for counting through line numbers */
MARKBUF start; /* start of the line */
MARKBUF end; /* end of the line's whitespace */
CHAR str[50];/* buffer for holding whitespace */
short *sw; /* shiftwidth table */
short *ts; /* tabstop table */
long i, col;
long multi;
/* for each line... */
start = xinf->defaddr;
sw = o_shiftwidth(markbuffer(&start));
ts = o_tabstop(markbuffer(&start));
for (line = xinf->from; line <= xinf->to; line++)
{
/* count the current whitespace */
scanalloc(&cp, marksetline(&start, line));
for (ws = 0; cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
{
if (*cp == ' ')
ws++;
else
ws += opt_totab(ts, ws);
}
end = *scanmark(&cp);
/* if this is an empty line, and no ! was given on the command
* line, then do nothing to this line.
*/
if (ws == 0 && *cp == '\n' && !xinf->bang)
{
scanfree(&cp);
continue;
}
/* if this is a preprocessor line, then don't shift it
* unless "!" was given.
*/
if (!xinf->bang
&& xinf->window
&& *cp == dmspreprocessor(xinf->window))
{
scanfree(&cp);
continue;
}
scanfree(&cp);
/* compute the amount of whitespace we want to have */
if (xinf->bang)
{
/* For the ^T/^D commands, align the text with the
* next shift column.
*/
if (xinf->command == EX_SHIFTL)
{
do
{
ws--;
} while (ws > 0 && !opt_istab(sw, ws));
}
else
ws += opt_totab(sw, ws);
}
else if (xinf->command == EX_SHIFTL)
{
for (multi = xinf->multi; ws > 0 && --multi >= 0; )
{
/* For the :< command, or < visual
* operator, subtract this columns width
* unless we're on the first char of the
* column, in which case use the width of
* the previous column.
*/
for (col = ws - 1; col > 0 && !opt_istab(sw, col); col--)
{
}
ws -= opt_totab(sw, col);
}
if (ws < 0)
ws = 0;
}
else /* xinf->command == EX_SHIFTR */
{
for (multi = xinf->multi; --multi >= 0; )
{
/* For the :> command, or > visual
* operator, add this shift column's width.
* To do that, we must first find its
* width.
*/
for (col = ws; col > 0 && !opt_istab(sw, col); col--)
{
}
ws += opt_totab(sw, col);
}
}
/* Replace the old whitespace with new whitespace. Since our
* buffer for holding new whitespace is of limited size, we
* may need to make several bufreplace() calls to do this.
*/
col = 0;
while (markoffset(&start) != markoffset(&end) || ws > col)
{
/* build new whitespace (as much of it as possible) */
i = 0;
if (o_autotab(markbuffer(&xinf->defaddr)))
{
while (ws >= col + opt_totab(ts, col)
&& i < QTY(str))
{
str[i++] = '\t';
col += opt_totab(ts, col);
}
}
while (ws > col && i < QTY(str))
{
str[i++] = ' ';
col++;
}
/* replace old whitespace with new */
bufreplace(&start, &end, str, i);
markaddoffset(&start, i);
marksetoffset(&end, markoffset(&start));
}
}
if (o_report != 0 && xinf->to - xinf->from + 1 >= o_report)
msg(MSG_INFO,"[ds]$1 lines $2ed",
xinf->to - xinf->from + 1, xinf->cmdname);
return RESULT_COMPLETE;
}
RESULT ex_undo(xinf)
EXINFO *xinf;
{
long l = 1;
assert(xinf->command == EX_UNDO || xinf->command == EX_REDO);
/* choose an undo/redo level to recover */
if (xinf->lhs)
{
if (!calcnumber(xinf->lhs) || (l = CHAR2long(xinf->lhs)) < 1)
{
msg(MSG_ERROR, "bad undo level");
return RESULT_ERROR;
}
}
/* if redo, then negate the undo value */
if (xinf->command == EX_REDO)
l = -l;
/* try to revert to the undo level */
l = bufundo(xinf->window->cursor, l);
/* if successful, adjust the cursor position */
if (l >= 0)
{
marksetoffset(xinf->window->cursor, l);
return RESULT_COMPLETE;
}
return RESULT_ERROR;
}
RESULT ex_write(xinf)
EXINFO *xinf;
{
char *name;
ELVBOOL success;
if (xinf->rhs)
{
name = tochar8(xinf->rhs);
}
else
{
assert(xinf->nfiles == 1);
name = xinf->file[0];
}
/* if writing to a different filename, remember that name */
if (name[0] != '!'
&& o_filename(markbuffer(xinf->fromaddr))
&& CHARcmp(name, o_filename(markbuffer(xinf->fromaddr))))
{
if (name[0] == '>' && name[1] == '>')
optprevfile(toCHAR(name + 2), 1);
else
optprevfile(toCHAR(name), 1);
}
/* actually write the file */
success = bufwrite(xinf->fromaddr, xinf->toaddr, name, xinf->bang);
return success ? RESULT_COMPLETE : RESULT_ERROR;
}
#ifdef FEATURE_MISC
RESULT ex_z(xinf)
EXINFO *xinf;
{
CHAR type = '+'; /* type of window to show */
long count = o_window; /* number of lines to show */
PFLAG pflag; /* how to show */
long line = xinf->from; /* first line to show */
long offset;
CHAR *scan;
/* choose a default printing style, from options */
if (o_number(xinf->window))
if (o_list(xinf->window))
pflag = PF_NUMLIST;
else
pflag = PF_NUMBER;
else
if (o_list(xinf->window))
pflag = PF_LIST;
else
pflag = PF_NUMBER;
/* If we were given arguments, then parse them */
for (scan = xinf->rhs; scan && *scan; scan++)
{
switch (*scan)
{
case '-':
case '+':
case '.':
case '^':
case '=':
type = *scan;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
for (count = 0; elvdigit(*scan); scan++)
{
count = count * 10 + *scan - '0';
}
scan--; /* we went one character too far */
break;
case 'l':
if (pflag == PF_NUMBER || pflag == PF_NUMLIST)
pflag = PF_NUMLIST;
else
pflag = PF_LIST;
break;
case '#':
if (pflag == PF_LIST || pflag == PF_NUMLIST)
pflag = PF_NUMLIST;
else
pflag = PF_NUMBER;
break;
case ' ':
case '\t':
case 'p':
/* ignore */
break;
default:
msg(MSG_ERROR, "bad argument to :z");
return RESULT_ERROR;
}
}
/* choose the first line, based on the type */
switch (type)
{
case '-': /* show the given line at the bottom */
line = xinf->from - count + 1;
break;
case '+': /* show the given line at the top */
line = xinf->from;
break;
case '.': /* show the given line in the middle */
line = xinf->from - count/2;
break;
case '^': /* show the window before the current line */
line = xinf->from - count * 2 + 1;
break;
case '=': /* show it in the middle, surrounded by lines of hyphens */
count -= 2;
line = xinf->from - count / 2;
break;
}
/* protect against readed past top or bottom of buffer */
if (line < 1)
{
count -= 1 - line;
line = 1;
}
if (line + count > o_buflines(markbuffer(xinf->fromaddr)))
{
count -= line - o_buflines(markbuffer(xinf->fromaddr));
}
/* construct a mark for the first line */
xinf->newcurs = markdup(xinf->fromaddr);
marksetline(xinf->newcurs, line);
/* print the lines */
if (type == '=')
{
/* for '=', we need to add lines of hyphens around given line */
if (line < xinf->from)
{
exprintlines(xinf->window, xinf->newcurs, xinf->from - line, pflag);
}
drawextext(xinf->window, toCHAR("-------------------------------------------------------------------------------\n"), 80);
exprintlines(xinf->window, xinf->fromaddr, 1, pflag);
drawextext(xinf->window, toCHAR("-------------------------------------------------------------------------------\n"), 80);
count -= (xinf->from - line) + 1;
if (count > 0)
{
marksetline(xinf->newcurs, xinf->from + 1);
exprintlines(xinf->window, xinf->newcurs, count, pflag);
}
/* leave the cursor on the given line */
marksetoffset(xinf->newcurs, markoffset(xinf->fromaddr));
}
else
{
/* print the lines all at once */
offset = exprintlines(xinf->window, xinf->newcurs, count, pflag);
/* leave the cursor at the start of the last line */
marksetoffset(xinf->newcurs, offset);
}
return RESULT_COMPLETE;
}
#endif /* FEATURE_MISC */
Jump to Line
Something went wrong with that request. Please try again.