/* * v_subst -- [buffer][count]s * Substitute characters. * * PUBLIC: int v_subst __P((SCR *, VICMD *)); */ int v_subst(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0); if (vp->m_stop.cno > len - 1) vp->m_stop.cno = len - 1; if (p != NULL && cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags)); }
/* * nonblank -- * Set the column number of the first non-blank character * including or after the starting column. On error, set * the column to 0, it's safest. * * PUBLIC: int nonblank(SCR *, recno_t, size_t *); */ int nonblank(SCR *sp, recno_t lno, size_t *cnop) { char *p; size_t cnt, len, off; int isempty; /* Default. */ off = *cnop; *cnop = 0; /* Get the line, succeeding in an empty file. */ if (db_eget(sp, lno, &p, &len, &isempty)) return (!isempty); /* Set the offset. */ if (len == 0 || off >= len) return (0); for (cnt = off, p = &p[off], len -= off; len && isblank(*p); ++cnt, ++p, --len); /* Set the return. */ *cnop = len ? cnt : cnt - 1; return (0); }
/* * v_Replace -- [count]R * Overwrite multiple characters. * * PUBLIC: int v_Replace __P((SCR *, VICMD *)); */ int v_Replace(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_REPLACE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_OVERWRITE | TXT_REPLACE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = len ? len - 1 : 0; return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); }
/* * v_ia -- [count]a * [count]A * Append text to the cursor position. * * PUBLIC: int v_ia __P((SCR *, VICMD *)); */ int v_ia(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_APPEND; sp->lno = vp->m_start.lno; /* Move the cursor one column to the right and repaint the screen. */ if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else if (len) { if (len == sp->cno + 1) { sp->cno = len; LF_SET(TXT_APPENDEOL); } else ++sp->cno; } else LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); }
/* * v_dollar -- [count]$ * Move to the last column. * * PUBLIC: int v_dollar __P((SCR *, VICMD *)); */ int v_dollar(SCR *sp, VICMD *vp) { size_t len; int isempty; /* * !!! * A count moves down count - 1 rows, so, "3$" is the same as "2j$". */ if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) { /* * !!! * Historically, if the $ is a motion, and deleting from * at or before the first non-blank of the line, it's a * line motion, and the line motion flag is set. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno)) return (1); if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); --vp->count; if (v_down(sp, vp)) return (1); } /* * !!! * Historically, it was illegal to use $ as a motion command on * an empty line. Unfortunately, even though C was historically * aliased to c$, it (and not c$) was special cased to work on * empty lines. Since we alias C to c$ too, we have a problem. * To fix it, we let c$ go through, on the assumption that it's * not a problem for it to work. */ if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) { if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) { v_eol(sp, NULL); return (1); } return (0); } /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start. Ignore others. */ vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); }
/* * v_chF -- [count]Fc * Search backward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chF __P((SCR *, VICMD *)); */ int v_chF(SCR *sp, VICMD *vp) { size_t len; u_long cnt; int isempty; ARG_CHAR_T key; CHAR_T *endp, *p; /* * !!! * If it's a dot command, it doesn't reset the key for which * we're searching, e.g. in "df1|f2|.|;", the ';' searches * for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = FSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = p - 1; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (--p > endp && *p != key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = (p - endp) - 1; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); }
/* * api_gline -- * Get a line. * * PUBLIC: int api_gline __P((SCR *, db_recno_t, CHAR_T **, size_t *)); */ int api_gline(SCR *sp, db_recno_t lno, CHAR_T **linepp, size_t *lenp) { int isempty; if (db_eget(sp, lno, linepp, lenp, &isempty)) { if (isempty) msgq(sp, M_ERR, "209|The file is empty"); return (1); } return (0); }
/* * v_chf -- [count]fc * Search forward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chf __P((SCR *, VICMD *)); */ int v_chf(SCR *sp, VICMD *vp) { size_t len; u_long cnt; int isempty; ARG_CHAR_T key; CHAR_T *endp, *p, *startp; /* * !!! * If it's a dot command, it doesn't reset the key for which we're * searching, e.g. in "df1|f2|.|;", the ';' searches for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = fSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = (startp = p) + len; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (++p < endp && *p != key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = p - startp; /* * Non-motion commands move to the end of the range. * Delete and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); }
/* * cs_init -- * Initialize character stream routines. * * PUBLIC: int cs_init __P((SCR *, VCS *)); */ int cs_init(SCR *sp, VCS *csp) { int isempty; if (db_eget(sp, csp->cs_lno, &csp->cs_bp, &csp->cs_len, &isempty)) { if (isempty) msgq(sp, M_BERR, "177|Empty file"); return (1); } if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_ch = csp->cs_bp[csp->cs_cno]; } return (0); }
/* * v_right -- [count]' ', [count]l * Move right by columns. * * PUBLIC: int v_right __P((SCR *, VICMD *)); */ int v_right(SCR *sp, VICMD *vp) { size_t len; int isempty; if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { if (isempty) goto eol; return (1); } /* It's always illegal to move right on empty lines. */ if (len == 0) { eol: v_eol(sp, NULL); return (1); } /* * Non-motion commands move to the end of the range. Delete and * yank stay at the start. Ignore others. Adjust the end of the * range for motion commands. * * !!! * Historically, "[cdsy]l" worked at the end of a line. Also, * EOL is a count sink. */ vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) { v_eol(sp, NULL); return (1); } if (vp->m_stop.cno >= len) { vp->m_stop.cno = len - 1; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; } else if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); }
/* * v_ii -- [count]i * [count]I * Insert text at the cursor position. * * PUBLIC: int v_ii __P((SCR *, VICMD *)); */ int v_ii(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_INSERT; sp->lno = vp->m_start.lno; if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); }
/* * 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); }
/* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec __P((SCR *, db_recno_t)); */ int sscr_exec(SCR *sp, db_recno_t lno) { SCRIPT *sc; db_recno_t last_lno; size_t blen, len, last_len; int isempty, matchprompt, rval; ssize_t nw; char *bp = NULL; const char *p; const CHAR_T *ip; size_t ilen; sc = sp->script; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, __UNCONST(&ip), &ilen)) return (1); INT2CHAR(sp, ip, ilen, p, last_len); if (last_len == sc->sh_prompt_len && memcmp(p, sc->sh_prompt, last_len) == 0) { matchprompt = 1; GET_SPACE_RETC(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, __UNCONST(&ip), &ilen, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (ilen == 0) goto empty; INT2CHAR(sp, ip, ilen, p, len); /* Delete any prompt. */ if (len >= sc->sh_prompt_len && memcmp(p, sc->sh_prompt, sc->sh_prompt_len) == 0) { len -= sc->sh_prompt_len; if (len == 0) { empty: msgq(sp, M_BERR, "151|No command to execute"); goto err1; } p += sc->sh_prompt_len; } /* Push the line to the shell. */ if ((size_t)(nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_GOTO(sp, char, bp, blen, last_len + len); memmove(bp + last_len, p, len); CHAR2INT(sp, bp, last_len + len, ip, ilen); if (db_set(sp, last_lno, ip, ilen)) err1: rval = 1; } if (matchprompt) alloc_err: FREE_SPACE(sp, bp, blen); return (rval); }
/* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec(SCR *, recno_t); */ int sscr_exec(SCR *sp, recno_t lno) { SCRIPT *sc; recno_t last_lno; size_t blen, len, last_len, tlen; int isempty, matchprompt, nw, rval; char *bp, *p; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len)) return (1); if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { matchprompt = 1; GET_SPACE_RET(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, &p, &len, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (len == 0) goto empty; /* Delete any prompt. */ if (sscr_matchprompt(sp, p, len, &tlen)) { if (tlen == len) { empty: msgq(sp, M_BERR, "No command to execute"); goto err1; } p += (len - tlen); len = tlen; } /* Push the line to the shell. */ sc = sp->script; if ((nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_RET(sp, bp, blen, last_len + len); memmove(bp + last_len, p, len); if (db_set(sp, last_lno, bp, last_len + len)) err1: rval = 1; } if (matchprompt) FREE_SPACE(sp, bp, blen); return (rval); }
/* * v_change -- [buffer][count]c[count]motion * [buffer][count]C * [buffer][count]S * Change command. * * PUBLIC: int v_change __P((SCR *, VICMD *)); */ int v_change(SCR *sp, VICMD *vp) { size_t blen, len; u_int32_t flags; int isempty, lmode, rval; CHAR_T *bp; CHAR_T *p; /* * 'c' can be combined with motion commands that set the resulting * cursor position, i.e. "cG". Clear the VM_RCM flags and make the * resulting cursor position stick, inserting text has its own rules * for cursor positioning. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* * Find out if the file is empty, it's easier to handle it as a * special case. */ if (vp->m_start.lno == vp->m_stop.lno && db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); return (v_ia(sp, vp)); } flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; /* * Move the cursor to the start of the change. Note, if autoindent * is turned on, the cc command in line mode changes from the first * *non-blank* character of the line, not the first character. And, * to make it just a bit more exciting, the initial space is handled * as auto-indent characters. */ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; if (lmode) { vp->m_start.cno = 0; if (O_ISSET(sp, O_AUTOINDENT)) { if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno)) return (1); LF_SET(TXT_AICHARS); } } sp->lno = vp->m_start.lno; sp->cno = vp->m_start.cno; LOG_CORRECT; /* * If not in line mode and changing within a single line, copy the * text and overwrite it. */ if (!lmode && vp->m_start.lno == vp->m_stop.lno) { /* * !!! * Historic practice, c did not cut into the numeric buffers, * only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * It's trickier if in line mode or changing over multiple lines. If * we're in line mode delete all of the lines and insert a replacement * line which the user edits. If there was leading whitespace in the * first line being changed, we copy it and use it as the replacement. * If we're not in line mode, we delete the text and start inserting. * * !!! * Copy the text. Historic practice, c did not cut into the numeric * buffers, only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines and there's leading text. */ if (lmode && vp->m_start.cno) { /* * Get a copy of the first line changed, and copy out the * leading text. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, vp->m_start.cno); MEMMOVEW(bp, p, vp->m_start.cno); } else bp = NULL; /* Delete the text. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines, insert a replacement line. */ if (lmode) { if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno)) return (1); sp->lno = vp->m_start.lno; len = sp->cno = vp->m_start.cno; } /* Get the line we're editing. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } /* Check to see if we're appending to the line. */ if (vp->m_start.cno >= len) LF_SET(TXT_APPENDEOL); rval = v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags); if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); }
/* * v_match -- % * Search to matching character. * * PUBLIC: int v_match __P((SCR *, VICMD *)); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)__P((SCR *, VCS *)); CHAR_T *p; char *cp; const char *match_chars; static MARK match = { 0, 0 }; static int match_dir; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. * Also fixed to do something sensible with "". */ match_chars = O_STR(sp, O_MATCHCHARS); /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = strchr(match_chars, startc); if (cp != NULL) break; } cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; /* Alternate back-forward search if startc and matchc the same */ if (startc == matchc) { /* are we continuing from where last match finished? */ if (match.lno == vp->m_start.lno && match.cno ==vp->m_start.cno) /* yes - continue in sequence */ match_dir++; else /* no - go forward, back, back, forward */ match_dir = 1; if (match_dir & 2) cnt++; } gc = cnt & 1 ? cs_prev : cs_next; cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == matchc && --cnt == 0) break; if (cs.cs_ch == startc) ++cnt; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; match.lno = vp->m_final.lno; match.cno = vp->m_final.cno; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!ISBLANK((UCHAR_T)*p)) return (0); F_SET(vp, VM_LMODE); return (0); }
/* * v_match -- % * Search to matching character. * * PUBLIC: int v_match(SCR *, VICMD *); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *); CHAR_T *p; CHAR_T *cp; const CHAR_T *match_chars; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. */ match_chars = VIP(sp)->mcs; /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = STRCHR(match_chars, startc); if (cp != NULL) { cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; gc = cnt & 1 ? cs_prev : cs_next; break; } } cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == startc) ++cnt; else if (cs.cs_ch == matchc && --cnt == 0) break; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!isblank(*p)) return (0); F_SET(vp, VM_LMODE); return (0); }