This is exedit.c in view mode; [Download] [Up]
/* exedit.c */ /* Copyright 1995 by Steve Kirkendall */ char id_exedit[] = "$Id: exedit.c,v 2.36 1996/10/01 19:46:29 steve Exp $"; #include "elvis.h" /* 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); /* this only works on the window's main buffer */ if (!xinf->window->state->acton) { msg(MSG_ERROR, "not main buffer"); return RESULT_ERROR; } /* different behavior, depending on the command... */ if (xinf->command == EX_CHANGE) { cutyank('\0', xinf->fromaddr, xinf->toaddr, 'L', True); 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)); markaddoffset(where, CHARlen(xinf->rhs)); bufreplace(where, where, toCHAR("\n"), 1); markaddoffset(where, 1); } 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 && !isalnum(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', (BOOLEAN)(xinf->command == EX_DELETE)); return RESULT_COMPLETE; } RESULT ex_global(xinf) EXINFO *xinf; { CHAR *cp; long lenln, endln; RESULT ret = RESULT_COMPLETE; MARK cursor; MARK orig; MARKBUF thisln; assert(xinf->command == EX_GLOBAL || xinf->command == EX_VGLOBAL); /* a command is required */ if (!xinf->rhs) { msg(MSG_ERROR, "[s]$1 requires a command", xinf->cmdname); return RESULT_ERROR; } /* only works when applied to window's main buffer */ if (!xinf->window->state->acton || xinf->window->state->acton->acton) { msg(MSG_ERROR, "[s]$1 only works on main buffer", xinf->cmdname); return RESULT_ERROR; } /* ":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; if (markbuffer(cursor) != markbuffer(xinf->fromaddr)) { marksetbuffer(cursor, markbuffer(xinf->fromaddr)); marksetoffset(cursor, markoffset(xinf->fromaddr)); } orig = markdup(cursor); /* 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; marksetoffset(xinf->fromaddr, cp ? markoffset(scanmark(&cp)) : markoffset(xinf->toaddr)); /* is this a selected line? */ if ((regexec(xinf->re, &thisln, True) ? EX_GLOBAL : EX_VGLOBAL) == xinf->command) { /* move the cursor to the matching line */ endln = markoffset(scanmark(&cp)); 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); /* 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(False)) break; } scanfree(&cp); /* 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 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->from == xinf->to) ? 1 : (xinf->to - xinf->from); if (xinf->from + newlines > o_buflines(markbuffer(xinf->fromaddr))) { msg(MSG_ERROR, "nothing to join with this line"); return RESULT_ERROR;; } /* 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 == ')' || isspace(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 != ')') || isspace(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); return RESULT_COMPLETE; } RESULT ex_move(xinf) EXINFO *xinf; { long oldto; /* detect errors */ if (markbuffer(xinf->fromaddr) == markbuffer(xinf->destaddr) && markoffset(xinf->fromaddr) <= markoffset(xinf->destaddr) && markoffset(xinf->destaddr) < markoffset(xinf->toaddr)) { msg(MSG_ERROR, "destination can't be inside source"); return RESULT_ERROR; } /* copy the text */ xinf->newcurs = markdup(xinf->destaddr); markaddoffset(xinf->newcurs, 1); oldto = markoffset(xinf->toaddr); bufpaste(xinf->destaddr, xinf->fromaddr, xinf->toaddr); /* leave the cursor on the last line of the destination */ if (markoffset(xinf->newcurs) > 1) markaddoffset(xinf->newcurs, -2); marksetoffset(xinf->newcurs, markoffset( (*dmnormal.move)(xinf->window, xinf->newcurs, 0L, 0L, False))); /* If moving (not copying) then delete source */ if (xinf->command == EX_MOVE) { /* be careful about the "to" offset. If the destination was * immediately after the source, then we just inserted the * text at "to", so "to" was adjusted... but we only want to * delete up to the old value of "to". */ if (markbuffer(xinf->toaddr) == markbuffer(xinf->destaddr) && markoffset(xinf->toaddr) == markoffset(xinf->destaddr)) { 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 ? (xinf->command == EX_LIST ? PF_NUMLIST : PF_NUMBER) : (xinf->command == EX_LIST ? PF_LIST : PF_PRINT)); break; case PF_LIST: pflag = (xinf->command == EX_NUMBER ? PF_NUMLIST : PF_LIST); break; case PF_NUMBER: pflag = (xinf->command == EX_LIST ? PF_NUMLIST : PF_NUMBER); break; /* !!it B4: this break seems to be needed here */ default: pflag = PF_NUMLIST; break; } /* print the lines */ last = exprintlines(xinf->window, xinf->fromaddr, xinf->to - xinf->from + 1, pflag); /* 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, (BOOLEAN)(xinf->from > 0), False, False); 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->state->flags & ELVIS_1LINE) { marksetoffset(xinf->newcurs, offset); } else { marksetoffset(xinf->newcurs, markoffset( (*xinf->window->md->move)(xinf->window, xinf->newcurs, -1, 0, False))); } 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 shift; /* amount to shift by */ 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 */ long i; /* compute the amount of shifting required */ shift = o_shiftwidth(markbuffer(&xinf->defaddr)) * xinf->multi; if (xinf->command == EX_SHIFTL) { shift = -shift; } /* for each line... */ start = xinf->defaddr; 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 = ws + o_tabstop(markbuffer(&xinf->defaddr)) - ws % o_tabstop(markbuffer(&xinf->defaddr)); } } 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; } scanfree(&cp); /* compute the amount of whitespace we want to have */ ws += shift; if (ws < 0) { ws = 0; } /* 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. */ while (markoffset(&start) != markoffset(&end) || ws > 0) { /* build new whitespace (as much of it as possible) */ i = 0; if (o_autotab(markbuffer(&xinf->defaddr))) { for (; ws >= o_tabstop(markbuffer(&xinf->defaddr)) && i < QTY(str); i++, ws -= o_tabstop(markbuffer(&xinf->defaddr))) { str[i] = '\t'; } } for (; ws > 0 && i < QTY(str); i++, ws--) { str[i] = ' '; } /* replace old whitespace with new */ bufreplace(&start, &end, str, i); markaddoffset(&start, i); marksetoffset(&end, markoffset(&start)); } } if (xinf->to - xinf->from + 1 >= o_report) /* !!it E5: report on shift */ msg(MSG_INFO,"[dC]$1 lines $2ed",xinf->to - xinf->from + 1,(xinf->command == EX_SHIFTL)? '<' : '>'); return RESULT_COMPLETE; } /* This function implements the :substitute command, and the :& and :~ * variations of that command. It is also used to perform the real work * of visual <&> command. */ RESULT ex_substitute(xinf) EXINFO *xinf; { CHAR *opt; /* substitution options */ long chline; /* # of lines changed */ long chsub; /* # of substitutions made */ long cursoff;/* offset where cursor should be moved to */ static PFLAG pflag; /* printing flag */ static BOOLEAN optg; /* boolean option: substitute globally in line? */ static BOOLEAN optx; /* boolean option: execute instead of substitute? */ static long count; /* numeric option: which instance in each line to sub */ MARKBUF posn; /* position within a line to be replaced */ long instance;/* number of instances matched so far within line */ CHAR *newp; /* replacement text */ CHAR *scan; /* used for scanning to find end of line */ int match; assert(xinf->command == EX_SUBSTITUTE || xinf->command == EX_SUBAGAIN); /* initialize "cursoff" just to silence a compiler warning */ cursoff = 0; /* ":s" is equivalent to ":&". */ if (!xinf->re) xinf->command = EX_SUBAGAIN; if (xinf->command == EX_SUBAGAIN) { /* same regular expression as last time */ xinf->re = regcomp(toCHAR(""), xinf->window->state->cursor); if (!xinf->re) { /* error message already given by regcomp() */ return RESULT_ERROR; } /* same replacement text as last time */ newp = regtilde(toCHAR(o_magic ? "~" : "\\~")); /* if visual "&", then turn off the "p" and "c" options */ if (xinf->bang) { pflag= PF_NONE; } } else /* xinf->command == CMD_SUBSTITUTE */ { /* generate the new text */ newp = regtilde(xinf->lhs ? xinf->lhs : toCHAR("")); /* analyse the option string */ if (!o_edcompatible) { pflag = xinf->pflag; optg = optx = False; count = 0; } for (opt = xinf->rhs; opt && *opt; opt++) { switch (*opt) { case 'g': optg = (BOOLEAN)!optg; break; case 'x': optx = (BOOLEAN)!optx; break; case 'p': pflag = (pflag==PF_PRINT) ? PF_PRINT : PF_NONE; break; case 'l': pflag = (pflag==PF_LIST) ? PF_LIST : PF_NONE; break; case '#': pflag = (pflag==PF_NUMBER) ? PF_NUMBER : PF_NONE; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': count = 0; do { count = count * 10 + *opt++ - '0'; } while (isdigit(*opt)); opt--; break; default: if (!isspace(*opt)) { msg(MSG_ERROR, "[C]unsupported flag '$1'", *opt); return RESULT_ERROR; } } } /* sanity checks */ if (count != 0 && optg) { msg(MSG_ERROR, "can't mix number and 'g' flag"); return RESULT_ERROR; } /* default behavior is to either replace only first instance, * or all instances, depending on the "gdefault" option. */ if (count == 0 && !optg) { if (o_gdefault && !o_edcompatible) optg = True; else count = 1; } } /* if no replacement text, fail */ if (!newp) { return RESULT_ERROR; } /* this command does its own printing; disable auto printing */ xinf->pflag = PF_NONE; /* reset the change counters */ chline = chsub = 0L; /* for each line in the range... */ for (posn = *xinf->fromaddr; markoffset(&posn) < markoffset(xinf->toaddr); ) { /* for each instance within the line... */ for (instance = 0, match = regexec(xinf->re, &posn, True); match && (optg || instance < count); match = regexec(xinf->re, &posn, False)) { /* increment the substitution change counter */ chsub++; instance++; /* if this is an instance we care about... */ if (optg || instance == count) { /* Either execute the replacement, or perform * the substitution */ opt = regsub(xinf->re, newp, (BOOLEAN)!optx); if (!opt) { return RESULT_ERROR; } if (optx) { exstring(xinf->window, opt); } safefree(opt); /* remember the offset of this change so we can * move the cursor there later. */ cursoff = xinf->re->startp[0]; } /* Move "posn" to the end of the matched region. If * the regexp could conceivably match a zero-length * string, then skip one character. */ marksetoffset(&posn, xinf->re->endp[0]); if (xinf->re->minlen == 0) { /* markaddoffset(&posn, 1);*/ if (scanchar(&posn) == '\n') break; } } /* if any changes were made, then increment chline */ if (optg ? instance > 0 : instance == count) { chline++; } /* scan forward for the end of the line */ for (scanalloc(&scan, &posn); scan && *scan != '\n'; scannext(&scan)) { } if (scan) scannext(&scan); if (scan) posn = *scanmark(&scan); else marksetoffset(&posn, o_bufchars(markbuffer(xinf->fromaddr))); scanfree(&scan); } /* If done from within a ":g" command, or used with "x" flag, * then finish silently. */ if (xinf->global || optx) { #if 0 rptlines = chline; rptlabel = "changed"; #endif return RESULT_COMPLETE; } /* Reporting */ if (chsub == 0) { msg(MSG_WARNING, "substitution failed"); } else if (chline >= o_report) { msg(MSG_INFO, "[dd]$1 substitutions on $2 lines", chsub, chline); xinf->newcurs = markalloc(xinf->re->buffer, cursoff); } 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; BOOLEAN success; if (xinf->rhs) { name = tochar8(xinf->rhs); } else if (xinf->nfiles >= 1) { assert(xinf->nfiles == 1); name = xinf->file[0]; } else { name = tochar8(o_filename(markbuffer(xinf->fromaddr))); if (!name) { msg(MSG_ERROR, "no file name"); return RESULT_ERROR; /* nishi */ } } /* if writing to a different filename, remember that name */ if (name[0] != '!' && o_filename(markbuffer(xinf->fromaddr)) && CHARcmp(name, o_filename(markbuffer(xinf->fromaddr)))) { optprevfile(toCHAR(name), 1); } /* actually write the file */ success = bufwrite(xinf->fromaddr, xinf->toaddr, name, xinf->bang); return success ? RESULT_COMPLETE : RESULT_ERROR; } RESULT ex_z(xinf) EXINFO *xinf; { CHAR type = '+'; /* type of window to show */ long count = o_window; /* number of lines to show */ PFLAG pflag = PF_PRINT; /* how to show */ long line = xinf->from; /* first line to show */ long offset; CHAR *scan; /* 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; isdigit(*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; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.