diff options
Diffstat (limited to 'src/nvim/quickfix.c')
-rw-r--r-- | src/nvim/quickfix.c | 2400 |
1 files changed, 1455 insertions, 945 deletions
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ee1cf2182f..29b3d19f52 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -57,19 +57,20 @@ struct dir_stack_T { */ typedef struct qfline_S qfline_T; struct qfline_S { - qfline_T *qf_next; /* pointer to next error in the list */ - qfline_T *qf_prev; /* pointer to previous error in the list */ - linenr_T qf_lnum; /* line number where the error occurred */ - int qf_fnum; /* file number for the line */ - int qf_col; /* column where the error occurred */ - int qf_nr; /* error number */ - char_u *qf_pattern; /* search pattern for the error */ - char_u *qf_text; /* description of the error */ - char_u qf_viscol; /* set to TRUE if qf_col is screen column */ - char_u qf_cleared; /* set to TRUE if line has been deleted */ - char_u qf_type; /* type of the error (mostly 'E'); 1 for - :helpgrep */ - char_u qf_valid; /* valid error message detected */ + qfline_T *qf_next; ///< pointer to next error in the list + qfline_T *qf_prev; ///< pointer to previous error in the list + linenr_T qf_lnum; ///< line number where the error occurred + int qf_fnum; ///< file number for the line + int qf_col; ///< column where the error occurred + int qf_nr; ///< error number + char_u *qf_module; ///< module name for this error + char_u *qf_pattern; ///< search pattern for the error + char_u *qf_text; ///< description of the error + char_u qf_viscol; ///< set to TRUE if qf_col is screen column + char_u qf_cleared; ///< set to TRUE if line has been deleted + char_u qf_type; ///< type of the error (mostly 'E'); 1 for + // :helpgrep + char_u qf_valid; ///< valid error message detected }; /* @@ -101,6 +102,7 @@ typedef struct qf_list_S { bool qf_multiline; bool qf_multiignore; bool qf_multiscan; + long qf_changedtick; } qf_list_T; /// Quickfix/Location list stack definition @@ -121,7 +123,7 @@ struct qf_info_S { static qf_info_T ql_info; // global quickfix list static unsigned last_qf_id = 0; // Last Used quickfix list id -#define FMT_PATTERNS 10 /* maximum number of % recognized */ +#define FMT_PATTERNS 11 // maximum number of % recognized /* * Structure used to hold the info of one part of 'errorformat' @@ -155,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 { @@ -176,6 +179,7 @@ typedef struct { typedef struct { char_u *namebuf; + char_u *module; char_u *errmsg; size_t errmsglen; long lnum; @@ -205,6 +209,8 @@ typedef struct { static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; +static char *e_loc_list_changed = N_("E926: Current location list was changed"); + /// Read the errorfile "efile" into memory, line by line, building the error /// list. Set the error list's title to qf_title. /// @@ -247,7 +253,8 @@ static struct fmtpattern { 'r', ".*" }, { 'p', "[- .]*" }, // NOLINT(whitespace/tab) { 'v', "\\d\\+" }, - { 's', ".\\+" } + { 's', ".\\+" }, + { 'o', ".\\+" } }; // Converts a 'errorformat' string to regular expression pattern @@ -423,7 +430,11 @@ static efm_T * parse_efm_option(char_u *efm) 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); while (efm[0] != NUL) { @@ -717,20 +728,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]; + int status; - // Always ignore case when looking for a matching error. - regmatch.rm_ic = true; - +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; } @@ -738,143 +746,15 @@ static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf, // Try to match each part of 'errorformat' until we find a complete // match or no match. fields->valid = true; -restofline: 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; - } - fields->namebuf[0] = NUL; - fields->pattern[0] = NUL; - if (!qfl->qf_multiscan) { - fields->errmsg[0] = NUL; + status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, + qfl->qf_multiline, qfl->qf_multiscan, + &tail); + if (status == QF_NOMEM) { + return status; } - 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 (status == QF_OK) { break; } } @@ -882,30 +762,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; } @@ -918,63 +784,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); - } - } - if (idx == 'Z') { - qfl->qf_multiline = qfl->qf_multiignore = false; + } 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; } - - 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 @@ -1034,6 +854,7 @@ qf_init_ext( } fields.namebuf = xmalloc(CMDBUFFSIZE + 1); + fields.module = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); fields.pattern = xmalloc(CMDBUFFSIZE + 1); @@ -1128,6 +949,7 @@ qf_init_ext( (*fields.namebuf || qfl->qf_directory) ? fields.namebuf : ((qfl->qf_currfile && fields.valid) ? qfl->qf_currfile : (char_u *)NULL), + fields.module, 0, fields.errmsg, fields.lnum, @@ -1172,6 +994,7 @@ qf_init_end: fclose(state.fd); } xfree(fields.namebuf); + xfree(fields.module); xfree(fields.errmsg); xfree(fields.pattern); xfree(state.growbuf); @@ -1187,6 +1010,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); @@ -1197,10 +1022,23 @@ static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) char_u *p = xmallocz(len); qi->qf_lists[qf_idx].qf_title = p; - snprintf((char *)p, len + 1, ":%s", (char *)title); + xstrlcpy((char *)p, (char *)title, len + 1); } } +/// The title of a quickfix/location list is set, by default, to the command +/// that created the quickfix list with the ":" prefix. +/// Create a quickfix list title string by prepending ":" to a user command. +/// Returns a pointer to a static buffer with the title. +static char_u * qf_cmdtitle(char_u *cmd) +{ + static char_u qftitle_str[IOSIZE]; + + snprintf((char *)qftitle_str, IOSIZE, ":%s", (char *)cmd); + + return qftitle_str; +} + // Prepare for adding a new quickfix list. If the current list is in the // middle of the stack, then all the following lists are freed and then // the new list is added. @@ -1230,9 +1068,301 @@ 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; + + // Always ignore case when looking for a matching error. + regmatch.rm_ic = true; + 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; @@ -1252,6 +1382,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; @@ -1273,6 +1404,7 @@ void qf_free_all(win_T *wp) /// @param qf_idx list index /// @param dir optional directory name /// @param fname file name or NULL +/// @param module module name or NULL /// @param bufnum buffer number or zero /// @param mesg message /// @param lnum line number @@ -1285,9 +1417,9 @@ void qf_free_all(win_T *wp) /// /// @returns OK or FAIL. static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, - int bufnum, char_u *mesg, long lnum, int col, - char_u vis_col, char_u *pattern, int nr, char_u type, - char_u valid) + char_u *module, int bufnum, char_u *mesg, long lnum, + int col, char_u vis_col, char_u *pattern, int nr, + char_u type, char_u valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1312,9 +1444,15 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, } else { qfp->qf_pattern = vim_strsave(pattern); } + if (module == NULL || *module == NUL) { + qfp->qf_module = NULL; + } else { + qfp->qf_module = vim_strsave(module); + } qfp->qf_nr = nr; - if (type != 1 && !vim_isprintc(type)) /* only printable chars allowed */ + if (type != 1 && !vim_isprintc(type)) { // only printable chars allowed type = 0; + } qfp->qf_type = (char_u)type; qfp->qf_valid = valid; @@ -1443,6 +1581,7 @@ void copy_loclist(win_T *from, win_T *to) to->w_llist->qf_curlist, NULL, NULL, + from_qfp->qf_module, 0, from_qfp->qf_text, from_qfp->qf_lnum, @@ -1473,6 +1612,7 @@ void copy_loclist(win_T *from, win_T *to) // Assign a new ID for the location list to_qfl->qf_id = ++last_qf_id; + to_qfl->qf_changedtick = 0L; /* When no valid entries are present in the list, qf_ptr points to * the first item in the list */ @@ -1699,6 +1839,27 @@ static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) return ds_ptr == NULL ? NULL : ds_ptr->dirname; } +/// Returns true, if a quickfix/location list with the given identifier exists. +static bool qflist_valid(win_T *wp, unsigned int qf_id) +{ + qf_info_T *qi = &ql_info; + + if (wp) { + qi = GET_LOC_LIST(wp); + if (!qi) { + return false; + } + } + + for (int i = 0; i < qi->qf_listcount; i++) { + if (qi->qf_lists[i].qf_id == qf_id) { + return true; + } + } + + return false; +} + /// When loading a file from the quickfix, the auto commands may modify it. /// This may invalidate the current quickfix entry. This function checks /// whether a entry is still present in the quickfix. @@ -1725,314 +1886,520 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) return true; } -/* - * jump to a quickfix line - * if dir == FORWARD go "errornr" valid entries forward - * if dir == BACKWARD go "errornr" valid entries backward - * if dir == FORWARD_FILE go "errornr" valid entries files backward - * if dir == BACKWARD_FILE go "errornr" valid entries files backward - * else if "errornr" is zero, redisplay the same line - * else go to entry "errornr" - */ -void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) +/// Get the next valid entry in the current quickfix/location list. The search +/// starts from the current entry. Returns NULL on failure. +static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, + int *qf_index, int dir) { - qf_info_T *ll_ref; - qfline_T *qf_ptr; - qfline_T *old_qf_ptr; - int qf_index; - int old_qf_fnum; - int old_qf_index; - int prev_index; - static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); - char_u *err = e_no_more_items; - linenr_T i; - buf_T *old_curbuf; - linenr_T old_lnum; - colnr_T screen_col; - colnr_T char_col; - char_u *line; - char_u *old_swb = p_swb; - unsigned old_swb_flags = swb_flags; - int opened_window = FALSE; - win_T *win; - win_T *altwin; - int flags; - win_T *oldwin = curwin; - int print_message = TRUE; - int len; - const bool old_KeyTyped = KeyTyped; // getting file may reset it - int ok = OK; - bool usable_win; + int idx = *qf_index; + int old_qf_fnum = qf_ptr->qf_fnum; - if (qi == NULL) - qi = &ql_info; + do { + if (idx == qi->qf_lists[qi->qf_curlist].qf_count + || qf_ptr->qf_next == NULL) { + return NULL; + } + idx++; + qf_ptr = qf_ptr->qf_next; + } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { - EMSG(_(e_quickfix)); - return; - } + *qf_index = idx; + return qf_ptr; +} - qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; - old_qf_ptr = qf_ptr; - qf_index = qi->qf_lists[qi->qf_curlist].qf_index; - old_qf_index = qf_index; - if (dir == FORWARD || dir == FORWARD_FILE) { /* next valid entry */ - while (errornr--) { - old_qf_ptr = qf_ptr; - prev_index = qf_index; - old_qf_fnum = qf_ptr->qf_fnum; - do { - if (qf_index == qi->qf_lists[qi->qf_curlist].qf_count - || qf_ptr->qf_next == NULL) { - qf_ptr = old_qf_ptr; - qf_index = prev_index; - if (err != NULL) { - EMSG(_(err)); - goto theend; - } - errornr = 0; - break; - } - ++qf_index; - qf_ptr = qf_ptr->qf_next; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid - && !qf_ptr->qf_valid) - || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - err = NULL; - } - } else if (dir == BACKWARD || dir == BACKWARD_FILE) { /* prev. valid entry */ - while (errornr--) { - old_qf_ptr = qf_ptr; - prev_index = qf_index; - old_qf_fnum = qf_ptr->qf_fnum; - do { - if (qf_index == 1 || qf_ptr->qf_prev == NULL) { - qf_ptr = old_qf_ptr; - qf_index = prev_index; - if (err != NULL) { - EMSG(_(err)); - goto theend; - } - errornr = 0; - break; - } - --qf_index; - qf_ptr = qf_ptr->qf_prev; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid - && !qf_ptr->qf_valid) - || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - err = NULL; +/// Get the previous valid entry in the current quickfix/location list. The +/// search starts from the current entry. Returns NULL on failure. +static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, + int *qf_index, int dir) +{ + int idx = *qf_index; + int old_qf_fnum = qf_ptr->qf_fnum; + + do { + if (idx == 1 || qf_ptr->qf_prev == NULL) { + return NULL; } - } else if (errornr != 0) { /* go to specified number */ - while (errornr < qf_index && qf_index > 1 && qf_ptr->qf_prev != NULL) { - --qf_index; - qf_ptr = qf_ptr->qf_prev; + idx--; + qf_ptr = qf_ptr->qf_prev; + } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); + + *qf_index = idx; + return qf_ptr; +} + +/// Get the n'th (errornr) previous/next valid entry from the current entry in +/// the quickfix list. +/// dir == FORWARD or FORWARD_FILE: next valid entry +/// dir == BACKWARD or BACKWARD_FILE: previous valid entry +static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, + qfline_T *qf_ptr, int *qf_index, int dir) +{ + qfline_T *prev_qf_ptr; + int prev_index; + static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); + char_u *err = e_no_more_items; + + while (errornr--) { + prev_qf_ptr = qf_ptr; + prev_index = *qf_index; + + if (dir == FORWARD || dir == FORWARD_FILE) { + qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); + } else { + qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); } - while (errornr > qf_index && qf_index < - qi->qf_lists[qi->qf_curlist].qf_count - && qf_ptr->qf_next != NULL) { - ++qf_index; - qf_ptr = qf_ptr->qf_next; + + if (qf_ptr == NULL) { + qf_ptr = prev_qf_ptr; + *qf_index = prev_index; + if (err != NULL) { + EMSG(_(err)); + return NULL; + } + break; } + + err = NULL; } - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; - if (qf_win_pos_update(qi, old_qf_index)) - /* No need to print the error message if it's visible in the error - * window */ - print_message = FALSE; + return qf_ptr; +} - /* - * For ":helpgrep" find a help window or open one. - */ - if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { - win_T *wp = NULL; +/// Get n'th (errornr) quickfix entry +static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, + int *cur_qfidx) +{ + int qf_idx = *cur_qfidx; - if (cmdmod.tab == 0) { - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } + // New error number is less than the current error number + while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) { + qf_idx--; + qf_ptr = qf_ptr->qf_prev; + } + + // New error number is greater than the current error number + while (errornr > qf_idx + && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count + && qf_ptr->qf_next != NULL) { + qf_idx++; + qf_ptr = qf_ptr->qf_next; + } + + *cur_qfidx = qf_idx; + return qf_ptr; +} + +/// Find a help window or open one. +static int jump_to_help_window(qf_info_T *qi, int *opened_window) +{ + win_T *wp = NULL; + + if (cmdmod.tab != 0) { + wp = NULL; + } else { + FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { + if (bt_help(wp2->w_buffer)) { + wp = wp2; + break; } } - if (wp != NULL && wp->w_buffer->b_nwindows > 0) - win_enter(wp, true); - else { - /* - * Split off help window; put it at far top if no position - * specified, the current window is vertically split and narrow. - */ - flags = WSP_HELP; - if (cmdmod.split == 0 && curwin->w_width != Columns - && curwin->w_width < 80) - flags |= WSP_TOP; - if (qi != &ql_info) - flags |= WSP_NEWLOC; /* don't copy the location list */ - - if (win_split(0, flags) == FAIL) - goto theend; - opened_window = TRUE; /* close it when fail */ - - if (curwin->w_height < p_hh) - win_setheight((int)p_hh); - - if (qi != &ql_info) { /* not a quickfix list */ - /* The new window should use the supplied location list */ - curwin->w_llist = qi; - qi->qf_refcount++; - } + } + + if (wp != NULL && wp->w_buffer->b_nwindows > 0) { + win_enter(wp, true); + } else { + // Split off help window; put it at far top if no position + // specified, the current window is vertically split and narrow. + int flags = WSP_HELP; + if (cmdmod.split == 0 + && curwin->w_width != Columns + && curwin->w_width < 80) { + flags |= WSP_TOP; } - if (!p_im) - restart_edit = 0; /* don't want insert mode in help file */ - } + if (qi != &ql_info) { + flags |= WSP_NEWLOC; // don't copy the location list + } - /* - * If currently in the quickfix window, find another window to show the - * file in. - */ - if (bt_quickfix(curbuf) && !opened_window) { - win_T *usable_win_ptr = NULL; + if (win_split(0, flags) == FAIL) { + return FAIL; + } - /* - * If there is no file specified, we don't know where to go. - * But do advance, otherwise ":cn" gets stuck. - */ - if (qf_ptr->qf_fnum == 0) - goto theend; + *opened_window = true; - usable_win = false; + if (curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } - ll_ref = curwin->w_llist_ref; - if (ll_ref != NULL) { - /* Find a window using the same location list that is not a - * quickfix window. */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_llist == ll_ref - && wp->w_buffer->b_p_bt[0] != 'q') { - usable_win = true; - usable_win_ptr = wp; - break; - } + if (qi != &ql_info) { // not a quickfix list + // The new window should use the supplied location list + curwin->w_llist = qi; + qi->qf_refcount++; + } + } + + if (!p_im) { + restart_edit = 0; // don't want insert mode in help file + } + + return OK; +} + +/// Find a suitable window for opening a file (qf_fnum) and jump to it. +/// If the file is already opened in a window, jump to it. +static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) +{ + win_T *usable_win_ptr = NULL; + int usable_win; + int flags; + win_T *win = NULL; + win_T *altwin; + + usable_win = 0; + + qf_info_T *ll_ref = curwin->w_llist_ref; + if (ll_ref != NULL) { + // Find a window using the same location list that is not a + // quickfix window. + FOR_ALL_WINDOWS_IN_TAB(usable_win_ptr2, curtab) { + if (usable_win_ptr2->w_llist == ll_ref + && !bt_quickfix(usable_win_ptr2->w_buffer)) { + usable_win_ptr = usable_win_ptr2; + usable_win = 1; + break; } } + } - if (!usable_win) { - /* Locate a window showing a normal buffer */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_p_bt[0] == NUL) { - usable_win = true; - break; - } + if (!usable_win) { + // Locate a window showing a normal buffer + FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { + if (win2->w_buffer->b_p_bt[0] == NUL) { + win = win2; + usable_win = 1; + break; } } + } - /* - * If no usable window is found and 'switchbuf' contains "usetab" - * then search in other tabs. - */ - if (!usable_win && (swb_flags & SWB_USETAB)) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { - goto_tabpage_win(tp, wp); - usable_win = true; - goto win_found; - } + // If no usable window is found and 'switchbuf' contains "usetab" + // then search in other tabs. + if (!usable_win && (swb_flags & SWB_USETAB)) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer->b_fnum == qf_fnum) { + goto_tabpage_win(tp, wp); + usable_win = 1; + goto win_found; } } + } win_found: - /* - * If there is only one window and it is the quickfix window, create a - * new one above the quickfix window. - */ - if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { - flags = WSP_ABOVE; - if (ll_ref != NULL) - flags |= WSP_NEWLOC; - if (win_split(0, flags) == FAIL) - goto failed; /* not enough room for window */ - opened_window = TRUE; /* close it when fail */ - p_swb = empty_option; /* don't split again */ - swb_flags = 0; - RESET_BINDING(curwin); - if (ll_ref != NULL) { - /* The new window should use the location list from the - * location list window */ - curwin->w_llist = ll_ref; - ll_ref->qf_refcount++; - } - } else { - if (curwin->w_llist_ref != NULL) { - /* In a location window */ - win = usable_win_ptr; + // If there is only one window and it is the quickfix window, create a + // new one above the quickfix window. + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { + flags = WSP_ABOVE; + if (ll_ref != NULL) { + flags |= WSP_NEWLOC; + } + if (win_split(0, flags) == FAIL) { + return FAIL; // not enough room for window + } + *opened_window = true; // close it when fail + p_swb = empty_option; // don't split again + swb_flags = 0; + RESET_BINDING(curwin); + if (ll_ref != NULL) { + // The new window should use the location list from the + // location list window + curwin->w_llist = ll_ref; + ll_ref->qf_refcount++; + } + } else { + if (curwin->w_llist_ref != NULL) { + // In a location window + win = usable_win_ptr; + if (win == NULL) { + // Find the window showing the selected file + FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { + if (win2->w_buffer->b_fnum == qf_fnum) { + win = win2; + break; + } + } if (win == NULL) { - /* Find the window showing the selected file */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { - win = wp; + // Find a previous usable window + win = curwin; + do { + if (win->w_buffer->b_p_bt[0] == NUL) { break; } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + } while (win != curwin); + } + } + win_goto(win); + + // If the location list for the window is not set, then set it + // to the location list from the location window + if (win->w_llist == NULL) { + win->w_llist = ll_ref; + ll_ref->qf_refcount++; + } + } else { + // Try to find a window that shows the right buffer. + // Default to the window just above the quickfix buffer. + win = curwin; + altwin = NULL; + for (;;) { + if (win->w_buffer->b_fnum == qf_fnum) { + break; + } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + if (IS_QF_WINDOW(win)) { + // Didn't find it, go to the window before the quickfix window. + if (altwin != NULL) { + win = altwin; + } else if (curwin->w_prev != NULL) { + win = curwin->w_prev; + } else { + win = curwin->w_next; } - if (win == NULL) { - /* Find a previous usable window */ - win = curwin; - do { - if (win->w_buffer->b_p_bt[0] == NUL) - break; - if (win->w_prev == NULL) - win = lastwin; /* wrap around the top */ - else - win = win->w_prev; /* go to previous window */ - } while (win != curwin); - } + break; } - win_goto(win); - /* If the location list for the window is not set, then set it - * to the location list from the location window */ - if (win->w_llist == NULL) { - win->w_llist = ll_ref; - ll_ref->qf_refcount++; + // Remember a usable window. + if (altwin == NULL && !win->w_p_pvw + && win->w_buffer->b_p_bt[0] == NUL) { + altwin = win; } + } + + win_goto(win); + } + } + + return OK; +} + +/// Edit the selected file or help file. +static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, + win_T *oldwin, int *opened_window, int *abort) +{ + int retval = OK; + + if (qf_ptr->qf_type == 1) { + // Open help file (do_ecmd() will set b_help flag, readfile() will + // set b_p_ro flag). + if (!can_abandon(curbuf, forceit)) { + EMSG(_(e_nowrtmsg)); + retval = false; + } else { + retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, + ECMD_HIDE + ECMD_SET_HELP, + oldwin == curwin ? curwin : NULL); + } + } else { + int old_qf_curlist = qi->qf_curlist; + unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + + retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, + GETF_SETMARK | GETF_SWITCH, forceit); + + if (qi != &ql_info) { + // Location list. Check whether the associated window is still + // present and the list is still valid. + if (!win_valid_any_tab(oldwin)) { + EMSG(_("E924: Current window was closed")); + *abort = true; + *opened_window = false; + } else if (!qflist_valid(oldwin, save_qfid)) { + EMSG(_(e_loc_list_changed)); + *abort = true; + } + } else if (old_qf_curlist != qi->qf_curlist + || !is_qf_entry_present(qi, qf_ptr)) { + if (qi == &ql_info) { + EMSG(_("E925: Current quickfix was changed")); } else { + EMSG(_(e_loc_list_changed)); + } + *abort = true; + } - /* - * Try to find a window that shows the right buffer. - * Default to the window just above the quickfix buffer. - */ - win = curwin; - altwin = NULL; - for (;; ) { - if (win->w_buffer->b_fnum == qf_ptr->qf_fnum) - break; - if (win->w_prev == NULL) - win = lastwin; /* wrap around the top */ - else - win = win->w_prev; /* go to previous window */ - - if (IS_QF_WINDOW(win)) { - /* Didn't find it, go to the window before the quickfix - * window. */ - if (altwin != NULL) - win = altwin; - else if (curwin->w_prev != NULL) - win = curwin->w_prev; - else - win = curwin->w_next; + if (*abort) { + retval = false; + } + } + + return retval; +} + +/// Goto the error line in the current file using either line/column number or a +/// search pattern. +static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, + char_u *qf_pattern) +{ + linenr_T i; + char_u *line; + colnr_T screen_col; + colnr_T char_col; + + if (qf_pattern == NULL) { + // Go to line with error, unless qf_lnum is 0. + i = qf_lnum; + if (i > 0) { + if (i > curbuf->b_ml.ml_line_count) { + i = curbuf->b_ml.ml_line_count; + } + curwin->w_cursor.lnum = i; + } + if (qf_col > 0) { + curwin->w_cursor.col = qf_col - 1; + curwin->w_cursor.coladd = 0; + if (qf_viscol == true) { + // Check each character from the beginning of the error + // line up to the error column. For each tab character + // found, reduce the error column value by the length of + // a tab character. + line = get_cursor_line_ptr(); + screen_col = 0; + for (char_col = 0; char_col < curwin->w_cursor.col; char_col++) { + if (*line == NUL) { break; } - - /* Remember a usable window. */ - if (altwin == NULL && !win->w_p_pvw - && win->w_buffer->b_p_bt[0] == NUL) - altwin = win; + if (*line++ == '\t') { + curwin->w_cursor.col -= 7 - (screen_col % 8); + screen_col += 8 - (screen_col % 8); + } else { + screen_col++; + } } - - win_goto(win); } + check_cursor(); + } else { + beginline(BL_WHITE | BL_FIX); + } + } else { + // Move the cursor to the first line in the buffer + pos_T save_cursor = curwin->w_cursor; + curwin->w_cursor.lnum = 0; + if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL, NULL)) { + curwin->w_cursor = save_cursor; + } + } +} + +/// Display quickfix list index and size message +static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, + buf_T *old_curbuf, linenr_T old_lnum) +{ + // Update the screen before showing the message, unless the screen + // scrolled up. + if (!msg_scrolled) { + update_topline_redraw(); + } + snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, + qi->qf_lists[qi->qf_curlist].qf_count, + qf_ptr->qf_cleared ? _(" (line deleted)") : "", + (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); + // Add the message, skipping leading whitespace and newlines. + int len = (int)STRLEN(IObuff); + qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); + + // Output the message. Overwrite to avoid scrolling when the 'O' + // flag is present in 'shortmess'; But when not jumping, print the + // whole message. + linenr_T i = msg_scroll; + if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) { + msg_scroll = true; + } else if (!msg_scrolled && shortmess(SHM_OVERALL)) { + msg_scroll = false; + } + msg_attr_keep(IObuff, 0, true, false); + msg_scroll = (int)i; +} + +/// jump to a quickfix line +/// if dir == FORWARD go "errornr" valid entries forward +/// if dir == BACKWARD go "errornr" valid entries backward +/// if dir == FORWARD_FILE go "errornr" valid entries files backward +/// if dir == BACKWARD_FILE go "errornr" valid entries files backward +/// else if "errornr" is zero, redisplay the same line +/// else go to entry "errornr" +void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) +{ + qfline_T *qf_ptr; + qfline_T *old_qf_ptr; + int qf_index; + int old_qf_index; + buf_T *old_curbuf; + linenr_T old_lnum; + char_u *old_swb = p_swb; + unsigned old_swb_flags = swb_flags; + int opened_window = false; + win_T *oldwin = curwin; + int print_message = true; + const bool old_KeyTyped = KeyTyped; // getting file may reset it + int retval = OK; + + if (qi == NULL) + qi = &ql_info; + + if (qi->qf_curlist >= qi->qf_listcount + || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + EMSG(_(e_quickfix)); + return; + } + + qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; + old_qf_ptr = qf_ptr; + qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + old_qf_index = qf_index; + if (dir != 0) { // next/prev valid entry + qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir); + if (qf_ptr == NULL) { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + goto theend; // The horror... the horror... + } + } else if (errornr != 0) { // go to specified number + qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index); + } + + qi->qf_lists[qi->qf_curlist].qf_index = qf_index; + if (qf_win_pos_update(qi, old_qf_index)) + /* No need to print the error message if it's visible in the error + * window */ + print_message = FALSE; + + // For ":helpgrep" find a help window or open one. + if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { + if (jump_to_help_window(qi, &opened_window) == FAIL) { + goto theend; + } + } + + // If currently in the quickfix window, find another window to show the + // file in. + if (bt_quickfix(curbuf) && !opened_window) { + // If there is no file specified, we don't know where to go. + // But do advance, otherwise ":cn" gets stuck. + if (qf_ptr->qf_fnum == 0) { + goto theend; + } + if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) { + goto failed; } } @@ -2044,122 +2411,28 @@ win_found: old_lnum = curwin->w_cursor.lnum; if (qf_ptr->qf_fnum != 0) { - if (qf_ptr->qf_type == 1) { - /* Open help file (do_ecmd() will set b_help flag, readfile() will - * set b_p_ro flag). */ - if (!can_abandon(curbuf, forceit)) { - EMSG(_(e_nowrtmsg)); - ok = false; - } else { - ok = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, - ECMD_HIDE + ECMD_SET_HELP, - oldwin == curwin ? curwin : NULL); - } - } else { - int old_qf_curlist = qi->qf_curlist; - bool is_abort = false; - - ok = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, - GETF_SETMARK | GETF_SWITCH, forceit); - if (qi != &ql_info && !win_valid_any_tab(oldwin)) { - EMSG(_("E924: Current window was closed")); - is_abort = true; - opened_window = false; - } else if (old_qf_curlist != qi->qf_curlist // -V560 - || !is_qf_entry_present(qi, qf_ptr)) { - if (qi == &ql_info) { - EMSG(_("E925: Current quickfix was changed")); - } else { - EMSG(_("E926: Current location list was changed")); - } - is_abort = true; - } - - if (is_abort) { - ok = false; - qi = NULL; - qf_ptr = NULL; - } + int abort = false; + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window, + &abort); + if (abort) { + qi = NULL; + qf_ptr = NULL; } } - if (ok == OK) { - /* When not switched to another buffer, still need to set pc mark */ - if (curbuf == old_curbuf) + if (retval == OK) { + // When not switched to another buffer, still need to set pc mark + if (curbuf == old_curbuf) { setpcmark(); - - if (qf_ptr->qf_pattern == NULL) { - /* - * Go to line with error, unless qf_lnum is 0. - */ - i = qf_ptr->qf_lnum; - if (i > 0) { - if (i > curbuf->b_ml.ml_line_count) - i = curbuf->b_ml.ml_line_count; - curwin->w_cursor.lnum = i; - } - if (qf_ptr->qf_col > 0) { - curwin->w_cursor.col = qf_ptr->qf_col - 1; - curwin->w_cursor.coladd = 0; - if (qf_ptr->qf_viscol == true) { - // Check each character from the beginning of the error - // line up to the error column. For each tab character - // found, reduce the error column value by the length of - // a tab character. - line = get_cursor_line_ptr(); - screen_col = 0; - for (char_col = 0; char_col < curwin->w_cursor.col; ++char_col) { - if (*line == NUL) - break; - if (*line++ == '\t') { - curwin->w_cursor.col -= 7 - (screen_col % 8); - screen_col += 8 - (screen_col % 8); - } else - ++screen_col; - } - } - check_cursor(); - } else - beginline(BL_WHITE | BL_FIX); - } else { - pos_T save_cursor; - - /* Move the cursor to the first line in the buffer */ - save_cursor = curwin->w_cursor; - curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', qf_ptr->qf_pattern, (long)1, - SEARCH_KEEP, NULL, NULL)) { - curwin->w_cursor = save_cursor; - } } + qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, + qf_ptr->qf_pattern); + if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped) foldOpenCursor(); if (print_message) { - /* Update the screen before showing the message, unless the screen - * scrolled up. */ - if (!msg_scrolled) - update_topline_redraw(); - sprintf((char *)IObuff, _("(%d of %d)%s%s: "), qf_index, - qi->qf_lists[qi->qf_curlist].qf_count, - qf_ptr->qf_cleared ? _(" (line deleted)") : "", - (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); - /* Add the message, skipping leading whitespace and newlines. */ - len = (int)STRLEN(IObuff); - qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); - - /* Output the message. Overwrite to avoid scrolling when the 'O' - * flag is present in 'shortmess'; But when not jumping, print the - * whole message. */ - i = msg_scroll; - if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) { - msg_scroll = true; - } else if (!msg_scrolled && shortmess(SHM_OVERALL)) { - msg_scroll = false; - } - msg_ext_set_kind("quickfix"); - msg_attr_keep(IObuff, 0, true, false); - msg_scroll = (int)i; + qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); } } else { if (opened_window) { @@ -2257,17 +2530,22 @@ void qf_list(exarg_T *eap) break; fname = NULL; - 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 (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); + } } - if (fname == NULL) - sprintf((char *)IObuff, "%2d", i); - else - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", - i, (char *)fname); msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index ? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D)); if (qfp->qf_lnum == 0) { @@ -2328,6 +2606,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; @@ -2397,6 +2677,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; @@ -2428,9 +2709,10 @@ static void qf_free_items(qf_info_T *qi, int idx) qfp = qfl->qf_start; qfpnext = qfp->qf_next; if (!stop) { + xfree(qfp->qf_module); xfree(qfp->qf_text); - stop = (qfp == qfpnext); xfree(qfp->qf_pattern); + stop = (qfp == qfpnext); xfree(qfp); if (stop) { // Somehow qf_count may have an incorrect value, set it to 1 @@ -2472,6 +2754,7 @@ static void qf_free(qf_info_T *qi, int idx) tv_free(qfl->qf_ctx); qfl->qf_ctx = NULL; qfl->qf_id = 0; + qfl->qf_changedtick = 0L; } /* @@ -2841,10 +3124,8 @@ static int is_qf_win(win_T *win, qf_info_T *qi) return FALSE; } -/* - * Find a window displaying the quickfix/location list 'qi' - * Searches in only the windows opened in the current tab. - */ +/// Find a window displaying the quickfix/location list 'qi' +/// Only searches in the current tabpage. static win_T *qf_find_win(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { @@ -2972,9 +3253,12 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) lnum = buf->b_ml.ml_line_count; } while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { - if (qfp->qf_fnum != 0 - && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL - && errbuf->b_fname != NULL) { + if (qfp->qf_module != NULL) { + STRCPY(IObuff, qfp->qf_module); + len = (int)STRLEN(IObuff); + } else if (qfp->qf_fnum != 0 + && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL + && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) { // :helpgrep STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); } else { @@ -3062,51 +3346,9 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) KeyTyped = old_KeyTyped; } -/* - * Return TRUE if "buf" is the quickfix buffer. - */ -int bt_quickfix(const buf_T *const buf) -{ - return buf != NULL && buf->b_p_bt[0] == 'q'; -} - -// Return TRUE if "buf" is a "nofile", "acwrite" or "terminal" buffer. -// This means the buffer name is not a file name. -int bt_nofile(buf_T *buf) -{ - return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') - || buf->b_p_bt[0] == 'a' || buf->terminal); -} - -// Return TRUE if "buf" is a "nowrite", "nofile" or "terminal" buffer. -int bt_dontwrite(buf_T *buf) -{ - return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal); -} - -int bt_dontwrite_msg(buf_T *buf) -{ - if (bt_dontwrite(buf)) { - EMSG(_("E382: Cannot write, 'buftype' option is set")); - return TRUE; - } - return FALSE; -} - -/* - * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide" - * and 'bufhidden'. - */ -int buf_hide(buf_T *buf) +static void qf_list_changed(qf_info_T *qi, int qf_idx) { - /* 'bufhidden' overrules 'hidden' and ":hide", check it first */ - switch (buf->b_p_bh[0]) { - case 'u': /* "unload" */ - case 'w': /* "wipe" */ - case 'd': return FALSE; /* "delete" */ - case 'h': return TRUE; /* "hide" */ - } - return p_hid || cmdmod.hide; + qi->qf_lists[qf_idx].qf_changedtick++; } /* @@ -3192,17 +3434,21 @@ void ex_make(exarg_T *eap) res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), - *eap->cmdlinep, enc); + qf_cmdtitle(*eap->cmdlinep), enc); if (wp != NULL) { qi = GET_LOC_LIST(wp); } + if (res >= 0 && qi != NULL) { + qf_list_changed(qi, qi->qf_curlist); + } if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - if (qi->qf_curlist < qi->qf_listcount) + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, + curbuf); + if (qi != NULL && qi->qf_curlist < qi->qf_listcount) { res = qi->qf_lists[qi->qf_curlist].qf_count; - else + } else { res = 0; + } } if (res > 0 && !eap->forceit) qf_jump(qi, 0, 0, FALSE); /* display first error */ @@ -3546,19 +3792,218 @@ void ex_cfile(exarg_T *eap) // first error. // :caddfile adds to an existing quickfix list. If there is no // quickfix list then a new list is created. - if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile - && eap->cmdidx != CMD_laddfile), - *eap->cmdlinep, enc) > 0 - && (eap->cmdidx == CMD_cfile - || eap->cmdidx == CMD_lfile)) { - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf); - if (wp != NULL) - qi = GET_LOC_LIST(wp); - qf_jump(qi, 0, 0, eap->forceit); /* display first error */ + int res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile + && eap->cmdidx != CMD_laddfile), + qf_cmdtitle(*eap->cmdlinep), enc); + if (wp != NULL) { + qi = GET_LOC_LIST(wp); + } + if (res >= 0 && qi != NULL) { + qf_list_changed(qi, qi->qf_curlist); + } + unsigned save_qfid = 0; + if (qi != NULL) { + save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + } + if (au_name != NULL) { + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf); + } + // Autocmd might have freed the quickfix/location list. Check whether it is + // still valid + if (qi != NULL && !qflist_valid(wp, save_qfid)) { + return; + } + if (res > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { + qf_jump(qi, 0, 0, eap->forceit); // display first error + } +} + +/// Return the vimgrep autocmd name. +static char_u *vgr_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_vimgrep: return (char_u *)"vimgrep"; + case CMD_lvimgrep: return (char_u *)"lvimgrep"; + case CMD_vimgrepadd: return (char_u *)"vimgrepadd"; + case CMD_lvimgrepadd: return (char_u *)"lvimgrepadd"; + case CMD_grep: return (char_u *)"grep"; + case CMD_lgrep: return (char_u *)"lgrep"; + case CMD_grepadd: return (char_u *)"grepadd"; + case CMD_lgrepadd: return (char_u *)"lgrepadd"; + default: return NULL; + } +} + +/// Initialize the regmatch used by vimgrep for pattern "s". +static void vgr_init_regmatch(regmmatch_T *regmatch, char_u *s) +{ + // Get the search pattern: either white-separated or enclosed in //. + regmatch->regprog = NULL; + + if (s == NULL || *s == NUL) { + // Pattern is empty, use last search pattern. + if (last_search_pat() == NULL) { + EMSG(_(e_noprevre)); + return; + } + regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else { - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf); + regmatch->regprog = vim_regcomp(s, RE_MAGIC); + } + + regmatch->rmm_ic = p_ic; + regmatch->rmm_maxcol = 0; +} + + +/// Display a file name when vimgrep is running. +static void vgr_display_fname(char_u *fname) +{ + msg_start(); + char_u *p = msg_strtrunc(fname, true); + if (p == NULL) { + msg_outtrans(fname); + } else { + msg_outtrans(p); + xfree(p); + } + msg_clr_eos(); + msg_didout = false; // overwrite this message + msg_nowait = true; // don't wait for this message + msg_col = 0; + ui_flush(); +} + +/// Load a dummy buffer to search for a pattern using vimgrep. +static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start, + char_u *dirname_now) +{ + char_u *save_ei = NULL; + + // Don't do Filetype autocommands to avoid loading syntax and + // indent scripts, a great speed improvement. + long save_mls = p_mls; + p_mls = 0; + + // Load file into a buffer, so that 'fileencoding' is detected, + // autocommands applied, etc. + buf_T *buf = load_dummy_buffer(fname, dirname_start, dirname_now); + + p_mls = save_mls; + au_event_restore(save_ei); + + return buf; +} + +/// Check whether a quickfix/location list is valid. Autocmds may remove or +/// change a quickfix list when vimgrep is running. If the list is not found, +/// create a new list. +static bool vgr_qflist_valid(qf_info_T *qi, unsigned save_qfid, + qfline_T *cur_qf_start, int loclist_cmd, + char_u *title) +{ + if (loclist_cmd) { + // Verify that the location list is still valid. An autocmd might have + // freed the location list. + if (!qflist_valid(curwin, save_qfid)) { + EMSG(_(e_loc_list_changed)); + return false; + } + } + if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) { + int idx; + // Autocommands changed the quickfix list. Find the one we were using + // and restore it. + for (idx = 0; idx < LISTCOUNT; idx++) { + if (cur_qf_start == qi->qf_lists[idx].qf_start) { + qi->qf_curlist = idx; + break; + } + } + if (idx == LISTCOUNT) { + // List cannot be found, create a new one. + qf_new_list(qi, title); + } + } + + return true; +} + + +/// Search for a pattern in all the lines in a buffer and add the matching lines +/// to a quickfix list. +static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, + regmmatch_T *regmatch, long tomatch, + int duplicate_name, int flags) +{ + bool found_match = false; + + for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) { + colnr_T col = 0; + while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, + NULL) > 0) { + // Pass the buffer number so that it gets used even for a + // dummy buffer, unless duplicate_name is set, then the + // buffer will be wiped out below. + if (qf_add_entry(qi, + qi->qf_curlist, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, + false), + regmatch->startpos[0].lnum + lnum, + regmatch->startpos[0].col + 1, + false, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + true // valid + ) == FAIL) { + got_int = true; + break; + } + found_match = true; + if (--tomatch == 0) { + break; + } + if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { + break; + } + col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); + if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + break; + } + } + line_breakcheck(); + if (got_int) { + break; + } + } + + return found_match; +} + +/// Jump to the first match and update the directory. +static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, + buf_T *first_match_buf, char_u *target_dir) +{ + buf_T *buf = curbuf; + qf_jump(qi, 0, 0, forceit); + if (buf != curbuf) { + // If we jumped to another buffer redrawing will already be + // taken care of. + *redraw_for_dummy = false; + } + + // Jump to the directory used after loading the buffer. + if (curbuf == first_match_buf && target_dir != NULL) { + exarg_T ea = { + .arg = target_dir, + .cmdidx = CMD_lcd, + }; + ex_cd(&ea); } } @@ -3578,8 +4023,9 @@ void ex_vimgrep(exarg_T *eap) char_u *p; int fi; qf_info_T *qi = &ql_info; + int loclist_cmd = false; qfline_T *cur_qf_start; - long lnum; + win_T *wp; buf_T *buf; int duplicate_name = FALSE; int using_dummy; @@ -3587,28 +4033,15 @@ void ex_vimgrep(exarg_T *eap) int found_match; buf_T *first_match_buf = NULL; time_t seconds = 0; - long save_mls; - char_u *save_ei = NULL; aco_save_T aco; int flags = 0; - colnr_T col; long tomatch; char_u *dirname_start = NULL; char_u *dirname_now = NULL; char_u *target_dir = NULL; char_u *au_name = NULL; - switch (eap->cmdidx) { - case CMD_vimgrep: au_name = (char_u *)"vimgrep"; break; - case CMD_lvimgrep: au_name = (char_u *)"lvimgrep"; break; - case CMD_vimgrepadd: au_name = (char_u *)"vimgrepadd"; break; - case CMD_lvimgrepadd: au_name = (char_u *)"lvimgrepadd"; break; - case CMD_grep: au_name = (char_u *)"grep"; break; - case CMD_lgrep: au_name = (char_u *)"lgrep"; break; - case CMD_grepadd: au_name = (char_u *)"grepadd"; break; - case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break; - default: break; - } + au_name = vgr_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { @@ -3621,6 +4054,7 @@ void ex_vimgrep(exarg_T *eap) || eap->cmdidx == CMD_lgrepadd || eap->cmdidx == CMD_lvimgrepadd) { qi = ll_get_or_alloc_list(curwin); + loclist_cmd = true; } if (eap->addr_count > 0) @@ -3630,28 +4064,17 @@ void ex_vimgrep(exarg_T *eap) /* Get the search pattern: either white-separated or enclosed in // */ regmatch.regprog = NULL; - char_u *title = vim_strsave(*eap->cmdlinep); + char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); p = skip_vimgrep_pat(eap->arg, &s, &flags); if (p == NULL) { EMSG(_(e_invalpat)); goto theend; } - if (s == NULL || *s == NUL) { - // Pattern is empty, use last search pattern. - if (last_search_pat() == NULL) { - EMSG(_(e_noprevre)); - goto theend; - } - regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); - } else { - regmatch.regprog = vim_regcomp(s, RE_MAGIC); - } - - if (regmatch.regprog == NULL) + vgr_init_regmatch(®match, s); + if (regmatch.regprog == NULL) { goto theend; - regmatch.rmm_ic = p_ic; - regmatch.rmm_maxcol = 0; + } p = skipwhite(p); if (*p == NUL) { @@ -3681,8 +4104,9 @@ void ex_vimgrep(exarg_T *eap) * ":lcd %:p:h" changes the meaning of short path names. */ os_dirname(dirname_start, MAXPATHL); - /* Remember the value of qf_start, so that we can check for autocommands - * changing the current quickfix list. */ + // Remember the current values of the quickfix list and qf_start, so that + // we can check for autocommands changing the current quickfix list. + unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; seconds = (time_t)0; @@ -3692,19 +4116,7 @@ void ex_vimgrep(exarg_T *eap) /* Display the file name every second or so, show the user we are * working on it. */ seconds = time(NULL); - msg_start(); - p = msg_strtrunc(fname, TRUE); - if (p == NULL) - msg_outtrans(fname); - else { - msg_outtrans(p); - xfree(p); - } - msg_clr_eos(); - msg_didout = FALSE; /* overwrite this message */ - msg_nowait = TRUE; /* don't wait for this message */ - msg_col = 0; - ui_flush(); + vgr_display_fname(fname); } buf = buflist_findname_exp(fnames[fi]); @@ -3714,88 +4126,28 @@ void ex_vimgrep(exarg_T *eap) using_dummy = TRUE; redraw_for_dummy = TRUE; - /* Don't do Filetype autocommands to avoid loading syntax and - * indent scripts, a great speed improvement. */ - save_ei = au_event_disable(",Filetype"); - /* Don't use modelines here, it's useless. */ - save_mls = p_mls; - p_mls = 0; - - /* Load file into a buffer, so that 'fileencoding' is detected, - * autocommands applied, etc. */ - buf = load_dummy_buffer(fname, dirname_start, dirname_now); - - p_mls = save_mls; - au_event_restore(save_ei); - } else - /* Use existing, loaded buffer. */ - using_dummy = FALSE; - - if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) { - int idx; + buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now); + } else { + // Use existing, loaded buffer. + using_dummy = false; + } - /* Autocommands changed the quickfix list. Find the one we were - * using and restore it. */ - for (idx = 0; idx < LISTCOUNT; ++idx) - if (cur_qf_start == qi->qf_lists[idx].qf_start) { - qi->qf_curlist = idx; - break; - } - if (idx == LISTCOUNT) { - /* List cannot be found, create a new one. */ - qf_new_list(qi, *eap->cmdlinep); - cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; - } + // Check whether the quickfix list is still valid + if (!vgr_qflist_valid(qi, save_qfid, cur_qf_start, loclist_cmd, + *eap->cmdlinep)) { + goto theend; } + cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; if (buf == NULL) { if (!got_int) smsg(_("Cannot open file \"%s\""), fname); } else { - /* Try for a match in all lines of the buffer. - * For ":1vimgrep" look for first match only. */ - found_match = FALSE; - for (lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; - ++lnum) { - col = 0; - while (vim_regexec_multi(®match, curwin, buf, lnum, - col, NULL, NULL) > 0) { - // Pass the buffer number so that it gets used even for a - // dummy buffer, unless duplicate_name is set, then the - // buffer will be wiped out below. - if (qf_add_entry(qi, - qi->qf_curlist, - NULL, // dir - fname, - duplicate_name ? 0 : buf->b_fnum, - ml_get_buf(buf, - regmatch.startpos[0].lnum + lnum, false), - regmatch.startpos[0].lnum + lnum, - regmatch.startpos[0].col + 1, - false, // vis_col - NULL, // search pattern - 0, // nr - 0, // type - true) // valid - == FAIL) { - got_int = true; - break; - } - found_match = TRUE; - if (--tomatch == 0) - break; - if ((flags & VGR_GLOBAL) == 0 - || regmatch.endpos[0].lnum > 0) - break; - col = regmatch.endpos[0].col - + (col == regmatch.endpos[0].col); - if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, FALSE))) - break; - } - line_breakcheck(); - if (got_int) - break; - } + // Try for a match in all lines of the buffer. + // For ":1vimgrep" look for first match only. + found_match = vgr_match_buflines(qi, fname, buf, ®match, tomatch, + duplicate_name, flags); + cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; if (using_dummy) { @@ -3858,6 +4210,7 @@ void ex_vimgrep(exarg_T *eap) qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; qi->qf_lists[qi->qf_curlist].qf_index = 1; + qf_list_changed(qi, qi->qf_curlist); qf_update_buffer(qi, NULL); @@ -3865,24 +4218,18 @@ void ex_vimgrep(exarg_T *eap) apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); + // The QuickFixCmdPost autocmd may free the quickfix list. Check the list + // is still valid. + wp = loclist_cmd ? curwin : NULL; + if (!qflist_valid(wp, save_qfid)) { + goto theend; + } + /* Jump to first match. */ if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { if ((flags & VGR_NOJUMP) == 0) { - buf = curbuf; - qf_jump(qi, 0, 0, eap->forceit); - if (buf != curbuf) - /* If we jumped to another buffer redrawing will already be - * taken care of. */ - redraw_for_dummy = FALSE; - - /* Jump to the directory used after loading the buffer. */ - if (curbuf == first_match_buf && target_dir != NULL) { - exarg_T ea; - - ea.arg = target_dir; - ea.cmdidx = CMD_lcd; - ex_cd(&ea); - } + vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, + target_dir); } } else EMSG2(_(e_nomatch2), s); @@ -3913,10 +4260,10 @@ static void restore_start_dir(char_u *dirname_start) if (STRCMP(dirname_start, dirname_now) != 0) { /* If the directory has changed, change it back by building up an * appropriate ex command and executing it. */ - exarg_T ea; - - ea.arg = dirname_start; - ea.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd; + exarg_T ea = { + .arg = dirname_start, + .cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd, + }; ex_cd(&ea); } xfree(dirname_now); @@ -4112,6 +4459,10 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol) == FAIL) || (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL) + || tv_dict_add_str(dict, S_LEN("module"), + (qfp->qf_module == NULL + ? "" + : (const char *)qfp->qf_module)) == FAIL || tv_dict_add_str(dict, S_LEN("pattern"), (qfp->qf_pattern == NULL ? "" @@ -4145,10 +4496,14 @@ enum { QF_GETLIST_WINID = 0x8, QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ID = 0x20, - QF_GETLIST_ALL = 0xFF + QF_GETLIST_IDX = 0x40, + QF_GETLIST_SIZE = 0x80, + QF_GETLIST_TICK = 0x100, + QF_GETLIST_ALL = 0x1FF }; -// Parse text from 'di' and return the quickfix list items +/// Parse text from 'di' and return the quickfix list items. +/// Existing quickfix lists are not modified. static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) { int status = FAIL; @@ -4186,35 +4541,85 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) return status; } -/// Return quickfix/location list details (title) as a -/// dictionary. 'what' contains the details to return. If 'list_idx' is -1, -/// then current list is used. Otherwise the specified list is used. -int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) +// Return the quickfix/location list number with the given identifier. +// Returns -1 if list is not found. +static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) { - qf_info_T *qi = &ql_info; - dictitem_T *di; - - if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { - return qf_get_list_from_lines(what, di, retdict); + for (int qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { + if (qi->qf_lists[qf_idx].qf_id == qfid) { + return qf_idx; + } } + return -1; +} - if (wp != NULL) { - qi = GET_LOC_LIST(wp); +/// Return the quickfix/location list window identifier in the current tabpage. +static int qf_winid(qf_info_T *qi) +{ + // The quickfix window can be opened even if the quickfix list is not set + // using ":copen". This is not true for location lists. + if (qi == NULL) { + return 0; } - // List is not present or is empty - if (qi == NULL || qi->qf_listcount == 0) { - // If querying for the size of the list, return 0 - if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) - && (di->di_tv.v_type == VAR_STRING) - && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) { - return tv_dict_add_nr(retdict, S_LEN("nr"), 0); - } - return FAIL; + win_T *win = qf_find_win(qi); + if (win != NULL) { + return win->handle; } + return 0; +} - int status = OK; +/// Convert the keys in 'what' to quickfix list property flags. +static int qf_getprop_keys2flags(dict_T *what) +{ int flags = QF_GETLIST_NONE; + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } + if (tv_dict_find(what, S_LEN("nr")) != NULL) { + flags |= QF_GETLIST_NR; + } + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + if (tv_dict_find(what, S_LEN("id")) != NULL) { + flags |= QF_GETLIST_ID; + } + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; + } + if (tv_dict_find(what, S_LEN("idx")) != NULL) { + flags |= QF_GETLIST_IDX; + } + if (tv_dict_find(what, S_LEN("size")) != NULL) { + flags |= QF_GETLIST_SIZE; + } + if (tv_dict_find(what, S_LEN("changedtick")) != NULL) { + flags |= QF_GETLIST_TICK; + } + + return flags; +} + +/// Return the quickfix list index based on 'nr' or 'id' in 'what'. +/// +/// If 'nr' and 'id' are not present in 'what' then return the current +/// quickfix list index. +/// If 'nr' is zero then return the current quickfix list index. +/// If 'nr' is '$' then return the last quickfix list index. +/// If 'id' is present then return the index of the quickfix list with that id. +/// If 'id' is zero then return the quickfix list index specified by 'nr'. +/// Return -1, if quickfix list is not present or if the stack is empty. +static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what) +{ + dictitem_T *di = NULL; + int qf_idx = qi->qf_curlist; // default is the current list if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list @@ -4223,7 +4628,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (di->di_tv.vval.v_number != 0) { qf_idx = (int)di->di_tv.vval.v_number - 1; if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { - return FAIL; + qf_idx = -1; } } } else if (di->di_tv.v_type == VAR_STRING @@ -4231,9 +4636,8 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) // Get the last quickfix list number qf_idx = qi->qf_listcount - 1; } else { - return FAIL; + qf_idx = -1; } - flags |= QF_GETLIST_NR; } if ((di = tv_dict_find(what, S_LEN("id"))) != NULL) { @@ -4241,75 +4645,161 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (di->di_tv.v_type == VAR_NUMBER) { // For zero, use the current list or the list specifed by 'nr' if (di->di_tv.vval.v_number != 0) { - for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { - if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { - break; - } - } - if (qf_idx == qi->qf_listcount) { - return FAIL; // List not found - } + qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number); } - flags |= QF_GETLIST_ID; } else { - return FAIL; + qf_idx = -1; } } - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; + return qf_idx; +} + +/// Return default values for quickfix list properties in retdict. +static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) +{ + int status = OK; + + if (flags & QF_GETLIST_TITLE) { + status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)""); } - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { + list_T *l = tv_list_alloc(kListLenMayKnow); + status = tv_dict_add_list(retdict, S_LEN("items"), l); } - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; + if ((status == OK) && (flags & QF_GETLIST_NR)) { + status = tv_dict_add_nr(retdict, S_LEN("nr"), 0); } - if (tv_dict_find(what, S_LEN("context")) != NULL) { - flags |= QF_GETLIST_CONTEXT; + if ((status == OK) && (flags & QF_GETLIST_WINID)) { + status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi)); } - if (tv_dict_find(what, S_LEN("items")) != NULL) { - flags |= QF_GETLIST_ITEMS; + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { + status = tv_dict_add_str(retdict, S_LEN("context"), (const char *)""); + } + if ((status == OK) && (flags & QF_GETLIST_ID)) { + status = tv_dict_add_nr(retdict, S_LEN("id"), 0); + } + if ((status == OK) && (flags & QF_GETLIST_IDX)) { + status = tv_dict_add_nr(retdict, S_LEN("idx"), 0); + } + if ((status == OK) && (flags & QF_GETLIST_SIZE)) { + status = tv_dict_add_nr(retdict, S_LEN("size"), 0); + } + if ((status == OK) && (flags & QF_GETLIST_TICK)) { + status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0); } - if (flags & QF_GETLIST_TITLE) { + return status; +} + +/// Return the quickfix list title as 'title' in retdict +static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) +{ char_u *t = qi->qf_lists[qf_idx].qf_title; if (t == NULL) { t = (char_u *)""; } - status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)t); + return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t); +} + +/// Return the quickfix list items/entries as 'items' in retdict +static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict) +{ + list_T *l = tv_list_alloc(kListLenMayKnow); + get_errorlist(qi, NULL, qf_idx, l); + tv_dict_add_list(retdict, S_LEN("items"), l); + + return OK; +} + +/// Return the quickfix list context (if any) as 'context' in retdict. +static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) +{ + int status; + + if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context")); + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + status = tv_dict_add(retdict, di); + if (status == FAIL) { + tv_dict_item_free(di); + } + } else { + status = tv_dict_add_str(retdict, S_LEN("context"), ""); + } + + return status; +} + +/// Return the quickfix list index as 'idx' in 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) { + // For empty lists, qf_index is set to 1 + idx = 0; + } + return tv_dict_add_nr(retdict, S_LEN("idx"), idx); +} + +/// Return quickfix/location list details (title) as a dictionary. +/// 'what' contains the details to return. If 'list_idx' is -1, +/// then current list is used. Otherwise the specified list is used. +int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) +{ + qf_info_T *qi = &ql_info; + dictitem_T *di = NULL; + int status = OK; + int qf_idx; + + if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { + return qf_get_list_from_lines(what, di, retdict); + } + + if (wp != NULL) { + qi = GET_LOC_LIST(wp); + } + + int flags = qf_getprop_keys2flags(what); + + if (qi != NULL && qi->qf_listcount != 0) { + qf_idx = qf_getprop_qfidx(qi, what); + } + + // List is not present or is empty + if (qi == NULL || qi->qf_listcount == 0 || qf_idx == -1) { + return qf_getprop_defaults(qi, flags, retdict); + } + + if (flags & QF_GETLIST_TITLE) { + status = qf_getprop_title(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_NR)) { status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1); } if ((status == OK) && (flags & QF_GETLIST_WINID)) { - win_T *win = qf_find_win(qi); - if (win != NULL) { - status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle); - } + status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi)); } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { - list_T *l = tv_list_alloc(kListLenMayKnow); - (void)get_errorlist(qi, NULL, qf_idx, l); - tv_dict_add_list(retdict, S_LEN("items"), l); + status = qf_getprop_items(qi, qf_idx, retdict); } - if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { - if (qi->qf_lists[qf_idx].qf_ctx != NULL) { - di = tv_dict_item_alloc_len(S_LEN("context")); - tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); - status = tv_dict_add(retdict, di); - if (status == FAIL) { - tv_dict_item_free(di); - } - } else { - status = tv_dict_add_str(retdict, S_LEN("context"), ""); - } + status = qf_getprop_ctx(qi, qf_idx, retdict); } - if ((status == OK) && (flags & QF_GETLIST_ID)) { status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id); } + if ((status == OK) && (flags & QF_GETLIST_IDX)) { + status = qf_getprop_idx(qi, qf_idx, retdict); + } + if ((status == OK) && (flags & QF_GETLIST_SIZE)) { + status = tv_dict_add_nr(retdict, S_LEN("size"), + qi->qf_lists[qf_idx].qf_count); + } + if ((status == OK) && (flags & QF_GETLIST_TICK)) { + status = tv_dict_add_nr(retdict, S_LEN("changedtick"), + qi->qf_lists[qf_idx].qf_changedtick); + } return status; } @@ -4347,6 +4837,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } char *const filename = tv_dict_get_string(d, "filename", true); + char *const module = tv_dict_get_string(d, "module", true); int bufnum = (int)tv_dict_get_number(d, "bufnr"); long lnum = (long)tv_dict_get_number(d, "lnum"); int col = (int)tv_dict_get_number(d, "col"); @@ -4384,6 +4875,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, qf_idx, NULL, // dir (char_u *)filename, + (char_u *)module, bufnum, (char_u *)text, lnum, @@ -4395,6 +4887,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, valid); xfree(filename); + xfree(module); xfree(pattern); xfree(text); @@ -4471,12 +4964,8 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, if (!newlist && (di = tv_dict_find(what, S_LEN("id"))) != NULL) { // Use the quickfix/location list with the specified id if (di->di_tv.v_type == VAR_NUMBER) { - for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { - if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { - break; - } - } - if (qf_idx == qi->qf_listcount) { + qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number); + if (qf_idx == -1) { return FAIL; // List not found } } else { @@ -4508,6 +4997,13 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, title_save, action == ' ' ? 'a' : action); + if (action == 'r') { + // When replacing the quickfix list entries using + // qf_add_entries(), the title is set with a ':' prefix. + // Restore the title with the saved title. + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = vim_strsave(title_save); + } xfree(title_save); } } @@ -4543,6 +5039,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, retval = OK; } + if (retval == OK) { + qf_list_changed(qi, qf_idx); + } + return retval; } @@ -4624,11 +5124,15 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, retval = qf_set_properties(qi, what, action, title); } else { retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); + if (retval == OK) { + qf_list_changed(qi, qi->qf_curlist); + } } 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; @@ -4645,7 +5149,7 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) } /// Mark the context of the quickfix list and the location lists (if present) as -/// "in use". So that garabage collection doesn't free the context. +/// "in use". So that garbage collection doesn't free the context. bool set_ref_in_quickfix(int copyID) { bool abort = mark_quickfix_ctx(&ql_info, copyID); @@ -4730,10 +5234,10 @@ void ex_cbuffer(exarg_T *eap) eap->line2 = buf->b_ml.ml_line_count; } if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count - || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) + || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) { EMSG(_(e_invrange)); - else { - char_u *qf_title = *eap->cmdlinep; + } else { + char_u *qf_title = qf_cmdtitle(*eap->cmdlinep); if (buf->b_sfname) { vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", @@ -4741,17 +5245,20 @@ void ex_cbuffer(exarg_T *eap) qf_title = IObuff; } - if (qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, - (eap->cmdidx != CMD_caddbuffer - && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title, NULL) > 0) { - if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, - curbuf->b_fname, true, curbuf); - } - if (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) { - qf_jump(qi, 0, 0, eap->forceit); // display first error - } + int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, + (eap->cmdidx != CMD_caddbuffer + && eap->cmdidx != CMD_laddbuffer), + eap->line1, eap->line2, qf_title, NULL); + if (res >= 0) { + qf_list_changed(qi, qi->qf_curlist); + } + if (au_name != NULL) { + apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + curbuf->b_fname, true, curbuf); + } + if (res > 0 && (eap->cmdidx == CMD_cbuffer + || eap->cmdidx == CMD_lbuffer)) { + qf_jump(qi, 0, 0, eap->forceit); // display first error } } } @@ -4766,11 +5273,6 @@ void ex_cexpr(exarg_T *eap) qf_info_T *qi = &ql_info; const char *au_name = NULL; - if (eap->cmdidx == CMD_lexpr || eap->cmdidx == CMD_lgetexpr - || eap->cmdidx == CMD_laddexpr) { - qi = ll_get_or_alloc_list(curwin); - } - switch (eap->cmdidx) { case CMD_cexpr: au_name = "cexpr"; @@ -4800,23 +5302,32 @@ void ex_cexpr(exarg_T *eap) } } + if (eap->cmdidx == CMD_lexpr + || eap->cmdidx == CMD_lgetexpr + || eap->cmdidx == CMD_laddexpr) { + qi = ll_get_or_alloc_list(curwin); + } + /* Evaluate the expression. When the result is a string or a list we can * use it to fill the errorlist. */ typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) || tv.v_type == VAR_LIST) { - if (qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, - (eap->cmdidx != CMD_caddexpr - && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) { - if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, - curbuf->b_fname, true, curbuf); - } - if (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) { - qf_jump(qi, 0, 0, eap->forceit); // display first error - } + int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, + (eap->cmdidx != CMD_caddexpr + && eap->cmdidx != CMD_laddexpr), + (linenr_T)0, (linenr_T)0, + qf_cmdtitle(*eap->cmdlinep), NULL); + if (res >= 0) { + qf_list_changed(qi, qi->qf_curlist); + } + if (au_name != NULL) { + apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + curbuf->b_fname, true, curbuf); + } + if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) { + qf_jump(qi, 0, 0, eap->forceit); // display first error } } else { EMSG(_("E777: String or List expected")); @@ -4890,16 +5401,13 @@ void ex_helpgrep(exarg_T *eap) } } - // Autocommands may change the list. Save it for later comparison - qf_info_T *save_qi = qi; - regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { // Create a new quickfix list. - qf_new_list(qi, *eap->cmdlinep); + qf_new_list(qi, qf_cmdtitle(*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, ","); @@ -4914,15 +5422,15 @@ void ex_helpgrep(exarg_T *eap) if (gen_expand_wildcards(1, buff_list, &fcount, &fnames, EW_FILE|EW_SILENT) == OK && fcount > 0) { - for (fi = 0; fi < fcount && !got_int; ++fi) { - /* Skip files for a different language. */ + for (fi = 0; fi < fcount && !got_int; fi++) { + // Skip files for a different language. if (lang != NULL - && STRNICMP(lang, fnames[fi] - + STRLEN(fnames[fi]) - 3, 2) != 0 + && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0 && !(STRNICMP(lang, "en", 2) == 0 && STRNICMP("txt", fnames[fi] - + STRLEN(fnames[fi]) - 3, 3) == 0)) + + STRLEN(fnames[fi]) - 3, 3) == 0)) { continue; + } fd = mch_fopen((char *)fnames[fi], "r"); if (fd != NULL) { lnum = 1; @@ -4939,6 +5447,7 @@ void ex_helpgrep(exarg_T *eap) qi->qf_curlist, NULL, // dir fnames[fi], + NULL, 0, line, lnum, @@ -4983,12 +5492,13 @@ void ex_helpgrep(exarg_T *eap) /* Darn, some plugin changed the value. */ free_string_option(save_cpo); + qf_list_changed(qi, qi->qf_curlist); qf_update_buffer(qi, NULL); if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); - if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { + if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) { // autocommands made "qi" invalid return; } |