Mercurial > vim
view src/textobject.c @ 33385:b5ad84fdc702 v9.0.1951
patch 9.0.1951: Vim9: hard to debug vim9_class errors from CI
Commit: https://github.com/vim/vim/commit/b90e3bc491c02bd6e4db1a1c1849ce3d21811c43
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Thu Sep 28 23:06:48 2023 +0200
patch 9.0.1951: Vim9: hard to debug vim9_class errors from CI
Problem: Vim9: hard to debug vim9_class errors from CI
Solution: Include the line number in assert_xxx() calls. Include the
entire error message in the tests. Fix the indentation in the
test file. Add tags for new error codes.
closes: #13206
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 28 Sep 2023 23:30:03 +0200 |
parents | 695b50472e85 |
children | bcf1429cb1fc |
line wrap: on
line source
/* 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; }