Mercurial > vim
diff src/textobject.c @ 32670:695b50472e85
Fix line endings issue
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Mon, 26 Jun 2023 13:13:12 +0200 |
parents | 448aef880252 |
children | bcf1429cb1fc |
line wrap: on
line diff
--- a/src/textobject.c +++ b/src/textobject.c @@ -1,2006 +1,2006 @@ -/* vi:set ts=8 sts=4 sw=4 noet: - * - * VIM - Vi IMproved by Bram Moolenaar - * - * Do ":help uganda" in Vim to read copying and usage conditions. - * Do ":help credits" in Vim to see a list of people who contributed. - * See README.txt for an overview of the Vim source code. - */ - -/* - * textobject.c: functions for text objects - */ -#include "vim.h" - -static int cls(void); -static int skip_chars(int, int); - -/* - * Find the start of the next sentence, searching in the direction specified - * by the "dir" argument. The cursor is positioned on the start of the next - * sentence when found. If the next sentence is found, return OK. Return FAIL - * otherwise. See ":h sentence" for the precise definition of a "sentence" - * text object. - */ - int -findsent(int dir, long count) -{ - pos_T pos, tpos; - pos_T prev_pos; - int c; - int (*func)(pos_T *); - int startlnum; - int noskip = FALSE; // do not skip blanks - int cpo_J; - int found_dot; - - pos = curwin->w_cursor; - if (dir == FORWARD) - func = incl; - else - func = decl; - - while (count--) - { - prev_pos = pos; - - /* - * if on an empty line, skip up to a non-empty line - */ - if (gchar_pos(&pos) == NUL) - { - do - if ((*func)(&pos) == -1) - break; - while (gchar_pos(&pos) == NUL); - if (dir == FORWARD) - goto found; - } - /* - * if on the start of a paragraph or a section and searching forward, - * go to the next line - */ - else if (dir == FORWARD && pos.col == 0 && - startPS(pos.lnum, NUL, FALSE)) - { - if (pos.lnum == curbuf->b_ml.ml_line_count) - return FAIL; - ++pos.lnum; - goto found; - } - else if (dir == BACKWARD) - decl(&pos); - - // go back to the previous non-white non-punctuation character - found_dot = FALSE; - while (c = gchar_pos(&pos), VIM_ISWHITE(c) - || vim_strchr((char_u *)".!?)]\"'", c) != NULL) - { - tpos = pos; - if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) - break; - - if (found_dot) - break; - if (vim_strchr((char_u *) ".!?", c) != NULL) - found_dot = TRUE; - - if (vim_strchr((char_u *) ")]\"'", c) != NULL - && vim_strchr((char_u *) ".!?)]\"'", gchar_pos(&tpos)) == NULL) - break; - - decl(&pos); - } - - // remember the line where the search started - startlnum = pos.lnum; - cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; - - for (;;) // find end of sentence - { - c = gchar_pos(&pos); - if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) - { - if (dir == BACKWARD && pos.lnum != startlnum) - ++pos.lnum; - break; - } - if (c == '.' || c == '!' || c == '?') - { - tpos = pos; - do - if ((c = inc(&tpos)) == -1) - break; - while (vim_strchr((char_u *)")]\"'", c = gchar_pos(&tpos)) - != NULL); - if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL - || (cpo_J && (c == ' ' && inc(&tpos) >= 0 - && gchar_pos(&tpos) == ' '))) - { - pos = tpos; - if (gchar_pos(&pos) == NUL) // skip NUL at EOL - inc(&pos); - break; - } - } - if ((*func)(&pos) == -1) - { - if (count) - return FAIL; - noskip = TRUE; - break; - } - } -found: - // skip white space - while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) - if (incl(&pos) == -1) - break; - - if (EQUAL_POS(prev_pos, pos)) - { - // didn't actually move, advance one character and try again - if ((*func)(&pos) == -1) - { - if (count) - return FAIL; - break; - } - ++count; - } - } - - setpcmark(); - curwin->w_cursor = pos; - return OK; -} - -/* - * Find the next paragraph or section in direction 'dir'. - * Paragraphs are currently supposed to be separated by empty lines. - * If 'what' is NUL we go to the next paragraph. - * If 'what' is '{' or '}' we go to the next section. - * If 'both' is TRUE also stop at '}'. - * Return TRUE if the next paragraph or section was found. - */ - int -findpar( - int *pincl, // Return: TRUE if last char is to be included - int dir, - long count, - int what, - int both) -{ - linenr_T curr; - int did_skip; // TRUE after separating lines have been skipped - int first; // TRUE on first line - int posix = (vim_strchr(p_cpo, CPO_PARA) != NULL); -#ifdef FEAT_FOLDING - linenr_T fold_first; // first line of a closed fold - linenr_T fold_last; // last line of a closed fold - int fold_skipped; // TRUE if a closed fold was skipped this - // iteration -#endif - - curr = curwin->w_cursor.lnum; - - while (count--) - { - did_skip = FALSE; - for (first = TRUE; ; first = FALSE) - { - if (*ml_get(curr) != NUL) - did_skip = TRUE; - -#ifdef FEAT_FOLDING - // skip folded lines - fold_skipped = FALSE; - if (first && hasFolding(curr, &fold_first, &fold_last)) - { - curr = ((dir > 0) ? fold_last : fold_first) + dir; - fold_skipped = TRUE; - } -#endif - - // POSIX has its own ideas of what a paragraph boundary is and it - // doesn't match historical Vi: It also stops at a "{" in the - // first column and at an empty line. - if (!first && did_skip && (startPS(curr, what, both) - || (posix && what == NUL && *ml_get(curr) == '{'))) - break; - -#ifdef FEAT_FOLDING - if (fold_skipped) - curr -= dir; -#endif - if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) - { - if (count) - return FALSE; - curr -= dir; - break; - } - } - } - setpcmark(); - if (both && *ml_get(curr) == '}') // include line with '}' - ++curr; - curwin->w_cursor.lnum = curr; - if (curr == curbuf->b_ml.ml_line_count && what != '}') - { - char_u *line = ml_get(curr); - - // Put the cursor on the last character in the last line and make the - // motion inclusive. - if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) - { - --curwin->w_cursor.col; - curwin->w_cursor.col -= - (*mb_head_off)(line, line + curwin->w_cursor.col); - *pincl = TRUE; - } - } - else - curwin->w_cursor.col = 0; - return TRUE; -} - -/* - * check if the string 's' is a nroff macro that is in option 'opt' - */ - static int -inmacro(char_u *opt, char_u *s) -{ - char_u *macro; - - for (macro = opt; macro[0]; ++macro) - { - // Accept two characters in the option being equal to two characters - // in the line. A space in the option matches with a space in the - // line or the line having ended. - if ( (macro[0] == s[0] - || (macro[0] == ' ' - && (s[0] == NUL || s[0] == ' '))) - && (macro[1] == s[1] - || ((macro[1] == NUL || macro[1] == ' ') - && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) - break; - ++macro; - if (macro[0] == NUL) - break; - } - return (macro[0] != NUL); -} - -/* - * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. - * If 'para' is '{' or '}' only check for sections. - * If 'both' is TRUE also stop at '}' - */ - int -startPS(linenr_T lnum, int para, int both) -{ - char_u *s; - - s = ml_get(lnum); - if (*s == para || *s == '\f' || (both && *s == '}')) - return TRUE; - if (*s == '.' && (inmacro(p_sections, s + 1) || - (!para && inmacro(p_para, s + 1)))) - return TRUE; - return FALSE; -} - -/* - * The following routines do the word searches performed by the 'w', 'W', - * 'b', 'B', 'e', and 'E' commands. - */ - -/* - * To perform these searches, characters are placed into one of three - * classes, and transitions between classes determine word boundaries. - * - * The classes are: - * - * 0 - white space - * 1 - punctuation - * 2 or higher - keyword characters (letters, digits and underscore) - */ - -static int cls_bigword; // TRUE for "W", "B" or "E" - -/* - * cls() - returns the class of character at curwin->w_cursor - * - * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars - * from class 2 and higher are reported as class 1 since only white space - * boundaries are of interest. - */ - static int -cls(void) -{ - int c; - - c = gchar_cursor(); - if (c == ' ' || c == '\t' || c == NUL) - return 0; - if (enc_dbcs != 0 && c > 0xFF) - { - // If cls_bigword, report multi-byte chars as class 1. - if (enc_dbcs == DBCS_KOR && cls_bigword) - return 1; - - // process code leading/trailing bytes - return dbcs_class(((unsigned)c >> 8), (c & 0xFF)); - } - if (enc_utf8) - { - c = utf_class(c); - if (c != 0 && cls_bigword) - return 1; - return c; - } - - // If cls_bigword is TRUE, report all non-blanks as class 1. - if (cls_bigword) - return 1; - - if (vim_iswordc(c)) - return 2; - return 1; -} - - -/* - * fwd_word(count, type, eol) - move forward one word - * - * Returns FAIL if the cursor was already at the end of the file. - * If eol is TRUE, last word stops at end of line (for operators). - */ - int -fwd_word( - long count, - int bigword, // "W", "E" or "B" - int eol) -{ - int sclass; // starting class - int i; - int last_line; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) - { -#ifdef FEAT_FOLDING - // When inside a range of folded lines, move to the last char of the - // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); -#endif - sclass = cls(); - - /* - * We always move at least one character, unless on the last - * character in the buffer. - */ - last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); - i = inc_cursor(); - if (i == -1 || (i >= 1 && last_line)) // started at last char in file - return FAIL; - if (i >= 1 && eol && count == 0) // started at last char in line - return OK; - - /* - * Go one char past end of current word (if any) - */ - if (sclass != 0) - while (cls() == sclass) - { - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) - return OK; - } - - /* - * go to next non-white - */ - while (cls() == 0) - { - /* - * We'll stop if we land on a blank line - */ - if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL) - break; - - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) - return OK; - } - } - return OK; -} - -/* - * bck_word() - move backward 'count' words - * - * If stop is TRUE and we are already on the start of a word, move one less. - * - * Returns FAIL if top of the file was reached. - */ - int -bck_word(long count, int bigword, int stop) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) - { -#ifdef FEAT_FOLDING - // When inside a range of folded lines, move to the first char of the - // first line. - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) - curwin->w_cursor.col = 0; -#endif - sclass = cls(); - if (dec_cursor() == -1) // started at start of file - return FAIL; - - if (!stop || sclass == cls() || sclass == 0) - { - /* - * Skip white space before the word. - * Stop on an empty line. - */ - while (cls() == 0) - { - if (curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) - goto finished; - if (dec_cursor() == -1) // hit start of file, stop here - return OK; - } - - /* - * Move backward to start of this word. - */ - if (skip_chars(cls(), BACKWARD)) - return OK; - } - - inc_cursor(); // overshot - forward one -finished: - stop = FALSE; - } - adjust_skipcol(); - return OK; -} - -/* - * end_word() - move to the end of the word - * - * There is an apparent bug in the 'e' motion of the real vi. At least on the - * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' - * motion crosses blank lines. When the real vi crosses a blank line in an - * 'e' motion, the cursor is placed on the FIRST character of the next - * non-blank line. The 'E' command, however, works correctly. Since this - * appears to be a bug, I have not duplicated it here. - * - * Returns FAIL if end of the file was reached. - * - * If stop is TRUE and we are already on the end of a word, move one less. - * If empty is TRUE stop on an empty line. - */ - int -end_word( - long count, - int bigword, - int stop, - int empty) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) - { -#ifdef FEAT_FOLDING - // When inside a range of folded lines, move to the last char of the - // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); -#endif - sclass = cls(); - if (inc_cursor() == -1) - return FAIL; - - /* - * If we're in the middle of a word, we just have to move to the end - * of it. - */ - if (cls() == sclass && sclass != 0) - { - /* - * Move forward to end of the current word - */ - if (skip_chars(sclass, FORWARD)) - return FAIL; - } - else if (!stop || sclass == 0) - { - /* - * We were at the end of a word. Go to the end of the next word. - * First skip white space, if 'empty' is TRUE, stop at empty line. - */ - while (cls() == 0) - { - if (empty && curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) - goto finished; - if (inc_cursor() == -1) // hit end of file, stop here - return FAIL; - } - - /* - * Move forward to the end of this word. - */ - if (skip_chars(cls(), FORWARD)) - return FAIL; - } - dec_cursor(); // overshot - one char backward -finished: - stop = FALSE; // we move only one word less - } - return OK; -} - -/* - * Move back to the end of the word. - * - * Returns FAIL if start of the file was reached. - */ - int -bckend_word( - long count, - int bigword, // TRUE for "B" - int eol) // TRUE: stop at end of line. -{ - int sclass; // starting class - int i; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) - { - sclass = cls(); - if ((i = dec_cursor()) == -1) - return FAIL; - if (eol && i == 1) - return OK; - - /* - * Move backward to before the start of this word. - */ - if (sclass != 0) - { - while (cls() == sclass) - if ((i = dec_cursor()) == -1 || (eol && i == 1)) - return OK; - } - - /* - * Move backward to end of the previous word - */ - while (cls() == 0) - { - if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) - break; - if ((i = dec_cursor()) == -1 || (eol && i == 1)) - return OK; - } - } - adjust_skipcol(); - return OK; -} - -/* - * Skip a row of characters of the same class. - * Return TRUE when end-of-file reached, FALSE otherwise. - */ - static int -skip_chars(int cclass, int dir) -{ - while (cls() == cclass) - if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) - return TRUE; - return FALSE; -} - -/* - * Go back to the start of the word or the start of white space - */ - static void -back_in_line(void) -{ - int sclass; // starting class - - sclass = cls(); - for (;;) - { - if (curwin->w_cursor.col == 0) // stop at start of line - break; - dec_cursor(); - if (cls() != sclass) // stop at start of word - { - inc_cursor(); - break; - } - } -} - - static void -find_first_blank(pos_T *posp) -{ - int c; - - while (decl(posp) != -1) - { - c = gchar_pos(posp); - if (!VIM_ISWHITE(c)) - { - incl(posp); - break; - } - } -} - -/* - * Skip count/2 sentences and count/2 separating white spaces. - */ - static void -findsent_forward( - long count, - int at_start_sent) // cursor is at start of sentence -{ - while (count--) - { - findsent(FORWARD, 1L); - if (at_start_sent) - find_first_blank(&curwin->w_cursor); - if (count == 0 || at_start_sent) - decl(&curwin->w_cursor); - at_start_sent = !at_start_sent; - } -} - -/* - * Find word under cursor, cursor at end. - * Used while an operator is pending, and in Visual mode. - */ - int -current_word( - oparg_T *oap, - long count, - int include, // TRUE: include word and white space - int bigword) // FALSE == word, TRUE == WORD -{ - pos_T start_pos; - pos_T pos; - int inclusive = TRUE; - int include_white = FALSE; - - cls_bigword = bigword; - CLEAR_POS(&start_pos); - - // Correct cursor when 'selection' is exclusive - if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor)) - dec_cursor(); - - /* - * When Visual mode is not active, or when the VIsual area is only one - * character, select the word and/or white space under the cursor. - */ - if (!VIsual_active || EQUAL_POS(curwin->w_cursor, VIsual)) - { - /* - * Go to start of current word or white space. - */ - back_in_line(); - start_pos = curwin->w_cursor; - - /* - * If the start is on white space, and white space should be included - * (" word"), or start is not on white space, and white space should - * not be included ("word"), find end of word. - */ - if ((cls() == 0) == include) - { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) - return FAIL; - } - else - { - /* - * If the start is not on white space, and white space should be - * included ("word "), or start is on white space and white - * space should not be included (" "), find start of word. - * If we end up in the first column of the next line (single char - * word) back up to end of the line. - */ - fwd_word(1L, bigword, TRUE); - if (curwin->w_cursor.col == 0) - decl(&curwin->w_cursor); - else - oneleft(); - - if (include) - include_white = TRUE; - } - - if (VIsual_active) - { - // should do something when inclusive == FALSE ! - VIsual = start_pos; - redraw_curbuf_later(UPD_INVERTED); // update the inversion - } - else - { - oap->start = start_pos; - oap->motion_type = MCHAR; - } - --count; - } - - /* - * When count is still > 0, extend with more objects. - */ - while (count > 0) - { - inclusive = TRUE; - if (VIsual_active && LT_POS(curwin->w_cursor, VIsual)) - { - /* - * In Visual mode, with cursor at start: move cursor back. - */ - if (decl(&curwin->w_cursor) == -1) - return FAIL; - if (include != (cls() != 0)) - { - if (bck_word(1L, bigword, TRUE) == FAIL) - return FAIL; - } - else - { - if (bckend_word(1L, bigword, TRUE) == FAIL) - return FAIL; - (void)incl(&curwin->w_cursor); - } - } - else - { - /* - * Move cursor forward one word and/or white area. - */ - if (incl(&curwin->w_cursor) == -1) - return FAIL; - if (include != (cls() == 0)) - { - if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) - return FAIL; - /* - * If end is just past a new-line, we don't want to include - * the first character on the line. - * Put cursor on last char of white. - */ - if (oneleft() == FAIL) - inclusive = FALSE; - } - else - { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) - return FAIL; - } - } - --count; - } - - if (include_white && (cls() != 0 - || (curwin->w_cursor.col == 0 && !inclusive))) - { - /* - * If we don't include white space at the end, move the start - * to include some white space there. This makes "daw" work - * better on the last word in a sentence (and "2daw" on last-but-one - * word). Also when "2daw" deletes "word." at the end of the line - * (cursor is at start of next line). - * But don't delete white space at start of line (indent). - */ - pos = curwin->w_cursor; // save cursor position - curwin->w_cursor = start_pos; - if (oneleft() == OK) - { - back_in_line(); - if (cls() == 0 && curwin->w_cursor.col > 0) - { - if (VIsual_active) - VIsual = curwin->w_cursor; - else - oap->start = curwin->w_cursor; - } - } - curwin->w_cursor = pos; // put cursor back at end - } - - if (VIsual_active) - { - if (*p_sel == 'e' && inclusive && LTOREQ_POS(VIsual, curwin->w_cursor)) - inc_cursor(); - if (VIsual_mode == 'V') - { - VIsual_mode = 'v'; - redraw_cmdline = TRUE; // show mode later - } - } - else - oap->inclusive = inclusive; - - return OK; -} - -/* - * Find sentence(s) under the cursor, cursor at end. - * When Visual active, extend it by one or more sentences. - */ - int -current_sent(oparg_T *oap, long count, int include) -{ - pos_T start_pos; - pos_T pos; - int start_blank; - int c; - int at_start_sent; - long ncount; - - start_pos = curwin->w_cursor; - pos = start_pos; - findsent(FORWARD, 1L); // Find start of next sentence. - - /* - * When the Visual area is bigger than one character: Extend it. - */ - if (VIsual_active && !EQUAL_POS(start_pos, VIsual)) - { -extend: - if (LT_POS(start_pos, VIsual)) - { - /* - * Cursor at start of Visual area. - * Find out where we are: - * - in the white space before a sentence - * - in a sentence or just after it - * - at the start of a sentence - */ - at_start_sent = TRUE; - decl(&pos); - while (LT_POS(pos, curwin->w_cursor)) - { - c = gchar_pos(&pos); - if (!VIM_ISWHITE(c)) - { - at_start_sent = FALSE; - break; - } - incl(&pos); - } - if (!at_start_sent) - { - findsent(BACKWARD, 1L); - if (EQUAL_POS(curwin->w_cursor, start_pos)) - at_start_sent = TRUE; // exactly at start of sentence - else - // inside a sentence, go to its end (start of next) - findsent(FORWARD, 1L); - } - if (include) // "as" gets twice as much as "is" - count *= 2; - while (count--) - { - if (at_start_sent) - find_first_blank(&curwin->w_cursor); - c = gchar_cursor(); - if (!at_start_sent || (!include && !VIM_ISWHITE(c))) - findsent(BACKWARD, 1L); - at_start_sent = !at_start_sent; - } - } - else - { - /* - * Cursor at end of Visual area. - * Find out where we are: - * - just before a sentence - * - just before or in the white space before a sentence - * - in a sentence - */ - incl(&pos); - at_start_sent = TRUE; - // not just before a sentence - if (!EQUAL_POS(pos, curwin->w_cursor)) - { - at_start_sent = FALSE; - while (LT_POS(pos, curwin->w_cursor)) - { - c = gchar_pos(&pos); - if (!VIM_ISWHITE(c)) - { - at_start_sent = TRUE; - break; - } - incl(&pos); - } - if (at_start_sent) // in the sentence - findsent(BACKWARD, 1L); - else // in/before white before a sentence - curwin->w_cursor = start_pos; - } - - if (include) // "as" gets twice as much as "is" - count *= 2; - findsent_forward(count, at_start_sent); - if (*p_sel == 'e') - ++curwin->w_cursor.col; - } - return OK; - } - - /* - * If the cursor started on a blank, check if it is just before the start - * of the next sentence. - */ - while (c = gchar_pos(&pos), VIM_ISWHITE(c)) // VIM_ISWHITE() is a macro - incl(&pos); - if (EQUAL_POS(pos, curwin->w_cursor)) - { - start_blank = TRUE; - find_first_blank(&start_pos); // go back to first blank - } - else - { - start_blank = FALSE; - findsent(BACKWARD, 1L); - start_pos = curwin->w_cursor; - } - if (include) - ncount = count * 2; - else - { - ncount = count; - if (start_blank) - --ncount; - } - if (ncount > 0) - findsent_forward(ncount, TRUE); - else - decl(&curwin->w_cursor); - - if (include) - { - /* - * If the blank in front of the sentence is included, exclude the - * blanks at the end of the sentence, go back to the first blank. - * If there are no trailing blanks, try to include leading blanks. - */ - if (start_blank) - { - find_first_blank(&curwin->w_cursor); - c = gchar_pos(&curwin->w_cursor); // VIM_ISWHITE() is a macro - if (VIM_ISWHITE(c)) - decl(&curwin->w_cursor); - } - else if (c = gchar_cursor(), !VIM_ISWHITE(c)) - find_first_blank(&start_pos); - } - - if (VIsual_active) - { - // Avoid getting stuck with "is" on a single space before a sentence. - if (EQUAL_POS(start_pos, curwin->w_cursor)) - goto extend; - if (*p_sel == 'e') - ++curwin->w_cursor.col; - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_cmdline = TRUE; // show mode later - redraw_curbuf_later(UPD_INVERTED); // update the inversion - } - else - { - // include a newline after the sentence, if there is one - if (incl(&curwin->w_cursor) == -1) - oap->inclusive = TRUE; - else - oap->inclusive = FALSE; - oap->start = start_pos; - oap->motion_type = MCHAR; - } - return OK; -} - -/* - * Find block under the cursor, cursor at end. - * "what" and "other" are two matching parenthesis/brace/etc. - */ - int -current_block( - oparg_T *oap, - long count, - int include, // TRUE == include white space - int what, // '(', '{', etc. - int other) // ')', '}', etc. -{ - pos_T old_pos; - pos_T *pos = NULL; - pos_T start_pos; - pos_T *end_pos; - pos_T old_start, old_end; - char_u *save_cpo; - int sol = FALSE; // '{' at start of line - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - - /* - * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. - */ - if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) - { - setpcmark(); - if (what == '{') // ignore indent - while (inindent(1)) - if (inc_cursor() != 0) - break; - if (gchar_cursor() == what) - // cursor on '(' or '{', move cursor just after it - ++curwin->w_cursor.col; - } - else if (LT_POS(VIsual, curwin->w_cursor)) - { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } - else - old_end = VIsual; - - /* - * Search backwards for unclosed '(', '{', etc.. - * Put this position in start_pos. - * Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the - * user wants. - */ - save_cpo = p_cpo; - p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); - if ((pos = findmatch(NULL, what)) != NULL) - { - while (count-- > 0) - { - if ((pos = findmatch(NULL, what)) == NULL) - break; - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } - else - { - while (count-- > 0) - { - if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) - break; - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } - p_cpo = save_cpo; - - /* - * Search for matching ')', '}', etc. - * Put this position in curwin->w_cursor. - */ - if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) - { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - - /* - * Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. - * If the ending '}', ')' or ']' is only preceded by indent, skip that - * indent. But only if the resulting area is not smaller than what we - * started with. - */ - while (!include) - { - incl(&start_pos); - sol = (curwin->w_cursor.col == 0); - decl(&curwin->w_cursor); - while (inindent(1)) - { - sol = TRUE; - if (decl(&curwin->w_cursor) != 0) - break; - } - - /* - * In Visual mode, when the resulting area is not bigger than what we - * started with, extend it to the next block, and then exclude again. - * Don't try to expand the area if the area is empty. - */ - if (!LT_POS(start_pos, old_start) && !LT_POS(old_end, curwin->w_cursor) - && !EQUAL_POS(start_pos, curwin->w_cursor) - && VIsual_active) - { - curwin->w_cursor = old_start; - decl(&curwin->w_cursor); - if ((pos = findmatch(NULL, what)) == NULL) - { - curwin->w_cursor = old_pos; - return FAIL; - } - start_pos = *pos; - curwin->w_cursor = *pos; - if ((end_pos = findmatch(NULL, other)) == NULL) - { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - } - else - break; - } - - if (VIsual_active) - { - if (*p_sel == 'e') - inc(&curwin->w_cursor); - if (sol && gchar_cursor() != NUL) - inc(&curwin->w_cursor); // include the line break - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(UPD_INVERTED); // update the inversion - showmode(); - } - else - { - oap->start = start_pos; - oap->motion_type = MCHAR; - oap->inclusive = FALSE; - if (sol) - incl(&curwin->w_cursor); - else if (LTOREQ_POS(start_pos, curwin->w_cursor)) - // Include the character under the cursor. - oap->inclusive = TRUE; - else - // End is before the start (no text in between <>, [], etc.): don't - // operate on any text. - curwin->w_cursor = start_pos; - } - - return OK; -} - -#if defined(FEAT_EVAL) || defined(PROTO) -/* - * Return TRUE if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". - * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>". - */ - static int -in_html_tag( - int end_tag) -{ - char_u *line = ml_get_curline(); - char_u *p; - int c; - int lc = NUL; - pos_T pos; - - if (enc_dbcs) - { - char_u *lp = NULL; - - // We search forward until the cursor, because searching backwards is - // very slow for DBCS encodings. - for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p)) - if (*p == '>' || *p == '<') - { - lc = *p; - lp = p; - } - if (*p != '<') // check for '<' under cursor - { - if (lc != '<') - return FALSE; - p = lp; - } - } - else - { - for (p = line + curwin->w_cursor.col; p > line; ) - { - if (*p == '<') // find '<' under/before cursor - break; - MB_PTR_BACK(line, p); - if (*p == '>') // find '>' before cursor - break; - } - if (*p != '<') - return FALSE; - } - - pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)(p - line); - - MB_PTR_ADV(p); - if (end_tag) - // check that there is a '/' after the '<' - return *p == '/'; - - // check that there is no '/' after the '<' - if (*p == '/') - return FALSE; - - // check that the matching '>' is not preceded by '/' - for (;;) - { - if (inc(&pos) < 0) - return FALSE; - c = *ml_get_pos(&pos); - if (c == '>') - break; - lc = c; - } - return lc != '/'; -} - -/* - * Find tag block under the cursor, cursor at end. - */ - int -current_tagblock( - oparg_T *oap, - long count_arg, - int include) // TRUE == include white space -{ - long count = count_arg; - long n; - pos_T old_pos; - pos_T start_pos; - pos_T end_pos; - pos_T old_start, old_end; - char_u *spat, *epat; - char_u *p; - char_u *cp; - int len; - int r; - int do_include = include; - int save_p_ws = p_ws; - int retval = FAIL; - int is_inclusive = TRUE; - - p_ws = FALSE; - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - if (!VIsual_active || *p_sel == 'e') - decl(&old_end); // old_end is inclusive - - /* - * If we start on "<aaa>" select that block. - */ - if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) - { - setpcmark(); - - // ignore indent - while (inindent(1)) - if (inc_cursor() != 0) - break; - - if (in_html_tag(FALSE)) - { - // cursor on start tag, move to its '>' - while (*ml_get_cursor() != '>') - if (inc_cursor() < 0) - break; - } - else if (in_html_tag(TRUE)) - { - // cursor on end tag, move to just before it - while (*ml_get_cursor() != '<') - if (dec_cursor() < 0) - break; - dec_cursor(); - old_end = curwin->w_cursor; - } - } - else if (LT_POS(VIsual, curwin->w_cursor)) - { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } - else - old_end = VIsual; - -again: - /* - * Search backwards for unclosed "<aaa>". - * Put this position in start_pos. - */ - for (n = 0; n < count; ++n) - { - if (do_searchpair((char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - (char_u *)"", - (char_u *)"</[^>]*>", BACKWARD, NULL, 0, - NULL, (linenr_T)0, 0L) <= 0) - { - curwin->w_cursor = old_pos; - goto theend; - } - } - start_pos = curwin->w_cursor; - - /* - * Search for matching "</aaa>". First isolate the "aaa". - */ - inc_cursor(); - p = ml_get_cursor(); - for (cp = p; *cp != NUL && *cp != '>' && !VIM_ISWHITE(*cp); MB_PTR_ADV(cp)) - ; - len = (int)(cp - p); - if (len == 0) - { - curwin->w_cursor = old_pos; - goto theend; - } - spat = alloc(len + 39); - epat = alloc(len + 9); - if (spat == NULL || epat == NULL) - { - vim_free(spat); - vim_free(epat); - curwin->w_cursor = old_pos; - goto theend; - } - sprintf((char *)spat, "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); - sprintf((char *)epat, "</%.*s>\\c", len, p); - - r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, - 0, NULL, (linenr_T)0, 0L); - - vim_free(spat); - vim_free(epat); - - if (r < 1 || LT_POS(curwin->w_cursor, old_end)) - { - // Can't find other end or it's before the previous end. Could be a - // HTML tag that doesn't have a matching end. Search backwards for - // another starting tag. - count = 1; - curwin->w_cursor = start_pos; - goto again; - } - - if (do_include) - { - // Include up to the '>'. - while (*ml_get_cursor() != '>') - if (inc_cursor() < 0) - break; - } - else - { - char_u *c = ml_get_cursor(); - - // Exclude the '<' of the end tag. - // If the closing tag is on new line, do not decrement cursor, but - // make operation exclusive, so that the linefeed will be selected - if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) - // do not decrement cursor - is_inclusive = FALSE; - else if (*c == '<') - dec_cursor(); - } - end_pos = curwin->w_cursor; - - if (!do_include) - { - // Exclude the start tag. - curwin->w_cursor = start_pos; - while (inc_cursor() >= 0) - if (*ml_get_cursor() == '>') - { - inc_cursor(); - start_pos = curwin->w_cursor; - break; - } - curwin->w_cursor = end_pos; - - // If we are in Visual mode and now have the same text as before set - // "do_include" and try again. - if (VIsual_active && EQUAL_POS(start_pos, old_start) - && EQUAL_POS(end_pos, old_end)) - { - do_include = TRUE; - curwin->w_cursor = old_start; - count = count_arg; - goto again; - } - } - - if (VIsual_active) - { - // If the end is before the start there is no text between tags, select - // the char under the cursor. - if (LT_POS(end_pos, start_pos)) - curwin->w_cursor = start_pos; - else if (*p_sel == 'e') - inc_cursor(); - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(UPD_INVERTED); // update the inversion - showmode(); - } - else - { - oap->start = start_pos; - oap->motion_type = MCHAR; - if (LT_POS(end_pos, start_pos)) - { - // End is before the start: there is no text between tags; operate - // on an empty area. - curwin->w_cursor = start_pos; - oap->inclusive = FALSE; - } - else - oap->inclusive = is_inclusive; - } - retval = OK; - -theend: - p_ws = save_p_ws; - return retval; -} -#endif - - int -current_par( - oparg_T *oap, - long count, - int include, // TRUE == include white space - int type) // 'p' for paragraph, 'S' for section -{ - linenr_T start_lnum; - linenr_T end_lnum; - int white_in_front; - int dir; - int start_is_white; - int prev_start_is_white; - int retval = OK; - int do_white = FALSE; - int t; - int i; - - if (type == 'S') // not implemented yet - return FAIL; - - start_lnum = curwin->w_cursor.lnum; - - /* - * When visual area is more than one line: extend it. - */ - if (VIsual_active && start_lnum != VIsual.lnum) - { -extend: - if (start_lnum < VIsual.lnum) - dir = BACKWARD; - else - dir = FORWARD; - for (i = count; --i >= 0; ) - { - if (start_lnum == - (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) - { - retval = FAIL; - break; - } - - prev_start_is_white = -1; - for (t = 0; t < 2; ++t) - { - start_lnum += dir; - start_is_white = linewhite(start_lnum); - if (prev_start_is_white == start_is_white) - { - start_lnum -= dir; - break; - } - for (;;) - { - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) - break; - if (start_is_white != linewhite(start_lnum + dir) - || (!start_is_white - && startPS(start_lnum + (dir > 0 - ? 1 : 0), 0, 0))) - break; - start_lnum += dir; - } - if (!include) - break; - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) - break; - prev_start_is_white = start_is_white; - } - } - curwin->w_cursor.lnum = start_lnum; - curwin->w_cursor.col = 0; - return retval; - } - - /* - * First move back to the start_lnum of the paragraph or white lines - */ - white_in_front = linewhite(start_lnum); - while (start_lnum > 1) - { - if (white_in_front) // stop at first white line - { - if (!linewhite(start_lnum - 1)) - break; - } - else // stop at first non-white line of start of paragraph - { - if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) - break; - } - --start_lnum; - } - - /* - * Move past the end of any white lines. - */ - end_lnum = start_lnum; - while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) - ++end_lnum; - - --end_lnum; - i = count; - if (!include && white_in_front) - --i; - while (i--) - { - if (end_lnum == curbuf->b_ml.ml_line_count) - return FAIL; - - if (!include) - do_white = linewhite(end_lnum + 1); - - if (include || !do_white) - { - ++end_lnum; - /* - * skip to end of paragraph - */ - while (end_lnum < curbuf->b_ml.ml_line_count - && !linewhite(end_lnum + 1) - && !startPS(end_lnum + 1, 0, 0)) - ++end_lnum; - } - - if (i == 0 && white_in_front && include) - break; - - /* - * skip to end of white lines after paragraph - */ - if (include || do_white) - while (end_lnum < curbuf->b_ml.ml_line_count - && linewhite(end_lnum + 1)) - ++end_lnum; - } - - /* - * If there are no empty lines at the end, try to find some empty lines at - * the start (unless that has been done already). - */ - if (!white_in_front && !linewhite(end_lnum) && include) - while (start_lnum > 1 && linewhite(start_lnum - 1)) - --start_lnum; - - if (VIsual_active) - { - // Problem: when doing "Vipipip" nothing happens in a single white - // line, we get stuck there. Trap this here. - if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) - goto extend; - if (VIsual.lnum != start_lnum) - { - VIsual.lnum = start_lnum; - VIsual.col = 0; - } - VIsual_mode = 'V'; - redraw_curbuf_later(UPD_INVERTED); // update the inversion - showmode(); - } - else - { - oap->start.lnum = start_lnum; - oap->start.col = 0; - oap->motion_type = MLINE; - } - curwin->w_cursor.lnum = end_lnum; - curwin->w_cursor.col = 0; - - return OK; -} - -/* - * Search quote char from string line[col]. - * Quote character escaped by one of the characters in "escape" is not counted - * as a quote. - * Returns column number of "quotechar" or -1 when not found. - */ - static int -find_next_quote( - char_u *line, - int col, - int quotechar, - char_u *escape) // escape characters, can be NULL -{ - int c; - - for (;;) - { - c = line[col]; - if (c == NUL) - return -1; - else if (escape != NULL && vim_strchr(escape, c)) - { - ++col; - if (line[col] == NUL) - return -1; - } - else if (c == quotechar) - break; - if (has_mbyte) - col += (*mb_ptr2len)(line + col); - else - ++col; - } - return col; -} - -/* - * Search backwards in "line" from column "col_start" to find "quotechar". - * Quote character escaped by one of the characters in "escape" is not counted - * as a quote. - * Return the found column or zero. - */ - static int -find_prev_quote( - char_u *line, - int col_start, - int quotechar, - char_u *escape) // escape characters, can be NULL -{ - int n; - - while (col_start > 0) - { - --col_start; - col_start -= (*mb_head_off)(line, line + col_start); - n = 0; - if (escape != NULL) - while (col_start - n > 0 && vim_strchr(escape, - line[col_start - n - 1]) != NULL) - ++n; - if (n & 1) - col_start -= n; // uneven number of escape chars, skip it - else if (line[col_start] == quotechar) - break; - } - return col_start; -} - -/* - * Find quote under the cursor, cursor at end. - * Returns TRUE if found, else FALSE. - */ - int -current_quote( - oparg_T *oap, - long count, - int include, // TRUE == include quote char - int quotechar) // Quote character -{ - char_u *line = ml_get_curline(); - int col_end; - int col_start = curwin->w_cursor.col; - int inclusive = FALSE; - int vis_empty = TRUE; // Visual selection <= 1 char - int vis_bef_curs = FALSE; // Visual starts before cursor - int did_exclusive_adj = FALSE; // adjusted pos for 'selection' - int inside_quotes = FALSE; // Looks like "i'" done before - int selected_quote = FALSE; // Has quote inside selection - int i; - int restore_vis_bef = FALSE; // restore VIsual on abort - - // When 'selection' is "exclusive" move the cursor to where it would be - // with 'selection' "inclusive", so that the logic is the same for both. - // The cursor then is moved forward after adjusting the area. - if (VIsual_active) - { - // this only works within one line - if (VIsual.lnum != curwin->w_cursor.lnum) - return FALSE; - - vis_bef_curs = LT_POS(VIsual, curwin->w_cursor); - vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); - if (*p_sel == 'e') - { - if (vis_bef_curs) - { - dec_cursor(); - did_exclusive_adj = TRUE; - } - else if (!vis_empty) - { - dec(&VIsual); - did_exclusive_adj = TRUE; - } - vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); - if (!vis_bef_curs && !vis_empty) - { - // VIsual needs to be the start of Visual selection. - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - vis_bef_curs = TRUE; - restore_vis_bef = TRUE; - } - } - } - - if (!vis_empty) - { - // Check if the existing selection exactly spans the text inside - // quotes. - if (vis_bef_curs) - { - inside_quotes = VIsual.col > 0 - && line[VIsual.col - 1] == quotechar - && line[curwin->w_cursor.col] != NUL - && line[curwin->w_cursor.col + 1] == quotechar; - i = VIsual.col; - col_end = curwin->w_cursor.col; - } - else - { - inside_quotes = curwin->w_cursor.col > 0 - && line[curwin->w_cursor.col - 1] == quotechar - && line[VIsual.col] != NUL - && line[VIsual.col + 1] == quotechar; - i = curwin->w_cursor.col; - col_end = VIsual.col; - } - - // Find out if we have a quote in the selection. - while (i <= col_end) - { - // check for going over the end of the line, which can happen if - // the line was changed after the Visual area was selected. - if (line[i] == NUL) - break; - if (line[i++] == quotechar) - { - selected_quote = TRUE; - break; - } - } - } - - if (!vis_empty && line[col_start] == quotechar) - { - // Already selecting something and on a quote character. Find the - // next quoted string. - if (vis_bef_curs) - { - // Assume we are on a closing quote: move to after the next - // opening quote. - col_start = find_next_quote(line, col_start + 1, quotechar, NULL); - if (col_start < 0) - goto abort_search; - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) - { - // We were on a starting quote perhaps? - col_end = col_start; - col_start = curwin->w_cursor.col; - } - } - else - { - col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) - goto abort_search; - col_start = find_prev_quote(line, col_end, quotechar, - curbuf->b_p_qe); - if (line[col_start] != quotechar) - { - // We were on an ending quote perhaps? - col_start = col_end; - col_end = curwin->w_cursor.col; - } - } - } - else - - if (line[col_start] == quotechar || !vis_empty) - { - int first_col = col_start; - - if (!vis_empty) - { - if (vis_bef_curs) - first_col = find_next_quote(line, col_start, quotechar, NULL); - else - first_col = find_prev_quote(line, col_start, quotechar, NULL); - } - - // The cursor is on a quote, we don't know if it's the opening or - // closing quote. Search from the start of the line to find out. - // Also do this when there is a Visual area, a' may leave the cursor - // in between two strings. - col_start = 0; - for (;;) - { - // Find open quote character. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0 || col_start > first_col) - goto abort_search; - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) - goto abort_search; - // If is cursor between start and end quote character, it is - // target text object. - if (col_start <= first_col && first_col <= col_end) - break; - col_start = col_end + 1; - } - } - else - { - // Search backward for a starting quote. - col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); - if (line[col_start] != quotechar) - { - // No quote before the cursor, look after the cursor. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0) - goto abort_search; - } - - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) - goto abort_search; - } - - // When "include" is TRUE, include spaces after closing quote or before - // the starting quote. - if (include) - { - if (VIM_ISWHITE(line[col_end + 1])) - while (VIM_ISWHITE(line[col_end + 1])) - ++col_end; - else - while (col_start > 0 && VIM_ISWHITE(line[col_start - 1])) - --col_start; - } - - // Set start position. After vi" another i" must include the ". - // For v2i" include the quotes. - if (!include && count < 2 && (vis_empty || !inside_quotes)) - ++col_start; - curwin->w_cursor.col = col_start; - if (VIsual_active) - { - // Set the start of the Visual area when the Visual area was empty, we - // were just inside quotes or the Visual area didn't start at a quote - // and didn't include a quote. - if (vis_empty - || (vis_bef_curs - && !selected_quote - && (inside_quotes - || (line[VIsual.col] != quotechar - && (VIsual.col == 0 - || line[VIsual.col - 1] != quotechar))))) - { - VIsual = curwin->w_cursor; - redraw_curbuf_later(UPD_INVERTED); - } - } - else - { - oap->start = curwin->w_cursor; - oap->motion_type = MCHAR; - } - - // Set end position. - curwin->w_cursor.col = col_end; - if ((include || count > 1 // After vi" another i" must include the ". - || (!vis_empty && inside_quotes) - ) && inc_cursor() == 2) - inclusive = TRUE; - if (VIsual_active) - { - if (vis_empty || vis_bef_curs) - { - // decrement cursor when 'selection' is not exclusive - if (*p_sel != 'e') - dec_cursor(); - } - else - { - // Cursor is at start of Visual area. Set the end of the Visual - // area when it was just inside quotes or it didn't end at a - // quote. - if (inside_quotes - || (!selected_quote - && line[VIsual.col] != quotechar - && (line[VIsual.col] == NUL - || line[VIsual.col + 1] != quotechar))) - { - dec_cursor(); - VIsual = curwin->w_cursor; - } - curwin->w_cursor.col = col_start; - } - if (VIsual_mode == 'V') - { - VIsual_mode = 'v'; - redraw_cmdline = TRUE; // show mode later - } - } - else - { - // Set inclusive and other oap's flags. - oap->inclusive = inclusive; - } - - return OK; - -abort_search: - if (VIsual_active && *p_sel == 'e') - { - if (did_exclusive_adj) - inc_cursor(); - if (restore_vis_bef) - { - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - } - } - return FALSE; -} +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * textobject.c: functions for text objects + */ +#include "vim.h" + +static int cls(void); +static int skip_chars(int, int); + +/* + * Find the start of the next sentence, searching in the direction specified + * by the "dir" argument. The cursor is positioned on the start of the next + * sentence when found. If the next sentence is found, return OK. Return FAIL + * otherwise. See ":h sentence" for the precise definition of a "sentence" + * text object. + */ + int +findsent(int dir, long count) +{ + pos_T pos, tpos; + pos_T prev_pos; + int c; + int (*func)(pos_T *); + int startlnum; + int noskip = FALSE; // do not skip blanks + int cpo_J; + int found_dot; + + pos = curwin->w_cursor; + if (dir == FORWARD) + func = incl; + else + func = decl; + + while (count--) + { + prev_pos = pos; + + /* + * if on an empty line, skip up to a non-empty line + */ + if (gchar_pos(&pos) == NUL) + { + do + if ((*func)(&pos) == -1) + break; + while (gchar_pos(&pos) == NUL); + if (dir == FORWARD) + goto found; + } + /* + * if on the start of a paragraph or a section and searching forward, + * go to the next line + */ + else if (dir == FORWARD && pos.col == 0 && + startPS(pos.lnum, NUL, FALSE)) + { + if (pos.lnum == curbuf->b_ml.ml_line_count) + return FAIL; + ++pos.lnum; + goto found; + } + else if (dir == BACKWARD) + decl(&pos); + + // go back to the previous non-white non-punctuation character + found_dot = FALSE; + while (c = gchar_pos(&pos), VIM_ISWHITE(c) + || vim_strchr((char_u *)".!?)]\"'", c) != NULL) + { + tpos = pos; + if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) + break; + + if (found_dot) + break; + if (vim_strchr((char_u *) ".!?", c) != NULL) + found_dot = TRUE; + + if (vim_strchr((char_u *) ")]\"'", c) != NULL + && vim_strchr((char_u *) ".!?)]\"'", gchar_pos(&tpos)) == NULL) + break; + + decl(&pos); + } + + // remember the line where the search started + startlnum = pos.lnum; + cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; + + for (;;) // find end of sentence + { + c = gchar_pos(&pos); + if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) + { + if (dir == BACKWARD && pos.lnum != startlnum) + ++pos.lnum; + break; + } + if (c == '.' || c == '!' || c == '?') + { + tpos = pos; + do + if ((c = inc(&tpos)) == -1) + break; + while (vim_strchr((char_u *)")]\"'", c = gchar_pos(&tpos)) + != NULL); + if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL + || (cpo_J && (c == ' ' && inc(&tpos) >= 0 + && gchar_pos(&tpos) == ' '))) + { + pos = tpos; + if (gchar_pos(&pos) == NUL) // skip NUL at EOL + inc(&pos); + break; + } + } + if ((*func)(&pos) == -1) + { + if (count) + return FAIL; + noskip = TRUE; + break; + } + } +found: + // skip white space + while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) + if (incl(&pos) == -1) + break; + + if (EQUAL_POS(prev_pos, pos)) + { + // didn't actually move, advance one character and try again + if ((*func)(&pos) == -1) + { + if (count) + return FAIL; + break; + } + ++count; + } + } + + setpcmark(); + curwin->w_cursor = pos; + return OK; +} + +/* + * Find the next paragraph or section in direction 'dir'. + * Paragraphs are currently supposed to be separated by empty lines. + * If 'what' is NUL we go to the next paragraph. + * If 'what' is '{' or '}' we go to the next section. + * If 'both' is TRUE also stop at '}'. + * Return TRUE if the next paragraph or section was found. + */ + int +findpar( + int *pincl, // Return: TRUE if last char is to be included + int dir, + long count, + int what, + int both) +{ + linenr_T curr; + int did_skip; // TRUE after separating lines have been skipped + int first; // TRUE on first line + int posix = (vim_strchr(p_cpo, CPO_PARA) != NULL); +#ifdef FEAT_FOLDING + linenr_T fold_first; // first line of a closed fold + linenr_T fold_last; // last line of a closed fold + int fold_skipped; // TRUE if a closed fold was skipped this + // iteration +#endif + + curr = curwin->w_cursor.lnum; + + while (count--) + { + did_skip = FALSE; + for (first = TRUE; ; first = FALSE) + { + if (*ml_get(curr) != NUL) + did_skip = TRUE; + +#ifdef FEAT_FOLDING + // skip folded lines + fold_skipped = FALSE; + if (first && hasFolding(curr, &fold_first, &fold_last)) + { + curr = ((dir > 0) ? fold_last : fold_first) + dir; + fold_skipped = TRUE; + } +#endif + + // POSIX has its own ideas of what a paragraph boundary is and it + // doesn't match historical Vi: It also stops at a "{" in the + // first column and at an empty line. + if (!first && did_skip && (startPS(curr, what, both) + || (posix && what == NUL && *ml_get(curr) == '{'))) + break; + +#ifdef FEAT_FOLDING + if (fold_skipped) + curr -= dir; +#endif + if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) + { + if (count) + return FALSE; + curr -= dir; + break; + } + } + } + setpcmark(); + if (both && *ml_get(curr) == '}') // include line with '}' + ++curr; + curwin->w_cursor.lnum = curr; + if (curr == curbuf->b_ml.ml_line_count && what != '}') + { + char_u *line = ml_get(curr); + + // Put the cursor on the last character in the last line and make the + // motion inclusive. + if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) + { + --curwin->w_cursor.col; + curwin->w_cursor.col -= + (*mb_head_off)(line, line + curwin->w_cursor.col); + *pincl = TRUE; + } + } + else + curwin->w_cursor.col = 0; + return TRUE; +} + +/* + * check if the string 's' is a nroff macro that is in option 'opt' + */ + static int +inmacro(char_u *opt, char_u *s) +{ + char_u *macro; + + for (macro = opt; macro[0]; ++macro) + { + // Accept two characters in the option being equal to two characters + // in the line. A space in the option matches with a space in the + // line or the line having ended. + if ( (macro[0] == s[0] + || (macro[0] == ' ' + && (s[0] == NUL || s[0] == ' '))) + && (macro[1] == s[1] + || ((macro[1] == NUL || macro[1] == ' ') + && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) + break; + ++macro; + if (macro[0] == NUL) + break; + } + return (macro[0] != NUL); +} + +/* + * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. + * If 'para' is '{' or '}' only check for sections. + * If 'both' is TRUE also stop at '}' + */ + int +startPS(linenr_T lnum, int para, int both) +{ + char_u *s; + + s = ml_get(lnum); + if (*s == para || *s == '\f' || (both && *s == '}')) + return TRUE; + if (*s == '.' && (inmacro(p_sections, s + 1) || + (!para && inmacro(p_para, s + 1)))) + return TRUE; + return FALSE; +} + +/* + * The following routines do the word searches performed by the 'w', 'W', + * 'b', 'B', 'e', and 'E' commands. + */ + +/* + * To perform these searches, characters are placed into one of three + * classes, and transitions between classes determine word boundaries. + * + * The classes are: + * + * 0 - white space + * 1 - punctuation + * 2 or higher - keyword characters (letters, digits and underscore) + */ + +static int cls_bigword; // TRUE for "W", "B" or "E" + +/* + * cls() - returns the class of character at curwin->w_cursor + * + * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars + * from class 2 and higher are reported as class 1 since only white space + * boundaries are of interest. + */ + static int +cls(void) +{ + int c; + + c = gchar_cursor(); + if (c == ' ' || c == '\t' || c == NUL) + return 0; + if (enc_dbcs != 0 && c > 0xFF) + { + // If cls_bigword, report multi-byte chars as class 1. + if (enc_dbcs == DBCS_KOR && cls_bigword) + return 1; + + // process code leading/trailing bytes + return dbcs_class(((unsigned)c >> 8), (c & 0xFF)); + } + if (enc_utf8) + { + c = utf_class(c); + if (c != 0 && cls_bigword) + return 1; + return c; + } + + // If cls_bigword is TRUE, report all non-blanks as class 1. + if (cls_bigword) + return 1; + + if (vim_iswordc(c)) + return 2; + return 1; +} + + +/* + * fwd_word(count, type, eol) - move forward one word + * + * Returns FAIL if the cursor was already at the end of the file. + * If eol is TRUE, last word stops at end of line (for operators). + */ + int +fwd_word( + long count, + int bigword, // "W", "E" or "B" + int eol) +{ + int sclass; // starting class + int i; + int last_line; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) + { +#ifdef FEAT_FOLDING + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) + coladvance((colnr_T)MAXCOL); +#endif + sclass = cls(); + + /* + * We always move at least one character, unless on the last + * character in the buffer. + */ + last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); + i = inc_cursor(); + if (i == -1 || (i >= 1 && last_line)) // started at last char in file + return FAIL; + if (i >= 1 && eol && count == 0) // started at last char in line + return OK; + + /* + * Go one char past end of current word (if any) + */ + if (sclass != 0) + while (cls() == sclass) + { + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) + return OK; + } + + /* + * go to next non-white + */ + while (cls() == 0) + { + /* + * We'll stop if we land on a blank line + */ + if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL) + break; + + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) + return OK; + } + } + return OK; +} + +/* + * bck_word() - move backward 'count' words + * + * If stop is TRUE and we are already on the start of a word, move one less. + * + * Returns FAIL if top of the file was reached. + */ + int +bck_word(long count, int bigword, int stop) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) + { +#ifdef FEAT_FOLDING + // When inside a range of folded lines, move to the first char of the + // first line. + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) + curwin->w_cursor.col = 0; +#endif + sclass = cls(); + if (dec_cursor() == -1) // started at start of file + return FAIL; + + if (!stop || sclass == cls() || sclass == 0) + { + /* + * Skip white space before the word. + * Stop on an empty line. + */ + while (cls() == 0) + { + if (curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) + goto finished; + if (dec_cursor() == -1) // hit start of file, stop here + return OK; + } + + /* + * Move backward to start of this word. + */ + if (skip_chars(cls(), BACKWARD)) + return OK; + } + + inc_cursor(); // overshot - forward one +finished: + stop = FALSE; + } + adjust_skipcol(); + return OK; +} + +/* + * end_word() - move to the end of the word + * + * There is an apparent bug in the 'e' motion of the real vi. At least on the + * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' + * motion crosses blank lines. When the real vi crosses a blank line in an + * 'e' motion, the cursor is placed on the FIRST character of the next + * non-blank line. The 'E' command, however, works correctly. Since this + * appears to be a bug, I have not duplicated it here. + * + * Returns FAIL if end of the file was reached. + * + * If stop is TRUE and we are already on the end of a word, move one less. + * If empty is TRUE stop on an empty line. + */ + int +end_word( + long count, + int bigword, + int stop, + int empty) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) + { +#ifdef FEAT_FOLDING + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) + coladvance((colnr_T)MAXCOL); +#endif + sclass = cls(); + if (inc_cursor() == -1) + return FAIL; + + /* + * If we're in the middle of a word, we just have to move to the end + * of it. + */ + if (cls() == sclass && sclass != 0) + { + /* + * Move forward to end of the current word + */ + if (skip_chars(sclass, FORWARD)) + return FAIL; + } + else if (!stop || sclass == 0) + { + /* + * We were at the end of a word. Go to the end of the next word. + * First skip white space, if 'empty' is TRUE, stop at empty line. + */ + while (cls() == 0) + { + if (empty && curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) + goto finished; + if (inc_cursor() == -1) // hit end of file, stop here + return FAIL; + } + + /* + * Move forward to the end of this word. + */ + if (skip_chars(cls(), FORWARD)) + return FAIL; + } + dec_cursor(); // overshot - one char backward +finished: + stop = FALSE; // we move only one word less + } + return OK; +} + +/* + * Move back to the end of the word. + * + * Returns FAIL if start of the file was reached. + */ + int +bckend_word( + long count, + int bigword, // TRUE for "B" + int eol) // TRUE: stop at end of line. +{ + int sclass; // starting class + int i; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) + { + sclass = cls(); + if ((i = dec_cursor()) == -1) + return FAIL; + if (eol && i == 1) + return OK; + + /* + * Move backward to before the start of this word. + */ + if (sclass != 0) + { + while (cls() == sclass) + if ((i = dec_cursor()) == -1 || (eol && i == 1)) + return OK; + } + + /* + * Move backward to end of the previous word + */ + while (cls() == 0) + { + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) + break; + if ((i = dec_cursor()) == -1 || (eol && i == 1)) + return OK; + } + } + adjust_skipcol(); + return OK; +} + +/* + * Skip a row of characters of the same class. + * Return TRUE when end-of-file reached, FALSE otherwise. + */ + static int +skip_chars(int cclass, int dir) +{ + while (cls() == cclass) + if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) + return TRUE; + return FALSE; +} + +/* + * Go back to the start of the word or the start of white space + */ + static void +back_in_line(void) +{ + int sclass; // starting class + + sclass = cls(); + for (;;) + { + if (curwin->w_cursor.col == 0) // stop at start of line + break; + dec_cursor(); + if (cls() != sclass) // stop at start of word + { + inc_cursor(); + break; + } + } +} + + static void +find_first_blank(pos_T *posp) +{ + int c; + + while (decl(posp) != -1) + { + c = gchar_pos(posp); + if (!VIM_ISWHITE(c)) + { + incl(posp); + break; + } + } +} + +/* + * Skip count/2 sentences and count/2 separating white spaces. + */ + static void +findsent_forward( + long count, + int at_start_sent) // cursor is at start of sentence +{ + while (count--) + { + findsent(FORWARD, 1L); + if (at_start_sent) + find_first_blank(&curwin->w_cursor); + if (count == 0 || at_start_sent) + decl(&curwin->w_cursor); + at_start_sent = !at_start_sent; + } +} + +/* + * Find word under cursor, cursor at end. + * Used while an operator is pending, and in Visual mode. + */ + int +current_word( + oparg_T *oap, + long count, + int include, // TRUE: include word and white space + int bigword) // FALSE == word, TRUE == WORD +{ + pos_T start_pos; + pos_T pos; + int inclusive = TRUE; + int include_white = FALSE; + + cls_bigword = bigword; + CLEAR_POS(&start_pos); + + // Correct cursor when 'selection' is exclusive + if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor)) + dec_cursor(); + + /* + * When Visual mode is not active, or when the VIsual area is only one + * character, select the word and/or white space under the cursor. + */ + if (!VIsual_active || EQUAL_POS(curwin->w_cursor, VIsual)) + { + /* + * Go to start of current word or white space. + */ + back_in_line(); + start_pos = curwin->w_cursor; + + /* + * If the start is on white space, and white space should be included + * (" word"), or start is not on white space, and white space should + * not be included ("word"), find end of word. + */ + if ((cls() == 0) == include) + { + if (end_word(1L, bigword, TRUE, TRUE) == FAIL) + return FAIL; + } + else + { + /* + * If the start is not on white space, and white space should be + * included ("word "), or start is on white space and white + * space should not be included (" "), find start of word. + * If we end up in the first column of the next line (single char + * word) back up to end of the line. + */ + fwd_word(1L, bigword, TRUE); + if (curwin->w_cursor.col == 0) + decl(&curwin->w_cursor); + else + oneleft(); + + if (include) + include_white = TRUE; + } + + if (VIsual_active) + { + // should do something when inclusive == FALSE ! + VIsual = start_pos; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } + else + { + oap->start = start_pos; + oap->motion_type = MCHAR; + } + --count; + } + + /* + * When count is still > 0, extend with more objects. + */ + while (count > 0) + { + inclusive = TRUE; + if (VIsual_active && LT_POS(curwin->w_cursor, VIsual)) + { + /* + * In Visual mode, with cursor at start: move cursor back. + */ + if (decl(&curwin->w_cursor) == -1) + return FAIL; + if (include != (cls() != 0)) + { + if (bck_word(1L, bigword, TRUE) == FAIL) + return FAIL; + } + else + { + if (bckend_word(1L, bigword, TRUE) == FAIL) + return FAIL; + (void)incl(&curwin->w_cursor); + } + } + else + { + /* + * Move cursor forward one word and/or white area. + */ + if (incl(&curwin->w_cursor) == -1) + return FAIL; + if (include != (cls() == 0)) + { + if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) + return FAIL; + /* + * If end is just past a new-line, we don't want to include + * the first character on the line. + * Put cursor on last char of white. + */ + if (oneleft() == FAIL) + inclusive = FALSE; + } + else + { + if (end_word(1L, bigword, TRUE, TRUE) == FAIL) + return FAIL; + } + } + --count; + } + + if (include_white && (cls() != 0 + || (curwin->w_cursor.col == 0 && !inclusive))) + { + /* + * If we don't include white space at the end, move the start + * to include some white space there. This makes "daw" work + * better on the last word in a sentence (and "2daw" on last-but-one + * word). Also when "2daw" deletes "word." at the end of the line + * (cursor is at start of next line). + * But don't delete white space at start of line (indent). + */ + pos = curwin->w_cursor; // save cursor position + curwin->w_cursor = start_pos; + if (oneleft() == OK) + { + back_in_line(); + if (cls() == 0 && curwin->w_cursor.col > 0) + { + if (VIsual_active) + VIsual = curwin->w_cursor; + else + oap->start = curwin->w_cursor; + } + } + curwin->w_cursor = pos; // put cursor back at end + } + + if (VIsual_active) + { + if (*p_sel == 'e' && inclusive && LTOREQ_POS(VIsual, curwin->w_cursor)) + inc_cursor(); + if (VIsual_mode == 'V') + { + VIsual_mode = 'v'; + redraw_cmdline = TRUE; // show mode later + } + } + else + oap->inclusive = inclusive; + + return OK; +} + +/* + * Find sentence(s) under the cursor, cursor at end. + * When Visual active, extend it by one or more sentences. + */ + int +current_sent(oparg_T *oap, long count, int include) +{ + pos_T start_pos; + pos_T pos; + int start_blank; + int c; + int at_start_sent; + long ncount; + + start_pos = curwin->w_cursor; + pos = start_pos; + findsent(FORWARD, 1L); // Find start of next sentence. + + /* + * When the Visual area is bigger than one character: Extend it. + */ + if (VIsual_active && !EQUAL_POS(start_pos, VIsual)) + { +extend: + if (LT_POS(start_pos, VIsual)) + { + /* + * Cursor at start of Visual area. + * Find out where we are: + * - in the white space before a sentence + * - in a sentence or just after it + * - at the start of a sentence + */ + at_start_sent = TRUE; + decl(&pos); + while (LT_POS(pos, curwin->w_cursor)) + { + c = gchar_pos(&pos); + if (!VIM_ISWHITE(c)) + { + at_start_sent = FALSE; + break; + } + incl(&pos); + } + if (!at_start_sent) + { + findsent(BACKWARD, 1L); + if (EQUAL_POS(curwin->w_cursor, start_pos)) + at_start_sent = TRUE; // exactly at start of sentence + else + // inside a sentence, go to its end (start of next) + findsent(FORWARD, 1L); + } + if (include) // "as" gets twice as much as "is" + count *= 2; + while (count--) + { + if (at_start_sent) + find_first_blank(&curwin->w_cursor); + c = gchar_cursor(); + if (!at_start_sent || (!include && !VIM_ISWHITE(c))) + findsent(BACKWARD, 1L); + at_start_sent = !at_start_sent; + } + } + else + { + /* + * Cursor at end of Visual area. + * Find out where we are: + * - just before a sentence + * - just before or in the white space before a sentence + * - in a sentence + */ + incl(&pos); + at_start_sent = TRUE; + // not just before a sentence + if (!EQUAL_POS(pos, curwin->w_cursor)) + { + at_start_sent = FALSE; + while (LT_POS(pos, curwin->w_cursor)) + { + c = gchar_pos(&pos); + if (!VIM_ISWHITE(c)) + { + at_start_sent = TRUE; + break; + } + incl(&pos); + } + if (at_start_sent) // in the sentence + findsent(BACKWARD, 1L); + else // in/before white before a sentence + curwin->w_cursor = start_pos; + } + + if (include) // "as" gets twice as much as "is" + count *= 2; + findsent_forward(count, at_start_sent); + if (*p_sel == 'e') + ++curwin->w_cursor.col; + } + return OK; + } + + /* + * If the cursor started on a blank, check if it is just before the start + * of the next sentence. + */ + while (c = gchar_pos(&pos), VIM_ISWHITE(c)) // VIM_ISWHITE() is a macro + incl(&pos); + if (EQUAL_POS(pos, curwin->w_cursor)) + { + start_blank = TRUE; + find_first_blank(&start_pos); // go back to first blank + } + else + { + start_blank = FALSE; + findsent(BACKWARD, 1L); + start_pos = curwin->w_cursor; + } + if (include) + ncount = count * 2; + else + { + ncount = count; + if (start_blank) + --ncount; + } + if (ncount > 0) + findsent_forward(ncount, TRUE); + else + decl(&curwin->w_cursor); + + if (include) + { + /* + * If the blank in front of the sentence is included, exclude the + * blanks at the end of the sentence, go back to the first blank. + * If there are no trailing blanks, try to include leading blanks. + */ + if (start_blank) + { + find_first_blank(&curwin->w_cursor); + c = gchar_pos(&curwin->w_cursor); // VIM_ISWHITE() is a macro + if (VIM_ISWHITE(c)) + decl(&curwin->w_cursor); + } + else if (c = gchar_cursor(), !VIM_ISWHITE(c)) + find_first_blank(&start_pos); + } + + if (VIsual_active) + { + // Avoid getting stuck with "is" on a single space before a sentence. + if (EQUAL_POS(start_pos, curwin->w_cursor)) + goto extend; + if (*p_sel == 'e') + ++curwin->w_cursor.col; + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_cmdline = TRUE; // show mode later + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } + else + { + // include a newline after the sentence, if there is one + if (incl(&curwin->w_cursor) == -1) + oap->inclusive = TRUE; + else + oap->inclusive = FALSE; + oap->start = start_pos; + oap->motion_type = MCHAR; + } + return OK; +} + +/* + * Find block under the cursor, cursor at end. + * "what" and "other" are two matching parenthesis/brace/etc. + */ + int +current_block( + oparg_T *oap, + long count, + int include, // TRUE == include white space + int what, // '(', '{', etc. + int other) // ')', '}', etc. +{ + pos_T old_pos; + pos_T *pos = NULL; + pos_T start_pos; + pos_T *end_pos; + pos_T old_start, old_end; + char_u *save_cpo; + int sol = FALSE; // '{' at start of line + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + + /* + * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. + */ + if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) + { + setpcmark(); + if (what == '{') // ignore indent + while (inindent(1)) + if (inc_cursor() != 0) + break; + if (gchar_cursor() == what) + // cursor on '(' or '{', move cursor just after it + ++curwin->w_cursor.col; + } + else if (LT_POS(VIsual, curwin->w_cursor)) + { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } + else + old_end = VIsual; + + /* + * Search backwards for unclosed '(', '{', etc.. + * Put this position in start_pos. + * Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the + * user wants. + */ + save_cpo = p_cpo; + p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); + if ((pos = findmatch(NULL, what)) != NULL) + { + while (count-- > 0) + { + if ((pos = findmatch(NULL, what)) == NULL) + break; + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } + else + { + while (count-- > 0) + { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) + break; + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } + p_cpo = save_cpo; + + /* + * Search for matching ')', '}', etc. + * Put this position in curwin->w_cursor. + */ + if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) + { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + + /* + * Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. + * If the ending '}', ')' or ']' is only preceded by indent, skip that + * indent. But only if the resulting area is not smaller than what we + * started with. + */ + while (!include) + { + incl(&start_pos); + sol = (curwin->w_cursor.col == 0); + decl(&curwin->w_cursor); + while (inindent(1)) + { + sol = TRUE; + if (decl(&curwin->w_cursor) != 0) + break; + } + + /* + * In Visual mode, when the resulting area is not bigger than what we + * started with, extend it to the next block, and then exclude again. + * Don't try to expand the area if the area is empty. + */ + if (!LT_POS(start_pos, old_start) && !LT_POS(old_end, curwin->w_cursor) + && !EQUAL_POS(start_pos, curwin->w_cursor) + && VIsual_active) + { + curwin->w_cursor = old_start; + decl(&curwin->w_cursor); + if ((pos = findmatch(NULL, what)) == NULL) + { + curwin->w_cursor = old_pos; + return FAIL; + } + start_pos = *pos; + curwin->w_cursor = *pos; + if ((end_pos = findmatch(NULL, other)) == NULL) + { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + } + else + break; + } + + if (VIsual_active) + { + if (*p_sel == 'e') + inc(&curwin->w_cursor); + if (sol && gchar_cursor() != NUL) + inc(&curwin->w_cursor); // include the line break + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } + else + { + oap->start = start_pos; + oap->motion_type = MCHAR; + oap->inclusive = FALSE; + if (sol) + incl(&curwin->w_cursor); + else if (LTOREQ_POS(start_pos, curwin->w_cursor)) + // Include the character under the cursor. + oap->inclusive = TRUE; + else + // End is before the start (no text in between <>, [], etc.): don't + // operate on any text. + curwin->w_cursor = start_pos; + } + + return OK; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". + * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>". + */ + static int +in_html_tag( + int end_tag) +{ + char_u *line = ml_get_curline(); + char_u *p; + int c; + int lc = NUL; + pos_T pos; + + if (enc_dbcs) + { + char_u *lp = NULL; + + // We search forward until the cursor, because searching backwards is + // very slow for DBCS encodings. + for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p)) + if (*p == '>' || *p == '<') + { + lc = *p; + lp = p; + } + if (*p != '<') // check for '<' under cursor + { + if (lc != '<') + return FALSE; + p = lp; + } + } + else + { + for (p = line + curwin->w_cursor.col; p > line; ) + { + if (*p == '<') // find '<' under/before cursor + break; + MB_PTR_BACK(line, p); + if (*p == '>') // find '>' before cursor + break; + } + if (*p != '<') + return FALSE; + } + + pos.lnum = curwin->w_cursor.lnum; + pos.col = (colnr_T)(p - line); + + MB_PTR_ADV(p); + if (end_tag) + // check that there is a '/' after the '<' + return *p == '/'; + + // check that there is no '/' after the '<' + if (*p == '/') + return FALSE; + + // check that the matching '>' is not preceded by '/' + for (;;) + { + if (inc(&pos) < 0) + return FALSE; + c = *ml_get_pos(&pos); + if (c == '>') + break; + lc = c; + } + return lc != '/'; +} + +/* + * Find tag block under the cursor, cursor at end. + */ + int +current_tagblock( + oparg_T *oap, + long count_arg, + int include) // TRUE == include white space +{ + long count = count_arg; + long n; + pos_T old_pos; + pos_T start_pos; + pos_T end_pos; + pos_T old_start, old_end; + char_u *spat, *epat; + char_u *p; + char_u *cp; + int len; + int r; + int do_include = include; + int save_p_ws = p_ws; + int retval = FAIL; + int is_inclusive = TRUE; + + p_ws = FALSE; + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + if (!VIsual_active || *p_sel == 'e') + decl(&old_end); // old_end is inclusive + + /* + * If we start on "<aaa>" select that block. + */ + if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) + { + setpcmark(); + + // ignore indent + while (inindent(1)) + if (inc_cursor() != 0) + break; + + if (in_html_tag(FALSE)) + { + // cursor on start tag, move to its '>' + while (*ml_get_cursor() != '>') + if (inc_cursor() < 0) + break; + } + else if (in_html_tag(TRUE)) + { + // cursor on end tag, move to just before it + while (*ml_get_cursor() != '<') + if (dec_cursor() < 0) + break; + dec_cursor(); + old_end = curwin->w_cursor; + } + } + else if (LT_POS(VIsual, curwin->w_cursor)) + { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } + else + old_end = VIsual; + +again: + /* + * Search backwards for unclosed "<aaa>". + * Put this position in start_pos. + */ + for (n = 0; n < count; ++n) + { + if (do_searchpair((char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + (char_u *)"", + (char_u *)"</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) + { + curwin->w_cursor = old_pos; + goto theend; + } + } + start_pos = curwin->w_cursor; + + /* + * Search for matching "</aaa>". First isolate the "aaa". + */ + inc_cursor(); + p = ml_get_cursor(); + for (cp = p; *cp != NUL && *cp != '>' && !VIM_ISWHITE(*cp); MB_PTR_ADV(cp)) + ; + len = (int)(cp - p); + if (len == 0) + { + curwin->w_cursor = old_pos; + goto theend; + } + spat = alloc(len + 39); + epat = alloc(len + 9); + if (spat == NULL || epat == NULL) + { + vim_free(spat); + vim_free(epat); + curwin->w_cursor = old_pos; + goto theend; + } + sprintf((char *)spat, "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); + sprintf((char *)epat, "</%.*s>\\c", len, p); + + r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, + 0, NULL, (linenr_T)0, 0L); + + vim_free(spat); + vim_free(epat); + + if (r < 1 || LT_POS(curwin->w_cursor, old_end)) + { + // Can't find other end or it's before the previous end. Could be a + // HTML tag that doesn't have a matching end. Search backwards for + // another starting tag. + count = 1; + curwin->w_cursor = start_pos; + goto again; + } + + if (do_include) + { + // Include up to the '>'. + while (*ml_get_cursor() != '>') + if (inc_cursor() < 0) + break; + } + else + { + char_u *c = ml_get_cursor(); + + // Exclude the '<' of the end tag. + // If the closing tag is on new line, do not decrement cursor, but + // make operation exclusive, so that the linefeed will be selected + if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) + // do not decrement cursor + is_inclusive = FALSE; + else if (*c == '<') + dec_cursor(); + } + end_pos = curwin->w_cursor; + + if (!do_include) + { + // Exclude the start tag. + curwin->w_cursor = start_pos; + while (inc_cursor() >= 0) + if (*ml_get_cursor() == '>') + { + inc_cursor(); + start_pos = curwin->w_cursor; + break; + } + curwin->w_cursor = end_pos; + + // If we are in Visual mode and now have the same text as before set + // "do_include" and try again. + if (VIsual_active && EQUAL_POS(start_pos, old_start) + && EQUAL_POS(end_pos, old_end)) + { + do_include = TRUE; + curwin->w_cursor = old_start; + count = count_arg; + goto again; + } + } + + if (VIsual_active) + { + // If the end is before the start there is no text between tags, select + // the char under the cursor. + if (LT_POS(end_pos, start_pos)) + curwin->w_cursor = start_pos; + else if (*p_sel == 'e') + inc_cursor(); + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } + else + { + oap->start = start_pos; + oap->motion_type = MCHAR; + if (LT_POS(end_pos, start_pos)) + { + // End is before the start: there is no text between tags; operate + // on an empty area. + curwin->w_cursor = start_pos; + oap->inclusive = FALSE; + } + else + oap->inclusive = is_inclusive; + } + retval = OK; + +theend: + p_ws = save_p_ws; + return retval; +} +#endif + + int +current_par( + oparg_T *oap, + long count, + int include, // TRUE == include white space + int type) // 'p' for paragraph, 'S' for section +{ + linenr_T start_lnum; + linenr_T end_lnum; + int white_in_front; + int dir; + int start_is_white; + int prev_start_is_white; + int retval = OK; + int do_white = FALSE; + int t; + int i; + + if (type == 'S') // not implemented yet + return FAIL; + + start_lnum = curwin->w_cursor.lnum; + + /* + * When visual area is more than one line: extend it. + */ + if (VIsual_active && start_lnum != VIsual.lnum) + { +extend: + if (start_lnum < VIsual.lnum) + dir = BACKWARD; + else + dir = FORWARD; + for (i = count; --i >= 0; ) + { + if (start_lnum == + (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) + { + retval = FAIL; + break; + } + + prev_start_is_white = -1; + for (t = 0; t < 2; ++t) + { + start_lnum += dir; + start_is_white = linewhite(start_lnum); + if (prev_start_is_white == start_is_white) + { + start_lnum -= dir; + break; + } + for (;;) + { + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) + break; + if (start_is_white != linewhite(start_lnum + dir) + || (!start_is_white + && startPS(start_lnum + (dir > 0 + ? 1 : 0), 0, 0))) + break; + start_lnum += dir; + } + if (!include) + break; + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) + break; + prev_start_is_white = start_is_white; + } + } + curwin->w_cursor.lnum = start_lnum; + curwin->w_cursor.col = 0; + return retval; + } + + /* + * First move back to the start_lnum of the paragraph or white lines + */ + white_in_front = linewhite(start_lnum); + while (start_lnum > 1) + { + if (white_in_front) // stop at first white line + { + if (!linewhite(start_lnum - 1)) + break; + } + else // stop at first non-white line of start of paragraph + { + if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) + break; + } + --start_lnum; + } + + /* + * Move past the end of any white lines. + */ + end_lnum = start_lnum; + while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) + ++end_lnum; + + --end_lnum; + i = count; + if (!include && white_in_front) + --i; + while (i--) + { + if (end_lnum == curbuf->b_ml.ml_line_count) + return FAIL; + + if (!include) + do_white = linewhite(end_lnum + 1); + + if (include || !do_white) + { + ++end_lnum; + /* + * skip to end of paragraph + */ + while (end_lnum < curbuf->b_ml.ml_line_count + && !linewhite(end_lnum + 1) + && !startPS(end_lnum + 1, 0, 0)) + ++end_lnum; + } + + if (i == 0 && white_in_front && include) + break; + + /* + * skip to end of white lines after paragraph + */ + if (include || do_white) + while (end_lnum < curbuf->b_ml.ml_line_count + && linewhite(end_lnum + 1)) + ++end_lnum; + } + + /* + * If there are no empty lines at the end, try to find some empty lines at + * the start (unless that has been done already). + */ + if (!white_in_front && !linewhite(end_lnum) && include) + while (start_lnum > 1 && linewhite(start_lnum - 1)) + --start_lnum; + + if (VIsual_active) + { + // Problem: when doing "Vipipip" nothing happens in a single white + // line, we get stuck there. Trap this here. + if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) + goto extend; + if (VIsual.lnum != start_lnum) + { + VIsual.lnum = start_lnum; + VIsual.col = 0; + } + VIsual_mode = 'V'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } + else + { + oap->start.lnum = start_lnum; + oap->start.col = 0; + oap->motion_type = MLINE; + } + curwin->w_cursor.lnum = end_lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +/* + * Search quote char from string line[col]. + * Quote character escaped by one of the characters in "escape" is not counted + * as a quote. + * Returns column number of "quotechar" or -1 when not found. + */ + static int +find_next_quote( + char_u *line, + int col, + int quotechar, + char_u *escape) // escape characters, can be NULL +{ + int c; + + for (;;) + { + c = line[col]; + if (c == NUL) + return -1; + else if (escape != NULL && vim_strchr(escape, c)) + { + ++col; + if (line[col] == NUL) + return -1; + } + else if (c == quotechar) + break; + if (has_mbyte) + col += (*mb_ptr2len)(line + col); + else + ++col; + } + return col; +} + +/* + * Search backwards in "line" from column "col_start" to find "quotechar". + * Quote character escaped by one of the characters in "escape" is not counted + * as a quote. + * Return the found column or zero. + */ + static int +find_prev_quote( + char_u *line, + int col_start, + int quotechar, + char_u *escape) // escape characters, can be NULL +{ + int n; + + while (col_start > 0) + { + --col_start; + col_start -= (*mb_head_off)(line, line + col_start); + n = 0; + if (escape != NULL) + while (col_start - n > 0 && vim_strchr(escape, + line[col_start - n - 1]) != NULL) + ++n; + if (n & 1) + col_start -= n; // uneven number of escape chars, skip it + else if (line[col_start] == quotechar) + break; + } + return col_start; +} + +/* + * Find quote under the cursor, cursor at end. + * Returns TRUE if found, else FALSE. + */ + int +current_quote( + oparg_T *oap, + long count, + int include, // TRUE == include quote char + int quotechar) // Quote character +{ + char_u *line = ml_get_curline(); + int col_end; + int col_start = curwin->w_cursor.col; + int inclusive = FALSE; + int vis_empty = TRUE; // Visual selection <= 1 char + int vis_bef_curs = FALSE; // Visual starts before cursor + int did_exclusive_adj = FALSE; // adjusted pos for 'selection' + int inside_quotes = FALSE; // Looks like "i'" done before + int selected_quote = FALSE; // Has quote inside selection + int i; + int restore_vis_bef = FALSE; // restore VIsual on abort + + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. + if (VIsual_active) + { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) + return FALSE; + + vis_bef_curs = LT_POS(VIsual, curwin->w_cursor); + vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); + if (*p_sel == 'e') + { + if (vis_bef_curs) + { + dec_cursor(); + did_exclusive_adj = TRUE; + } + else if (!vis_empty) + { + dec(&VIsual); + did_exclusive_adj = TRUE; + } + vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); + if (!vis_bef_curs && !vis_empty) + { + // VIsual needs to be the start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = TRUE; + restore_vis_bef = TRUE; + } + } + } + + if (!vis_empty) + { + // Check if the existing selection exactly spans the text inside + // quotes. + if (vis_bef_curs) + { + inside_quotes = VIsual.col > 0 + && line[VIsual.col - 1] == quotechar + && line[curwin->w_cursor.col] != NUL + && line[curwin->w_cursor.col + 1] == quotechar; + i = VIsual.col; + col_end = curwin->w_cursor.col; + } + else + { + inside_quotes = curwin->w_cursor.col > 0 + && line[curwin->w_cursor.col - 1] == quotechar + && line[VIsual.col] != NUL + && line[VIsual.col + 1] == quotechar; + i = curwin->w_cursor.col; + col_end = VIsual.col; + } + + // Find out if we have a quote in the selection. + while (i <= col_end) + { + // check for going over the end of the line, which can happen if + // the line was changed after the Visual area was selected. + if (line[i] == NUL) + break; + if (line[i++] == quotechar) + { + selected_quote = TRUE; + break; + } + } + } + + if (!vis_empty && line[col_start] == quotechar) + { + // Already selecting something and on a quote character. Find the + // next quoted string. + if (vis_bef_curs) + { + // Assume we are on a closing quote: move to after the next + // opening quote. + col_start = find_next_quote(line, col_start + 1, quotechar, NULL); + if (col_start < 0) + goto abort_search; + col_end = find_next_quote(line, col_start + 1, quotechar, + curbuf->b_p_qe); + if (col_end < 0) + { + // We were on a starting quote perhaps? + col_end = col_start; + col_start = curwin->w_cursor.col; + } + } + else + { + col_end = find_prev_quote(line, col_start, quotechar, NULL); + if (line[col_end] != quotechar) + goto abort_search; + col_start = find_prev_quote(line, col_end, quotechar, + curbuf->b_p_qe); + if (line[col_start] != quotechar) + { + // We were on an ending quote perhaps? + col_start = col_end; + col_end = curwin->w_cursor.col; + } + } + } + else + + if (line[col_start] == quotechar || !vis_empty) + { + int first_col = col_start; + + if (!vis_empty) + { + if (vis_bef_curs) + first_col = find_next_quote(line, col_start, quotechar, NULL); + else + first_col = find_prev_quote(line, col_start, quotechar, NULL); + } + + // The cursor is on a quote, we don't know if it's the opening or + // closing quote. Search from the start of the line to find out. + // Also do this when there is a Visual area, a' may leave the cursor + // in between two strings. + col_start = 0; + for (;;) + { + // Find open quote character. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0 || col_start > first_col) + goto abort_search; + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, + curbuf->b_p_qe); + if (col_end < 0) + goto abort_search; + // If is cursor between start and end quote character, it is + // target text object. + if (col_start <= first_col && first_col <= col_end) + break; + col_start = col_end + 1; + } + } + else + { + // Search backward for a starting quote. + col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); + if (line[col_start] != quotechar) + { + // No quote before the cursor, look after the cursor. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0) + goto abort_search; + } + + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, + curbuf->b_p_qe); + if (col_end < 0) + goto abort_search; + } + + // When "include" is TRUE, include spaces after closing quote or before + // the starting quote. + if (include) + { + if (VIM_ISWHITE(line[col_end + 1])) + while (VIM_ISWHITE(line[col_end + 1])) + ++col_end; + else + while (col_start > 0 && VIM_ISWHITE(line[col_start - 1])) + --col_start; + } + + // Set start position. After vi" another i" must include the ". + // For v2i" include the quotes. + if (!include && count < 2 && (vis_empty || !inside_quotes)) + ++col_start; + curwin->w_cursor.col = col_start; + if (VIsual_active) + { + // Set the start of the Visual area when the Visual area was empty, we + // were just inside quotes or the Visual area didn't start at a quote + // and didn't include a quote. + if (vis_empty + || (vis_bef_curs + && !selected_quote + && (inside_quotes + || (line[VIsual.col] != quotechar + && (VIsual.col == 0 + || line[VIsual.col - 1] != quotechar))))) + { + VIsual = curwin->w_cursor; + redraw_curbuf_later(UPD_INVERTED); + } + } + else + { + oap->start = curwin->w_cursor; + oap->motion_type = MCHAR; + } + + // Set end position. + curwin->w_cursor.col = col_end; + if ((include || count > 1 // After vi" another i" must include the ". + || (!vis_empty && inside_quotes) + ) && inc_cursor() == 2) + inclusive = TRUE; + if (VIsual_active) + { + if (vis_empty || vis_bef_curs) + { + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') + dec_cursor(); + } + else + { + // Cursor is at start of Visual area. Set the end of the Visual + // area when it was just inside quotes or it didn't end at a + // quote. + if (inside_quotes + || (!selected_quote + && line[VIsual.col] != quotechar + && (line[VIsual.col] == NUL + || line[VIsual.col + 1] != quotechar))) + { + dec_cursor(); + VIsual = curwin->w_cursor; + } + curwin->w_cursor.col = col_start; + } + if (VIsual_mode == 'V') + { + VIsual_mode = 'v'; + redraw_cmdline = TRUE; // show mode later + } + } + else + { + // Set inclusive and other oap's flags. + oap->inclusive = inclusive; + } + + return OK; + +abort_search: + if (VIsual_active && *p_sel == 'e') + { + if (did_exclusive_adj) + inc_cursor(); + if (restore_vis_bef) + { + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + } + } + return FALSE; +}