/* * v_cfirst -- [count]_ * Move to the first non-blank character in a line. * * PUBLIC: int v_cfirst __P((SCR *, VICMD *)); */ int v_cfirst(SCR *sp, VICMD *vp) { db_recno_t cnt, lno; /* * !!! * If the _ is a motion component, it makes the command a line motion * e.g. "d_" deletes the line. It also means that the cursor doesn't * move. * * The _ command never failed in the first column. */ if (ISMOTION(vp)) F_SET(vp, VM_LMODE); /* * !!! * Historically a specified count makes _ move down count - 1 * rows, so, "3_" is the same as "2j_". */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt != 1) { --vp->count; return (v_down(sp, vp)); } /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case _ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The _ command has to fail if the file is empty and we're doing * a delete. If deleting line 1, and 0 is the first nonblank, * make the check. */ if (vp->m_stop.lno == 1 && vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) { if (db_last(sp, &lno)) return (1); if (lno == 0) { v_sol(sp); return (1); } } /* * Delete and non-motion commands move to the end of the range, * yank stays at the start. Ignore others. */ vp->m_final = ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop; return (0); }
/* * 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_first -- ^ * Move to the first non-blank character in this line. * * PUBLIC: int v_first __P((SCR *, VICMD *)); */ int v_first(SCR *sp, VICMD *vp) { /* * !!! * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case ^ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The ^ command succeeded if used as a command when the cursor was * on the first non-blank in the line, but failed if used as a motion * component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) { v_sol(sp); return (1); } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending charcter. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); }
/* * v_ncol -- [count]| * Move to column count or the first column on this line. If the * requested column is past EOL, move to EOL. The nasty part is * that we have to know character column widths to make this work. * * PUBLIC: int v_ncol __P((SCR *, VICMD *)); */ int v_ncol(SCR *sp, VICMD *vp) { if (F_ISSET(vp, VC_C1SET) && vp->count > 1) { --vp->count; vp->m_stop.cno = vs_colpos(sp, vp->m_start.lno, (size_t)vp->count); /* * !!! * The | command succeeded if used as a command and the cursor * didn't move, but failed if used as a motion component in the * same situation. */ if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } } else { /* * !!! * The | command succeeded if used as a command in column 0 * without a count, but failed if used as a motion component * in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (1); } vp->m_stop.cno = 0; } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending charcter. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); }
/* * v_left -- [count]^H, [count]h * Move left by columns. * * PUBLIC: int v_left __P((SCR *, VICMD *)); */ int v_left(SCR *sp, VICMD *vp) { db_recno_t cnt; /* * !!! * The ^H and h commands always failed in the first column. */ if (vp->m_start.cno == 0) { v_sol(sp); return (1); } /* Find the end of the range. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (vp->m_start.cno > cnt) vp->m_stop.cno = vp->m_start.cno - cnt; else vp->m_stop.cno = 0; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; 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_search -- * The search commands. */ static int v_search(SCR *sp, VICMD *vp, CHAR_T *ptrn, size_t plen, u_int flags, dir_t dir) { /* Display messages. */ LF_SET(SEARCH_MSG); /* If it's a motion search, offset past end-of-line is okay. */ if (ISMOTION(vp)) LF_SET(SEARCH_EOL); /* * XXX * Warn if the search wraps. See the comment above, in v_exaddr(). */ if (!KEYS_WAITING(sp)) LF_SET(SEARCH_WMSG); switch (dir) { case BACKWARD: if (b_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case FORWARD: if (f_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case NOTSET: msgq(sp, M_ERR, "189|No previous search pattern"); return (1); default: abort(); } /* Correct motion commands, otherwise, simply move to the location. */ if (ISMOTION(vp)) { if (v_correct(sp, vp, 0)) return(1); } else vp->m_final = 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); }
/* * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+ * Move down by lines. * * PUBLIC: int v_down(SCR *, VICMD *); */ int v_down(SCR *sp, VICMD *vp) { recno_t lno; lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (!db_exist(sp, lno)) { v_eof(sp, &vp->m_start); return (1); } vp->m_stop.lno = lno; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; 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); }
/* * v_zero -- 0 * Move to the first column on this line. * * PUBLIC: int v_zero __P((SCR *, VICMD *)); */ int v_zero(SCR *sp, VICMD *vp) { /* * !!! * The 0 command succeeded if used as a command in the first column * but failed if used as a motion component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (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_stop.cno = 0; if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); }
static void goto_adjust(VICMD *vp) { /* Guess that it's the end of the range. */ vp->m_final = vp->m_stop; /* * Non-motion commands move the cursor to the end of the range, and * then to the NEXT nonblank of the line. Historic vi always moved * to the first nonblank in the line; since the H, M, and L commands * are logical motions in this implementation, we do the next nonblank * so that it looks approximately the same to the user. To make this * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table. * * If it's a motion, it's more complicated. The best possible solution * is probably to display the first nonblank of the line the cursor * will eventually rest on. This is tricky, particularly given that if * the associated command is a delete, we don't yet know what line that * will be. So, we clear the VM_RCM_SETNNB flag, and set the first * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently * long, this can cause the cursor to warp out of the screen. It's too * hard to fix. * * XXX * The G command is always first nonblank, so it's okay to reset it. */ if (ISMOTION(vp)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } else return; /* * If moving backward in the file, delete and yank move to the end * of the range, unless the line didn't change, in which case yank * doesn't move. If moving forward in the file, delete and yank * stay at the start of the range. Ignore others. */ if (vp->m_stop.lno < vp->m_start.lno || (vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno < vp->m_start.cno)) { if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno) vp->m_final = vp->m_start; } else vp->m_final = vp->m_start; }
/* * v_cht -- [count]tc * Search forward in the line for the character before the next * occurrence of the specified character. * * PUBLIC: int v_cht __P((SCR *, VICMD *)); */ int v_cht(SCR *sp, VICMD *vp) { if (v_chf(sp, vp)) return (1); /* * v_chf places the cursor on the character, where the 't' * command wants it to its left. We know this is safe since * we had to move right for v_chf() to have succeeded. */ --vp->m_stop.cno; /* * Make any necessary correction to the motion decision made * by the v_chf routine. */ if (!ISMOTION(vp)) vp->m_final = vp->m_stop; VIP(sp)->csearchdir = tSEARCH; return (0); }
/* * mark -- * Mark commands. */ static int mark(SCR *sp, VICMD *vp, int getmark, enum which cmd) { dir_t dir; MARK m; size_t len; if (getmark && mark_get(sp, vp->character, &vp->m_stop, M_BERR)) return (1); /* * !!! * Historically, BQMARKS for character positions that no longer * existed acted as FQMARKS. * * FQMARKS move to the first non-blank. */ switch (cmd) { case BQMARK: if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); if (vp->m_stop.cno < len || (vp->m_stop.cno == len && len == 0)) break; if (ISMOTION(vp)) F_SET(vp, VM_LMODE); cmd = FQMARK; /* FALLTHROUGH */ case FQMARK: vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); break; default: abort(); } /* Non-motion commands move to the end of the range. */ if (!ISMOTION(vp)) { vp->m_final = vp->m_stop; return (0); } /* * !!! * If a motion component to a BQMARK, the cursor has to move. */ if (cmd == BQMARK && vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command. */ 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)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; dir = BACKWARD; } else dir = FORWARD; /* * Yank cursor motion, when associated with marks as motion commands, * historically behaved as follows: * * ` motion ' motion * Line change? Line change? * Y N Y N * -------------- --------------- * FORWARD: | NM NM | NM NM * | | * BACKWARD: | M M | M NM(1) * * where NM means the cursor didn't move, and M means the cursor * moved to the mark. * * As the cursor was usually moved for yank commands associated * with backward motions, this implementation regularizes it by * changing the NM at position (1) to be an M. This makes mark * motions match search motions, which is probably A Good Thing. * * Delete cursor motion was always to the start of the text region, * regardless. Ignore other motion commands. */ #ifdef HISTORICAL_PRACTICE if (ISCMD(vp->rkp, 'y')) { if ((cmd == BQMARK || (cmd == FQMARK && vp->m_start.lno != vp->m_stop.lno)) && (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 = vp->m_stop; } else if (ISCMD(vp->rkp, 'd')) 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 = vp->m_stop; #else vp->m_final = vp->m_start; #endif /* * Forward marks are always line oriented, and it's set in the * vcmd.c table. */ if (cmd == FQMARK) return (0); /* * BQMARK'S moving backward and starting at column 0, and ones moving * forward and ending at column 0 are corrected to the last column of * the previous line. Otherwise, adjust the starting/ending point to * the character before the current one (this is safe because we know * the search had to move to succeed). * * Mark motions become line mode opertions if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); }
/* * eword -- * Move forward to the end of the word. */ static int eword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; int nmw, omw; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the next character is whitespace, move past * it. (This doesn't count as a word move.) Stay at the character * past the current one, it sets word "state" for the 'e' command. */ if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) goto start; } if (cs_fblank(sp, &cs)) return (1); /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'e' command, the definition of a word keeps * switching. */ start: if (type == BIGWORD) while (cnt--) { nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; } /* * When we reach the start of the word after the last * word, we're done. If we changed state, back up one * to the end of the previous word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (nmw == omw && cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * 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); }
/* * bword -- * Move backward by words. */ static int bword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; int nmw, omw; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the previous character is whitespace, move * past it. (This doesn't count as a word move.) Stay at the * character before the current one, it sets word "state" for the * 'b' command. */ if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == 0 && !ISBLANK2(cs.cs_ch)) goto start; } if (cs_bblank(sp, &cs)) return (1); /* * Cyclically move to the beginning of the previous word -- this * involves skipping over word characters and then any trailing * non-word characters. Note, for the 'b' command, the definition * of a word keeps switching. */ start: if (type == BIGWORD) while (cnt--) { nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; } /* * When we reach the end of the word before the last * word, we're done. If we changed state, move forward * one to the end of the next word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (nmw == omw && cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch)) if (cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } /* If we didn't move, we must be at SOF. */ ret: if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_sof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. * * !!! * The historic vi didn't get this right -- the `yb' command yanked * the right stuff and even updated the cursor value, but the cursor * was not actually updated on the screen. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); }
/* * v_sectionf -- [count]]] * Move forward count sections/functions. * * !!! * Using ]] as a motion command was a bit special, historically. It could * match } as well as the usual { and section values. If it matched a { or * a section, it did NOT include the matched line. If it matched a }, it * did include the line. No clue why. * * PUBLIC: int v_sectionf(SCR *, VICMD *); */ int v_sectionf(SCR *sp, VICMD *vp) { recno_t cnt, lno; size_t len; CHAR_T *p; char *list, *lp; /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); /* * !!! * If the starting 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, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{' || (ISMOTION(vp) && p[0] == '}')) { if (!--cnt) { if (p[0] == '{') goto adjust1; goto adjust2; } continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { /* * !!! * If not cutting this line, adjust to the end * of the previous one. Otherwise, position to * column 0. */ adjust1: if (ISMOTION(vp)) goto ret1; adjust2: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret2; } } /* If moving forward, reached EOF, check to see if we started there. */ if (vp->m_start.lno == lno - 1) { v_eof(sp, NULL); return (1); } ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.lno = lno; vp->m_stop.cno = len ? len - 1 : 0; /* * Non-motion commands go to the end of the range. Delete and * yank stay at the start of the range. Ignore others. */ ret2: if (ISMOTION(vp)) { vp->m_final = vp->m_start; if (F_ISSET(vp, VM_LMODE)) vp->m_final.cno = 0; } else vp->m_final = vp->m_stop; return (0); }
/* * fword -- * Move forward by words. */ static int fword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; int nmw, omw; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * If in white-space: * If the count is 1, and it's a change command, we're done. * Else, move to the first non-white-space character, which * counts as a single word move. If it's a motion command, * don't move off the end of the line. */ if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && ISBLANK2(cs.cs_ch))) { if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) { if (ISCMD(vp->rkp, 'c')) return (0); if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) { if (cs_fspace(sp, &cs)) return (1); goto ret; } } if (cs_fblank(sp, &cs)) return (1); --cnt; } /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'w' command, the definition of a word keeps * switching. */ if (type == BIGWORD) while (cnt--) { nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; } /* * If a motion command and we're at the end of the * last word, we're done. Delete and yank eat any * trailing blanks, but we don't move off the end * of the line regardless. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (nmw == omw && cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; nmw = ISMULTIWIDTH(sp, cs.cs_ch); for (;;) { omw = nmw; if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch) || (nmw = ISMULTIWIDTH(sp, cs.cs_ch)) != omw) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK2(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Adjust the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; if (ISMOTION(vp) && cs.cs_flags == 0) --vp->m_stop.cno; /* * 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); }
/* * 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_exaddr -- * Do a vi search (which is really an ex address). */ static int v_exaddr(SCR *sp, VICMD *vp, dir_t dir) { static EXCMDLIST fake = { L("search") }; EXCMD *cmdp; GS *gp; TEXT *tp; recno_t s_lno; size_t len, s_cno, tlen; int err, nb, type; char buf[20]; CHAR_T *cmd, *t; CHAR_T *w; size_t wlen; /* * !!! * If using the search command as a motion, any addressing components * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/. */ if (F_ISSET(vp, VC_ISDOT)) return (v_search(sp, vp, NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir)); /* Get the search pattern. */ if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH, TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT | (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0))) return (1); tp = TAILQ_FIRST(sp->tiq); /* If the user backspaced over the prompt, do nothing. */ if (tp->term == TERM_BS) return (1); /* * If the user was doing an incremental search, then we've already * updated the cursor and moved to the right location. Return the * correct values, we're done. */ if (tp->term == TERM_SEARCH) { vp->m_stop.lno = sp->lno; vp->m_stop.cno = sp->cno; if (ISMOTION(vp)) return (v_correct(sp, vp, 0)); vp->m_final = vp->m_stop; return (0); } /* * If the user entered <escape> or <carriage-return>, the length is * 1 and the right thing will happen, i.e. the prompt will be used * as a command character. * * Build a fake ex command structure. */ gp = sp->gp; gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; F_INIT(&gp->excmd, E_VISEARCH); /* * XXX * Warn if the search wraps. This is a pretty special case, but it's * nice feature that wasn't in the original implementations of ex/vi. * (It was added at some point to System V's version.) This message * is only displayed if there are no keys in the queue. The problem is * the command is going to succeed, and the message is informational, * not an error. If a macro displays it repeatedly, e.g., the pattern * only occurs once in the file and wrapscan is set, you lose big. For * example, if the macro does something like: * * :map K /pattern/^MjK * * Each search will display the message, but the following "/pattern/" * will immediately overwrite it, with strange results. The System V * vi displays the "wrapped" message multiple times, but because it's * overwritten each time, it's not as noticeable. As we don't discard * messages, it's a real problem for us. */ if (!KEYS_WAITING(sp)) F_SET(&gp->excmd, E_SEARCH_WMSG); /* Save the current line/column. */ s_lno = sp->lno; s_cno = sp->cno; /* * !!! * Historically, vi / and ? commands were full-blown ex addresses, * including ';' delimiters, trailing <blank>'s, multiple search * strings (separated by semi-colons) and, finally, full-blown z * commands after the / and ? search strings. (If the search was * being used as a motion, the trailing z command was ignored. * Also, we do some argument checking on the z command, to be sure * that it's not some other random command.) For multiple search * strings, leading <blank>'s at the second and subsequent strings * were eaten as well. This has some (unintended?) side-effects: * the command /ptrn/;3 is legal and results in moving to line 3. * I suppose you could use it to optionally move to line 3... * * !!! * Historically, if any part of the search command failed, the cursor * remained unmodified (even if ; was used). We have to play games * because the underlying ex parser thinks we're modifying the cursor * as we go, but I think we're compatible with historic practice. * * !!! * Historically, the command "/STRING/; " failed, apparently it * confused the parser. We're not that compatible. */ cmdp = &gp->excmd; if (ex_range(sp, cmdp, &err)) return (1); /* * Remember where any remaining command information is, and clean * up the fake ex command. */ cmd = cmdp->cp; len = cmdp->clen; gp->excmd.clen = 0; if (err) goto err2; /* Copy out the new cursor position and make sure it's okay. */ switch (cmdp->addrcnt) { case 1: vp->m_stop = cmdp->addr1; break; case 2: vp->m_stop = cmdp->addr2; break; } if (!db_exist(sp, vp->m_stop.lno)) { ex_badaddr(sp, &fake, vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK); goto err2; } /* * !!! * Historic practice is that a trailing 'z' was ignored if it was a * motion command. Should probably be an error, but not worth the * effort. */ if (ISMOTION(vp)) return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA))); /* * !!! * Historically, if it wasn't a motion command, a delta in the search * pattern turns it into a first nonblank movement. */ nb = F_ISSET(cmdp, E_DELTA); /* Check for the 'z' command. */ if (len != 0) { if (*cmd != 'z') goto err1; /* No blanks, just like the z command. */ for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen) if (!isdigit(*t)) break; if (tlen && (*t == '-' || *t == '.' || *t == '+' || *t == '^')) { ++t; --tlen; type = 1; } else type = 0; if (tlen) goto err1; /* The z command will do the nonblank for us. */ nb = 0; /* Default to z+. */ if (!type && v_event_push(sp, NULL, L("+"), 1, CH_NOMAP | CH_QUOTED)) return (1); /* Push the user's command. */ if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED)) return (1); /* Push line number so get correct z display. */ tlen = snprintf(buf, sizeof(buf), "%lu", (u_long)vp->m_stop.lno); CHAR2INT(sp, buf, tlen, w, wlen); if (v_event_push(sp, NULL, w, wlen, CH_NOMAP | CH_QUOTED)) return (1); /* Don't refresh until after 'z' happens. */ F_SET(VIP(sp), VIP_S_REFRESH); } /* Non-motion commands move to the end of the range. */ vp->m_final = vp->m_stop; if (nb) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } return (0); err1: msgq(sp, M_ERR, "188|Characters after search string, line offset and/or z command"); err2: vp->m_final.lno = s_lno; vp->m_final.cno = s_cno; return (1); }
/* * 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); }
/* * v_sentenceb -- [count]( * Move backward count sentences. * * PUBLIC: int v_sentenceb __P((SCR *, VICMD *)); */ int v_sentenceb(SCR *sp, VICMD *vp) { VCS cs; db_recno_t slno; size_t len, scno; u_long cnt; int last; /* * !!! * Historic vi permitted the user to hit SOF repeatedly. */ if (vp->m_start.lno == 1 && vp->m_start.cno == 0) return (0); cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * In empty lines, skip to the previous non-white-space character. * If in text, skip to the prevous white-space character. Believe * it or not, in the paragraph: * ab cd. * AB CD. * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia, * Berkeley was once a major center of drug activity. */ if (cs.cs_flags == CS_EMP) { if (cs_bblank(sp, &cs)) return (1); for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != CS_EOL) break; } } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } for (last = 0;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */ break; if (cs.cs_flags == CS_EOL) { last = 1; continue; } if (cs.cs_flags == CS_EMP) { if (--cnt == 0) goto ret; if (cs_bblank(sp, &cs)) return (1); last = 0; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': if (!last || --cnt != 0) { last = 0; continue; } ret: slno = cs.cs_lno; scno = cs.cs_cno; /* * Move to the start of the sentence, skipping blanks * and special characters. */ do { if (cs_next(sp, &cs)) return (1); } while (!cs.cs_flags && (cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'')); if ((cs.cs_flags || isblank(cs.cs_ch)) && cs_fblank(sp, &cs)) return (1); /* * If it was ". xyz", with the cursor on the 'x', or * "end. ", with the cursor in the spaces, or the * beginning of a sentence preceded by an empty line, * we can end up where we started. Fix it. */ if (vp->m_start.lno != cs.cs_lno || vp->m_start.cno != cs.cs_cno) goto okret; /* * Well, if an empty line preceded possible blanks * and the sentence, it could be a real sentence. */ for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_EOL) continue; if (cs.cs_flags == 0 && isblank(cs.cs_ch)) continue; break; } if (cs.cs_flags == CS_EMP) goto okret; /* But it wasn't; try again. */ ++cnt; cs.cs_lno = slno; cs.cs_cno = scno; last = 0; break; case '\t': last = 1; break; default: last = cs.cs_flags == CS_EOL || isblank(cs.cs_ch) || cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0; } } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * If the starting and stopping cursor positions are at the first * columns in the line, i.e. the movement is cutting an entire line, * the buffer is in line mode, and the starting position is the last * character of the previous line. * * All commands move to the end of the range. Adjust the start of * the range for motion commands. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (db_get(sp, --vp->m_start.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_start.cno = len ? len - 1 : 0; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); }
/* * v_sentencef -- [count]) * Move forward count sentences. * * PUBLIC: int v_sentencef __P((SCR *, VICMD *)); */ int v_sentencef(SCR *sp, VICMD *vp) { enum { BLANK, NONE, PERIOD } state; VCS cs; size_t len; u_long cnt; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * If in white-space, the next start of sentence counts as one. * This may not handle " . " correctly, but it's real unclear * what correctly means in that case. */ if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) { if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) { if (vp->m_start.lno != cs.cs_lno || vp->m_start.cno != cs.cs_cno) goto okret; return (1); } } for (state = NONE;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) break; if (cs.cs_flags == CS_EOL) { if ((state == PERIOD || state == BLANK) && --cnt == 0) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && isblank(cs.cs_ch) && cs_fblank(sp, &cs)) return (1); goto okret; } state = NONE; continue; } if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */ if (--cnt == 0) goto okret; if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) goto okret; state = NONE; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': state = PERIOD; break; case ')': case ']': case '"': case '\'': if (state != PERIOD) state = NONE; break; case '\t': if (state == PERIOD) state = BLANK; /* FALLTHROUGH */ case ' ': if (state == PERIOD) { state = BLANK; break; } if (state == BLANK && --cnt == 0) { if (cs_fblank(sp, &cs)) return (1); goto okret; } /* FALLTHROUGH */ default: state = NONE; break; } } /* EOF is a movement sink, but it's an error not to have moved. */ if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) { v_eof(sp, NULL); return (1); } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * Historic, uh, features, yeah, that's right, call 'em features. * If the starting and ending cursor positions are at the first * column in their lines, i.e. the movement is cutting entire lines, * the buffer is in line mode, and the ending position is the last * character of the previous line. Note check to make sure that * it's not within a single line. * * 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. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (vp->m_start.lno < vp->m_stop.lno) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; } F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); }