/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1981 Regents of the University of California */ #include "ex.h" #include "ex_tty.h" #include "ex_vis.h" #ifndef PRESUNEUC #include /* Undef putchar/getchar if they're defined. */ #ifdef putchar # undef putchar #endif #ifdef getchar # undef getchar #endif #endif /* PRESUNEUC */ extern size_t strlcpy(char *, const char *, size_t); /* * Low level routines for operations sequences, * and mostly, insert mode (and a subroutine * to read an input line, including in the echo area.) */ extern unsigned char *vUA1, *vUA2; /* extern; also in ex_vops.c */ extern unsigned char *vUD1, *vUD2; /* extern; also in ex_vops.c */ #ifdef XPG6 /* XPG6 assertion 313 & 254 [count]r\n : Also used in ex_vmain.c */ extern int redisplay; #endif int vmaxrep(unsigned char, int); static void imultlinerep(int, line *, int, int); static void omultlinerep(int, line *, int); #ifdef XPG6 static void rmultlinerep(int, int); #endif void fixdisplay(void); /* * Obleeperate characters in hardcopy * open with \'s. */ void bleep(int i, unsigned char *cp) { i -= lcolumn(nextchr(cp)); do putchar('\\' | QUOTE); while (--i >= 0); rubble = 1; } /* * Common code for middle part of delete * and change operating on parts of lines. */ int vdcMID(void) { unsigned char *cp; squish(); setLAST(); if (FIXUNDO) vundkind = VCHNG, CP(vutmp, linebuf); if (wcursor < cursor) cp = wcursor, wcursor = cursor, cursor = cp; vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor; /* * XPG6 assertion 273: Set vmcurs so that undo positions the * cursor column correctly when we've moved off the initial line * that was changed, as with the C, c, and s commands, * when G has moved us off the line, or when a * multi-line change was done. */ fixundo(); return (lcolumn(wcursor)); } /* * Take text from linebuf and stick it * in the VBSIZE buffer BUF. Used to save * deleted text of part of line. */ void takeout(unsigned char *BUF) { unsigned char *cp; if (wcursor < linebuf) wcursor = linebuf; if (cursor == wcursor) { (void) beep(); return; } if (wcursor < cursor) { cp = wcursor; wcursor = cursor; cursor = cp; } setBUF(BUF); if ((unsigned char)BUF[128] == 0200) (void) beep(); } /* * Are we at the end of the printed representation of the * line? Used internally in hardcopy open. */ int ateopr(void) { wchar_t i, c; wchar_t *cp = vtube[destline] + destcol; for (i = WCOLS - destcol; i > 0; i--) { c = *cp++; if (c == 0) { /* * Optimization to consider returning early, saving * CPU time. We have to make a special check that * we aren't missing a mode indicator. */ if (destline == WECHO && destcol < WCOLS-11 && vtube[WECHO][WCOLS-20]) return 0; return (1); } if (c != ' ' && (c & QUOTE) == 0) return (0); } return (1); } /* * Append. * * This routine handles the top level append, doing work * as each new line comes in, and arranging repeatability. * It also handles append with repeat counts, and calculation * of autoindents for new lines. */ bool vaifirst; bool gobbled; unsigned char *ogcursor; static int INSCDCNT; /* number of ^D's (backtabs) in insertion buffer */ static int inscdcnt; /* * count of ^D's (backtabs) not seen yet when doing * repeat of insertion */ void vappend(int ch, int cnt, int indent) { int i; unsigned char *gcursor; bool escape; int repcnt, savedoomed; short oldhold = hold; int savecnt = cnt; line *startsrcline; int startsrccol, endsrccol; int gotNL = 0; int imultlinecnt = 0; int omultlinecnt = 0; if ((savecnt > 1) && (ch == 'o' || ch == 'O')) { omultlinecnt = 1; } #ifdef XPG6 if ((savecnt > 1) && (ch == 'a' || ch == 'A' || ch == 'i' || ch == 'I')) imultlinecnt = 1; #endif /* XPG6 */ /* * Before a move in hardopen when the line is dirty * or we are in the middle of the printed representation, * we retype the line to the left of the cursor so the * insert looks clean. */ if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) { rubble = 1; gcursor = cursor; i = *gcursor; *gcursor = ' '; wcursor = gcursor; (void) vmove(); *gcursor = i; } /* * If vrep() passed indent = 0, this is the 'r' command, * so don't autoindent until the last char. */ vaifirst = indent == 0; /* * Handle replace character by (eventually) * limiting the number of input characters allowed * in the vgetline routine. */ if (ch == 'r') repcnt = 2; else repcnt = 0; /* * If an autoindent is specified, then * generate a mixture of blanks to tabs to implement * it and place the cursor after the indent. * Text read by the vgetline routine will be placed in genbuf, * so the indent is generated there. */ if (value(vi_AUTOINDENT) && indent != 0) { unsigned char x; gcursor = genindent(indent); *gcursor = 0; vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf)); } else { gcursor = genbuf; *gcursor = 0; if (ch == 'o') vfixcurs(); } /* * Prepare for undo. Pointers delimit inserted portion of line. */ vUA1 = vUA2 = cursor; /* * If we are not in a repeated command and a ^@ comes in * then this means the previous inserted text. * If there is none or it was too long to be saved, * then beep() and also arrange to undo any damage done * so far (e.g. if we are a change.) */ switch (ch) { case 'r': break; case 'a': /* * TRANSLATION_NOTE * "A" is a terse mode message corresponding to * "APPEND MODE". * Translated message of "A" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("A")); } else { vshowmode(gettext("APPEND MODE")); } break; case 's': /* * TRANSLATION_NOTE * "S" is a terse mode message corresponding to * "SUBSTITUTE MODE". * Translated message of "S" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("S")); } else { vshowmode(gettext("SUBSTITUTE MODE")); } break; case 'c': /* * TRANSLATION_NOTE * "C" is a terse mode message corresponding to * "CHANGE MODE". * Translated message of "C" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("C")); } else { vshowmode(gettext("CHANGE MODE")); } break; case 'R': /* * TRANSLATION_NOTE * "R" is a terse mode message corresponding to * "REPLACE MODE". * Translated message of "R" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("R")); } else { vshowmode(gettext("REPLACE MODE")); } break; case 'o': /* * TRANSLATION_NOTE * "O" is a terse mode message corresponding to * "OPEN MODE". * Translated message of "O" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("O")); } else { vshowmode(gettext("OPEN MODE")); } break; case 'i': /* * TRANSLATION_NOTE * "I" is a terse mode message corresponding to * "INSERT MODE" and the following "INPUT MODE". * Translated message of "I" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("I")); } else { vshowmode(gettext("INSERT MODE")); } break; default: /* * TRANSLATION_NOTE * "I" is a terse mode message corresponding to * "INPUT MODE" and the previous "INSERT MODE". * Translated message of "I" must be 1 character (not byte). * Or, just leave it. */ if (value(vi_TERSE)) { vshowmode(gettext("I")); } else { vshowmode(gettext("INPUT MODE")); } } ixlatctl(1); if ((vglobp && *vglobp == 0) || peekbr()) { if (INS[128] == 0200) { (void) beep(); if (!splitw) ungetkey('u'); doomed = 0; hold = oldhold; return; } /* * Unread input from INS. * An escape will be generated at end of string. * Hold off n^^2 type update on dumb terminals. */ vglobp = INS; inscdcnt = INSCDCNT; hold |= HOLDQIK; } else if (vglobp == 0) { /* * Not a repeated command, get * a new inserted text for repeat. */ INS[0] = 0; INS[128] = 0; INSCDCNT = 0; } /* * For wrapmargin to hack away second space after a '.' * when the first space caused a line break we keep * track that this happened in gobblebl, which says * to gobble up a blank silently. */ gobblebl = 0; startsrcline = dot; startsrccol = cursor - linebuf; /* * Text gathering loop. * New text goes into genbuf starting at gcursor. * cursor preserves place in linebuf where text will eventually go. */ if (*cursor == 0 || state == CRTOPEN) hold |= HOLDROL; for (;;) { if (ch == 'r' && repcnt == 0) escape = 0; else { ixlatctl(1); /* * When vgetline() returns, gcursor is * pointing to '\0' and vgetline() has * read an ESCAPE or NL. */ gcursor = vgetline(repcnt, gcursor, &escape, ch); if (escape == '\n') { gotNL = 1; #ifdef XPG6 if (ch == 'r') { /* * XPG6 assertion 313 [count]r\n : * Arrange to set cursor correctly. */ endsrccol = gcursor - genbuf - 1; } #endif /* XPG6 */ } else { /* * Upon escape, gcursor is pointing to '\0' * terminating the string in genbuf. */ endsrccol = gcursor - genbuf - 1; } ixlatctl(0); /* * After an append, stick information * about the ^D's and ^^D's and 0^D's in * the repeated text buffer so repeated * inserts of stuff indented with ^D as backtab's * can work. */ if (HADUP) addtext("^"); else if (HADZERO) addtext("0"); if(!vglobp) INSCDCNT = CDCNT; while (CDCNT > 0) { addtext("\004"); CDCNT--; } if (gobbled) addtext(" "); addtext(ogcursor); } repcnt = 0; /* * Smash the generated and preexisting indents together * and generate one cleanly made out of tabs and spaces * if we are using autoindent and this isn't 'r' command. */ if (!vaifirst && value(vi_AUTOINDENT)) { i = fixindent(indent); if (!HADUP) indent = i; gcursor = strend(genbuf); } /* * Set cnt to 1 to avoid repeating the text on the same line. * Do this for commands 'i', 'I', 'a', and 'A', if we're * inserting anything with a newline for XPG6. Always do this * for commands 'o' and 'O'. */ if ((imultlinecnt && gotNL) || omultlinecnt) { cnt = 1; } /* * Limit the repetition count based on maximum * possible line length; do output implied * by further count (> 1) and cons up the new line * in linebuf. */ cnt = vmaxrep(ch, cnt); /* * cursor points to linebuf * Copy remaining old text (cursor) in original * line to after new text (gcursor + 1) in genbuf. */ CP(gcursor + 1, cursor); /* * For [count] r \n command, when replacing [count] chars * with '\n', this loop replaces [count] chars with "". */ do { /* cp new text (genbuf) into linebuf (cursor) */ CP(cursor, genbuf); if (cnt > 1) { int oldhold = hold; Outchar = vinschar; hold |= HOLDQIK; viprintf("%s", genbuf); hold = oldhold; Outchar = vputchar; } /* point cursor after new text in linebuf */ cursor += gcursor - genbuf; } while (--cnt > 0); endim(); vUA2 = cursor; /* add the remaining old text after the cursor */ if (escape != '\n') CP(cursor, gcursor + 1); /* * If doomed characters remain, clobber them, * and reopen the line to get the display exact. * eg. c$ to change to end of line */ if (state != HARDOPEN) { DEPTH(vcline) = 0; savedoomed = doomed; if (doomed > 0) { int cind = cindent(); physdc(cind, cind + doomed); doomed = 0; } if(MB_CUR_MAX > 1) rewrite = _ON; i = vreopen(LINE(vcline), lineDOT(), vcline); if(MB_CUR_MAX > 1) rewrite = _OFF; #ifdef TRACE if (trace) fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed); #endif if (ch == 'R') doomed = savedoomed; } /* * Unless we are continuing on to another line * (got a NL), break out of the for loop (got * an ESCAPE). */ if (escape != '\n') { vshowmode(""); break; } /* * Set up for the new line. * First save the current line, then construct a new * first image for the continuation line consisting * of any new autoindent plus the pushed ahead text. */ killU(); addtext(gobblebl ? " " : "\n"); /* save vutmp (for undo state) into temp file */ vsave(); cnt = 1; if (value(vi_AUTOINDENT)) { if (value(vi_LISP)) indent = lindent(dot + 1); else if (!HADUP && vaifirst) indent = whitecnt(linebuf); vaifirst = 0; strcLIN(vpastwh(gcursor + 1)); gcursor = genindent(indent); *gcursor = 0; if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2]) gcursor = genbuf; CP(gcursor, linebuf); } else { /* * Put gcursor at start of genbuf to wipe * out previous line in preparation for * the next vgetline() loop. */ CP(genbuf, gcursor + 1); gcursor = genbuf; } /* * If we started out as a single line operation and are now * turning into a multi-line change, then we had better yank * out dot before it changes so that undo will work * correctly later. */ if (FIXUNDO && vundkind == VCHNG) { vremote(1, yank, 0); undap1--; } /* * Now do the append of the new line in the buffer, * and update the display, ie: append genbuf to * the file after dot. If slowopen * we don't do very much. */ vdoappend(genbuf); vundkind = VMANYINS; vcline++; if (state != VISUAL) vshow(dot, NOLINE); else { i += LINE(vcline - 1); vopen(dot, i); if (value(vi_SLOWOPEN)) vscrap(); else vsync1(LINE(vcline)); } switch (ch) { case 'r': break; case 'a': if (value(vi_TERSE)) { vshowmode(gettext("A")); } else { vshowmode(gettext("APPEND MODE")); } break; case 's': if (value(vi_TERSE)) { vshowmode(gettext("S")); } else { vshowmode(gettext("SUBSTITUTE MODE")); } break; case 'c': if (value(vi_TERSE)) { vshowmode(gettext("C")); } else { vshowmode(gettext("CHANGE MODE")); } break; case 'R': if (value(vi_TERSE)) { vshowmode(gettext("R")); } else { vshowmode(gettext("REPLACE MODE")); } break; case 'i': if (value(vi_TERSE)) { vshowmode(gettext("I")); } else { vshowmode(gettext("INSERT MODE")); } break; case 'o': if (value(vi_TERSE)) { vshowmode(gettext("O")); } else { vshowmode(gettext("OPEN MODE")); } break; default: if (value(vi_TERSE)) { vshowmode(gettext("I")); } else { vshowmode(gettext("INPUT MODE")); } } strcLIN(gcursor); /* zero genbuf */ *gcursor = 0; cursor = linebuf; vgotoCL(nqcolumn(cursor - 1, genbuf)); } /* end for (;;) loop in vappend() */ if (imultlinecnt && gotNL) { imultlinerep(savecnt, startsrcline, startsrccol, endsrccol); } else if (omultlinecnt) { omultlinerep(savecnt, startsrcline, endsrccol); #ifdef XPG6 } else if (savecnt > 1 && ch == 'r' && gotNL) { /* * XPG6 assertion 313 & 254 : Position cursor for [count]r\n * then insert [count -1] newlines. */ endsrccol = gcursor - genbuf - 1; rmultlinerep(savecnt, endsrccol); #endif /* XPG6 */ } /* * All done with insertion, position the cursor * and sync the screen. */ hold = oldhold; if ((imultlinecnt && gotNL) || omultlinecnt) { fixdisplay(); #ifdef XPG6 } else if (savecnt > 1 && ch == 'r' && gotNL) { fixdisplay(); /* * XPG6 assertion 313 & 254 [count]r\n : Set flag to call * fixdisplay() after operate() has finished. To be sure that * the text (after the last \n followed by an indent) is always * displayed, fixdisplay() is called right before getting * the next command. */ redisplay = 1; #endif /* XPG6 */ } else if (cursor > linebuf) { cursor = lastchr(linebuf, cursor); #ifdef XPG6 /* * XPG6 assertion 313 & 254 [count]r\n : * For 'r' command, when the replacement char causes new * lines to be created, point cursor to first non-blank. * The old code, ie: cursor = lastchr(linebuf, cursor); * set cursor to the blank before the first non-blank * for r\n */ if (ch == 'r' && gotNL && isblank((int)*cursor)) ++cursor; #endif /* XPG6 */ } if (state != HARDOPEN) vsyncCL(); else if (cursor > linebuf) back1(); doomed = 0; wcursor = cursor; (void) vmove(); } /* * XPG6 * To repeat multi-line input for [count]a, [count]A, [count]i, [count]I, * or a subsequent [count]. : * insert input count-1 more times. */ static void imultlinerep(int savecnt, line *startsrcline, int startsrccol, int endsrccol) { int tmpcnt = 2; /* 1st insert counts as 1 repeat */ line *srcline, *endsrcline; size_t destsize = LBSIZE - endsrccol - 1; endsrcline = dot; /* Save linebuf into temp file before moving off the line. */ vsave(); /* * At this point the temp file contains the first iteration of * a multi-line insert, and we need to repeat it savecnt - 1 * more times in the temp file. dot is the last line in the * first iteration of the insert. Decrement dot so that * vdoappend() will append each new line before the last line. */ --dot; --vcline; /* * Use genbuf to rebuild the last line in the 1st iteration * of the repeated insert, then copy this line to the temp file. */ (void) strlcpy((char *)genbuf, (char *)linebuf, sizeof (genbuf)); getaline(*startsrcline); if (strlcpy((char *)(genbuf + endsrccol + 1), (char *)(linebuf + startsrccol), destsize) >= destsize) { error(gettext("Line too long")); } vdoappend(genbuf); vcline++; /* * Loop from the second line of the first iteration * through endsrcline, appending after dot. */ ++startsrcline; while (tmpcnt <= savecnt) { for (srcline = startsrcline; srcline <= endsrcline; ++srcline) { if ((tmpcnt == savecnt) && (srcline == endsrcline)) { /* * The last line is already in place, * just make it the current line. */ vcline++; dot++; getDOT(); cursor = linebuf + endsrccol; } else { getaline(*srcline); /* copy linebuf to temp file */ vdoappend(linebuf); vcline++; } } ++tmpcnt; } } /* * To repeat input for [count]o, [count]O, or a subsequent [count]. : * append input count-1 more times to the end of the already added * text, each time starting on a new line. */ static void omultlinerep(int savecnt, line *startsrcline, int endsrccol) { int tmpcnt = 2; /* 1st insert counts as 1 repeat */ line *srcline, *endsrcline; endsrcline = dot; /* Save linebuf into temp file before moving off the line. */ vsave(); /* * Loop from the first line of the first iteration * through endsrcline, appending after dot. */ while (tmpcnt <= savecnt) { for (srcline = startsrcline; srcline <= endsrcline; ++srcline) { getaline(*srcline); /* copy linebuf to temp file */ vdoappend(linebuf); vcline++; } ++tmpcnt; } cursor = linebuf + endsrccol; } #ifdef XPG6 /* * XPG6 assertion 313 & 254 : To repeat '\n' for [count]r\n * insert '\n' savecnt-1 more times before the already added '\n'. */ static void rmultlinerep(int savecnt, int endsrccol) { int tmpcnt = 2; /* 1st replacement counts as 1 repeat */ /* Save linebuf into temp file before moving off the line. */ vsave(); /* * At this point the temp file contains the line followed by '\n', * which is preceded by indentation if autoindent is set. * '\n' must be repeated [savecnt - 1] more times in the temp file. * dot is the current line containing the '\n'. Decrement dot so that * vdoappend() will append each '\n' before the current '\n'. * This will allow only the last line to contain any autoindent * characters. */ --dot; --vcline; /* * Append after dot. */ while (tmpcnt <= savecnt) { linebuf[0] = '\0'; /* append linebuf below current line in temp file */ vdoappend(linebuf); vcline++; ++tmpcnt; } /* set the current line to the line after the last '\n' */ ++dot; ++vcline; /* point cursor after (linebuf + endsrccol) */ vcursaft(linebuf + endsrccol); } #endif /* XPG6 */ /* * Similiar to a ctrl-l, however always vrepaint() in case the last line * of the repeat would exceed the bottom of the screen. */ void fixdisplay(void) { vclear(); vdirty(0, vcnt); if (state != VISUAL) { vclean(); vcnt = 0; vmoveto(dot, cursor, 0); } else { vredraw(WTOP); vrepaint(cursor); vfixcurs(); } } /* * Subroutine for vgetline to back up a single character position, * backwards around end of lines (vgoto can't hack columns which are * less than 0 in general). */ void back1(void) { vgoto(destline - 1, WCOLS + destcol - 1); } /* * Get a line into genbuf after gcursor. * Cnt limits the number of input characters * accepted and is used for handling the replace * single character command. Aescaped is the location * where we stick a termination indicator (whether we * ended with an ESCAPE or a newline/return. * * We do erase-kill type processing here and also * are careful about the way we do this so that it is * repeatable. (I.e. so that your kill doesn't happen, * when you repeat an insert if it was escaped with \ the * first time you did it. commch is the command character * involved, including the prompt for readline. */ unsigned char * vgetline(cnt, gcursor, aescaped, commch) int cnt; unsigned char *gcursor; bool *aescaped; unsigned char commch; { int c, ch; unsigned char *cp, *pcp; int x, y, iwhite, backsl=0; unsigned char *iglobp; int (*OO)() = Outchar; int length, width; unsigned char multic[MULTI_BYTE_MAX+1]; wchar_t wchar = 0; unsigned char *p; int len; /* * Clear the output state and counters * for autoindent backwards motion (counts of ^D, etc.) * Remember how much white space at beginning of line so * as not to allow backspace over autoindent. */ *aescaped = 0; ogcursor = gcursor; flusho(); CDCNT = 0; HADUP = 0; HADZERO = 0; gobbled = 0; iwhite = whitecnt(genbuf); iglobp = vglobp; /* * Clear abbreviation recursive-use count */ abbrepcnt = 0; /* * Carefully avoid using vinschar in the echo area. */ if (splitw) Outchar = vputchar; else { Outchar = vinschar; vprepins(); } for (;;) { length = 0; backsl = 0; if (gobblebl) gobblebl--; if (cnt != 0) { cnt--; if (cnt == 0) goto vadone; } c = getkey(); if (c != ATTN) c &= 0377; ch = c; maphopcnt = 0; if (vglobp == 0 && Peekkey == 0 && commch != 'r') while ((ch = map(c, immacs, commch)) != c) { c = ch; if (!value(vi_REMAP)) break; if (++maphopcnt > 256) error(gettext("Infinite macro loop")); } if (!iglobp) { /* * Erase-kill type processing. * Only happens if we were not reading * from untyped input when we started. * Map users erase to ^H, kill to -1 for switch. */ if (c == tty.c_cc[VERASE]) c = CTRL('h'); else if (c == tty.c_cc[VKILL]) c = -1; switch (c) { /* * ^? Interrupt drops you back to visual * command mode with an unread interrupt * still in the input buffer. * * ^\ Quit does the same as interrupt. * If you are a ex command rather than * a vi command this will drop you * back to command mode for sure. */ case ATTN: case QUIT: ungetkey(c); goto vadone; /* * ^H Backs up a character in the input. * * BUG: Can't back around line boundaries. * This is hard because stuff has * already been saved for repeat. */ case CTRL('h'): bakchar: cp = lastchr(ogcursor, gcursor); if (cp < ogcursor) { if (splitw) { /* * Backspacing over readecho * prompt. Pretend delete but * don't beep. */ ungetkey(c); goto vadone; } (void) beep(); continue; } goto vbackup; /* * ^W Back up a white/non-white word. */ case CTRL('w'): wdkind = 1; for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--) continue; pcp = lastchr(ogcursor, cp); for (c = wordch(pcp); cp > ogcursor && wordof(c, pcp); cp = pcp, pcp = lastchr(ogcursor, cp)) continue; goto vbackup; /* * users kill Kill input on this line, back to * the autoindent. */ case -1: cp = ogcursor; vbackup: if (cp == gcursor) { (void) beep(); continue; } endim(); *cp = 0; c = cindent(); vgotoCL(nqcolumn(lastchr(linebuf, cursor), genbuf)); if (doomed >= 0) doomed += c - cindent(); gcursor = cp; continue; /* * \ Followed by erase or kill * maps to just the erase or kill. */ case '\\': x = destcol, y = destline; putchar('\\'); vcsync(); c = getkey(); if (c == tty.c_cc[VERASE] || c == tty.c_cc[VKILL]) { vgoto(y, x); if (doomed >= 0) doomed++; multic[0] = wchar = c; length = 1; goto def; } ungetkey(c), c = '\\'; backsl = 1; break; /* * ^Q Super quote following character * Only ^@ is verboten (trapped at * a lower level) and \n forces a line * split so doesn't really go in. * * ^V Synonym for ^Q */ case CTRL('q'): case CTRL('v'): x = destcol, y = destline; putchar('^'); vgoto(y, x); c = getkey(); #ifdef USG if (c == ATTN) c = tty.c_cc[VINTR]; #endif if (c != NL) { if (doomed >= 0) doomed++; multic[0] = wchar = c; length = 1; goto def; } break; } } /* * If we get a blank not in the echo area * consider splitting the window in the wrapmargin. */ if(!backsl) { ungetkey(c); if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { (void) beep(); continue; } } else { length = 1; multic[0] = '\\'; } if (c != NL && !splitw) { if (c == ' ' && gobblebl) { gobbled = 1; continue; } if ((width = wcwidth(wchar)) <= 0) width = (wchar <= 0177 ? 1 : 4); if (value(vi_WRAPMARGIN) && (outcol + width - 1 >= OCOLUMNS - value(vi_WRAPMARGIN) || backsl && outcol==0) && commch != 'r') { /* * At end of word and hit wrapmargin. * Move the word to next line and keep going. */ unsigned char *wp; int bytelength; #ifndef PRESUNEUC unsigned char *tgcursor; wchar_t wc1, wc2; tgcursor = gcursor; #endif /* PRESUNEUC */ wdkind = 1; strncpy(gcursor, multic, length); gcursor += length; if (backsl) { #ifdef PRESUNEUC if((length = mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { #else if((length = _mbftowc((char *)multic, &wchar, getkey, &Peekkey)) <= 0) { #endif /* PRESUNEUC */ (void) beep(); continue; } strncpy(gcursor, multic, length); gcursor += length; } *gcursor = 0; /* * Find end of previous word if we are past it. */ for (cp=gcursor; cp>ogcursor && isspace(cp[-1]); cp--) ; #ifdef PRESUNEUC /* find screen width of previous word */ width = 0; for(wp = cp; *wp; ) #else /* count screen width of pending characters */ width = 0; for(wp = tgcursor; wp < cp;) #endif /* PRESUNEUC */ if((bytelength = mbtowc(&wchar, (char *)wp, MULTI_BYTE_MAX)) < 0) { width+=4; wp++; } else { int curwidth = wcwidth(wchar); if(curwidth <= 0) width += (*wp < 0200 ? 2 : 4); else width += curwidth; wp += bytelength; } #ifdef PRESUNEUC if (outcol+(backsl?OCOLUMNS:0) - width >= OCOLUMNS - value(vi_WRAPMARGIN)) { #else if (outcol+(backsl?OCOLUMNS:0) + width -1 >= OCOLUMNS - value(vi_WRAPMARGIN)) { #endif /* PRESUNEUC */ /* * Find beginning of previous word. */ #ifdef PRESUNEUC for (; cp>ogcursor && !isspace(cp[-1]); cp--) ; #else wc1 = wc2 = 0; while (cp>ogcursor) { if (isspace(cp[-1])) { break; } if (!multibyte) { cp--; continue; } wp = (unsigned char *)(cp - MB_CUR_MAX); if (wp < ogcursor) wp = ogcursor; while (cp > wp) { /* 7tabs */if (wc2) { /* 7tabs */ if ((bytelength = mbtowc(&wc1, (char *)wp, cp-wp)) != cp-wp) { /* 7tabs */ wp++; /* 7tabs */ wc1 = 0; /* 7tabs */ continue; /* 7tabs */ } /* 7tabs */} else { /* 7tabs */ if ((bytelength = mbtowc(&wc2, (char *)wp, cp-wp)) != cp-wp) { /* 7tabs */ wp++; /* 7tabs */ wc2 = 0; /* 7tabs */ continue; /* 7tabs */ } /* 7tabs */} /* 7tabs */if (wc1) { /* 7tabs */ if (wdbdg && (!iswascii(wc1) || !iswascii(wc2))) { /* 7tabs */ if ((*wdbdg)(wc1, wc2, 2) < 5) { /* 7tabs */ goto ws; /* 7tabs */ } /* 7tabs */ } /* 7tabs */ wc2 = wc1; /* 7tabs */ wc1 = 0; /* 7tabs */ cp -= bytelength - 1; /* 7tabs */ break; /* 7tabs */} else { /* 7tabs */ cp -= bytelength - 1; /* 7tabs */ break; /* 7tabs */} } cp--; } ws: #endif /* PRESUNEUC */ if (cp <= ogcursor) { /* * There is a single word that * is too long to fit. Just * let it pass, but beep for * each new letter to warn * the luser. */ gcursor -= length; c = *gcursor; *gcursor = 0; (void) beep(); goto dontbreak; } /* * Save it for next line. */ macpush(cp, 0); #ifdef PRESUNEUC cp--; #endif /* PRESUNEUC */ } macpush("\n", 0); /* * Erase white space before the word. */ while (cp > ogcursor && isspace(cp[-1])) cp--; /* skip blank */ gobblebl = 3; goto vbackup; } dontbreak:; } /* * Word abbreviation mode. */ if (anyabbrs && gcursor > ogcursor && !wordch(multic) && wordch(lastchr(ogcursor, gcursor))) { int wdtype, abno; multic[length] = 0; wdkind = 1; cp = lastchr(ogcursor, gcursor); pcp = lastchr(ogcursor, cp); for (wdtype = wordch(pcp); cp > ogcursor && wordof(wdtype, pcp); cp = pcp, pcp = lastchr(ogcursor, pcp)) ; *gcursor = 0; for (abno=0; abbrevs[abno].mapto; abno++) { if (eq(cp, abbrevs[abno].cap)) { if(abbrepcnt == 0) { if(reccnt(abbrevs[abno].cap, abbrevs[abno].mapto)) abbrepcnt = 1; macpush(multic, 0); macpush(abbrevs[abno].mapto); goto vbackup; } else abbrepcnt = 0; } } } switch (c) { /* * ^M Except in repeat maps to \n. */ case CR: if (vglobp) { multic[0] = wchar = c; length = 1; goto def; } c = '\n'; /* FALLTHROUGH */ /* * \n Start new line. */ case NL: *aescaped = c; goto vadone; /* * escape End insert unless repeat and more to repeat. */ case ESCAPE: if (lastvgk) { multic[0] = wchar = c; length = 1; goto def; } goto vadone; /* * ^D Backtab. * ^T Software forward tab. * * Unless in repeat where this means these * were superquoted in. */ case CTRL('t'): if (vglobp) { multic[0] = wchar = c; length = 1; goto def; } /* fall into ... */ *gcursor = 0; cp = vpastwh(genbuf); c = whitecnt(genbuf); if (ch == CTRL('t')) { /* * ^t just generates new indent replacing * current white space rounded up to soft * tab stop increment. */ if (cp != gcursor) /* * BUG: Don't hack ^T except * right after initial * white space. */ continue; cp = genindent(iwhite = backtab(c + value(vi_SHIFTWIDTH) + 1)); ogcursor = cp; goto vbackup; } /* FALLTHROUGH */ /* * ^D works only if we are at the (end of) the * generated autoindent. We count the ^D for repeat * purposes. */ case CTRL('d'): /* check if ^d was superquoted in */ if(vglobp && inscdcnt <= 0) { multic[0] = wchar = c; length = 1; goto def; } if(vglobp) inscdcnt--; *gcursor = 0; cp = vpastwh(genbuf); c = whitecnt(genbuf); if (c == iwhite && c != 0) if (cp == gcursor) { iwhite = backtab(c); CDCNT++; ogcursor = cp = genindent(iwhite); goto vbackup; } else if (&cp[1] == gcursor && (*cp == '^' || *cp == '0')) { /* * ^^D moves to margin, then back * to current indent on next line. * * 0^D moves to margin and then * stays there. */ HADZERO = *cp == '0'; ogcursor = cp = genbuf; HADUP = 1 - HADZERO; CDCNT = 1; endim(); back1(); (void) vputchar(' '); goto vbackup; } if (vglobp && vglobp - iglobp >= 2) { if ((p = vglobp - MB_CUR_MAX) < iglobp) p = iglobp; for ( ; p < &vglobp[-2]; p += len) { if ((len = mblen((char *)p, MB_CUR_MAX)) <= 0) len = 1; } if ((p == &vglobp[-2]) && (*p == '^' || *p == '0') && gcursor == ogcursor + 1) goto bakchar; } continue; default: /* * Possibly discard control inputs. */ if (!vglobp && junk(c)) { (void) beep(); continue; } def: if (!backsl) { putchar(wchar); flush(); } if (gcursor + length - 1 > &genbuf[LBSIZE - 2]) error(gettext("Line too long")); (void)strncpy(gcursor, multic, length); gcursor += length; vcsync(); if (value(vi_SHOWMATCH) && !iglobp) if (c == ')' || c == '}') lsmatch(gcursor); continue; } } vadone: *gcursor = 0; if (Outchar != termchar) Outchar = OO; endim(); return (gcursor); } int vgetsplit(); unsigned char *vsplitpt; /* * Append the line in buffer at lp * to the buffer after dot. */ void vdoappend(unsigned char *lp) { int oing = inglobal; vsplitpt = lp; inglobal = 1; (void)append(vgetsplit, dot); inglobal = oing; } /* * Subroutine for vdoappend to pass to append. */ int vgetsplit(void) { if (vsplitpt == 0) return (EOF); strcLIN(vsplitpt); vsplitpt = 0; return (0); } /* * Vmaxrep determines the maximum repetition factor * allowed that will yield total line length less than * LBSIZE characters and also does hacks for the R command. */ int vmaxrep(unsigned char ch, int cnt) { int len; unsigned char *cp; int repcnt, oldcnt, replen; if (cnt > LBSIZE - 2) cnt = LBSIZE - 2; if (ch == 'R') { len = strlen(cursor); oldcnt = 0; for(cp = cursor; *cp; ) { oldcnt++; cp = nextchr(cp); } repcnt = 0; for(cp = genbuf; *cp; ) { repcnt++; cp = nextchr(cp); } /* * if number of characters in replacement string * (repcnt) is less than number of characters following * cursor (oldcnt), find end of repcnt * characters after cursor */ if(repcnt < oldcnt) { for(cp = cursor; repcnt > 0; repcnt--) cp = nextchr(cp); len = cp - cursor; } CP(cursor, cursor + len); vUD2 += len; } len = strlen(linebuf); replen = strlen(genbuf); if (len + cnt * replen <= LBSIZE - 2) return (cnt); cnt = (LBSIZE - 2 - len) / replen; if (cnt == 0) { vsave(); error(gettext("Line too long")); } return (cnt); } /* * Determine how many occurrences of word 'CAP' are in 'MAPTO'. To be * considered an occurrence there must be both a nonword-prefix, a * complete match of 'CAP' within 'MAPTO', and a nonword-suffix. * Note that the beginning and end of 'MAPTO' are considered to be * valid nonword delimiters. */ int reccnt(unsigned char *cap, unsigned char *mapto) { int i, cnt, final; cnt = 0; final = strlen(mapto) - strlen(cap); for (i=0; i <= final; i++) if ((strncmp(cap, mapto+i, strlen(cap)) == 0) /* match */ && (i == 0 || !wordch(&mapto[i-1])) /* prefix ok */ && (i == final || !wordch(&mapto[i+strlen(cap)]))) /* suffix ok */ cnt++; return (cnt); }