/* * v_curword -- * Get the word (tagstring, actually) the cursor is on. * * PUBLIC: int v_curword __P((SCR *)); */ int v_curword(SCR *sp) { VI_PRIVATE *vip; size_t beg, end, len; int moved; CHAR_T *p; if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) return (1); /* * !!! * Historically, tag commands skipped over any leading whitespace * characters. Make this true in general when using cursor words. * If movement, getting a cursor word implies moving the cursor to * its beginning. Refresh now. * * !!! * Find the beginning/end of the keyword. Keywords are currently * used for cursor-word searching and for tags. Historical vi * only used the word in a tag search from the cursor to the end * of the word, i.e. if the cursor was on the 'b' in " abc ", the * tag was "bc". For consistency, we make cursor word searches * follow the same rule. */ for (moved = 0, beg = sp->cno; beg < len && ISSPACE(p[beg]); moved = 1, ++beg); if (beg >= len) { msgq(sp, M_BERR, "212|Cursor not in a word"); return (1); } if (moved) { sp->cno = beg; (void)vs_refresh(sp, 0); } /* * Find the end of the word. * * !!! * Historically, vi accepted any non-blank as initial character * when building up a tagstring. Required by IEEE 1003.1-2001. */ for (end = beg; ++end < len && inword(p[end]);); vip = VIP(sp); vip->klen = len = (end - beg); BINC_RETW(sp, vip->keyw, vip->keywlen, len+1); MEMMOVE(vip->keyw, p + beg, len); vip->keyw[len] = '\0'; /* XXX */ return (0); }
/* * v_replace -- [count]r<char> * * !!! * The r command in historic vi was almost beautiful in its badness. For * example, "r<erase>" and "r<word erase>" beeped the terminal and deleted * a single character. "Nr<carriage return>", where N was greater than 1, * inserted a single carriage return. "r<escape>" did cancel the command, * but "r<literal><escape>" erased a single character. To enter a literal * <literal> character, it required three <literal> characters after the * command. This may not be right, but at least it's not insane. * * PUBLIC: int v_replace __P((SCR *, VICMD *)); */ int v_replace(SCR *sp, VICMD *vp) { EVENT ev; VI_PRIVATE *vip; TEXT *tp; size_t blen, len; u_long cnt; int quote, rval; CHAR_T *bp; CHAR_T *p; vip = VIP(sp); /* * If the line doesn't exist, or it's empty, replacement isn't * allowed. It's not hard to implement, but: * * 1: It's historic practice (vi beeped before the replacement * character was even entered). * 2: For consistency, this change would require that the more * general case, "Nr", when the user is < N characters from * the end of the line, also work, which would be a bit odd. * 3: Replacing with a <newline> has somewhat odd semantics. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); if (len == 0) { msgq(sp, M_BERR, "186|No characters to replace"); return (1); } /* * Figure out how many characters to be replace. For no particular * reason (other than that the semantics of replacing the newline * are confusing) only permit the replacement of the characters in * the current line. I suppose we could append replacement characters * to the line, but I see no compelling reason to do so. Check this * before we get the character to match historic practice, where Nr * failed immediately if there were less than N characters from the * cursor to the end of the line. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + cnt - 1; if (vp->m_stop.cno > len - 1) { v_eol(sp, &vp->m_start); return (1); } /* * If it's not a repeat, reset the current mode and get a replacement * character. */ quote = 0; if (!F_ISSET(vp, VC_ISDOT)) { sp->showmode = SM_REPLACE; if (vs_refresh(sp, 0)) return (1); next: if (v_event_get(sp, &ev, 0, 0)) return (1); switch (ev.e_event) { case E_CHARACTER: /* * <literal_next> means escape the next character. * <escape> means they changed their minds. */ if (!quote) { if (ev.e_value == K_VLNEXT) { quote = 1; goto next; } if (ev.e_value == K_ESCAPE) return (0); } vip->rlast = ev.e_c; vip->rvalue = ev.e_value; break; case E_ERR: case E_EOF: F_SET(sp, SC_EXIT_FORCE); return (1); case E_INTERRUPT: /* <interrupt> means they changed their minds. */ return (0); case E_WRESIZE: /* <resize> interrupts the input mode. */ v_emsg(sp, NULL, VIM_WRESIZE); return (0); case E_REPAINT: if (vs_repaint(sp, &ev)) return (1); goto next; default: v_event_err(sp, &ev); return (0); } } /* Copy the line. */ GET_SPACE_RETW(sp, bp, blen, len); MEMMOVE(bp, p, len); p = bp; /* * Versions of nvi before 1.57 created N new lines when they replaced * N characters with <carriage-return> or <newline> characters. This * is different from the historic vi, which replaced N characters with * a single new line. Users complained, so we match historic practice. */ if ((!quote && vip->rvalue == K_CR) || vip->rvalue == K_NL) { /* Set return line. */ vp->m_stop.lno = vp->m_start.lno + 1; vp->m_stop.cno = 0; /* The first part of the current line. */ if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno)) goto err_ret; /* * The rest of the current line. And, of course, now it gets * tricky. If there are characters left in the line and if * the autoindent edit option is set, white space after the * replaced character is discarded, autoindent is applied, and * the cursor moves to the last indent character. */ p += vp->m_start.cno + cnt; len -= vp->m_start.cno + cnt; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) for (; len && isblank(*p); --len, ++p); if ((tp = text_init(sp, p, len, len)) == NULL) goto err_ret; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) { if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp)) goto err_ret; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; } else vp->m_stop.cno = 0; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len)) err_ret: rval = 1; else { text_free(tp); rval = 0; } } else { STRSET(bp + vp->m_start.cno, vip->rlast, cnt); rval = db_set(sp, vp->m_start.lno, bp, len); } FREE_SPACEW(sp, bp, blen); vp->m_final = vp->m_stop; return (rval); }
static int s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq; recno_t elno, lno, slno; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; unsigned long ul; char *bp, *lb; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; if ((ul = strtoul(s, &s, 10)) >= UINT_MAX) errno = ERANGE; if (*s == '\0') /* Loop increment correction. */ --s; if (errno == ERANGE) { if (ul >= UINT_MAX) msgq(sp, M_ERR, "Count overflow"); else msgq(sp, M_SYSERR, NULL); return (1); } lno = (recno_t)ul; /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) { memset(&tiq, 0, sizeof(TEXTH)); TAILQ_INIT(&tiq); } break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ for (matched = quit = 0, lno = cmdp->addr1.lno, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RET(sp, bp, blen, llen); } else ADD_SPACE_RET(sp, bp, blen, llen); memcpy(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, (char *)s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1) ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, "Confirm change? [n]", NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { if (ex_print(sp, cmdp, &from, &to, 0) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, &tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(&tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len) /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RET(sp, bp, blen, llen) memcpy(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len) /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACE(sp, bp, blen); if (lb != NULL) free(lb); return (rval); }
/* * vs_resolve -- * Deal with message output. * * PUBLIC: int vs_resolve __P((SCR *, SCR *, int)); */ int vs_resolve(SCR *sp, SCR *csp, int forcewait) { EVENT ev; GS *gp; WIN *wp; MSGS *mp; VI_PRIVATE *vip; size_t oldy, oldx; int redraw; /* * Vs_resolve is called from the main vi loop and the refresh function * to periodically ensure that the user has seen any messages that have * been displayed and that any status lines are correct. The sp screen * is the screen we're checking, usually the current screen. When it's * not, csp is the current screen, used for final cursor positioning. */ gp = sp->gp; wp = sp->wp; vip = VIP(sp); if (csp == NULL) csp = sp; /* Save the cursor position. */ (void)gp->scr_cursor(csp, &oldy, &oldx); /* Ring the bell if it's scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) { F_CLR(gp, G_BELLSCHED); (void)gp->scr_bell(sp); } /* Display new file status line. */ if (F_ISSET(sp, SC_STATUS)) { F_CLR(sp, SC_STATUS); msgq_status(sp, sp->lno, MSTAT_TRUNCATE); } /* Report on line modifications. */ mod_rpt(sp); /* * Flush any saved messages. If the screen isn't ready, refresh * it. (A side-effect of screen refresh is that we can display * messages.) Once this is done, don't trust the cursor. That * extra refresh screwed the pooch. */ if (gp->msgq.lh_first != NULL) { if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1)) return (1); while ((mp = gp->msgq.lh_first) != NULL) { wp->scr_msg(sp, mp->mtype, mp->buf, mp->len); LIST_REMOVE(mp, q); free(mp->buf); free(mp); } F_SET(vip, VIP_CUR_INVALID); } switch (vip->totalcount) { case 0: redraw = 0; break; case 1: /* * If we're switching screens, we have to wait for messages, * regardless. If we don't wait, skip updating the modeline. */ if (forcewait) vs_scroll(sp, NULL, SCROLL_W); else F_SET(vip, VIP_S_MODELINE); redraw = 0; break; default: /* * If >1 message line in use, prompt the user to continue and * repaint overwritten lines. */ vs_scroll(sp, NULL, SCROLL_W); ev.e_event = E_REPAINT; ev.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; ev.e_tlno = sp->rows; redraw = 1; break; } /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; /* Redraw. */ if (redraw) (void)v_erepaint(sp, &ev); /* Restore the cursor position. */ (void)gp->scr_move(csp, oldy, oldx); return (0); }
/* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { CHAR_T *endp, *p, *t; SCRIPT *sc; struct pollfd pfd[1]; recno_t lno; size_t blen, len, tlen; u_int value; int nr, rval; char *bp; /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); #define MINREAD 1024 GET_SPACE_RET(sp, bp, blen, MINREAD); endp = bp; /* Read the characters. */ rval = 1; sc = sp->script; more: switch (nr = read(sc->sh_master, endp, MINREAD)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); rval = 0; goto ret; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto ret; default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { value = KEY_VAL(sp, *p); if (value == K_CR || value == K_NL) { len = p - t; if (db_append(sp, 1, lno++, t, len)) goto ret; t = p + 1; } } if (p > t) { len = p - t; /* * If the last thing from the shell isn't another prompt, wait * up to 1/10 of a second for more stuff to show up, so that * we don't break the output into two separate lines. Don't * want to hang indefinitely because some program is hanging, * confused the shell, or whatever. */ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { pfd[0].fd = sc->sh_master; pfd[0].events = POLLIN; if (poll(pfd, 1, 100) > 0) { memmove(bp, t, len); endp = bp + len; goto more; } } if (sscr_setprompt(sp, t, len)) return (1); if (db_append(sp, 1, lno++, t, len)) goto ret; } /* The cursor moves to EOF. */ sp->lno = lno; sp->cno = len ? len - 1 : 0; rval = vs_refresh(sp, 1); ret: FREE_SPACE(sp, bp, blen); return (rval); }
/* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { struct timeval tv; char *endp, *p, *t; SCRIPT *sc; fd_set rdfd; db_recno_t lno; size_t len; ssize_t nr; char bp[1024]; const CHAR_T *ip; size_t ilen = 0; /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); endp = bp; /* Read the characters. */ sc = sp->script; more: switch (nr = read(sc->sh_master, endp, bp + sizeof(bp) - endp)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); return (0); case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); return (1); default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { if (*p == '\r' || *p == '\n') { len = p - t; if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); t = p + 1; } } /* * If the last thing from the shell isn't another prompt, wait up to * 1/10 of a second for more stuff to show up, so that we don't break * the output into two separate lines. Don't want to hang indefinitely * because some program is hanging, confused the shell, or whatever. * Note that sc->sh_prompt can be NULL here. */ len = p - t; if (sc->sh_prompt == NULL || len != sc->sh_prompt_len || memcmp(t, sc->sh_prompt, len) != 0) { tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&rdfd); FD_SET(sc->sh_master, &rdfd); if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { if (len == sizeof(bp)) { if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); endp = bp; } else { memmove(bp, t, len); endp = bp + len; } goto more; } if (sscr_setprompt(sp, t, len)) return (1); } /* Append the remains into the file, and the cursor moves to EOF. */ if (len > 0) { if (CHAR2INT(sp, t, len, ip, ilen) || db_append(sp, 1, lno++, ip, ilen)) return (1); sp->cno = ilen - 1; } else sp->cno = 0; sp->lno = lno; return (vs_refresh(sp, 1)); }
/* * vi -- * Main vi command loop. * * PUBLIC: int vi __P((SCR **)); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd = { 0 }, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Point to the command structure. */ vp = &cmd; /* Reset strange attraction. */ F_SET(vp, VM_RCM_SET); /* Initialize the vi screen. */ if (v_init(sp)) return (1); /* Set the focus. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); for (vip = VIP(sp), rval = 0;;) { /* Resolve messages. */ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) goto ret; /* * If not skipping a refresh, return to command mode and * refresh the screen. */ if (F_ISSET(vip, VIP_S_REFRESH)) F_CLR(vip, VIP_S_REFRESH); else { sp->showmode = SM_COMMAND; if (vs_refresh(sp, 0)) goto ret; } /* Set the new favorite position. */ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { F_CLR(vip, VIP_RCM_LAST); (void)vs_column(sp, &sp->rcm); } /* * If not currently in a map, log the cursor position, * and set a flag so that this command can become the * DOT command. */ if (MAPPED_KEYS_WAITING(sp)) mapped = 1; else { if (log_cursor(sp)) goto err; mapped = 0; } /* * There may be an ex command waiting, and we returned here * only because we exited a screen or file. In this case, * we simply go back into the ex parser. */ if (EXCMD_RUNNING(gp)) { vp->kp = &vikeys[':']; goto ex_continue; } /* Refresh the command structure. */ memset(vp, 0, sizeof(VICMD)); /* * We get a command, which may or may not have an associated * motion. If it does, we get it too, calling its underlying * function to get the resulting mark. We then call the * command setting the cursor to the resulting mark. * * !!! * Vi historically flushed mapped characters on error, but * entering extra <escape> characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading <escape> characters in maps to clean up vi * state before the map was interpreted. Beauty! */ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { case GC_ERR: goto err; case GC_ERR_NOFLUSH: goto gc_err_noflush; case GC_EVENT: goto gc_event; case GC_FATAL: goto ret; case GC_INTERRUPT: goto intr; case GC_OK: break; } /* Check for security setting. */ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); goto err; } /* * Historical practice: if a dot command gets a new count, * any motion component goes away, i.e. "d3w2." deletes a * total of 5 words. */ if (F_ISSET(vp, VC_ISDOT) && comcount) DOTMOTION->count = 1; /* Copy the key flags into the local structure. */ F_SET(vp, vp->kp->flags); /* Prepare to set the previous context. */ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { abs.lno = sp->lno; abs.cno = sp->cno; } /* * Set the three cursor locations to the current cursor. The * underlying routines don't bother if the cursor doesn't move. * This also handles line commands (e.g. Y) defaulting to the * current line. */ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; /* * Do any required motion; v_motion sets the from MARK and the * line mode flag, as well as the VM_RCM flags. */ if (F_ISSET(vp, V_MOTION) && v_motion(sp, DOTMOTION, vp, &mapped)) { if (INTERRUPTED(sp)) goto intr; goto err; } /* * If a count is set and the command is line oriented, set the * to MARK here relative to the cursor/from MARK. This is for * commands that take both counts and motions, i.e. "4yy" and * "y%". As there's no way the command can know which the user * did, we have to do it here. (There are commands that are * line oriented and that take counts ("#G", "#H"), for which * this calculation is either completely meaningless or wrong. * Each command must validate the value for itself. */ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) vp->m_stop.lno += vp->count - 1; /* Increment the command count. */ ++sp->ccnt; #if defined(DEBUG) && defined(COMLOG) v_comlog(sp, vp); #endif /* Call the function. */ ex_continue: if (vp->kp->func(sp, vp)) goto err; gc_event: #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "232|vi: temporary buffer not released"); } #endif /* * If we're exiting this screen, move to the next one, or, if * there aren't any more, return to the main editor loop. The * ordering is careful, don't discard the contents of sp until * the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) goto ret; if (vs_discard(sp, &next)) goto ret; if (next == NULL && vs_swap(sp, &next, NULL)) goto ret; *spp = next; if (screen_end(sp)) goto ret; if (next == NULL) break; /* Switch screens, change focus. */ sp = next; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); continue; } /* * Set the dot command structure. * * !!! * Historically, commands which used mapped keys did not * set the dot command, with the exception of the text * input commands. */ if (F_ISSET(vp, V_DOT) && !mapped) { *DOT = cmd; F_SET(DOT, VC_ISDOT); /* * If a count was supplied for both the command and * its motion, the count was used only for the motion. * Turn the count back on for the dot structure. */ if (F_ISSET(vp, VC_C1RESET)) F_SET(DOT, VC_C1SET); /* VM flags aren't retained. */ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); } /* * Some vi row movements are "attracted" to the last position * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET * commands' candle. If the movement is to the EOL the vi * command handles it. If it's to the beginning, we handle it * here. * * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB * flag, but do the work themselves. The reason is that they * have to modify the column in case they're being used as a * motion component. Other similar commands (e.g. +, -) don't * have to modify the column because they are always line mode * operations when used as motions, so the column number isn't * of any interest. * * Does this totally violate the screen and editor layering? * You betcha. As they say, if you think you understand it, * you don't. */ switch (F_ISSET(vp, VM_RCM_MASK)) { case 0: case VM_RCM_SET: break; case VM_RCM: vp->m_final.cno = vs_rcm(sp, vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); break; case VM_RCM_SETLAST: F_SET(vip, VIP_RCM_LAST); break; case VM_RCM_SETFNB: vp->m_final.cno = 0; /* FALLTHROUGH */ case VM_RCM_SETNNB: if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) goto err; break; default: abort(); } /* Update the cursor. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; /* * Set the absolute mark -- set even if a tags or similar * command, since the tag may be moving to the same file. */ if ((F_ISSET(vp, V_ABS) || (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) || (F_ISSET(vp, V_ABS_C) && (sp->lno != abs.lno || sp->cno != abs.cno))) && mark_set(sp, ABSMARK1, &abs, 1)) goto err; if (0) { err: if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "110|Vi command failed: mapped keys discarded"); } /* * Check and clear interrupts. There's an obvious race, but * it's not worth fixing. */ gc_err_noflush: if (INTERRUPTED(sp)) { intr: CLR_INTERRUPT(sp); if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_ERR, "231|Interrupted: mapped keys discarded"); else msgq(sp, M_ERR, "236|Interrupted"); } /* If the last command switched screens, update. */ if (F_ISSET(sp, SC_SSWITCH)) { F_CLR(sp, SC_SSWITCH); /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ if (vs_refresh(sp, 1)) return (1); } /* If the last command switched files, change focus. */ if (F_ISSET(sp, SC_FSWITCH)) { F_CLR(sp, SC_FSWITCH); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } /* If leaving vi, return to the main editor loop. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { *spp = sp; v_dtoh(sp); gp->scr_discard(sp, NULL); break; } } if (0) ret: rval = 1; return (rval); }
/* * v_increment -- [count]#[#+-] * Increment/decrement a keyword number. * * PUBLIC: int v_increment __P((SCR *, VICMD *)); */ int v_increment(SCR *sp, VICMD *vp) { enum nresult nret; u_long ulval; long change, ltmp, lval; size_t beg, blen, end, len, nlen, wlen; int base, isempty, rval; CHAR_T *ntype, nbuf[100]; CHAR_T *bp, *p, *t; /* Validate the operator. */ if (vp->character == '#') vp->character = '+'; if (vp->character != '+' && vp->character != '-') { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } /* If new value set, save it off, but it has to fit in a long. */ if (F_ISSET(vp, VC_C1SET)) { if (vp->count > LONG_MAX) { inc_err(sp, NUM_OVER); return (1); } change = vp->count; } else change = 1; /* Get the line. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nonum; return (1); } /* * Skip any leading space before the number. Getting a cursor word * implies moving the cursor to its beginning, if we moved, refresh * now. */ for (beg = vp->m_start.cno; beg < len && ISSPACE(p[beg]); ++beg); if (beg >= len) goto nonum; if (beg != vp->m_start.cno) { sp->cno = beg; (void)vs_refresh(sp, 0); } #undef ishex #define ishex(c) (ISXDIGIT(c)) #undef isoctal #define isoctal(c) ((c) >= '0' && (c) <= '7') /* * Look for 0[Xx], or leading + or - signs, guess at the base. * The character after that must be a number. Wlen is set to * the remaining characters in the line that could be part of * the number. */ wlen = len - beg; if (p[beg] == '0' && wlen > 2 && (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { base = 16; end = beg + 2; if (!ishex(p[end])) goto decimal; ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; } else if (p[beg] == '0' && wlen > 1) { base = 8; end = beg + 1; if (!isoctal(p[end])) goto decimal; ntype = fmt[OCTAL]; } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { base = 10; end = beg + 1; ntype = fmt[SDEC]; if (!isdigit(p[end])) goto nonum; } else { decimal: base = 10; end = beg; ntype = fmt[DEC]; if (!isdigit(p[end])) { nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); return (1); } } /* Find the end of the word, possibly correcting the base. */ while (++end < len) { switch (base) { case 8: if (isoctal(p[end])) continue; if (p[end] == '8' || p[end] == '9') { base = 10; ntype = fmt[DEC]; continue; } break; case 10: if (isdigit(p[end])) continue; break; case 16: if (ishex(p[end])) continue; break; default: abort(); /* NOTREACHED */ } break; } wlen = (end - beg); /* * XXX * If the line was at the end of the buffer, we have to copy it * so we can guarantee that it's NULL-terminated. We make the * buffer big enough to fit the line changes as well, and only * allocate once. */ GET_SPACE_RETW(sp, bp, blen, len + 50); if (end == len) { MEMMOVE(bp, &p[beg], wlen); bp[wlen] = '\0'; t = bp; } else t = &p[beg]; /* * Octal or hex deal in unsigned longs, everything else is done * in signed longs. */ if (base == 10) { if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) goto err; ltmp = vp->character == '-' ? -change : change; if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { nret = NUM_OVER; goto err; } if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { nret = NUM_UNDER; goto err; } lval += ltmp; /* If we cross 0, signed numbers lose their sign. */ if (lval == 0 && ntype == fmt[SDEC]) ntype = fmt[DEC]; nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, lval); } else { if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) goto err; if (vp->character == '+') { if (!NPFITS(ULONG_MAX, ulval, change)) { nret = NUM_OVER; goto err; } ulval += change; } else { if (ulval < change) { nret = NUM_UNDER; goto err; } ulval -= change; } /* Correct for literal "0[Xx]" in format. */ if (base == 16) wlen -= 2; nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, wlen, ulval); } /* Build the new line. */ MEMMOVE(bp, p, beg); MEMMOVE(bp + beg, nbuf, nlen); MEMMOVE(bp + beg + nlen, p + end, len - beg - (end - beg)); len = beg + nlen + (len - beg - (end - beg)); nret = NUM_OK; rval = db_set(sp, vp->m_start.lno, bp, len); if (0) { err: rval = 1; inc_err(sp, nret); } if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); }