diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f38cf231fe..66cc184177 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -157,7 +157,8 @@ enum { QF_OK = 1, QF_END_OF_INPUT = 2, QF_NOMEM = 3, - QF_IGNORE_LINE = 4 + QF_IGNORE_LINE = 4, + QF_MULTISCAN = 5, }; typedef struct { @@ -723,21 +724,17 @@ static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; - size_t len; - int i; int idx = 0; char_u *tail = NULL; - regmatch_T regmatch; qf_list_T *qfl = &qi->qf_lists[qf_idx]; - - // Always ignore case when looking for a matching error. - regmatch.rm_ic = true; + int status; restofline: // If there was no %> item start at the first pattern if (fmt_start == NULL) { fmt_ptr = fmt_first; } else { + // Otherwise start from the last used pattern. fmt_ptr = fmt_start; fmt_start = NULL; } @@ -747,151 +744,13 @@ restofline: fields->valid = true; for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) { idx = fmt_ptr->prefix; - if (qfl->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) { - continue; + status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, + qfl->qf_multiline, qfl->qf_multiscan, + &tail); + if (status == QF_NOMEM) { + return status; } - fields->namebuf[0] = NUL; - fields->module[0] = NUL; - fields->pattern[0] = NUL; - if (!qfl->qf_multiscan) { - fields->errmsg[0] = NUL; - } - fields->lnum = 0; - fields->col = 0; - fields->use_viscol = false; - fields->enr = -1; - fields->type = 0; - tail = NULL; - - regmatch.regprog = fmt_ptr->prog; - int r = vim_regexec(®match, linebuf, (colnr_T)0); - fmt_ptr->prog = regmatch.regprog; - if (r) { - if ((idx == 'C' || idx == 'Z') && !qfl->qf_multiline) { - continue; - } - if (vim_strchr((char_u *)"EWI", idx) != NULL) { - fields->type = (char_u)idx; - } else { - fields->type = 0; - } - // 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) { - continue; - } - // 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)) { - continue; - } - } - if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n - if (regmatch.startp[i] == NULL) { - continue; - } - fields->enr = (int)atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l - if (regmatch.startp[i] == NULL) { - continue; - } - fields->lnum = atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c - if (regmatch.startp[i] == NULL) { - continue; - } - fields->col = (int)atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t - if (regmatch.startp[i] == NULL) { - continue; - } - fields->type = *regmatch.startp[i]; - } - if (fmt_ptr->flags == '+' && !qfl->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) { - continue; - } - 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) { - continue; - } - 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) { - continue; - } - 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; - } - } - fields->col++; - fields->use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v - if (regmatch.startp[i] == NULL) { - continue; - } - 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) { - continue; - } - len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > CMDBUFFSIZE - 5) { - len = CMDBUFFSIZE - 5; - } - STRCPY(fields->pattern, "^\\V"); - xstrlcat((char *)fields->pattern, (char *)regmatch.startp[i], - CMDBUFFSIZE+1); - 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) { - continue; - } - len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > CMDBUFFSIZE) { - len = CMDBUFFSIZE; - } - xstrlcat((char *)fields->module, (char *)regmatch.startp[i], len); - } + if (status == QF_OK) { break; } } @@ -899,30 +758,16 @@ restofline: if (fmt_ptr == NULL || idx == 'D' || idx == 'X') { if (fmt_ptr != NULL) { - if (idx == 'D') { // enter directory - if (*fields->namebuf == NUL) { - EMSG(_("E379: Missing or empty directory name")); - return QF_FAIL; - } - qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack, - false); - if (qfl->qf_directory == NULL) { - return QF_FAIL; - } - } else if (idx == 'X') { // leave directory - qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack); + // 'D' and 'X' directory specifiers. + status = qf_parse_dir_pfx(idx, fields, qfl); + if (status != QF_OK) { + return status; } } - fields->namebuf[0] = NUL; // no match found, remove file name - fields->lnum = 0; // don't jump to this line - fields->valid = false; - if (linelen >= fields->errmsglen) { - // linelen + null terminator - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; + status = qf_parse_line_nomatch(linebuf, linelen, fields); + if (status != QF_OK) { + return status; } - // copy whole line to error message - STRLCPY(fields->errmsg, linebuf, linelen + 1); if (fmt_ptr == NULL) { qfl->qf_multiline = qfl->qf_multiignore = false; } @@ -935,63 +780,17 @@ restofline: if (vim_strchr((char_u *)"AEWI", idx) != NULL) { qfl->qf_multiline = true; // start of a multi-line message qfl->qf_multiignore = false; // reset continuation - } else if (vim_strchr((char_u *)"CZ", idx) - != NULL) { // continuation of multi-line msg - if (!qfl->qf_multiignore) { - qfline_T *qfprev = qfl->qf_last; - if (qfprev == NULL) { - return QF_FAIL; - } - if (*fields->errmsg) { - size_t textlen = STRLEN(qfprev->qf_text); - qfprev->qf_text = xrealloc(qfprev->qf_text, - textlen + STRLEN(fields->errmsg) + 2); - qfprev->qf_text[textlen] = '\n'; - STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg); - } - if (qfprev->qf_nr == -1) { - qfprev->qf_nr = fields->enr; - } - if (vim_isprintc(fields->type) && !qfprev->qf_type) { - qfprev->qf_type = fields->type; // only printable chars allowed - } - if (!qfprev->qf_lnum) { - qfprev->qf_lnum = fields->lnum; - } - if (!qfprev->qf_col) { - qfprev->qf_col = fields->col; - } - qfprev->qf_viscol = fields->use_viscol; - if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, - *fields->namebuf || qfl->qf_directory - ? fields->namebuf - : qfl->qf_currfile && fields->valid - ? qfl->qf_currfile : 0); - } + } else if (vim_strchr((char_u *)"CZ", idx) != NULL) { + // continuation of multi-line msg + status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields); + if (status != QF_OK) { + return status; } - if (idx == 'Z') { - qfl->qf_multiline = qfl->qf_multiignore = false; - } - - line_breakcheck(); - return QF_IGNORE_LINE; } else if (vim_strchr((char_u *)"OPQ", idx) != NULL) { // global file names - fields->valid = false; - if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) { - if (*fields->namebuf && idx == 'P') { - qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack, - true); - } else if (idx == 'Q') { - qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack); - } - *fields->namebuf = NUL; - if (tail && *tail) { - STRMOVE(IObuff, skipwhite(tail)); - qfl->qf_multiscan = true; - goto restofline; - } + status = qf_parse_file_pfx(idx, fields, qfl, tail); + if (status == QF_MULTISCAN) { + goto restofline; } } if (fmt_ptr->flags == '-') { // generally exclude this line @@ -1207,6 +1006,8 @@ qf_init_end: return retval; } +/// Set the title of the specified quickfix list. Frees the previous title. +/// Prepends ':' to the title. static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { xfree(qi->qf_lists[qf_idx].qf_title); @@ -1250,9 +1051,299 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } -/* - * Free a location list - */ +/// 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. +static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, + regmatch_T *regmatch, qffields_T *fields, + int qf_multiline, int qf_multiscan, char_u **tail) +{ + char_u idx = fmt_ptr->prefix; + int i; + size_t len; + + if ((idx == 'C' || idx == 'Z') && !qf_multiline) { + return QF_FAIL; + } + if (vim_strchr((char_u *)"EWI", idx) != NULL) { + fields->type = idx; + } else { + fields->type = 0; + } + + // 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; + } + } + 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; + } + STRNCAT(fields->module, regmatch->startp[i], len); + } + + return QF_OK; +} + +/// Parse an error line in 'linebuf' using a single error format string in +/// 'fmt_ptr->prog' and return the matching values in 'fields'. +/// Returns QF_OK if the efm format matches completely and the fields are +/// successfully copied. Otherwise returns QF_FAIL or QF_NOMEM. +static int qf_parse_get_fields(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, + qffields_T *fields, int qf_multiline, + int qf_multiscan, char_u **tail) +{ + regmatch_T regmatch; + int status = QF_FAIL; + int r; + + if (qf_multiscan && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL) { + return QF_FAIL; + } + + fields->namebuf[0] = NUL; + fields->module[0] = NUL; + fields->pattern[0] = NUL; + if (!qf_multiscan) { + fields->errmsg[0] = NUL; + } + fields->lnum = 0; + fields->col = 0; + fields->use_viscol = false; + fields->enr = -1; + fields->type = 0; + *tail = NULL; + + regmatch.regprog = fmt_ptr->prog; + r = vim_regexec(®match, linebuf, (colnr_T)0); + fmt_ptr->prog = regmatch.regprog; + if (r) { + status = qf_parse_match(linebuf, linelen, fmt_ptr, ®match, fields, + qf_multiline, qf_multiscan, tail); + } + + return status; +} + +/// Parse directory error format prefixes (%D and %X). +/// Push and pop directories from the directory stack when scanning directory +/// names. +static int qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl) +{ + if (idx == 'D') { // enter directory + if (*fields->namebuf == NUL) { + EMSG(_("E379: Missing or empty directory name")); + return QF_FAIL; + } + qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack, false); + if (qfl->qf_directory == NULL) { + return QF_FAIL; + } + } else if (idx == 'X') { // leave directory + qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack); + } + + return QF_OK; +} + +/// Parse global file name error format prefixes (%O, %P and %Q). +static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl, + char_u *tail) +{ + fields->valid = false; + if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) { + if (*fields->namebuf && idx == 'P') { + qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack, + true); + } else if (idx == 'Q') { + qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack); + } + *fields->namebuf = NUL; + if (tail && *tail) { + STRMOVE(IObuff, skipwhite(tail)); + qfl->qf_multiscan = true; + return QF_MULTISCAN; + } + } + + return QF_OK; +} + +/// Parse a non-error line (a line which doesn't match any of the error +/// format in 'efm'). +static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen, + qffields_T *fields) +{ + fields->namebuf[0] = NUL; // no match found, remove file name + fields->lnum = 0; // don't jump to this line + fields->valid = false; + if (linelen >= fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + // copy whole line to error message + STRLCPY(fields->errmsg, linebuf, linelen + 1); + + return QF_OK; +} + +/// Parse multi-line error format prefixes (%C and %Z) +static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, + qf_list_T *qfl, qffields_T *fields) +{ + if (!qfl->qf_multiignore) { + qfline_T *qfprev = qfl->qf_last; + + if (qfprev == NULL) { + return QF_FAIL; + } + if (*fields->errmsg && !qfl->qf_multiignore) { + size_t textlen = strlen((char *)qfprev->qf_text); + size_t errlen = strlen((char *)fields->errmsg); + qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2); + qfprev->qf_text[textlen] = '\n'; + STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg); + } + if (qfprev->qf_nr == -1) { + qfprev->qf_nr = fields->enr; + } + if (vim_isprintc(fields->type) && !qfprev->qf_type) { + // only printable chars allowed + qfprev->qf_type = fields->type; + } + + if (!qfprev->qf_lnum) { + qfprev->qf_lnum = fields->lnum; + } + if (!qfprev->qf_col) { + qfprev->qf_col = fields->col; + } + qfprev->qf_viscol = fields->use_viscol; + if (!qfprev->qf_fnum) { + qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, + *fields->namebuf || qfl->qf_directory + ? fields->namebuf + : qfl->qf_currfile && fields->valid + ? qfl->qf_currfile : 0); + } + } + if (idx == 'Z') { + qfl->qf_multiline = qfl->qf_multiignore = false; + } + line_breakcheck(); + + return QF_IGNORE_LINE; +} + +/// Free a location list. static void ll_free_all(qf_info_T **pqi) { int i; @@ -1272,6 +1363,7 @@ static void ll_free_all(qf_info_T **pqi) } } +/// Free all the quickfix/location lists in the stack. void qf_free_all(win_T *wp) { int i; @@ -2497,6 +2589,8 @@ static void qf_fmt_text(char_u *text, char_u *buf, int bufsize) buf[i] = NUL; } +/// Display information (list number, list size and the title) about a +/// quickfix/location list. static void qf_msg(qf_info_T *qi, int which, char *lead) { char *title = (char *)qi->qf_lists[which].qf_title; @@ -2566,6 +2660,7 @@ void qf_age(exarg_T *eap) qf_update_buffer(qi, NULL); } +/// Display the information about all the quickfix/location lists in the stack. void qf_history(exarg_T *eap) { qf_info_T *qi = &ql_info; @@ -5016,6 +5111,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, return retval; } +/// Mark the context as in use for all the lists in a quickfix stack. static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) { bool abort = false; @@ -5292,7 +5388,7 @@ void ex_helpgrep(exarg_T *eap) // Create a new quickfix list. qf_new_list(qi, *eap->cmdlinep); - /* Go through all directories in 'runtimepath' */ + // Go through all the directories in 'runtimepath' p = p_rtp; while (*p != NUL && !got_int) { copy_option_part(&p, NameBuff, MAXPATHL, ",");