changeset 14477:1ccf5dcd88ca v8.1.0252

patch 8.1.0252: quickfix functions are too long commit https://github.com/vim/vim/commit/de3b3677f7eace66be454196db0fbf710cfc8c5e Author: Bram Moolenaar <Bram@vim.org> Date: Tue Aug 7 21:54:41 2018 +0200 patch 8.1.0252: quickfix functions are too long Problem: Quickfix functions are too long. Solution: Refactor. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/2950)
author Christian Brabandt <cb@256bit.org>
date Tue, 07 Aug 2018 22:00:04 +0200
parents 756695a41362
children b35bae59c521
files src/quickfix.c src/version.c
diffstat 2 files changed, 517 insertions(+), 302 deletions(-) [+]
line wrap: on
line diff
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -201,106 +201,109 @@ static struct fmtpattern
 
 /*
  * Convert an errorformat pattern to a regular expression pattern.
- * See fmt_pat definition above for the list of supported patterns.
+ * See fmt_pat definition above for the list of supported patterns.  The
+ * pattern specifier is supplied in "efmpat".  The converted pattern is stored
+ * in "regpat".  Returns a pointer to the location after the pattern.
  */
     static char_u *
-fmtpat_to_regpat(
-	char_u	*efmp,
-	efm_T	*fmt_ptr,
+efmpat_to_regpat(
+	char_u	*efmpat,
+	char_u	*regpat,
+	efm_T	*efminfo,
 	int	idx,
 	int	round,
-	char_u	*ptr,
 	char_u	*errmsg)
 {
     char_u	*srcptr;
 
-    if (fmt_ptr->addr[idx])
+    if (efminfo->addr[idx])
     {
 	/* Each errorformat pattern can occur only once */
 	sprintf((char *)errmsg,
-		_("E372: Too many %%%c in format string"), *efmp);
+		_("E372: Too many %%%c in format string"), *efmpat);
 	EMSG(errmsg);
 	return NULL;
     }
     if ((idx && idx < 6
-		&& vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL)
+		&& vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
 	    || (idx == 6
-		&& vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL))
+		&& vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL))
     {
 	sprintf((char *)errmsg,
-		_("E373: Unexpected %%%c in format string"), *efmp);
+		_("E373: Unexpected %%%c in format string"), *efmpat);
 	EMSG(errmsg);
 	return NULL;
     }
-    fmt_ptr->addr[idx] = (char_u)++round;
-    *ptr++ = '\\';
-    *ptr++ = '(';
+    efminfo->addr[idx] = (char_u)++round;
+    *regpat++ = '\\';
+    *regpat++ = '(';
 #ifdef BACKSLASH_IN_FILENAME
-    if (*efmp == 'f')
+    if (*efmpat == 'f')
     {
 	/* Also match "c:" in the file name, even when
 	 * checking for a colon next: "%f:".
 	 * "\%(\a:\)\=" */
-	STRCPY(ptr, "\\%(\\a:\\)\\=");
-	ptr += 10;
+	STRCPY(regpat, "\\%(\\a:\\)\\=");
+	regpat += 10;
     }
 #endif
-    if (*efmp == 'f' && efmp[1] != NUL)
-    {
-	if (efmp[1] != '\\' && efmp[1] != '%')
+    if (*efmpat == 'f' && efmpat[1] != NUL)
+    {
+	if (efmpat[1] != '\\' && efmpat[1] != '%')
 	{
 	    /* A file name may contain spaces, but this isn't
 	     * in "\f".  For "%f:%l:%m" there may be a ":" in
 	     * the file name.  Use ".\{-1,}x" instead (x is
 	     * the next character), the requirement that :999:
 	     * follows should work. */
-	    STRCPY(ptr, ".\\{-1,}");
-	    ptr += 7;
+	    STRCPY(regpat, ".\\{-1,}");
+	    regpat += 7;
 	}
 	else
 	{
 	    /* File name followed by '\\' or '%': include as
 	     * many file name chars as possible. */
-	    STRCPY(ptr, "\\f\\+");
-	    ptr += 4;
+	    STRCPY(regpat, "\\f\\+");
+	    regpat += 4;
 	}
     }
     else
     {
 	srcptr = (char_u *)fmt_pat[idx].pattern;
-	while ((*ptr = *srcptr++) != NUL)
-	    ++ptr;
-    }
-    *ptr++ = '\\';
-    *ptr++ = ')';
-
-    return ptr;
+	while ((*regpat = *srcptr++) != NUL)
+	    ++regpat;
+    }
+    *regpat++ = '\\';
+    *regpat++ = ')';
+
+    return regpat;
 }
 
 /*
  * Convert a scanf like format in 'errorformat' to a regular expression.
+ * Returns a pointer to the location after the pattern.
  */
     static char_u *
 scanf_fmt_to_regpat(
+	char_u	**pefmp,
 	char_u	*efm,
 	int	len,
-	char_u	**pefmp,
-	char_u	*ptr,
+	char_u	*regpat,
 	char_u	*errmsg)
 {
     char_u	*efmp = *pefmp;
 
-    if (*++efmp == '[' || *efmp == '\\')
-    {
-	if ((*ptr++ = *efmp) == '[')	/* %*[^a-z0-9] etc. */
+    if (*efmp == '[' || *efmp == '\\')
+    {
+	if ((*regpat++ = *efmp) == '[')	/* %*[^a-z0-9] etc. */
 	{
 	    if (efmp[1] == '^')
-		*ptr++ = *++efmp;
+		*regpat++ = *++efmp;
 	    if (efmp < efm + len)
 	    {
-		*ptr++ = *++efmp;	    /* could be ']' */
+		*regpat++ = *++efmp;	    /* could be ']' */
 		while (efmp < efm + len
-			&& (*ptr++ = *++efmp) != ']')
+			&& (*regpat++ = *++efmp) != ']')
 		    /* skip */;
 		if (efmp == efm + len)
 		{
@@ -310,9 +313,9 @@ scanf_fmt_to_regpat(
 	    }
 	}
 	else if (efmp < efm + len)	/* %*\D, %*\s etc. */
-	    *ptr++ = *++efmp;
-	*ptr++ = '\\';
-	*ptr++ = '+';
+	    *regpat++ = *++efmp;
+	*regpat++ = '\\';
+	*regpat++ = '+';
     }
     else
     {
@@ -325,36 +328,35 @@ scanf_fmt_to_regpat(
 
     *pefmp = efmp;
 
-    return ptr;
+    return regpat;
 }
 
 /*
  * Analyze/parse an errorformat prefix.
  */
-    static int
-efm_analyze_prefix(char_u **pefmp, efm_T *fmt_ptr, char_u *errmsg)
-{
-    char_u	*efmp = *pefmp;
-
+    static char_u *
+efm_analyze_prefix(char_u *efmp, efm_T *efminfo, char_u *errmsg)
+{
     if (vim_strchr((char_u *)"+-", *efmp) != NULL)
-	fmt_ptr->flags = *efmp++;
+	efminfo->flags = *efmp++;
     if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL)
-	fmt_ptr->prefix = *efmp;
+	efminfo->prefix = *efmp;
     else
     {
 	sprintf((char *)errmsg,
 		_("E376: Invalid %%%c in format string prefix"), *efmp);
 	EMSG(errmsg);
-	return FAIL;
-    }
-
-    *pefmp = efmp;
-
-    return OK;
-}
-
-/*
- * Converts a 'errorformat' string to regular expression pattern
+	return NULL;
+    }
+
+    return efmp;
+}
+
+/*
+ * Converts a 'errorformat' string part in 'efm' to a regular expression
+ * pattern.  The resulting regex pattern is returned in "regpat". Additional
+ * information about the 'erroformat' pattern is returned in "fmt_ptr".
+ * Returns OK or FAIL.
  */
     static int
 efm_to_regpat(
@@ -370,7 +372,7 @@ efm_to_regpat(
     int		idx = 0;
 
     /*
-     * Build regexp pattern from current 'errorformat' option
+     * Build a regexp pattern for a 'errorformat' option part
      */
     ptr = regpat;
     *ptr++ = '^';
@@ -385,17 +387,18 @@ efm_to_regpat(
 		    break;
 	    if (idx < FMT_PATTERNS)
 	    {
-		ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr,
+		ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round,
 								errmsg);
 		if (ptr == NULL)
-		    return -1;
+		    return FAIL;
 		round++;
 	    }
 	    else if (*efmp == '*')
 	    {
-		ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg);
+		++efmp;
+		ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg);
 		if (ptr == NULL)
-		    return -1;
+		    return FAIL;
 	    }
 	    else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL)
 		*ptr++ = *efmp;		/* regexp magic characters */
@@ -405,15 +408,20 @@ efm_to_regpat(
 		fmt_ptr->conthere = TRUE;
 	    else if (efmp == efm + 1)		/* analyse prefix */
 	    {
-		if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg) == FAIL)
-		    return -1;
+		/*
+		 * prefix is allowed only at the beginning of the errorformat
+		 * option part
+		 */
+		efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg);
+		if (efmp == NULL)
+		    return FAIL;
 	    }
 	    else
 	    {
 		sprintf((char *)errmsg,
 			_("E377: Invalid %%%c in format string"), *efmp);
 		EMSG(errmsg);
-		return -1;
+		return FAIL;
 	    }
 	}
 	else			/* copy normal character */
@@ -429,9 +437,12 @@ efm_to_regpat(
     *ptr++ = '$';
     *ptr = NUL;
 
-    return 0;
-}
-
+    return OK;
+}
+
+/*
+ * Free the 'errorformat' information list
+ */
     static void
 free_efm_list(efm_T **efm_first)
 {
@@ -446,7 +457,48 @@ free_efm_list(efm_T **efm_first)
     fmt_start = NULL;
 }
 
-/* Parse 'errorformat' option */
+/*
+ * Compute the size of the buffer used to convert a 'errorformat' pattern into
+ * a regular expression pattern.
+ */
+    static int
+efm_regpat_bufsz(char_u *efm)
+{
+    int sz;
+    int i;
+
+    sz = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2);
+    for (i = FMT_PATTERNS; i > 0; )
+	sz += (int)STRLEN(fmt_pat[--i].pattern);
+#ifdef BACKSLASH_IN_FILENAME
+    sz += 12; /* "%f" can become twelve chars longer (see efm_to_regpat) */
+#else
+    sz += 2; /* "%f" can become two chars longer */
+#endif
+
+    return sz;
+}
+
+/*
+ * Return the length of a 'errorformat' option part (separated by ",").
+ */
+    static int
+efm_option_part_len(char_u *efm)
+{
+    int len;
+
+    for (len = 0; efm[len] != NUL && efm[len] != ','; ++len)
+	if (efm[len] == '\\' && efm[len + 1] != NUL)
+	    ++len;
+
+    return len;
+}
+
+/*
+ * Parse the 'errorformat' option. Multiple parts in the 'errorformat' option
+ * are parsed and converted to regular expressions. Returns information about
+ * the parsed 'errorformat' option.
+ */
     static efm_T *
 parse_efm_option(char_u *efm)
 {
@@ -457,8 +509,7 @@ parse_efm_option(char_u *efm)
     efm_T	*fmt_last = NULL;
     char_u	*fmtstr = NULL;
     int		len;
-    int		i;
-    int		round;
+    int		sz;
 
     errmsglen = CMDBUFFSIZE + 1;
     errmsg = alloc_id(errmsglen, aid_qf_errmsg);
@@ -473,15 +524,8 @@ parse_efm_option(char_u *efm)
     /*
      * Get some space to modify the format string into.
      */
-    i = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2);
-    for (round = FMT_PATTERNS; round > 0; )
-	i += (int)STRLEN(fmt_pat[--round].pattern);
-#ifdef BACKSLASH_IN_FILENAME
-    i += 12; /* "%f" can become twelve chars longer (see efm_to_regpat) */
-#else
-    i += 2; /* "%f" can become two chars longer */
-#endif
-    if ((fmtstr = alloc(i)) == NULL)
+    sz = efm_regpat_bufsz(efm);
+    if ((fmtstr = alloc(sz)) == NULL)
 	goto parse_efm_error;
 
     while (efm[0] != NUL)
@@ -501,11 +545,9 @@ parse_efm_option(char_u *efm)
 	/*
 	 * Isolate one part in the 'errorformat' option
 	 */
-	for (len = 0; efm[len] != NUL && efm[len] != ','; ++len)
-	    if (efm[len] == '\\' && efm[len + 1] != NUL)
-		++len;
-
-	if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1)
+	len = efm_option_part_len(efm);
+
+	if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == FAIL)
 	    goto parse_efm_error;
 	if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL)
 	    goto parse_efm_error;
@@ -539,6 +581,10 @@ enum {
     QF_MULTISCAN = 5,
 };
 
+/*
+ * State information used to parse lines and add entries to a quickfix/location
+ * list.
+ */
 typedef struct {
     char_u	*linebuf;
     int		linelen;
@@ -554,6 +600,9 @@ typedef struct {
     vimconv_T	vc;
 } qfstate_T;
 
+/*
+ * Allocate more memory for the line buffer used for parsing lines.
+ */
     static char_u *
 qf_grow_linebuf(qfstate_T *state, int newsz)
 {
@@ -861,10 +910,251 @@ typedef struct {
 } qffields_T;
 
 /*
- * Parse the error format matches in 'regmatch' and set the values in 'fields'.
- * fmt_ptr contains the 'efm' format specifiers/prefixes that have a match.
- * Returns QF_OK if all the matches are successfully parsed. On failure,
- * returns QF_FAIL or QF_NOMEM.
+ * Parse the match for filename ('%f') pattern in regmatch.
+ * Return the matched value in "fields->namebuf".
+ */
+    static int
+qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int prefix)
+{
+    int c;
+
+    if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL)
+	return QF_FAIL;
+
+    /* Expand ~/file and $HOME/file to full path. */
+    c = *rmp->endp[midx];
+    *rmp->endp[midx] = NUL;
+    expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE);
+    *rmp->endp[midx] = c;
+
+    /*
+     * For separate filename patterns (%O, %P and %Q), the specified file
+     * should exist.
+     */
+    if (vim_strchr((char_u *)"OPQ", prefix) != NULL
+	    && mch_getperm(fields->namebuf) == -1)
+	return QF_FAIL;
+
+    return QF_OK;
+}
+
+/*
+ * Parse the match for error number ('%n') pattern in regmatch.
+ * Return the matched value in "fields->enr".
+ */
+    static int
+qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    fields->enr = (int)atol((char *)rmp->startp[midx]);
+    return QF_OK;
+}
+
+/*
+ * Parse the match for line number (%l') pattern in regmatch.
+ * Return the matched value in "fields->lnum".
+ */
+    static int
+qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    fields->lnum = atol((char *)rmp->startp[midx]);
+    return QF_OK;
+}
+
+/*
+ * Parse the match for column number ('%c') pattern in regmatch.
+ * Return the matched value in "fields->col".
+ */
+    static int
+qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    fields->col = (int)atol((char *)rmp->startp[midx]);
+    return QF_OK;
+}
+
+/*
+ * Parse the match for error type ('%t') pattern in regmatch.
+ * Return the matched value in "fields->type".
+ */
+    static int
+qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    fields->type = *rmp->startp[midx];
+    return QF_OK;
+}
+
+/*
+ * Parse the match for '%+' format pattern. The whole matching line is included
+ * in the error string.  Return the matched line in "fields->errmsg".
+ */
+    static int
+qf_parse_fmt_plus(char_u *linebuf, int linelen, qffields_T *fields)
+{
+    char_u	*p;
+
+    if (linelen >= fields->errmsglen)
+    {
+	/* linelen + null terminator */
+	if ((p = vim_realloc(fields->errmsg, linelen + 1)) == NULL)
+	    return QF_NOMEM;
+	fields->errmsg = p;
+	fields->errmsglen = linelen + 1;
+    }
+    vim_strncpy(fields->errmsg, linebuf, linelen);
+    return QF_OK;
+}
+
+/*
+ * Parse the match for error message ('%m') pattern in regmatch.
+ * Return the matched value in "fields->errmsg".
+ */
+    static int
+qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    char_u	*p;
+    int		len;
+
+    if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL)
+	return QF_FAIL;
+    len = (int)(rmp->endp[midx] - rmp->startp[midx]);
+    if (len >= fields->errmsglen)
+    {
+	/* len + null terminator */
+	if ((p = vim_realloc(fields->errmsg, len + 1)) == NULL)
+	    return QF_NOMEM;
+	fields->errmsg = p;
+	fields->errmsglen = len + 1;
+    }
+    vim_strncpy(fields->errmsg, rmp->startp[midx], len);
+    return QF_OK;
+}
+
+/*
+ * Parse the match for rest of a single-line file message ('%r') pattern.
+ * Return the matched value in "tail".
+ */
+    static int
+qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    *tail = rmp->startp[midx];
+    return QF_OK;
+}
+
+/*
+ * Parse the match for the pointer line ('%p') pattern in regmatch.
+ * Return the matched value in "fields->col".
+ */
+    static int
+qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    char_u	*match_ptr;
+
+    if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL)
+	return QF_FAIL;
+    fields->col = 0;
+    for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx];
+								++match_ptr)
+    {
+	++fields->col;
+	if (*match_ptr == TAB)
+	{
+	    fields->col += 7;
+	    fields->col -= fields->col % 8;
+	}
+    }
+    ++fields->col;
+    fields->use_viscol = TRUE;
+    return QF_OK;
+}
+
+/*
+ * Parse the match for the virtual column number ('%v') pattern in regmatch.
+ * Return the matched value in "fields->col".
+ */
+    static int
+qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    if (rmp->startp[midx] == NULL)
+	return QF_FAIL;
+    fields->col = (int)atol((char *)rmp->startp[midx]);
+    fields->use_viscol = TRUE;
+    return QF_OK;
+}
+
+/*
+ * Parse the match for the search text ('%s') pattern in regmatch.
+ * Return the matched value in "fields->pattern".
+ */
+    static int
+qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    int		len;
+
+    if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL)
+	return QF_FAIL;
+    len = (int)(rmp->endp[midx] - rmp->startp[midx]);
+    if (len > CMDBUFFSIZE - 5)
+	len = CMDBUFFSIZE - 5;
+    STRCPY(fields->pattern, "^\\V");
+    STRNCAT(fields->pattern, rmp->startp[midx], len);
+    fields->pattern[len + 3] = '\\';
+    fields->pattern[len + 4] = '$';
+    fields->pattern[len + 5] = NUL;
+    return QF_OK;
+}
+
+/*
+ * Parse the match for the module ('%o') pattern in regmatch.
+ * Return the matched value in "fields->module".
+ */
+    static int
+qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+    int		len;
+
+    if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL)
+	return QF_FAIL;
+    len = (int)(rmp->endp[midx] - rmp->startp[midx]);
+    if (len > CMDBUFFSIZE)
+	len = CMDBUFFSIZE;
+    STRNCAT(fields->module, rmp->startp[midx], len);
+    return QF_OK;
+}
+
+/*
+ * 'errorformat' format pattern parser functions.
+ * The '%f' and '%r' formats are parsed differently from other formats.
+ * See qf_parse_match() for details.
+ */
+static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) =
+{
+    NULL,
+    qf_parse_fmt_n,
+    qf_parse_fmt_l,
+    qf_parse_fmt_c,
+    qf_parse_fmt_t,
+    qf_parse_fmt_m,
+    NULL,
+    qf_parse_fmt_p,
+    qf_parse_fmt_v,
+    qf_parse_fmt_s,
+    qf_parse_fmt_o
+};
+
+/*
+ * Parse the error format pattern matches in "regmatch" and set the values in
+ * "fields".  fmt_ptr contains the 'efm' format specifiers/prefixes that have a
+ * match.  Returns QF_OK if all the matches are successfully parsed. On
+ * failure, returns QF_FAIL or QF_NOMEM.
  */
     static int
 qf_parse_match(
@@ -877,10 +1167,10 @@ qf_parse_match(
 	int		qf_multiscan,
 	char_u		**tail)
 {
-    char_u	*p;
     int		idx = fmt_ptr->prefix;
     int		i;
-    int		len;
+    int		midx;
+    int		status;
 
     if ((idx == 'C' || idx == 'Z') && !qf_multiline)
 	return QF_FAIL;
@@ -893,128 +1183,26 @@ qf_parse_match(
      * We check for an actual submatch, because "\[" and "\]" in
      * the 'errorformat' may cause the wrong submatch to be used.
      */
-    if ((i = (int)fmt_ptr->addr[0]) > 0)		/* %f */
-    {
-	int c;
-
-	if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL)
-	    return QF_FAIL;
-
-	/* Expand ~/file and $HOME/file to full path. */
-	c = *regmatch->endp[i];
-	*regmatch->endp[i] = NUL;
-	expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE);
-	*regmatch->endp[i] = c;
-
-	if (vim_strchr((char_u *)"OPQ", idx) != NULL
-		&& mch_getperm(fields->namebuf) == -1)
-	    return QF_FAIL;
-    }
-    if ((i = (int)fmt_ptr->addr[1]) > 0)		/* %n */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	fields->enr = (int)atol((char *)regmatch->startp[i]);
-    }
-    if ((i = (int)fmt_ptr->addr[2]) > 0)		/* %l */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	fields->lnum = atol((char *)regmatch->startp[i]);
-    }
-    if ((i = (int)fmt_ptr->addr[3]) > 0)		/* %c */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	fields->col = (int)atol((char *)regmatch->startp[i]);
-    }
-    if ((i = (int)fmt_ptr->addr[4]) > 0)		/* %t */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	fields->type = *regmatch->startp[i];
-    }
-    if (fmt_ptr->flags == '+' && !qf_multiscan)	/* %+ */
-    {
-	if (linelen >= fields->errmsglen)
-	{
-	    /* linelen + null terminator */
-	    if ((p = vim_realloc(fields->errmsg, linelen + 1)) == NULL)
-		return QF_NOMEM;
-	    fields->errmsg = p;
-	    fields->errmsglen = linelen + 1;
-	}
-	vim_strncpy(fields->errmsg, linebuf, linelen);
-    }
-    else if ((i = (int)fmt_ptr->addr[5]) > 0)	/* %m */
-    {
-	if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL)
-	    return QF_FAIL;
-	len = (int)(regmatch->endp[i] - regmatch->startp[i]);
-	if (len >= fields->errmsglen)
+    for (i = 0; i < FMT_PATTERNS; i++)
+    {
+	status = QF_OK;
+	midx = (int)fmt_ptr->addr[i];
+	if (i == 0 && midx > 0)				/* %f */
+	    status = qf_parse_fmt_f(regmatch, midx, fields, idx);
+	else if (i == 5)
 	{
-	    /* len + null terminator */
-	    if ((p = vim_realloc(fields->errmsg, len + 1)) == NULL)
-		return QF_NOMEM;
-	    fields->errmsg = p;
-	    fields->errmsglen = len + 1;
-	}
-	vim_strncpy(fields->errmsg, regmatch->startp[i], len);
-    }
-    if ((i = (int)fmt_ptr->addr[6]) > 0)		/* %r */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	*tail = regmatch->startp[i];
-    }
-    if ((i = (int)fmt_ptr->addr[7]) > 0)		/* %p */
-    {
-	char_u	*match_ptr;
-
-	if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL)
-	    return QF_FAIL;
-	fields->col = 0;
-	for (match_ptr = regmatch->startp[i];
-		match_ptr != regmatch->endp[i]; ++match_ptr)
-	{
-	    ++fields->col;
-	    if (*match_ptr == TAB)
-	    {
-		fields->col += 7;
-		fields->col -= fields->col % 8;
-	    }
+	    if (fmt_ptr->flags == '+' && !qf_multiscan)	/* %+ */
+		status = qf_parse_fmt_plus(linebuf, linelen, fields);
+	    else if (midx > 0)				/* %m */
+		status = qf_parse_fmt_m(regmatch, midx, fields);
 	}
-	++fields->col;
-	fields->use_viscol = TRUE;
-    }
-    if ((i = (int)fmt_ptr->addr[8]) > 0)		/* %v */
-    {
-	if (regmatch->startp[i] == NULL)
-	    return QF_FAIL;
-	fields->col = (int)atol((char *)regmatch->startp[i]);
-	fields->use_viscol = TRUE;
-    }
-    if ((i = (int)fmt_ptr->addr[9]) > 0)		/* %s */
-    {
-	if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL)
-	    return QF_FAIL;
-	len = (int)(regmatch->endp[i] - regmatch->startp[i]);
-	if (len > CMDBUFFSIZE - 5)
-	    len = CMDBUFFSIZE - 5;
-	STRCPY(fields->pattern, "^\\V");
-	STRNCAT(fields->pattern, regmatch->startp[i], len);
-	fields->pattern[len + 3] = '\\';
-	fields->pattern[len + 4] = '$';
-	fields->pattern[len + 5] = NUL;
-    }
-    if ((i = (int)fmt_ptr->addr[10]) > 0)		/* %o */
-    {
-	if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL)
-	    return QF_FAIL;
-	len = (int)(regmatch->endp[i] - regmatch->startp[i]);
-	if (len > CMDBUFFSIZE)
-	    len = CMDBUFFSIZE;
-	STRNCAT(fields->module, regmatch->startp[i], len);
+	else if (i == 6 && midx > 0)			/* %r */
+	    status = qf_parse_fmt_r(regmatch, midx, tail);
+	else if (midx > 0)				/* others */
+	    status = (qf_parse_fmt[i])(regmatch, midx, fields);
+
+	if (status != QF_OK)
+	    return status;
     }
 
     return QF_OK;
@@ -1308,6 +1496,18 @@ restofline:
 }
 
 /*
+ * Returns TRUE if the specified quickfix/location list is empty.
+ */
+    static int
+qf_list_empty(qf_info_T *qi, int qf_idx)
+{
+    if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT)
+	return TRUE;
+    return qi->qf_lists[qf_idx].qf_count <= 0;
+}
+
+
+/*
  * Allocate the fields used for parsing lines and populating a quickfix list.
  */
     static int
@@ -1450,7 +1650,7 @@ qf_init_ext(
     {
 	/* Adding to existing list, use last entry. */
 	adding = TRUE;
-	if (qi->qf_lists[qf_idx].qf_count > 0)
+	if (!qf_list_empty(qi, qf_idx))
 	    old_last = qi->qf_lists[qf_idx].qf_last;
     }
 
@@ -1777,8 +1977,7 @@ qf_add_entry(
     qfp->qf_valid = valid;
 
     lastp = &qi->qf_lists[qf_idx].qf_last;
-    if (qi->qf_lists[qf_idx].qf_count == 0)
-				/* first element in the list */
+    if (qf_list_empty(qi, qf_idx))	/* first element in the list */
     {
 	qi->qf_lists[qf_idx].qf_start = qfp;
 	qi->qf_lists[qf_idx].qf_ptr = qfp;
@@ -1875,7 +2074,7 @@ copy_loclist(win_T *from, win_T *to)
     to->w_llist->qf_listcount = qi->qf_listcount;
 
     /* Copy the location lists one at a time */
-    for (idx = 0; idx < qi->qf_listcount; idx++)
+    for (idx = 0; idx < qi->qf_listcount; ++idx)
     {
 	qf_list_T   *from_qfl;
 	qf_list_T   *to_qfl;
@@ -2907,7 +3106,7 @@ qf_jump(qf_info_T	*qi,
 	qi = &ql_info;
 
     if (qi->qf_curlist >= qi->qf_listcount
-	|| qi->qf_lists[qi->qf_curlist].qf_count == 0)
+	|| qf_list_empty(qi, qi->qf_curlist))
     {
 	EMSG(_(e_quickfix));
 	return;
@@ -3033,24 +3232,105 @@ theend:
 }
 
 /*
+ * Highlight attributes used for displaying entries from the quickfix list.
+ */
+static int	qfFileAttr;
+static int	qfSepAttr;
+static int	qfLineAttr;
+
+/*
+ * Display information about a single entry from the quickfix/location list.
+ * Used by ":clist/:llist" commands.
+ */
+    static void
+qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx)
+{
+    char_u	*fname;
+    buf_T	*buf;
+    int		filter_entry;
+
+    fname = NULL;
+    if (qfp->qf_module != NULL && *qfp->qf_module != NUL)
+	vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx,
+						(char *)qfp->qf_module);
+    else {
+	if (qfp->qf_fnum != 0
+		&& (buf = buflist_findnr(qfp->qf_fnum)) != NULL)
+	{
+	    fname = buf->b_fname;
+	    if (qfp->qf_type == 1)	/* :helpgrep */
+		fname = gettail(fname);
+	}
+	if (fname == NULL)
+	    sprintf((char *)IObuff, "%2d", qf_idx);
+	else
+	    vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
+		    qf_idx, (char *)fname);
+    }
+
+    // Support for filtering entries using :filter /pat/ clist
+    // Match against the module name, file name, search pattern and
+    // text of the entry.
+    filter_entry = TRUE;
+    if (qfp->qf_module != NULL && *qfp->qf_module != NUL)
+	filter_entry &= message_filtered(qfp->qf_module);
+    if (filter_entry && fname != NULL)
+	filter_entry &= message_filtered(fname);
+    if (filter_entry && qfp->qf_pattern != NULL)
+	filter_entry &= message_filtered(qfp->qf_pattern);
+    if (filter_entry)
+	filter_entry &= message_filtered(qfp->qf_text);
+    if (filter_entry)
+	return;
+
+    msg_putchar('\n');
+    msg_outtrans_attr(IObuff, qf_idx == qi->qf_lists[qi->qf_curlist].qf_index
+	    ? HL_ATTR(HLF_QFL) : qfFileAttr);
+
+    if (qfp->qf_lnum != 0)
+	msg_puts_attr((char_u *)":", qfSepAttr);
+    if (qfp->qf_lnum == 0)
+	IObuff[0] = NUL;
+    else if (qfp->qf_col == 0)
+	sprintf((char *)IObuff, "%ld", qfp->qf_lnum);
+    else
+	sprintf((char *)IObuff, "%ld col %d",
+		qfp->qf_lnum, qfp->qf_col);
+    sprintf((char *)IObuff + STRLEN(IObuff), "%s",
+	    (char *)qf_types(qfp->qf_type, qfp->qf_nr));
+    msg_puts_attr(IObuff, qfLineAttr);
+    msg_puts_attr((char_u *)":", qfSepAttr);
+    if (qfp->qf_pattern != NULL)
+    {
+	qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
+	msg_puts(IObuff);
+	msg_puts_attr((char_u *)":", qfSepAttr);
+    }
+    msg_puts((char_u *)" ");
+
+    /* Remove newlines and leading whitespace from the text.  For an
+     * unrecognized line keep the indent, the compiler may mark a word
+     * with ^^^^. */
+    qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
+				? skipwhite(qfp->qf_text) : qfp->qf_text,
+				IObuff, IOSIZE);
+    msg_prt_line(IObuff, FALSE);
+    out_flush();		/* show one line at a time */
+}
+
+/*
  * ":clist": list all errors
  * ":llist": list all locations
  */
     void
 qf_list(exarg_T *eap)
 {
-    buf_T	*buf;
-    char_u	*fname;
     qfline_T	*qfp;
     int		i;
     int		idx1 = 1;
     int		idx2 = -1;
     char_u	*arg = eap->arg;
     int		plus = FALSE;
-    int		qfFileAttr;
-    int		qfSepAttr;
-    int		qfLineAttr;
-    int		filter_entry;
     int		all = eap->forceit;	/* if not :cl!, only show
 						   recognised errors */
     qf_info_T	*qi = &ql_info;
@@ -3066,7 +3346,7 @@ qf_list(exarg_T *eap)
     }
 
     if (qi->qf_curlist >= qi->qf_listcount
-	|| qi->qf_lists[qi->qf_curlist].qf_count == 0)
+	|| qf_list_empty(qi, qi->qf_curlist))
     {
 	EMSG(_(e_quickfix));
 	return;
@@ -3123,75 +3403,9 @@ qf_list(exarg_T *eap)
 	    if (got_int)
 		break;
 
-	    fname = NULL;
-	    if (qfp->qf_module != NULL && *qfp->qf_module != NUL)
-		vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)qfp->qf_module);
-	    else {
-		if (qfp->qf_fnum != 0
-				&& (buf = buflist_findnr(qfp->qf_fnum)) != NULL)
-		{
-		    fname = buf->b_fname;
-		    if (qfp->qf_type == 1)	/* :helpgrep */
-			fname = gettail(fname);
-		}
-		if (fname == NULL)
-		    sprintf((char *)IObuff, "%2d", i);
-		else
-		    vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
-							     i, (char *)fname);
-	    }
-
-	    // Support for filtering entries using :filter /pat/ clist
-	    // Match against the module name, file name, search pattern and
-	    // text of the entry.
-	    filter_entry = TRUE;
-	    if (qfp->qf_module != NULL && *qfp->qf_module != NUL)
-		filter_entry &= message_filtered(qfp->qf_module);
-	    if (filter_entry && fname != NULL)
-		filter_entry &= message_filtered(fname);
-	    if (filter_entry && qfp->qf_pattern != NULL)
-		filter_entry &= message_filtered(qfp->qf_pattern);
-	    if (filter_entry)
-		filter_entry &= message_filtered(qfp->qf_text);
-	    if (filter_entry)
-		goto next_entry;
-
-	    msg_putchar('\n');
-	    msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
-					   ? HL_ATTR(HLF_QFL) : qfFileAttr);
-
-	    if (qfp->qf_lnum != 0)
-		msg_puts_attr((char_u *)":", qfSepAttr);
-	    if (qfp->qf_lnum == 0)
-		IObuff[0] = NUL;
-	    else if (qfp->qf_col == 0)
-		sprintf((char *)IObuff, "%ld", qfp->qf_lnum);
-	    else
-		sprintf((char *)IObuff, "%ld col %d",
-						   qfp->qf_lnum, qfp->qf_col);
-	    sprintf((char *)IObuff + STRLEN(IObuff), "%s",
-				  (char *)qf_types(qfp->qf_type, qfp->qf_nr));
-	    msg_puts_attr(IObuff, qfLineAttr);
-	    msg_puts_attr((char_u *)":", qfSepAttr);
-	    if (qfp->qf_pattern != NULL)
-	    {
-		qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
-		msg_puts(IObuff);
-		msg_puts_attr((char_u *)":", qfSepAttr);
-	    }
-	    msg_puts((char_u *)" ");
-
-	    /* Remove newlines and leading whitespace from the text.  For an
-	     * unrecognized line keep the indent, the compiler may mark a word
-	     * with ^^^^. */
-	    qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
-				     ? skipwhite(qfp->qf_text) : qfp->qf_text,
-							      IObuff, IOSIZE);
-	    msg_prt_line(IObuff, FALSE);
-	    out_flush();		/* show one line at a time */
+	    qf_list_entry(qi, qfp, i);
 	}
 
-next_entry:
 	qfp = qfp->qf_next;
 	if (qfp == NULL)
 	    break;
@@ -3320,7 +3534,7 @@ qf_history(exarg_T *eap)
     if (eap->cmdidx == CMD_lhistory)
 	qi = GET_LOC_LIST(curwin);
     if (qi == NULL || (qi->qf_listcount == 0
-				&& qi->qf_lists[qi->qf_curlist].qf_count == 0))
+				&& qf_list_empty(qi, qi->qf_curlist)))
 	MSG(_("No entries"));
     else
 	for (i = 0; i < qi->qf_listcount; ++i)
@@ -3421,7 +3635,7 @@ qf_mark_adjust(
     }
 
     for (idx = 0; idx < qi->qf_listcount; ++idx)
-	if (qi->qf_lists[idx].qf_count)
+	if (!qf_list_empty(qi, idx))
 	    for (i = 0, qfp = qi->qf_lists[idx].qf_start;
 			i < qi->qf_lists[idx].qf_count && qfp != NULL;
 			++i, qfp = qfp->qf_next)
@@ -3552,7 +3766,7 @@ ex_cwindow(exarg_T *eap)
      * it if we have errors; otherwise, leave it closed.
      */
     if (qi->qf_lists[qi->qf_curlist].qf_nonevalid
-	    || qi->qf_lists[qi->qf_curlist].qf_count == 0
+	    || qf_list_empty(qi, qi->qf_curlist)
 	    || qi->qf_curlist >= qi->qf_listcount)
     {
 	if (win != NULL)
@@ -5154,7 +5368,7 @@ ex_vimgrep(exarg_T *eap)
 	qi->qf_curlist = qf_id2nr(qi, save_qfid);
 
     /* Jump to first match. */
-    if (qi->qf_lists[qi->qf_curlist].qf_count > 0)
+    if (!qf_list_empty(qi, qi->qf_curlist))
     {
 	if ((flags & VGR_NOJUMP) == 0)
 	    vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy,
@@ -5387,8 +5601,7 @@ get_errorlist(qf_info_T *qi_arg, win_T *
     if (qf_idx == INVALID_QFIDX)
 	qf_idx = qi->qf_curlist;
 
-    if (qf_idx >= qi->qf_listcount
-	    || qi->qf_lists[qf_idx].qf_count == 0)
+    if (qf_idx >= qi->qf_listcount || qf_list_empty(qi, qf_idx))
 	return FAIL;
 
     qfp = qi->qf_lists[qf_idx].qf_start;
@@ -5709,7 +5922,7 @@ qf_getprop_ctx(qf_info_T *qi, int qf_idx
 qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
 {
     int idx = qi->qf_lists[qf_idx].qf_index;
-    if (qi->qf_lists[qf_idx].qf_count == 0)
+    if (qf_list_empty(qi, qf_idx))
 	/* For empty lists, qf_index is set to 1 */
 	idx = 0;
     return dict_add_number(retdict, "idx", idx);
@@ -5798,7 +6011,7 @@ qf_add_entries(
 	qf_new_list(qi, title);
 	qf_idx = qi->qf_curlist;
     }
-    else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0)
+    else if (action == 'a' && !qf_list_empty(qi, qf_idx))
 	/* Adding to existing list, use last entry. */
 	old_last = qi->qf_lists[qf_idx].qf_last;
     else if (action == 'r')
@@ -5887,7 +6100,7 @@ qf_add_entries(
     {
 	qi->qf_lists[qf_idx].qf_ptr =
 	    qi->qf_lists[qf_idx].qf_start;
-	if (qi->qf_lists[qf_idx].qf_count > 0)
+	if (!qf_list_empty(qi, qf_idx))
 	    qi->qf_lists[qf_idx].qf_index = 1;
     }
 
@@ -6746,7 +6959,7 @@ ex_helpgrep(exarg_T *eap)
     }
 
     /* Jump to first match. */
-    if (qi->qf_lists[qi->qf_curlist].qf_count > 0)
+    if (!qf_list_empty(qi, qi->qf_curlist))
 	qf_jump(qi, 0, 0, FALSE);
     else
 	EMSG2(_(e_nomatch2), eap->arg);
--- a/src/version.c
+++ b/src/version.c
@@ -795,6 +795,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    252,
+/**/
     251,
 /**/
     250,