diff options
Diffstat (limited to 'src/nvim/quickfix.c')
-rw-r--r-- | src/nvim/quickfix.c | 420 |
1 files changed, 245 insertions, 175 deletions
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 528829e63d..c5e8d4b490 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -264,15 +264,152 @@ static struct fmtpattern { 'o', ".\\+" } }; +// Convert an errorformat pattern to a regular expression pattern. +// See fmt_pat definition above for the list of supported patterns. +static char_u *fmtpat_to_regpat( + const char_u *efmp, + efm_T *fmt_ptr, + int idx, + int round, + char_u *ptr, + char_u *errmsg, + size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + if (fmt_ptr->addr[idx]) { + // Each errorformat pattern can occur only once + snprintf((char *)errmsg, errmsglen, + _("E372: Too many %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + if ((idx && idx < 6 + && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + || (idx == 6 + && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { + snprintf((char *)errmsg, errmsglen, + _("E373: Unexpected %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + fmt_ptr->addr[idx] = (char_u)++round; + *ptr++ = '\\'; + *ptr++ = '('; +#ifdef BACKSLASH_IN_FILENAME + if (*efmp == 'f') { + // Also match "c:" in the file name, even when + // checking for a colon next: "%f:". + // "\%(\a:\)\=" + STRCPY(ptr, "\\%(\\a:\\)\\="); + ptr += 10; + } +#endif + if (*efmp == 'f' && efmp[1] != NUL) { + if (efmp[1] != '\\' && efmp[1] != '%') { + // A file name may contain spaces, but this isn't + // in "\f". For "%f:%l:%m" there may be a ":" in + // the file name. Use ".\{-1,}x" instead (x is + // the next character), the requirement that :999: + // follows should work. + STRCPY(ptr, ".\\{-1,}"); + ptr += 7; + } else { + // File name followed by '\\' or '%': include as + // many file name chars as possible. + STRCPY(ptr, "\\f\\+"); + ptr += 4; + } + } else { + char_u *srcptr = (char_u *)fmt_pat[idx].pattern; + while ((*ptr = *srcptr++) != NUL) { + ptr++; + } + } + *ptr++ = '\\'; + *ptr++ = ')'; + + return ptr; +} + +// Convert a scanf like format in 'errorformat' to a regular expression. +static char_u *scanf_fmt_to_regpat( + const char_u *efm, + int len, + const char_u **pefmp, + char_u *ptr, + char_u *errmsg, + size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + const char_u *efmp = *pefmp; + + if (*++efmp == '[' || *efmp == '\\') { + if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. + if (efmp[1] == '^') { + *ptr++ = *++efmp; + } + if (efmp < efm + len) { + *ptr++ = *++efmp; // could be ']' + while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + } + if (efmp == efm + len) { + EMSG(_("E374: Missing ] in format string")); + return NULL; + } + } + } else if (efmp < efm + len) { // %*\D, %*\s etc. + *ptr++ = *++efmp; + } + *ptr++ = '\\'; + *ptr++ = '+'; + } else { + // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? + snprintf((char *)errmsg, errmsglen, + _("E375: Unsupported %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + + *pefmp = efmp; + + return ptr; +} + +// Analyze/parse an errorformat prefix. +static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, + char_u *errmsg, size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + const char_u *efmp = *pefmp; + + if (vim_strchr((char_u *)"+-", *efmp) != NULL) { + fmt_ptr->flags = *efmp++; + } + if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { + fmt_ptr->prefix = *efmp; + } else { + snprintf((char *)errmsg, errmsglen, + _("E376: Invalid %%%c in format string prefix"), *efmp); + EMSG(errmsg); + return FAIL; + } + + *pefmp = efmp; + + return OK; +} + + // Converts a 'errorformat' string to regular expression pattern -static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, - char_u *regpat, char_u *errmsg) +static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) + FUNC_ATTR_NONNULL_ALL { // Build regexp pattern from current 'errorformat' option char_u *ptr = regpat; *ptr++ = '^'; int round = 0; - for (char_u *efmp = efm; efmp < efm + len; efmp++) { + for (const char_u *efmp = efm; efmp < efm + len; efmp++) { if (*efmp == '%') { efmp++; int idx; @@ -282,89 +419,15 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - if (fmt_ptr->addr[idx]) { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E372: Too many %%%c in format string"), *efmp); - EMSG(errmsg); - return -1; - } - if ((idx - && idx < 6 - && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) - || (idx == 6 - && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E373: Unexpected %%%c in format string"), *efmp); - EMSG(errmsg); + ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, + errmsg, errmsglen); + if (ptr == NULL) { return -1; } round++; - fmt_ptr->addr[idx] = (char_u)round; - *ptr++ = '\\'; - *ptr++ = '('; -#ifdef BACKSLASH_IN_FILENAME - if (*efmp == 'f') { - // Also match "c:" in the file name, even when - // checking for a colon next: "%f:". - // "\%(\a:\)\=" - STRCPY(ptr, "\\%(\\a:\\)\\="); - ptr += 10; - } -#endif - if (*efmp == 'f' && efmp[1] != NUL) { - if (efmp[1] != '\\' && efmp[1] != '%') { - // A file name may contain spaces, but this isn't - // in "\f". For "%f:%l:%m" there may be a ":" in - // the file name. Use ".\{-1,}x" instead (x is - // the next character), the requirement that :999: - // follows should work. - STRCPY(ptr, ".\\{-1,}"); - ptr += 7; - } else { - // File name followed by '\\' or '%': include as - // many file name chars as possible. - STRCPY(ptr, "\\f\\+"); - ptr += 4; - } - } else { - char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; - } - } - *ptr++ = '\\'; - *ptr++ = ')'; } else if (*efmp == '*') { - if (*++efmp == '[' || *efmp == '\\') { - if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. - if (efmp[1] == '^') { - *ptr++ = *++efmp; - } - if (efmp < efm + len) { - efmp++; - *ptr++ = *efmp; // could be ']' - while (efmp < efm + len) { - efmp++; - if ((*ptr++ = *efmp) == ']') { - break; - } - } - if (efmp == efm + len) { - EMSG(_("E374: Missing ] in format string")); - return -1; - } - } - } else if (efmp < efm + len) { // %*\D, %*\s etc. - efmp++; - *ptr++ = *efmp; - } - *ptr++ = '\\'; - *ptr++ = '+'; - } else { - // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E375: Unsupported %%%c in format string"), *efmp); - EMSG(errmsg); + ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + if (ptr == NULL) { return -1; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { @@ -374,15 +437,7 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (vim_strchr((char_u *)"+-", *efmp) != NULL) { - fmt_ptr->flags = *efmp++; - } - if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { - fmt_ptr->prefix = *efmp; - } else { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E376: Invalid %%%c in format string prefix"), *efmp); - EMSG(errmsg); + if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { return -1; } } else { @@ -461,7 +516,7 @@ static efm_T * parse_efm_option(char_u *efm) } } - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { @@ -1499,6 +1554,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, * Allocate a new location list */ static qf_info_T *ll_new_list(void) + FUNC_ATTR_NONNULL_RET { qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; @@ -2084,7 +2140,7 @@ static win_T *qf_find_win_with_normal_buf(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_p_bt[0] == NUL) { + if (bt_normal(wp->w_buffer)) { return wp; } } @@ -2148,7 +2204,7 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // Find a previous usable window win = curwin; do { - if (win->w_buffer->b_p_bt[0] == NUL) { + if (bt_normal(win->w_buffer)) { break; } if (win->w_prev == NULL) { @@ -2202,7 +2258,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Remember a usable window. if (altwin == NULL && !win->w_p_pvw - && win->w_buffer->b_p_bt[0] == NUL) { + && bt_normal(win->w_buffer)) { altwin = win; } } @@ -4412,7 +4468,7 @@ void ex_vimgrep(exarg_T *eap) goto theend; } - /* Jump to first match. */ + // Jump to first match. if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, @@ -4974,15 +5030,86 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) return status; } +// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the +// items in the dict 'd'. +static int qf_add_entry_from_dict( + qf_info_T *qi, + int qf_idx, + const dict_T *d, + bool first_entry) + FUNC_ATTR_NONNULL_ALL +{ + static bool did_bufnr_emsg; + + if (first_entry) { + did_bufnr_emsg = false; + } + + 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"); + const long lnum = (long)tv_dict_get_number(d, "lnum"); + const int col = (int)tv_dict_get_number(d, "col"); + const char_u vcol = (char_u)tv_dict_get_number(d, "vcol"); + const int nr = (int)tv_dict_get_number(d, "nr"); + const char *const type = tv_dict_get_string(d, "type", false); + char *const pattern = tv_dict_get_string(d, "pattern", true); + char *text = tv_dict_get_string(d, "text", true); + if (text == NULL) { + text = xcalloc(1, 1); + } + bool valid = true; + if ((filename == NULL && bufnum == 0) + || (lnum == 0 && pattern == NULL)) { + valid = false; + } + + // Mark entries with non-existing buffer number as not valid. Give the + // error message only once. + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { + if (!did_bufnr_emsg) { + did_bufnr_emsg = true; + EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum); + } + valid = false; + bufnum = 0; + } + + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = tv_dict_get_number(d, "valid"); + } + + const int status = qf_add_entry(qi, + qf_idx, + NULL, // dir + (char_u *)filename, + (char_u *)module, + bufnum, + (char_u *)text, + lnum, + col, + vcol, // vis_col + (char_u *)pattern, // search pattern + nr, + (char_u)(type == NULL ? NUL : *type), + valid); + + xfree(filename); + xfree(module); + xfree(pattern); + xfree(text); + + return status; +} + /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { - dict_T *d; qfline_T *old_last = NULL; int retval = OK; - bool did_bufnr_emsg = false; if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list @@ -5001,68 +5128,13 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; // Skip non-dict items. } - d = TV_LIST_ITEM_TV(li)->vval.v_dict; + const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict; if (d == NULL) { continue; } - 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"); - char_u vcol = (char_u)tv_dict_get_number(d, "vcol"); - int nr = (int)tv_dict_get_number(d, "nr"); - const char *type_str = tv_dict_get_string(d, "type", false); - const char_u type = (char_u)(uint8_t)(type_str == NULL ? NUL : *type_str); - char *const pattern = tv_dict_get_string(d, "pattern", true); - char *text = tv_dict_get_string(d, "text", true); - if (text == NULL) { - text = xcalloc(1, 1); - } - bool valid = true; - if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) { - valid = false; - } - - /* Mark entries with non-existing buffer number as not valid. Give the - * error message only once. */ - if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { - if (!did_bufnr_emsg) { - did_bufnr_emsg = TRUE; - EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum); - } - valid = false; - bufnum = 0; - } - - // If the 'valid' field is present it overrules the detected value. - if (tv_dict_find(d, "valid", -1) != NULL) { - valid = (int)tv_dict_get_number(d, "valid"); - } - - int status = qf_add_entry(qi, - qf_idx, - NULL, // dir - (char_u *)filename, - (char_u *)module, - bufnum, - (char_u *)text, - lnum, - col, - vcol, // vis_col - (char_u *)pattern, // search pattern - nr, - type, - valid); - - xfree(filename); - xfree(module); - xfree(pattern); - xfree(text); - - if (status == FAIL) { - retval = FAIL; + retval = qf_add_entry_from_dict(qi, qf_idx, d, li == tv_list_first(list)); + if (retval == FAIL) { break; } }); @@ -5595,7 +5667,7 @@ void ex_cexpr(exarg_T *eap) // Get the location list for ":lhelpgrep" static qf_info_T *hgr_get_ll(bool *new_ll) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { win_T *wp; qf_info_T *qi; @@ -5615,9 +5687,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) } if (qi == NULL) { // Allocate a new location list for help text matches - if ((qi = ll_new_list()) == NULL) { - return NULL; - } + qi = ll_new_list(); *new_ll = true; } @@ -5713,14 +5783,14 @@ static void hgr_search_files_in_dir( } } -// Search for a pattern in all the help files in the 'runtimepath'. +// Search for a pattern in all the help files in the 'runtimepath' +// and add the matches to a quickfix list. +// 'lang' is the language specifier. If supplied, then only matches in the +// specified language are found. static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch, - char_u *arg) - FUNC_ATTR_NONNULL_ALL + const char_u *lang) + FUNC_ATTR_NONNULL_ARG(1, 2) { - // Check for a specified language - char_u *const lang = check_help_lang(arg); - // Go through all directories in 'runtimepath' char_u *p = p_rtp; while (*p != NUL && !got_int) { @@ -5755,11 +5825,10 @@ void ex_helpgrep(exarg_T *eap) if (eap->cmdidx == CMD_lhelpgrep) { qi = hgr_get_ll(&new_qi); - if (qi == NULL) { - return; - } } + // Check for a specified language + char_u *const lang = check_help_lang(eap->arg); regmatch_T regmatch = { .regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING), .rm_ic = false, @@ -5768,7 +5837,7 @@ void ex_helpgrep(exarg_T *eap) // Create a new quickfix list. qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep)); - hgr_search_in_rtp(qi, ®match, eap->arg); + hgr_search_in_rtp(qi, ®match, lang); vim_regfree(regmatch.regprog); @@ -5778,11 +5847,12 @@ void ex_helpgrep(exarg_T *eap) qi->qf_lists[qi->qf_curlist].qf_index = 1; } - if (p_cpo == empty_option) + if (p_cpo == empty_option) { p_cpo = save_cpo; - else - /* Darn, some plugin changed the value. */ + } else { + // Darn, some plugin changed the value. free_string_option(save_cpo); + } qf_list_changed(qi, qi->qf_curlist); qf_update_buffer(qi, NULL); @@ -5803,8 +5873,8 @@ void ex_helpgrep(exarg_T *eap) EMSG2(_(e_nomatch2), eap->arg); if (eap->cmdidx == CMD_lhelpgrep) { - /* If the help window is not opened or if it already points to the - * correct location list, then free the new location list. */ + // If the help window is not opened or if it already points to the + // correct location list, then free the new location list. if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) { if (new_qi) { ll_free_all(&qi); |