return !((colnr_T)(p - line) <= col);
}
+/*
+ * Check if line[] contains a "//" comment, ignoring matches inside strings.
+ * Return MAXCOL if not, otherwise return the column.
+ * The line is scanned once (skipping strings), so this stays linear even on
+ * lines with many slashes (e.g. base64 data).
+ */
+ int
+check_linecomment(char_u *line)
+{
+ char_u *p;
+
+ p = line;
+ // skip Lispish one-line comments
+ if (curbuf->b_p_lisp)
+ {
+ if (vim_strchr(p, ';') != NULL) // there may be comments
+ {
+ int in_str = FALSE; // inside of string
+
+ p = line; // scan from start
+ while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
+ {
+ if (*p == '"')
+ {
+ if (in_str)
+ {
+ if (*(p - 1) != '\\') // skip escaped quote
+ in_str = FALSE;
+ }
+ else if (p == line || ((p - line) >= 2
+ // skip #\" form
+ && *(p - 1) != '\\' && *(p - 2) != '#'))
+ in_str = TRUE;
+ }
+ else if (!in_str && ((p - line) < 2
+ || (*(p - 1) != '\\' && *(p - 2) != '#'))
+ && !is_pos_in_string(line, (colnr_T)(p - line)))
+ break; // found!
+ ++p;
+ }
+ }
+ else
+ p = NULL;
+ }
+ else
+ {
+ // Scan the line once, skipping over strings, char constants and raw
+ // strings, instead of testing each '/' with is_pos_in_string() (which
+ // rescans from the start, making this quadratic on lines with many
+ // slashes).
+ for ( ; *p != NUL; ++p)
+ {
+ p = skip_string(p);
+ if (*p == NUL)
+ break;
+ // Accept a double /, unless it's preceded with * and followed by
+ // *, because * / / * is an end and start of a C comment.
+ if (p[0] == '/' && p[1] == '/'
+ && (p == line || p[-1] != '*' || p[2] != '*'))
+ break;
+ }
+ if (*p == NUL)
+ p = NULL;
+ }
+
+ if (p == NULL)
+ return MAXCOL;
+ return (int)(p - line);
+}
+
/*
* Find the start of a comment, not knowing if we are in a comment right now.
* Search starts at w_cursor.lnum and goes backwards.
/* cindent.c */
int cin_is_cinword(char_u *line);
int is_pos_in_string(char_u *line, colnr_T col);
+int check_linecomment(char_u *line);
pos_T *find_start_comment(int ind_maxcomment);
int cindent_on(void);
void parse_cino(buf_T *buf);
int searchc(cmdarg_T *cap, int t_cmd);
pos_T *findmatch(oparg_T *oap, int initc);
pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int maxtravel);
-int check_linecomment(char_u *line);
void showmatch(int c);
int current_search(long count, int forward);
int linewhite(linenr_T lnum);
}
}
- // Track block comment state when FM_SKIPCOMM is set.
+ // Track block comment state when FM_SKIPCOMM is set. Markers inside a
+ // string are not comments, so skip them while "inquote" is set.
// Backward: '/' of end-marker enters comment; '*' of start-marker exits.
// Forward: '/' of start-marker enters comment; '/' of end-marker exits.
- if (skip_comments && !comment_dir)
+ if (skip_comments && !comment_dir && !inquote)
{
if (backwards)
{
return (pos_T *)NULL; // never found it
}
-/*
- * Check if line[] contains a / / comment.
- * Return MAXCOL if not, otherwise return the column.
- */
- int
-check_linecomment(char_u *line)
-{
- char_u *p;
-
- p = line;
- // skip Lispish one-line comments
- if (curbuf->b_p_lisp)
- {
- if (vim_strchr(p, ';') != NULL) // there may be comments
- {
- int in_str = FALSE; // inside of string
-
- p = line; // scan from start
- while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
- {
- if (*p == '"')
- {
- if (in_str)
- {
- if (*(p - 1) != '\\') // skip escaped quote
- in_str = FALSE;
- }
- else if (p == line || ((p - line) >= 2
- // skip #\" form
- && *(p - 1) != '\\' && *(p - 2) != '#'))
- in_str = TRUE;
- }
- else if (!in_str && ((p - line) < 2
- || (*(p - 1) != '\\' && *(p - 2) != '#'))
- && !is_pos_in_string(line, (colnr_T)(p - line)))
- break; // found!
- ++p;
- }
- }
- else
- p = NULL;
- }
- else
- while ((p = vim_strchr(p, '/')) != NULL)
- {
- // Accept a double /, unless it's preceded with * and followed by
- // *, because * / / * is an end and start of a C comment. Only
- // accept the position if it is not inside a string.
- if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
- && !is_pos_in_string(line, (colnr_T)(p - line)))
- break;
- ++p;
- }
-
- if (p == NULL)
- return MAXCOL;
- return (int)(p - line);
-}
-
/*
* Move cursor briefly to character matching the one under the cursor.
* Used for Insert mode and "r" command.
bwipe!
endfunc
+" A "//" inside a string must not be treated as a line comment by "%". The
+" line is scanned in a single pass, so this stays fast even on lines with many
+" slashes (e.g. base64 data).
+func Test_normal_percent_skip_comment_string()
+ new
+ setlocal comments=s1:/*,mb:*,ex:*/,://
+
+ " The "//" inside the string is not a comment, so "(" matches the real ")".
+ call setline(1, ['("a // b")'])
+ call cursor(1, 1)
+ normal %
+ call assert_equal([1, 10], [line('.'), col('.')])
+
+ " JSON-like: "{" matches the closing "}" although the string has slashes.
+ silent! %delete _
+ call setline(1, ['{', ' "k": "x//y",', '}'])
+ call cursor(1, 1)
+ normal %
+ call assert_equal([3, 1], [line('.'), col('.')])
+
+ " A "/*" inside a string must not start a block comment, so "(" still
+ " matches the real ")" after the string.
+ silent! %delete _
+ call setline(1, ['( "a /* b" )'])
+ call cursor(1, 1)
+ normal %
+ call assert_equal([1, 12], [line('.'), col('.')])
+
+ " A real /* */ block comment is still skipped: "(" matches the last ")".
+ silent! %delete _
+ call setline(1, ['( /* ) */ x )'])
+ call cursor(1, 1)
+ normal %
+ call assert_equal([1, 13], [line('.'), col('.')])
+
+ bwipe!
+endfunc
+
" Test for << and >> commands to shift text by 'shiftwidth'
func Test_normal_shift_rightleft()
new
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 689,
/**/
688,
/**/