diff options
-rw-r--r-- | src/nvim/quickfix.c | 717 | ||||
-rw-r--r-- | src/nvim/testdir/test_quickfix.vim | 4 |
2 files changed, 444 insertions, 277 deletions
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 8f891751d6..f14d9f445b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -163,6 +163,8 @@ enum { QF_MULTISCAN = 5, }; +// State information used to parse lines and add entries to a quickfix/location +// list. typedef struct { char_u *linebuf; size_t linelen; @@ -266,92 +268,95 @@ static struct fmtpattern }; // Convert an errorformat pattern to a regular expression pattern. -// See fmt_pat definition above for the list of supported patterns. -static char_u *fmtpat_to_regpat( - const char_u *efmp, - efm_T *fmt_ptr, +// 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 * efmpat_to_regpat( + const char_u *efmpat, + char_u *regpat, + efm_T *efminfo, int idx, int round, - char_u *ptr, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - if (fmt_ptr->addr[idx]) { + if (efminfo->addr[idx]) { // Each errorformat pattern can occur only once snprintf((char *)errmsg, errmsglen, - _("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)) { snprintf((char *)errmsg, errmsglen, - _("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 { char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; + while ((*regpat = *srcptr++) != NUL) { + regpat++; } } - *ptr++ = '\\'; - *ptr++ = ')'; + *regpat++ = '\\'; + *regpat++ = ')'; - return ptr; + return regpat; } // Convert a scanf like format in 'errorformat' to a regular expression. -static char_u *scanf_fmt_to_regpat( +// Returns a pointer to the location after the pattern. +static char_u * scanf_fmt_to_regpat( + const char_u **pefmp, const char_u *efm, int len, - const char_u **pefmp, - char_u *ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { const 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 ']' - while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + *regpat++ = *++efmp; // could be ']' + while (efmp < efm + len && (*regpat++ = *++efmp) != ']') { } if (efmp == efm + len) { EMSG(_("E374: Missing ] in format string")); @@ -359,10 +364,10 @@ static char_u *scanf_fmt_to_regpat( } } } else if (efmp < efm + len) { // %*\D, %*\s etc. - *ptr++ = *++efmp; + *regpat++ = *++efmp; } - *ptr++ = '\\'; - *ptr++ = '+'; + *regpat++ = '\\'; + *regpat++ = '+'; } else { // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? snprintf((char *)errmsg, errmsglen, @@ -373,31 +378,27 @@ static char_u *scanf_fmt_to_regpat( *pefmp = efmp; - return ptr; + return regpat; } // Analyze/parse an errorformat prefix. -static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, - char_u *errmsg, size_t errmsglen) +static char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, + char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - const char_u *efmp = *pefmp; - 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 { snprintf((char *)errmsg, errmsglen, _("E376: Invalid %%%c in format string prefix"), *efmp); EMSG(errmsg); - return FAIL; + return NULL; } - *pefmp = efmp; - - return OK; + return efmp; } @@ -420,16 +421,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, - errmsg, errmsglen); + ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg, + errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } round++; } else if (*efmp == '*') { - ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + efmp++; + ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { *ptr++ = *efmp; // regexp magic characters @@ -438,14 +440,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { - return -1; + // prefix is allowed only at the beginning of the errorformat + // option part + efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen); + if (efmp == NULL) { + return FAIL; } } else { snprintf((char *)errmsg, CMDBUFFSIZE + 1, _("E377: Invalid %%%c in format string"), *efmp); EMSG(errmsg); - return -1; + return FAIL; } } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { @@ -461,7 +466,7 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, *ptr++ = '$'; *ptr = NUL; - return 0; + return OK; } static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls @@ -477,7 +482,42 @@ static void 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 size_t efm_regpat_bufsz(char_u *efm) +{ + size_t sz; + + sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); + for (int i = FMT_PATTERNS - 1; i >= 0; ) { + sz += 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) { efm_T *fmt_ptr = NULL; @@ -489,16 +529,8 @@ static efm_T * parse_efm_option(char_u *efm) char_u *errmsg = xmalloc(errmsglen); // Get some space to modify the format string into. - size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); - for (int round = FMT_PATTERNS - 1; round >= 0; ) { - i += 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 - char_u *fmtstr = xmalloc(i); + size_t sz = efm_regpat_bufsz(efm); + char_u *fmtstr = xmalloc(sz); while (efm[0] != NUL) { // Allocate a new eformat structure and put it at the end of the list @@ -511,13 +543,9 @@ static efm_T * parse_efm_option(char_u *efm) fmt_last = fmt_ptr; // Isolate one part in the 'errorformat' option - for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { - if (efm[len] == '\\' && efm[len + 1] != NUL) { - len++; - } - } + len = efm_option_part_len(efm); - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { @@ -543,6 +571,7 @@ parse_efm_end: return fmt_first; } +// Allocate more memory for the line buffer used for parsing lines. static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz) { // If the line exceeds LINE_MAXLEN exclude the last @@ -1011,7 +1040,7 @@ qf_init_ext( } else { // 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; } } @@ -1183,6 +1212,219 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } +// 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) +{ + char_u 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 + && !os_path_exists(fields->namebuf)) { + 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, + size_t linelen, + qffields_T *fields) +{ + if (linelen >= fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + STRLCPY(fields->errmsg, linebuf, linelen + 1); + 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) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len >= fields->errmsglen) { + // len + null terminator + fields->errmsg = xrealloc(fields->errmsg, len + 1); + fields->errmsglen = len + 1; + } + STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + 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) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE - 5) { + len = CMDBUFFSIZE - 5; + } + STRCPY(fields->pattern, "^\\V"); + xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4); + 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) +{ + size_t len; + size_t dsize; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + dsize = STRLEN(fields->module) + len + 1; + if (dsize > CMDBUFFSIZE) { + dsize = CMDBUFFSIZE; + } + xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize); + 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 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, @@ -1193,7 +1435,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, { char_u idx = fmt_ptr->prefix; int i; - size_t len; + int midx; + int status; if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; @@ -1207,118 +1450,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, // Extract error message data from matched line. // 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 - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - - // Expand ~/file and $HOME/file to full path. - char_u 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 - && !os_path_exists(fields->namebuf)) { - 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 - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; - } - STRLCPY(fields->errmsg, linebuf, linelen + 1); - } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len >= fields->errmsglen) { - // len + null terminator - fields->errmsg = xrealloc(fields->errmsg, len + 1); - fields->errmsglen = len + 1; - } - STRLCPY(fields->errmsg, regmatch->startp[i], len + 1); - } - 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 - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - fields->col = 0; - char_u *match_ptr; - 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; + 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) { + 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); } + } 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); } - 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 = (size_t)(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 = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len > CMDBUFFSIZE) { - len = CMDBUFFSIZE; + + if (status != QF_OK) { + return status; } - STRNCAT(fields->module, regmatch->startp[i], len); } return QF_OK; @@ -1572,7 +1723,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_valid = valid; lastp = &qi->qf_lists[qf_idx].qf_last; - if (qi->qf_lists[qf_idx].qf_count == 0) { + 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; @@ -2633,22 +2784,106 @@ 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; + + 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 = path_tail(fname); + } + } + if (fname == NULL) { + snprintf((char *)IObuff, IOSIZE, "%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. + bool 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(":", qfSepAttr); + } + if (qfp->qf_lnum == 0) { + IObuff[0] = NUL; + } else if (qfp->qf_col == 0) { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", + qfp->qf_lnum, qfp->qf_col); + } + vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + msg_puts_attr((const char *)IObuff, qfLineAttr); + msg_puts_attr(":", qfSepAttr); + if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); + msg_puts((const char *)IObuff); + msg_puts_attr(":", qfSepAttr); + } + msg_puts(" "); + + // 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); + ui_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 qfFileAttr; - int qfSepAttr; - int qfLineAttr; int all = eap->forceit; // if not :cl!, only show // recognised errors qf_info_T *qi = &ql_info; @@ -2717,80 +2952,9 @@ void qf_list(exarg_T *eap) 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 = path_tail(fname); - } - } - if (fname == NULL) { - snprintf((char *)IObuff, IOSIZE, "%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. - bool 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(":", qfSepAttr); - } - if (qfp->qf_lnum == 0) { - IObuff[0] = NUL; - } else if (qfp->qf_col == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", - qfp->qf_lnum, qfp->qf_col); - } - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr((const char *)IObuff, qfLineAttr); - msg_puts_attr(":", qfSepAttr); - if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); - msg_puts((const char *)IObuff); - msg_puts_attr(":", qfSepAttr); - } - msg_puts(" "); - - /* 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); - ui_flush(); /* show one line at a time */ + qf_list_entry(qi, qfp, i); } -next_entry: qfp = qfp->qf_next; if (qfp == NULL) { break; @@ -2995,8 +3159,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qi = wp->w_llist; } - for (idx = 0; idx < qi->qf_listcount; ++idx) - if (qi->qf_lists[idx].qf_count) + for (idx = 0; idx < qi->qf_listcount; idx++) { + 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) { @@ -3011,6 +3175,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qfp->qf_lnum += amount_after; } } + } + } return found_one; } @@ -4596,7 +4762,7 @@ void ex_vimgrep(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)) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); @@ -4805,7 +4971,7 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) } if (qf_idx >= qi->qf_listcount - || qi->qf_lists[qf_idx].qf_count == 0) { + || qf_list_empty(qi, qf_idx)) { return FAIL; } @@ -5119,7 +5285,7 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) static int 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; } @@ -5276,7 +5442,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // make place for a new list 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') { @@ -5308,7 +5474,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } if (action != 'a') { 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; } } @@ -6023,11 +6189,12 @@ void ex_helpgrep(exarg_T *eap) } } - /* Jump to first match. */ - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) - qf_jump(qi, 0, 0, FALSE); - else + // Jump to first match. + if (!qf_list_empty(qi, qi->qf_curlist)) { + qf_jump(qi, 0, 0, false); + } else { EMSG2(_(e_nomatch2), eap->arg); + } if (eap->cmdidx == CMD_lhelpgrep) { // If the help window is not opened or if it already points to the diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 6f58b0084c..c0f39884ea 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1045,8 +1045,8 @@ func Test_efm2() set efm=%f:%s cexpr 'Xtestfile:Line search text' let l = getqflist() - call assert_equal(l[0].pattern, '^\VLine search text\$') - call assert_equal(l[0].lnum, 0) + call assert_equal('^\VLine search text\$', l[0].pattern) + call assert_equal(0, l[0].lnum) let l = split(execute('clist', ''), "\n") call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) |