diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/ex_cmds.lua | 24 | ||||
-rw-r--r-- | src/nvim/memory.c | 2 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 2457 | ||||
-rw-r--r-- | src/nvim/testdir/test_quickfix.vim | 143 |
4 files changed, 1713 insertions, 913 deletions
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 6317ec77ff..f7aa8a994a 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -323,6 +323,12 @@ return { func='ex_abclear', }, { + command='cabove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='caddbuffer', flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR), addr_type=ADDR_LINES, @@ -359,6 +365,12 @@ return { func='ex_cbuffer', }, { + command='cbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='cbottom', flags=bit.bor(TRLBAR), addr_type=ADDR_LINES, @@ -1273,6 +1285,12 @@ return { func='ex_last', }, { + command='labove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='language', flags=bit.bor(EXTRA, TRLBAR, CMDWIN), addr_type=ADDR_LINES, @@ -1309,6 +1327,12 @@ return { func='ex_cbuffer', }, { + command='lbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='lbottom', flags=bit.bor(TRLBAR), addr_type=ADDR_LINES, diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 64aae71433..9bc6b23ce3 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -693,6 +693,8 @@ void free_all_mem(void) clear_hl_tables(false); list_free_log(); + + check_quickfix_busy(); } #endif diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a9e0c22a76..cf5194d16f 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -80,6 +80,15 @@ struct qfline_S { #define LISTCOUNT 10 #define INVALID_QFIDX (-1) +/// Quickfix list type. +typedef enum +{ + QFLT_QUICKFIX, ///< Quickfix list - global list + QFLT_LOCATION, ///< Location list - per window list + QFLT_INTERNAL ///< Internal - Temporary list used by + // getqflist()/getloclist() +} qfltype_T; + /// Quickfix/Location list definition /// /// Usually the list contains one or more entries. But an empty list can be @@ -87,6 +96,7 @@ struct qfline_S { /// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { unsigned qf_id; ///< Unique identifier for this list + qfltype_T qfl_type; qfline_T *qf_start; ///< pointer to the first error qfline_T *qf_last; ///< pointer to the last error qfline_T *qf_ptr; ///< pointer to the current error @@ -120,6 +130,7 @@ struct qf_info_S { int qf_listcount; /* current number of lists */ int qf_curlist; /* current error list */ qf_list_T qf_lists[LISTCOUNT]; + qfltype_T qfl_type; // type of list }; static qf_info_T ql_info; // global quickfix list @@ -154,6 +165,13 @@ struct efm_S { int conthere; /* %> used */ }; +/// List of location lists to be deleted. +/// Used to delay the deletion of locations lists by autocmds. +typedef struct qf_delq_S { + struct qf_delq_S *next; + qf_info_T *qi; +} qf_delq_T; + enum { QF_FAIL = 0, QF_OK = 1, @@ -163,6 +181,8 @@ enum { QF_MULTISCAN = 5, }; +/// State information used to parse lines and add entries to a quickfix/location +/// list. typedef struct { char_u *linebuf; size_t linelen; @@ -196,14 +216,19 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.c.generated.h" #endif -/* Quickfix window check helper macro */ + +static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); + +// Quickfix window check helper macro #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) /* Location list window check helper macro */ #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi == &ql_info) -#define IS_LL_STACK(qi) (qi != &ql_info) +#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' @@ -211,6 +236,14 @@ typedef struct { // #define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +// Macro to loop through all the items in a quickfix list +// Quickfix item index starts from 1, so i below starts at 1 +#define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ + for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && i <= qfl->qf_count && qfp != NULL; \ + i++, qfp = qfp->qf_next) + + // Looking up a buffer can be slow if there are many. Remember the last one // to make this a lot faster if there are multiple matches in the same file. static char_u *qf_last_bufname = NULL; @@ -218,6 +251,50 @@ static bufref_T qf_last_bufref = { NULL, 0, 0 }; static char *e_loc_list_changed = N_("E926: Current location list was changed"); +// Counter to prevent autocmds from freeing up location lists when they are +// still being used. +static int quickfix_busy = 0; +static qf_delq_T *qf_delq_head = NULL; + +/// Process the next line from a file/buffer/list/string and add it +/// to the quickfix list 'qfl'. +static int qf_init_process_nextline(qf_list_T *qfl, + efm_T *fmt_first, + qfstate_T *state, + qffields_T *fields) +{ + int status; + + // Get the next line from a file/buffer/list/string + status = qf_get_nextline(state); + if (status != QF_OK) { + return status; + } + + status = qf_parse_line(qfl, state->linebuf, state->linelen, + fmt_first, fields); + if (status != QF_OK) { + return status; + } + + return qf_add_entry(qfl, + qfl->qf_directory, + (*fields->namebuf || qfl->qf_directory != NULL) + ? fields->namebuf + : ((qfl->qf_currfile != NULL && fields->valid) + ? qfl->qf_currfile : (char_u *)NULL), + fields->module, + 0, + fields->errmsg, + fields->lnum, + fields->col, + fields->use_viscol, + fields->pattern, + fields->enr, + fields->type, + fields->valid); +} + /// Read the errorfile "efile" into memory, line by line, building the error /// list. Set the error list's title to qf_title. /// @@ -265,93 +342,96 @@ 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, +/// Convert an errorformat pattern to a regular expression pattern. +/// See fmt_pat definition above for the list of supported patterns. The +/// pattern specifier is supplied in "efmpat". The converted pattern is stored +/// in "regpat". Returns a pointer to the location after the pattern. +static char_u * efmpat_to_regpat( + const char_u *efmpat, + char_u *regpat, + efm_T *efminfo, int idx, int round, - char_u *ptr, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - if (fmt_ptr->addr[idx]) { + if (efminfo->addr[idx]) { // Each errorformat pattern can occur only once snprintf((char *)errmsg, errmsglen, - _("E372: Too many %%%c in format string"), *efmp); + _("E372: Too many %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } if ((idx && idx < 6 - && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) || (idx == 6 - && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { + && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) { snprintf((char *)errmsg, errmsglen, - _("E373: Unexpected %%%c in format string"), *efmp); + _("E373: Unexpected %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } - fmt_ptr->addr[idx] = (char_u)++round; - *ptr++ = '\\'; - *ptr++ = '('; + efminfo->addr[idx] = (char_u)++round; + *regpat++ = '\\'; + *regpat++ = '('; #ifdef BACKSLASH_IN_FILENAME - if (*efmp == 'f') { + if (*efmpat == 'f') { // Also match "c:" in the file name, even when // checking for a colon next: "%f:". // "\%(\a:\)\=" - STRCPY(ptr, "\\%(\\a:\\)\\="); - ptr += 10; + STRCPY(regpat, "\\%(\\a:\\)\\="); + regpat += 10; } #endif - if (*efmp == 'f' && efmp[1] != NUL) { - if (efmp[1] != '\\' && efmp[1] != '%') { + if (*efmpat == 'f' && efmpat[1] != NUL) { + if (efmpat[1] != '\\' && efmpat[1] != '%') { // A file name may contain spaces, but this isn't // in "\f". For "%f:%l:%m" there may be a ":" in // the file name. Use ".\{-1,}x" instead (x is // the next character), the requirement that :999: // follows should work. - STRCPY(ptr, ".\\{-1,}"); - ptr += 7; + STRCPY(regpat, ".\\{-1,}"); + regpat += 7; } else { // File name followed by '\\' or '%': include as // many file name chars as possible. - STRCPY(ptr, "\\f\\+"); - ptr += 4; + STRCPY(regpat, "\\f\\+"); + regpat += 4; } } else { char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; + while ((*regpat = *srcptr++) != NUL) { + regpat++; } } - *ptr++ = '\\'; - *ptr++ = ')'; + *regpat++ = '\\'; + *regpat++ = ')'; - return ptr; + return regpat; } -// Convert a scanf like format in 'errorformat' to a regular expression. -static char_u *scanf_fmt_to_regpat( +/// Convert a scanf like format in 'errorformat' to a regular expression. +/// Returns a pointer to the location after the pattern. +static char_u * scanf_fmt_to_regpat( + const char_u **pefmp, const char_u *efm, int len, - const char_u **pefmp, - char_u *ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { const char_u *efmp = *pefmp; - if (*++efmp == '[' || *efmp == '\\') { - if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. + if (*efmp == '[' || *efmp == '\\') { + if ((*regpat++ = *efmp) == '[') { // %*[^a-z0-9] etc. if (efmp[1] == '^') { - *ptr++ = *++efmp; + *regpat++ = *++efmp; } if (efmp < efm + len) { - *ptr++ = *++efmp; // could be ']' - while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + *regpat++ = *++efmp; // could be ']' + while (efmp < efm + len && (*regpat++ = *++efmp) != ']') { } if (efmp == efm + len) { EMSG(_("E374: Missing ] in format string")); @@ -359,10 +439,10 @@ static char_u *scanf_fmt_to_regpat( } } } else if (efmp < efm + len) { // %*\D, %*\s etc. - *ptr++ = *++efmp; + *regpat++ = *++efmp; } - *ptr++ = '\\'; - *ptr++ = '+'; + *regpat++ = '\\'; + *regpat++ = '+'; } else { // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? snprintf((char *)errmsg, errmsglen, @@ -373,31 +453,27 @@ static char_u *scanf_fmt_to_regpat( *pefmp = efmp; - return ptr; + return regpat; } -// Analyze/parse an errorformat prefix. -static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, - char_u *errmsg, size_t errmsglen) +/// Analyze/parse an errorformat prefix. +static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, + char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - const char_u *efmp = *pefmp; - if (vim_strchr((char_u *)"+-", *efmp) != NULL) { - fmt_ptr->flags = *efmp++; + efminfo->flags = *efmp++; } if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { - fmt_ptr->prefix = *efmp; + efminfo->prefix = *efmp; } else { snprintf((char *)errmsg, errmsglen, _("E376: Invalid %%%c in format string prefix"), *efmp); EMSG(errmsg); - return FAIL; + return NULL; } - *pefmp = efmp; - - return OK; + return efmp; } @@ -420,16 +496,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, - errmsg, errmsglen); + ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg, + errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } round++; } else if (*efmp == '*') { - ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + efmp++; + ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { *ptr++ = *efmp; // regexp magic characters @@ -438,14 +515,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { - return -1; + // prefix is allowed only at the beginning of the errorformat + // option part + efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen); + if (efmp == NULL) { + return FAIL; } } else { snprintf((char *)errmsg, CMDBUFFSIZE + 1, _("E377: Invalid %%%c in format string"), *efmp); EMSG(errmsg); - return -1; + return FAIL; } } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { @@ -461,7 +541,7 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, *ptr++ = '$'; *ptr = NUL; - return 0; + return OK; } static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls @@ -477,7 +557,42 @@ static void free_efm_list(efm_T **efm_first) fmt_start = NULL; } -// Parse 'errorformat' option +/// Compute the size of the buffer used to convert a 'errorformat' pattern into +/// a regular expression pattern. +static size_t efm_regpat_bufsz(char_u *efm) +{ + size_t sz; + + sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); + for (int i = FMT_PATTERNS - 1; i >= 0; ) { + sz += STRLEN(fmt_pat[i--].pattern); + } +#ifdef BACKSLASH_IN_FILENAME + sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat) +#else + sz += 2; // "%f" can become two chars longer +#endif + + return sz; +} + +/// Return the length of a 'errorformat' option part (separated by ","). +static int efm_option_part_len(char_u *efm) +{ + int len; + + for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { + if (efm[len] == '\\' && efm[len + 1] != NUL) { + len++; + } + } + + return len; +} + +/// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option +/// are parsed and converted to regular expressions. Returns information about +/// the parsed 'errorformat' option. static efm_T * parse_efm_option(char_u *efm) { efm_T *fmt_ptr = NULL; @@ -489,16 +604,8 @@ static efm_T * parse_efm_option(char_u *efm) char_u *errmsg = xmalloc(errmsglen); // Get some space to modify the format string into. - size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); - for (int round = FMT_PATTERNS - 1; round >= 0; ) { - i += STRLEN(fmt_pat[round--].pattern); - } -#ifdef BACKSLASH_IN_FILENAME - i += 12; // "%f" can become twelve chars longer (see efm_to_regpat) -#else - i += 2; // "%f" can become two chars longer -#endif - char_u *fmtstr = xmalloc(i); + size_t sz = efm_regpat_bufsz(efm); + char_u *fmtstr = xmalloc(sz); while (efm[0] != NUL) { // Allocate a new eformat structure and put it at the end of the list @@ -511,13 +618,9 @@ static efm_T * parse_efm_option(char_u *efm) fmt_last = fmt_ptr; // Isolate one part in the 'errorformat' option - for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { - if (efm[len] == '\\' && efm[len + 1] != NUL) { - len++; - } - } + len = efm_option_part_len(efm); - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { @@ -543,6 +646,7 @@ parse_efm_end: return fmt_first; } +/// Allocate more memory for the line buffer used for parsing lines. static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz) { // If the line exceeds LINE_MAXLEN exclude the last @@ -784,32 +888,41 @@ static int qf_get_nextline(qfstate_T *state) return QF_OK; } -// Returns true if the specified quickfix/location stack is empty +/// Returns true if the specified quickfix/location stack is empty static bool qf_stack_empty(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return qi == NULL || qi->qf_listcount <= 0; } -// Returns true if the specified quickfix/location list is empty. -static bool qf_list_empty(const qf_info_T *qi, int qf_idx) +/// Returns true if the specified quickfix/location list is empty. +static bool qf_list_empty(qf_list_T *qfl) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) { - return true; - } - return qi->qf_lists[qf_idx].qf_count <= 0; + return qfl == NULL || qfl->qf_count <= 0; +} + +/// Returns true if the specified quickfix/location list is not empty and +/// has valid entries. +static bool qf_list_has_valid_entries(qf_list_T *qfl) +{ + return !qf_list_empty(qfl) && !qfl->qf_nonevalid; +} + +/// Return a pointer to a list in the specified quickfix stack +static qf_list_T * qf_get_list(qf_info_T *qi, int idx) +{ + return &qi->qf_lists[idx]; } /// Parse a line and get the quickfix fields. /// Return the QF_ status. -static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf, +static int qf_parse_line(qf_list_T *qfl, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; int idx = 0; char_u *tail = NULL; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; int status; restofline: @@ -865,7 +978,7 @@ restofline: qfl->qf_multiignore = false; // reset continuation } 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); + status = qf_parse_multiline_pfx(idx, qfl, fields); if (status != QF_OK) { return status; } @@ -1011,12 +1124,12 @@ qf_init_ext( } else { // Adding to existing list, use last entry. adding = true; - if (qi->qf_lists[qf_idx].qf_count > 0) { + if (!qf_list_empty(qf_get_list(qi, qf_idx) )) { old_last = qi->qf_lists[qf_idx].qf_last; } } - qf_list_T *qfl = &qi->qf_lists[qf_idx]; + qf_list_T *qfl = qf_get_list(qi, qf_idx); // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { @@ -1054,39 +1167,14 @@ qf_init_ext( * Try to recognize one of the error formats in each line. */ while (!got_int) { - // Get the next line from a file/buffer/list/string - status = qf_get_nextline(&state); + status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); if (status == QF_END_OF_INPUT) { // end of input break; } - - status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen, - fmt_first, &fields); if (status == QF_FAIL) { goto error2; } - if (status == QF_IGNORE_LINE) { - continue; - } - if (qf_add_entry(qi, - qf_idx, - qfl->qf_directory, - (*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, - fields.col, - fields.use_viscol, - fields.pattern, - fields.enr, - fields.type, - fields.valid) == FAIL) { - goto error2; - } line_breakcheck(); } if (state.fd == NULL || !ferror(state.fd)) { @@ -1109,7 +1197,7 @@ qf_init_ext( error2: if (!adding) { // Error when creating a new list. Free the new list - qf_free(qi, qi->qf_curlist); + qf_free(qfl); qi->qf_listcount--; if (qi->qf_curlist > 0) { qi->qf_curlist--; @@ -1127,16 +1215,16 @@ qf_init_end: /// 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, const char_u *title) +static void qf_store_title(qf_list_T *qfl, const char_u *title) FUNC_ATTR_NONNULL_ARG(1) { - XFREE_CLEAR(qi->qf_lists[qf_idx].qf_title); + XFREE_CLEAR(qfl->qf_title); if (title != NULL) { size_t len = STRLEN(title) + 1; char_u *p = xmallocz(len); - qi->qf_lists[qf_idx].qf_title = p; + qfl->qf_title = p; xstrlcpy((char *)p, (const char *)title, len + 1); } } @@ -1154,35 +1242,256 @@ static char_u * qf_cmdtitle(char_u *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. +/// Return a pointer to the current list in the specified quickfix stack +static qf_list_T * qf_get_curlist(qf_info_T *qi) +{ + return qf_get_list(qi, qi->qf_curlist); +} + +/// 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. static void qf_new_list(qf_info_T *qi, const char_u *qf_title) { int i; + qf_list_T *qfl; // If the current entry is not the last entry, delete entries beyond // the current entry. This makes it possible to browse in a tree-like // way with ":grep'. - while (qi->qf_listcount > qi->qf_curlist + 1) - qf_free(qi, --qi->qf_listcount); + while (qi->qf_listcount > qi->qf_curlist + 1) { + qf_free(&qi->qf_lists[--qi->qf_listcount]); + } /* * When the stack is full, remove to oldest entry * Otherwise, add a new entry. */ if (qi->qf_listcount == LISTCOUNT) { - qf_free(qi, 0); - for (i = 1; i < LISTCOUNT; ++i) + qf_free(&qi->qf_lists[0]); + for (i = 1; i < LISTCOUNT; i++) { qi->qf_lists[i - 1] = qi->qf_lists[i]; + } qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; - memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qi->qf_curlist, qf_title); - qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; + qfl = qf_get_curlist(qi); + memset(qfl, 0, sizeof(qf_list_T)); + qf_store_title(qfl, qf_title); + qfl->qfl_type = qi->qfl_type; + qfl->qf_id = ++last_qf_id; +} + +/// Parse the match for filename ('%f') pattern in regmatch. +/// Return the matched value in "fields->namebuf". +static int qf_parse_fmt_f(regmatch_T *rmp, + int midx, + qffields_T *fields, + int prefix) +{ + char_u c; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + + // Expand ~/file and $HOME/file to full path. + c = *rmp->endp[midx]; + *rmp->endp[midx] = NUL; + expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); + *rmp->endp[midx] = c; + + // For separate filename patterns (%O, %P and %Q), the specified file + // should exist. + if (vim_strchr((char_u *)"OPQ", prefix) != NULL + && !os_path_exists(fields->namebuf)) { + return QF_FAIL; + } + + return QF_OK; +} + +/// Parse the match for error number ('%n') pattern in regmatch. +/// Return the matched value in "fields->enr". +static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->enr = (int)atol((char *)rmp->startp[midx]); + return QF_OK; } +/// Parse the match for line number (%l') pattern in regmatch. +/// Return the matched value in "fields->lnum". +static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->lnum = atol((char *)rmp->startp[midx]); + return QF_OK; +} + +/// Parse the match for column number ('%c') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + +/// Parse the match for error type ('%t') pattern in regmatch. +/// Return the matched value in "fields->type". +static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->type = *rmp->startp[midx]; + return QF_OK; +} + +/// Parse the match for '%+' format pattern. The whole matching line is included +/// in the error string. Return the matched line in "fields->errmsg". +static int qf_parse_fmt_plus(char_u *linebuf, + size_t linelen, + qffields_T *fields) +{ + if (linelen >= fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + STRLCPY(fields->errmsg, linebuf, linelen + 1); + return QF_OK; +} + +/// Parse the match for error message ('%m') pattern in regmatch. +/// Return the matched value in "fields->errmsg". +static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len >= fields->errmsglen) { + // len + null terminator + fields->errmsg = xrealloc(fields->errmsg, len + 1); + fields->errmsglen = len + 1; + } + STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + return QF_OK; +} + +/// Parse the match for rest of a single-line file message ('%r') pattern. +/// Return the matched value in "tail". +static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + *tail = rmp->startp[midx]; + return QF_OK; +} + +/// Parse the match for the pointer line ('%p') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) +{ + char_u *match_ptr; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + fields->col = 0; + for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx]; + match_ptr++) { + fields->col++; + if (*match_ptr == TAB) { + fields->col += 7; + fields->col -= fields->col % 8; + } + } + fields->col++; + fields->use_viscol = true; + return QF_OK; +} + +/// Parse the match for the virtual column number ('%v') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + fields->use_viscol = true; + return QF_OK; +} + +/// Parse the match for the search text ('%s') pattern in regmatch. +/// Return the matched value in "fields->pattern". +static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE - 5) { + len = CMDBUFFSIZE - 5; + } + STRCPY(fields->pattern, "^\\V"); + xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4); + fields->pattern[len + 3] = '\\'; + fields->pattern[len + 4] = '$'; + fields->pattern[len + 5] = NUL; + return QF_OK; +} + +/// Parse the match for the module ('%o') pattern in regmatch. +/// Return the matched value in "fields->module". +static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + size_t dsize; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + dsize = STRLEN(fields->module) + len + 1; + if (dsize > CMDBUFFSIZE) { + dsize = CMDBUFFSIZE; + } + xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize); + return QF_OK; +} + +/// 'errorformat' format pattern parser functions. +/// The '%f' and '%r' formats are parsed differently from other formats. +/// See qf_parse_match() for details. +static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = { + NULL, + qf_parse_fmt_n, + qf_parse_fmt_l, + qf_parse_fmt_c, + qf_parse_fmt_t, + qf_parse_fmt_m, + NULL, + qf_parse_fmt_p, + qf_parse_fmt_v, + qf_parse_fmt_s, + qf_parse_fmt_o +}; + /// Parse the error format matches in 'regmatch' and set the values in 'fields'. /// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match. /// Returns QF_OK if all the matches are successfully parsed. On failure, @@ -1193,7 +1502,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, { char_u idx = fmt_ptr->prefix; int i; - size_t len; + int midx; + int status; if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; @@ -1207,118 +1517,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, // Extract error message data from matched line. // We check for an actual submatch, because "\[" and "\]" in // the 'errorformat' may cause the wrong submatch to be used. - if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - - // Expand ~/file and $HOME/file to full path. - char_u c = *regmatch->endp[i]; - *regmatch->endp[i] = NUL; - expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE); - *regmatch->endp[i] = c; - - if (vim_strchr((char_u *)"OPQ", idx) != NULL - && !os_path_exists(fields->namebuf)) { - return QF_FAIL; - } - } - if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->enr = (int)atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->lnum = atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->col = (int)atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->type = *regmatch->startp[i]; - } - if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ - if (linelen >= fields->errmsglen) { - // linelen + null terminator - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; - } - STRLCPY(fields->errmsg, linebuf, linelen + 1); - } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len >= fields->errmsglen) { - // len + null terminator - fields->errmsg = xrealloc(fields->errmsg, len + 1); - fields->errmsglen = len + 1; - } - STRLCPY(fields->errmsg, regmatch->startp[i], len + 1); - } - if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - *tail = regmatch->startp[i]; - } - if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - fields->col = 0; - char_u *match_ptr; - for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i]; - match_ptr++) { - fields->col++; - if (*match_ptr == TAB) { - fields->col += 7; - fields->col -= fields->col % 8; + for (i = 0; i < FMT_PATTERNS; i++) { + status = QF_OK; + midx = (int)fmt_ptr->addr[i]; + if (i == 0 && midx > 0) { // %f + status = qf_parse_fmt_f(regmatch, midx, fields, idx); + } else if (i == 5) { + if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ + status = qf_parse_fmt_plus(linebuf, linelen, fields); + } else if (midx > 0) { // %m + status = qf_parse_fmt_m(regmatch, midx, fields); } + } else if (i == 6 && midx > 0) { // %r + status = qf_parse_fmt_r(regmatch, midx, tail); + } else if (midx > 0) { // others + status = (qf_parse_fmt[i])(regmatch, midx, fields); } - fields->col++; - fields->use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->col = (int)atol((char *)regmatch->startp[i]); - fields->use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len > CMDBUFFSIZE - 5) { - len = CMDBUFFSIZE - 5; - } - STRCPY(fields->pattern, "^\\V"); - STRNCAT(fields->pattern, regmatch->startp[i], len); - fields->pattern[len + 3] = '\\'; - fields->pattern[len + 4] = '$'; - fields->pattern[len + 5] = NUL; - } - if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len > CMDBUFFSIZE) { - len = CMDBUFFSIZE; + + if (status != QF_OK) { + return status; } - STRNCAT(fields->module, regmatch->startp[i], len); } return QF_OK; @@ -1430,8 +1648,7 @@ static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen, } /// 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) +static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) { if (!qfl->qf_multiignore) { qfline_T *qfprev = qfl->qf_last; @@ -1462,7 +1679,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, } qfprev->qf_viscol = fields->use_viscol; if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, + qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory, *fields->namebuf || qfl->qf_directory ? fields->namebuf : qfl->qf_currfile && fields->valid @@ -1477,7 +1694,18 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, return QF_IGNORE_LINE; } -/// Free a location list. +/// Queue location list stack delete request. +static void locstack_queue_delreq(qf_info_T *qi) +{ + qf_delq_T *q; + + q = xmalloc(sizeof(qf_delq_T)); + q->qi = qi; + q->next = qf_delq_head; + qf_delq_head = q; +} + +/// Free a location list stack static void ll_free_all(qf_info_T **pqi) { int i; @@ -1490,10 +1718,17 @@ static void ll_free_all(qf_info_T **pqi) qi->qf_refcount--; if (qi->qf_refcount < 1) { - /* No references to this location list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); - xfree(qi); + // No references to this location list. + // If the location list is still in use, then queue the delete request + // to be processed later. + if (quickfix_busy > 0) { + locstack_queue_delreq(qi); + } else { + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); + } + xfree(qi); + } } } @@ -1507,16 +1742,61 @@ void qf_free_all(win_T *wp) /* location list */ ll_free_all(&wp->w_llist); ll_free_all(&wp->w_llist_ref); - } else - /* quickfix list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); + } else { + // quickfix list + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); + } + } +} + +/// Delay freeing of location list stacks when the quickfix code is running. +/// Used to avoid problems with autocmds freeing location list stacks when the +/// quickfix code is still referencing the stack. +/// Must always call decr_quickfix_busy() exactly once after this. +static void incr_quickfix_busy(void) +{ + quickfix_busy++; } +/// Safe to free location list stacks. Process any delayed delete requests. +static void decr_quickfix_busy(void) +{ + quickfix_busy--; + if (quickfix_busy == 0) { + // No longer referencing the location lists. Process all the pending + // delete requests. + while (qf_delq_head != NULL) { + qf_delq_T *q = qf_delq_head; + + qf_delq_head = q->next; + ll_free_all(&q->qi); + xfree(q); + } + } +#ifdef ABORT_ON_INTERNAL_ERROR + if (quickfix_busy < 0) { + EMSG("quickfix_busy has become negative"); + abort(); + } +#endif +} + +#if defined(EXITFREE) +void check_quickfix_busy(void) +{ + if (quickfix_busy != 0) { + EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy); +# ifdef ABORT_ON_INTERNAL_ERROR + abort(); +# endif + } +} +#endif + /// Add an entry to the end of the list of errors. /// -/// @param qi quickfix list -/// @param qf_idx list index +/// @param qfl quickfix list entry /// @param dir optional directory name /// @param fname file name or NULL /// @param module module name or NULL @@ -1530,8 +1810,8 @@ void qf_free_all(win_T *wp) /// @param type type character /// @param valid valid entry /// -/// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, +/// @returns QF_OK or QF_FAIL. +static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, 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) @@ -1545,10 +1825,10 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_fnum = bufnum; if (buf != NULL) { buf->b_has_qf_entry |= - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; } } else { - qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname); + qfp->qf_fnum = qf_get_fnum(qfl, dir, fname); } qfp->qf_text = vim_strsave(mesg); qfp->qf_lnum = lnum; @@ -1571,12 +1851,12 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qf_idx].qf_last; - if (qi->qf_lists[qf_idx].qf_count == 0) { + lastp = &qfl->qf_last; + if (qf_list_empty(qfl)) { // first element in the list - qi->qf_lists[qf_idx].qf_start = qfp; - qi->qf_lists[qf_idx].qf_ptr = qfp; - qi->qf_lists[qf_idx].qf_index = 0; + qfl->qf_start = qfp; + qfl->qf_ptr = qfp; + qfl->qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1586,32 +1866,29 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qf_idx].qf_count++; - if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { + qfl->qf_count++; + if (qfl->qf_index == 0 && qfp->qf_valid) { // first valid entry - qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; - qi->qf_lists[qf_idx].qf_ptr = qfp; + qfl->qf_index = qfl->qf_count; + qfl->qf_ptr = qfp; } - return OK; + return QF_OK; } -/* - * Allocate a new location list - */ -static qf_info_T *ll_new_list(void) +/// Allocate a new quickfix/location list stack +static qf_info_T *qf_alloc_stack(qfltype_T qfltype) FUNC_ATTR_NONNULL_RET { qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; + qi->qfl_type = qfltype; return qi; } -/* - * Return the location list for window 'wp'. - * If not present, allocate a location list - */ +/// Return the location list stack for window 'wp'. +/// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) { if (IS_LL_WINDOW(wp)) @@ -1624,26 +1901,63 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) */ ll_free_all(&wp->w_llist_ref); - if (wp->w_llist == NULL) - wp->w_llist = ll_new_list(); /* new location list */ + if (wp->w_llist == NULL) { + wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list + } return wp->w_llist; } -// Copy location list entries from 'from_qfl' to 'to_qfl'. -static int copy_loclist_entries(const qf_list_T *from_qfl, - qf_list_T *to_qfl, - qf_info_T *to_qi) +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. If +/// the location list is not found, then returns NULL and prints an error +/// message if 'print_emsg' is TRUE. +static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = GET_LOC_LIST(curwin); + if (qi == NULL) { + if (print_emsg) { + EMSG(_(e_loclist)); + } + return NULL; + } + } + + return qi; +} + +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. +/// If the location list is not present, then allocates a new one. +/// Returns NULL if the allocation fails. For a location list command, sets +/// 'pwinp' to curwin. +static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp) +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = ll_get_or_alloc_list(curwin); + if (qi == NULL) { + return NULL; + } + *pwinp = curwin; + } + + return qi; +} + +/// Copy location list entries from 'from_qfl' to 'to_qfl'. +static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { int i; - const qfline_T *from_qfp; + qfline_T *from_qfp; // copy all the location entries in this list - for (i = 0, from_qfp = from_qfl->qf_start; - i < from_qfl->qf_count && from_qfp != NULL; - i++, from_qfp = from_qfp->qf_next) { - if (qf_add_entry(to_qi, - to_qi->qf_curlist, + FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) { + if (qf_add_entry(to_qfl, NULL, NULL, from_qfp->qf_module, @@ -1655,7 +1969,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, from_qfp->qf_pattern, from_qfp->qf_nr, 0, - from_qfp->qf_valid) == FAIL) { + from_qfp->qf_valid) == QF_FAIL) { return FAIL; } @@ -1673,13 +1987,12 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, return OK; } -// Copy the specified location list 'from_qfl' to 'to_qfl'. -static int copy_loclist(const qf_list_T *from_qfl, - qf_list_T *to_qfl, - qf_info_T *to_qi) +/// Copy the specified location list 'from_qfl' to 'to_qfl'. +static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { // Some of the fields are populated by qf_add_entry() + to_qfl->qfl_type = from_qfl->qfl_type; to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; to_qfl->qf_count = 0; to_qfl->qf_index = 0; @@ -1697,8 +2010,9 @@ static int copy_loclist(const qf_list_T *from_qfl, } else { to_qfl->qf_ctx = NULL; } + if (from_qfl->qf_count) { - if (copy_loclist_entries(from_qfl, to_qfl, to_qi) == FAIL) { + if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) { return FAIL; } } @@ -1739,7 +2053,7 @@ void copy_loclist_stack(win_T *from, win_T *to) } // allocate a new location list - to->w_llist = ll_new_list(); + to->w_llist = qf_alloc_stack(QFLT_LOCATION); to->w_llist->qf_listcount = qi->qf_listcount; @@ -1747,8 +2061,8 @@ void copy_loclist_stack(win_T *from, win_T *to) for (int idx = 0; idx < qi->qf_listcount; idx++) { to->w_llist->qf_curlist = idx; - if (copy_loclist(&qi->qf_lists[idx], &to->w_llist->qf_lists[idx], - to->w_llist) == FAIL) { + if (copy_loclist(qf_get_list(qi, idx), + qf_get_list(to->w_llist, idx)) == FAIL) { qf_free_all(to); return; } @@ -1757,10 +2071,9 @@ void copy_loclist_stack(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; // current list } -// Get buffer number for file "directory/fname". -// Also sets the b_has_qf_entry flag. -static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, - char_u *fname) +/// Get buffer number for file "directory/fname". +/// Also sets the b_has_qf_entry flag. +static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname ) { char_u *ptr = NULL; char_u *bufname; @@ -1783,7 +2096,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, // directory change. if (!os_path_exists(ptr)) { xfree(ptr); - directory = qf_guess_filepath(qi, qf_idx, fname); + directory = qf_guess_filepath(qfl, fname); if (directory) { ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true); } else { @@ -1811,7 +2124,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, return 0; } buf->b_has_qf_entry = - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; return buf->b_fnum; } @@ -1913,30 +2226,29 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr) } } -// Check in which directory of the directory stack the given file can be -// found. -// Returns a pointer to the directory name or NULL if not found. -// Cleans up intermediate directory entries. -// -// TODO(vim): How to solve the following problem? -// If we have this directory tree: -// ./ -// ./aa -// ./aa/bb -// ./bb -// ./bb/x.c -// and make says: -// making all in aa -// making all in bb -// x.c:9: Error -// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. -// qf_guess_filepath will return NULL. -static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) +/// Check in which directory of the directory stack the given file can be +/// found. +/// Returns a pointer to the directory name or NULL if not found. +/// Cleans up intermediate directory entries. +/// +/// TODO(vim): How to solve the following problem? +/// If we have this directory tree: +/// ./ +/// ./aa +/// ./aa/bb +/// ./bb +/// ./bb/x.c +/// and make says: +/// making all in aa +/// making all in bb +/// x.c:9: Error +/// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. +/// qf_guess_filepath will return NULL. +static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename) { struct dir_stack_T *ds_ptr; struct dir_stack_T *ds_tmp; char_u *fullname; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; // no dirs on the stack - there's nothing we can do if (qfl->qf_dir_stack == NULL) { @@ -1994,22 +2306,19 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id) /// This may invalidate the current quickfix entry. This function checks /// whether an entry is still present in the quickfix list. /// Similar to location list. -static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) +static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) { - qf_list_T *qfl; qfline_T *qfp; int i; - qfl = &qi->qf_lists[qi->qf_curlist]; - // Search for the entry in the current list - for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) { - if (qfp == NULL || qfp == qf_ptr) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + if (qfp == qf_ptr) { break; } } - if (i == qfl->qf_count) { // Entry is not found + if (i > qfl->qf_count) { // Entry is not found return false; } @@ -2018,20 +2327,19 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) /// 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, +static qfline_T *get_next_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; int old_qf_fnum = qf_ptr->qf_fnum; do { - if (idx == qi->qf_lists[qi->qf_curlist].qf_count - || qf_ptr->qf_next == NULL) { + if (idx == qfl->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) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -2040,7 +2348,7 @@ static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// 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, +static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; @@ -2052,7 +2360,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, } idx--; qf_ptr = qf_ptr->qf_prev; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -2063,12 +2371,11 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// 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, +static qfline_T *get_nth_valid_entry(qf_list_T *qfl, 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--) { @@ -2076,9 +2383,9 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, prev_index = *qf_index; if (dir == FORWARD || dir == FORWARD_FILE) { - qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir); } else { - qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir); } if (qf_ptr == NULL) { @@ -2098,7 +2405,7 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, } /// Get n'th (errornr) quickfix entry -static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, +static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, int *cur_qfidx) { int qf_idx = *cur_qfidx; @@ -2111,7 +2418,7 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, // 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_idx < qfl->qf_count && qf_ptr->qf_next != NULL) { qf_idx++; qf_ptr = qf_ptr->qf_next; @@ -2133,6 +2440,13 @@ static win_T *qf_find_help_win(void) return NULL; } +/// Set the location list for the specified window to 'qi'. +static void win_set_loclist(win_T *wp, qf_info_T *qi) +{ + wp->w_llist = qi; + qi->qf_refcount++; +} + /// Find a help window or open one. static int jump_to_help_window(qf_info_T *qi, int *opened_window) { @@ -2172,8 +2486,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) if (IS_LL_STACK(qi)) { // not a quickfix list // The new window should use the supplied location list - curwin->w_llist = qi; - qi->qf_refcount++; + win_set_loclist(curwin, qi); } } @@ -2209,7 +2522,7 @@ static win_T *qf_find_win_with_normal_buf(void) return NULL; } -// Go to a window in any tabpage containing the specified file. Returns TRUE +// Go to a window in any tabpage containing the specified file. Returns true // if successfully jumped to the window. Otherwise returns FALSE. static bool qf_goto_tabwin_with_file(int fnum) { @@ -2239,8 +2552,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref) 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++; + win_set_loclist(curwin, ll_ref); } return OK; } @@ -2281,9 +2593,10 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // 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++; + if (win->w_llist == NULL && ll_ref != NULL) { + // The new window should use the location list from the + // location list window + win_set_loclist(win, ll_ref); } } @@ -2382,6 +2695,8 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { + qf_list_T *qfl = qf_get_curlist(qi); + qfltype_T qfl_type = qfl->qfl_type; int retval = OK; if (qf_ptr->qf_type == 1) { @@ -2396,12 +2711,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, oldwin == curwin ? curwin : NULL); } } else { - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qfl->qf_id; retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); - if (IS_LL_STACK(qi)) { + if (qfl_type == QFLT_LOCATION) { // Location list. Check whether the associated window is still // present and the list is still valid. if (!win_valid_any_tab(oldwin)) { @@ -2412,8 +2727,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, EMSG(_(e_loc_list_changed)); *abort = true; } - } else if (!is_qf_entry_present(qi, qf_ptr)) { - if (IS_QF_STACK(qi)) { + } else if (!is_qf_entry_present(qfl, qf_ptr)) { + if (qfl_type == QFLT_QUICKFIX) { EMSG(_("E925: Current quickfix was changed")); } else { EMSG(_(e_loc_list_changed)); @@ -2477,7 +2792,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, update_topline_redraw(); } snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, - qi->qf_lists[qi->qf_curlist].qf_count, + qf_get_curlist(qi)->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. @@ -2507,6 +2822,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, /// else go to entry "errornr" void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { + qf_list_T *qfl; qfline_T *qf_ptr; qfline_T *old_qf_ptr; int qf_index; @@ -2524,31 +2840,34 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) if (qi == NULL) qi = &ql_info; - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } - qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; + qfl = qf_get_curlist(qi); + + qf_ptr = qfl->qf_ptr; old_qf_ptr = qf_ptr; - qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + qf_index = qfl->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); + qf_ptr = get_nth_valid_entry(qfl, 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); + qf_ptr = get_nth_entry(qfl, 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; + qfl->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)) { @@ -2617,8 +2936,8 @@ failed: } theend: if (qi != NULL) { - qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr; - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; + qfl->qf_ptr = qf_ptr; + qfl->qf_index = qf_index; } if (p_swb != old_swb && opened_window) { /* Restore old 'switchbuf' value, but not when an autocommand or @@ -2631,35 +2950,117 @@ theend: } } + +// Highlight attributes used for displaying entries from the quickfix list. +static int qfFileAttr; +static int qfSepAttr; +static int qfLineAttr; + +/// Display information about a single entry from the quickfix/location list. +/// Used by ":clist/:llist" commands. +/// 'cursel' will be set to true for the currently selected entry in the +/// quickfix list. +static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) +{ + char_u *fname; + buf_T *buf; + + fname = NULL; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, + (char *)qfp->qf_module); + } else { + if (qfp->qf_fnum != 0 + && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) { + fname = buf->b_fname; + if (qfp->qf_type == 1) { // :helpgrep + fname = path_tail(fname); + } + } + if (fname == NULL) { + snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", + qf_idx, (char *)fname); + } + } + + // Support for filtering entries using :filter /pat/ clist + // Match against the module name, file name, search pattern and + // text of the entry. + bool filter_entry = true; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + filter_entry &= message_filtered(qfp->qf_module); + } + if (filter_entry && fname != NULL) { + filter_entry &= message_filtered(fname); + } + if (filter_entry && qfp->qf_pattern != NULL) { + filter_entry &= message_filtered(qfp->qf_pattern); + } + if (filter_entry) { + filter_entry &= message_filtered(qfp->qf_text); + } + if (filter_entry) { + return; + } + + msg_putchar('\n'); + msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); + + if (qfp->qf_lnum != 0) { + msg_puts_attr(":", qfSepAttr); + } + if (qfp->qf_lnum == 0) { + IObuff[0] = NUL; + } else if (qfp->qf_col == 0) { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", + qfp->qf_lnum, qfp->qf_col); + } + vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + msg_puts_attr((const char *)IObuff, qfLineAttr); + msg_puts_attr(":", qfSepAttr); + if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); + msg_puts((const char *)IObuff); + msg_puts_attr(":", qfSepAttr); + } + msg_puts(" "); + + // Remove newlines and leading whitespace from the text. For an + // unrecognized line keep the indent, the compiler may mark a word + // with ^^^^. */ + qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) + ? skipwhite(qfp->qf_text) : qfp->qf_text, + IObuff, IOSIZE); + msg_prt_line(IObuff, false); + ui_flush(); // show one line at a time +} + /* * ":clist": list all errors * ":llist": list all locations */ void qf_list(exarg_T *eap) { - buf_T *buf; - char_u *fname; - qfline_T *qfp; + qf_list_T *qfl; + qfline_T *qfp; int i; int idx1 = 1; int idx2 = -1; char_u *arg = eap->arg; - int qfFileAttr; - int qfSepAttr; - int qfLineAttr; int all = eap->forceit; // if not :cl!, only show // recognised errors - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -2673,12 +3074,13 @@ void qf_list(exarg_T *eap) EMSG(_(e_trailing)); return; } + qfl = qf_get_curlist(qi); if (plus) { - i = qi->qf_lists[qi->qf_curlist].qf_index; + i = qfl->qf_index; idx2 = i + idx1; idx1 = i; } else { - i = qi->qf_lists[qi->qf_curlist].qf_count; + i = qfl->qf_count; if (idx1 < 0) { idx1 = (-idx1 > i) ? 0 : idx1 + i + 1; } @@ -2705,95 +3107,13 @@ void qf_list(exarg_T *eap) qfLineAttr = HL_ATTR(HLF_N); } - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) { + if (qfl->qf_nonevalid) { all = true; } - qfp = qi->qf_lists[qi->qf_curlist].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { - if (got_int) { - break; - } - - fname = NULL; - if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, - (char *)qfp->qf_module); - } else { - if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) { - fname = buf->b_fname; - if (qfp->qf_type == 1) { // :helpgrep - fname = path_tail(fname); - } - } - if (fname == NULL) { - snprintf((char *)IObuff, IOSIZE, "%2d", i); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); - } - } - - // Support for filtering entries using :filter /pat/ clist - // Match against the module name, file name, search pattern and - // text of the entry. - bool filter_entry = true; - if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered(qfp->qf_module); - } - if (filter_entry && fname != NULL) { - filter_entry &= message_filtered(fname); - } - if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered(qfp->qf_pattern); - } - if (filter_entry) { - filter_entry &= message_filtered(qfp->qf_text); - } - if (filter_entry) { - goto next_entry; - } - msg_putchar('\n'); - msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index - ? HL_ATTR(HLF_QFL) : qfFileAttr); - - if (qfp->qf_lnum != 0) { - msg_puts_attr(":", qfSepAttr); - } - if (qfp->qf_lnum == 0) { - IObuff[0] = NUL; - } else if (qfp->qf_col == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", - qfp->qf_lnum, qfp->qf_col); - } - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr((const char *)IObuff, qfLineAttr); - msg_puts_attr(":", qfSepAttr); - if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); - msg_puts((const char *)IObuff); - msg_puts_attr(":", qfSepAttr); - } - msg_puts(" "); - - /* Remove newlines and leading whitespace from the text. For an - * unrecognized line keep the indent, the compiler may mark a word - * with ^^^^. */ - qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) - ? skipwhite(qfp->qf_text) : qfp->qf_text, - IObuff, IOSIZE); - msg_prt_line(IObuff, FALSE); - ui_flush(); /* show one line at a time */ - } - -next_entry: - qfp = qfp->qf_next; - if (qfp == NULL) { - break; + qf_list_entry(qfp, i, i == qfl->qf_index); } - i++; os_breakcheck(); } } @@ -2848,23 +3168,17 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) msg(buf); } -/* - * ":colder [count]": Up in the quickfix stack. - * ":cnewer [count]": Down in the quickfix stack. - * ":lolder [count]": Up in the location list stack. - * ":lnewer [count]": Down in the location list stack. - */ +/// ":colder [count]": Up in the quickfix stack. +/// ":cnewer [count]": Down in the quickfix stack. +/// ":lolder [count]": Up in the location list stack. +/// ":lnewer [count]": Down in the location list stack. void qf_age(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; int count; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } if (eap->addr_count != 0) { @@ -2895,13 +3209,10 @@ void qf_age(exarg_T *eap) /// Display the information about all the quickfix/location lists in the stack. void qf_history(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = qf_cmd_get_stack(eap, false); int i; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - } - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { MSG(_("No entries")); } else { for (i = 0; i < qi->qf_listcount; i++) { @@ -2912,12 +3223,11 @@ void qf_history(exarg_T *eap) /// Free all the entries in the error list "idx". Note that other information /// associated with the list like context and title are not freed. -static void qf_free_items(qf_info_T *qi, int idx) +static void qf_free_items(qf_list_T *qfl) { qfline_T *qfp; qfline_T *qfpnext; bool stop = false; - qf_list_T *qfl = &qi->qf_lists[idx]; while (qfl->qf_count && qfl->qf_start != NULL) { qfp = qfl->qf_start; @@ -2958,10 +3268,9 @@ static void qf_free_items(qf_info_T *qi, int idx) /// Free error list "idx". Frees all the entries in the quickfix list, /// associated context information and the title. -static void qf_free(qf_info_T *qi, int idx) +static void qf_free(qf_list_T *qfl) { - qf_list_T *qfl = &qi->qf_lists[idx]; - qf_free_items(qi, idx); + qf_free_items(qfl); XFREE_CLEAR(qfl->qf_title); tv_free(qfl->qf_ctx); @@ -2993,11 +3302,10 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qi = wp->w_llist; } - for (idx = 0; idx < qi->qf_listcount; ++idx) - if (qi->qf_lists[idx].qf_count) - for (i = 0, qfp = qi->qf_lists[idx].qf_start; - i < qi->qf_lists[idx].qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + for (idx = 0; idx < qi->qf_listcount; idx++) { + qf_list_T *qfl = qf_get_list(qi, idx); + if (!qf_list_empty(qfl)) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_fnum == curbuf->b_fnum) { found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { @@ -3009,6 +3317,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qfp->qf_lnum += amount_after; } } + } + } return found_one; } @@ -3068,7 +3378,7 @@ void qf_view_result(bool split) if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); } - if (qf_list_empty(qi, qi->qf_curlist)) { + if (qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -3096,15 +3406,16 @@ void qf_view_result(bool split) */ void ex_cwindow(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; + qf_list_T *qfl; win_T *win; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } + qfl = qf_get_curlist(qi); + /* Look for an existing quickfix window. */ win = qf_find_win(qi); @@ -3114,8 +3425,8 @@ void ex_cwindow(exarg_T *eap) * it if we have errors; otherwise, leave it closed. */ if (qf_stack_empty(qi) - || qi->qf_lists[qi->qf_curlist].qf_nonevalid - || qf_list_empty(qi, qi->qf_curlist)) { + || qfl->qf_nonevalid + || qf_list_empty(qf_get_curlist(qi))) { if (win != NULL) { ex_cclose(eap); } @@ -3130,13 +3441,11 @@ void ex_cwindow(exarg_T *eap) */ void ex_cclose(exarg_T *eap) { - win_T *win = NULL; - qf_info_T *qi = &ql_info; + win_T *win = NULL; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return; } /* Find existing quickfix window and close it. */ @@ -3240,24 +3549,30 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height) return OK; } -/* - * ":copen": open a window that shows the list of errors. - * ":lopen": open a window that shows the location list. - */ +/// Set "w:quickfix_title" if "qi" has a title. +static void qf_set_title_var(qf_list_T *qfl) +{ + if (qfl->qf_title != NULL) { + set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title); + } +} + +/// ":copen": open a window that shows the list of errors. +/// ":lopen": open a window that shows the location list. void ex_copen(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; + qf_list_T *qfl; int height; int status = FAIL; + int lnum; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } + incr_quickfix_busy(); + if (eap->addr_count != 0) { assert(eap->line2 <= INT_MAX); height = (int)eap->line2; @@ -3273,16 +3588,23 @@ void ex_copen(exarg_T *eap) } if (status == FAIL) { if (qf_open_new_cwindow(qi, height) == FAIL) { + decr_quickfix_busy(); return; } } - qf_set_title_var(qi); + qfl = qf_get_curlist(qi); + qf_set_title_var(qfl); + // Save the current index here, as updating the quickfix buffer may free + // the quickfix list + lnum = qfl->qf_index; // Fill the buffer with the quickfix list. - qf_fill_buffer(qi, curbuf, NULL); + qf_fill_buffer(qfl, curbuf, NULL); + + decr_quickfix_busy(); - curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index; + curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; check_cursor(); update_topline(); // scroll to show the line @@ -3306,17 +3628,13 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curbuf = curwin->w_buffer; } -// :cbottom/:lbottom command. +/// :cbottom/:lbottom command. void ex_cbottom(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } win_T *win = qf_find_win(qi); @@ -3338,7 +3656,7 @@ linenr_T qf_current_entry(win_T *wp) /* In the location list window, use the referenced location list */ qi = wp->w_llist_ref; - return qi->qf_lists[qi->qf_curlist].qf_index; + return qf_get_curlist(qi)->qf_index; } /* @@ -3352,7 +3670,7 @@ qf_win_pos_update ( ) { win_T *win; - int qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + int qf_index = qf_get_curlist(qi)->qf_index; /* * Put the cursor on the current error in the quickfix window, so that @@ -3375,7 +3693,7 @@ qf_win_pos_update ( } /// Checks whether the given window is displaying the specified -/// quickfix/location list buffer. +/// quickfix/location stack. static int is_qf_win(const win_T *win, const qf_info_T *qi) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3395,7 +3713,7 @@ static int is_qf_win(const win_T *win, const qf_info_T *qi) return false; } -/// Find a window displaying the quickfix/location list 'qi' +/// Find a window displaying the quickfix/location stack 'qi' /// Only searches in the current tabpage. static win_T *qf_find_win(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -3433,7 +3751,7 @@ static void qf_update_win_titlevar(qf_info_T *qi) if ((win = qf_find_win(qi)) != NULL) { win_T *curwin_save = curwin; curwin = win; - qf_set_title_var(qi); + qf_set_title_var(qf_get_curlist(qi)); curwin = curwin_save; } } @@ -3459,7 +3777,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) qf_update_win_titlevar(qi); - qf_fill_buffer(qi, buf, old_last); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last); buf_inc_changedtick(buf); if (old_last == NULL) { @@ -3477,15 +3795,6 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) } } -// Set "w:quickfix_title" if "qi" has a title. -static void qf_set_title_var(qf_info_T *qi) -{ - if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) { - set_internal_string_var((char_u *)"w:quickfix_title", - qi->qf_lists[qi->qf_curlist].qf_title); - } -} - // Add an error line to the quickfix buffer. static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, char_u *dirname) @@ -3552,13 +3861,13 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, return OK; } -// Fill current buffer with quickfix errors, replacing any previous contents. -// curbuf must be the quickfix buffer! -// If "old_last" is not NULL append the items after this one. -// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() -// is used and autocommands will be triggered. -static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) - FUNC_ATTR_NONNULL_ARG(1, 2) +/// Fill current buffer with quickfix errors, replacing any previous contents. +/// curbuf must be the quickfix buffer! +/// If "old_last" is not NULL append the items after this one. +/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() +/// is used and autocommands will be triggered. +static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) + FUNC_ATTR_NONNULL_ARG(2) { linenr_T lnum; qfline_T *qfp; @@ -3577,20 +3886,20 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) } // Check if there is anything to display - if (!qf_stack_empty(qi)) { + if (qfl != NULL) { char_u dirname[MAXPATHL]; *dirname = NUL; // Add one line for each error if (old_last == NULL) { - qfp = qi->qf_lists[qi->qf_curlist].qf_start; + qfp = qfl->qf_start; lnum = 0; } else { qfp = old_last->qf_next; lnum = buf->b_ml.ml_line_count; } - while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { + while (lnum < qfl->qf_count) { if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) { break; } @@ -3633,9 +3942,9 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) KeyTyped = old_KeyTyped; } -static void qf_list_changed(qf_info_T *qi, int qf_idx) +static void qf_list_changed(qf_list_T *qfl) { - qi->qf_lists[qf_idx].qf_changedtick++; + qfl->qf_changedtick++; } /// Return the quickfix/location list number with the given identifier. @@ -3651,15 +3960,15 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) return INVALID_QFIDX; } -// If the current list is not "save_qfid" and we can find the list with that ID -// then make it the current list. -// This is used when autocommands may have changed the current list. -// Returns OK if successfully restored the list. Returns FAIL if the list with -// the specified identifier (save_qfid) is not found in the stack. +/// If the current list is not "save_qfid" and we can find the list with that ID +/// then make it the current list. +/// This is used when autocommands may have changed the current list. +/// Returns OK if successfully restored the list. Returns FAIL if the list with +/// the specified identifier (save_qfid) is not found in the stack. static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi->qf_lists[qi->qf_curlist].qf_id != save_qfid) { + if (qf_get_curlist(qi)->qf_id != save_qfid) { const int curlist = qf_id2nr(qi, save_qfid); if (curlist < 0) { // list is not present @@ -3678,7 +3987,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) return; } // Autocommands might have cleared the list, check for that - if (!qf_list_empty(qi, qi->qf_curlist)) { + if (!qf_list_empty(qf_get_curlist(qi))) { qf_jump(qi, 0, 0, forceit); } } @@ -3787,6 +4096,7 @@ void ex_make(exarg_T *eap) do_shell((char_u *)cmd, 0); + incr_quickfix_busy(); res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, @@ -3799,11 +4109,11 @@ void ex_make(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); @@ -3814,6 +4124,7 @@ void ex_make(exarg_T *eap) } cleanup: + decr_quickfix_busy(); os_remove((char *)fname); xfree(fname); xfree(cmd); @@ -3871,23 +4182,20 @@ static char_u *get_mef_name(void) size_t qf_get_size(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + qf_info_T *qi; + qf_list_T *qfl; + + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } int prev_fnum = 0; size_t sz = 0; qfline_T *qfp; - size_t i; - assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0); - for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start; - i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + int i; + assert(qf_get_curlist(qi)->qf_count >= 0); + qfl = qf_get_curlist(qi); + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (!qfp->qf_valid) { continue; } @@ -3910,18 +4218,14 @@ size_t qf_get_size(exarg_T *eap) size_t qf_get_cur_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } - assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0); - return (size_t)qi->qf_lists[qi->qf_curlist].qf_index; + assert(qf_get_curlist(qi)->qf_index >= 0); + return (size_t)qf_get_curlist(qi)->qf_index; } /// Returns the current index in the quickfix/location list, @@ -3930,20 +4234,16 @@ size_t qf_get_cur_idx(exarg_T *eap) int qf_get_cur_valid_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 1; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 1; } - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qf_list_T *qfl = qf_get_curlist(qi); // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } @@ -3978,24 +4278,20 @@ int qf_get_cur_valid_idx(exarg_T *eap) /// Used by :cdo, :ldo, :cfdo and :lfdo commands. /// For :cdo and :ldo, returns the 'n'th valid error entry. /// For :cfdo and :lfdo, returns the 'n'th valid file entry. -static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo) +static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) FUNC_ATTR_NONNULL_ALL { - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; - // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } int prev_fnum = 0; size_t eidx = 0; - size_t i; + int i; qfline_T *qfp; assert(qfl->qf_count >= 0); - for (i = 1, qfp = qfl->qf_start; - i <= (size_t)qfl->qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_valid) { if (fdo) { if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) { @@ -4013,24 +4309,18 @@ static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo) } } - return i <= (size_t)qfl->qf_count ? i : 1; + return i <= qfl->qf_count ? (size_t)i : 1; } -/* - * ":cc", ":crewind", ":cfirst" and ":clast". - * ":ll", ":lrewind", ":lfirst" and ":llast". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cc", ":crewind", ":cfirst" and ":clast". +/// ":ll", ":lrewind", ":lfirst" and ":llast". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cc(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; @@ -4065,8 +4355,9 @@ void ex_cc(exarg_T *eap) } else { n = 1; } - size_t valid_entry = qf_get_nth_valid_entry(qi, n, - eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); + size_t valid_entry = qf_get_nth_valid_entry( + qf_get_curlist(qi), n, + eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); assert(valid_entry <= INT_MAX); errornr = (int)valid_entry; } @@ -4074,21 +4365,15 @@ void ex_cc(exarg_T *eap) qf_jump(qi, 0, errornr, eap->forceit); } -/* - * ":cnext", ":cnfile", ":cNext" and ":cprevious". - * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cnext", ":cnfile", ":cNext" and ":cprevious". +/// ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cnext(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; @@ -4133,6 +4418,297 @@ void ex_cnext(exarg_T *eap) qf_jump(qi, dir, errornr, eap->forceit); } +/// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'. +/// The index of the entry is stored in 'errornr'. +/// Returns NULL if an entry is not found. +static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl, + int bnr, + int *errornr) +{ + qfline_T *qfp = NULL; + int idx = 0; + + // Find the first entry in this file + FOR_ALL_QFL_ITEMS(qfl, qfp, idx) { + if (qfp->qf_fnum == bnr) { + break; + } + } + + *errornr = idx; + return qfp; +} + +/// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the first entry. Assumes the entries are sorted in +/// the quickfix list by line number. +static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_prev != NULL + && entry->qf_fnum == entry->qf_prev->qf_fnum + && entry->qf_lnum == entry->qf_prev->qf_lnum) { + entry = entry->qf_prev; + (*errornr)--; + } + + return entry; +} + +/// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the last entry. Assumes the entries are sorted in +/// the quickfix list by line number. +static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_next != NULL + && entry->qf_fnum == entry->qf_next->qf_fnum + && entry->qf_lnum == entry->qf_next->qf_lnum) { + entry = entry->qf_next; + (*errornr)++; + } + + return entry; +} + +/// Find the first quickfix entry below line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found after 'lnum'. +static qfline_T *qf_find_entry_on_next_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + if (qfp->qf_lnum > lnum) { + // First entry is after line 'lnum' + return qfp; + } + + // Find the entry just before or at the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum <= lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) { + // No entries found after 'lnum' + return NULL; + } + + // Use the entry just after line 'lnum' + qfp = qfp->qf_next; + (*errornr)++; + + return qfp; +} + +/// Find the first quickfix entry before line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found before 'lnum'. +static qfline_T *qf_find_entry_on_prev_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + // Find the entry just before the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum < lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_lnum >= lnum) { // entry is after 'lnum' + return NULL; + } + + // If multiple entries are on the same line, then use the first entry + qfp = qf_find_first_entry_on_line(qfp, errornr); + + return qfp; +} + +/// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in +/// the direction 'dir'. +static qfline_T *qf_find_closest_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + int dir, + int *errornr) +{ + qfline_T *qfp; + + *errornr = 0; + + // Find the first entry in this file + qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr); + if (qfp == NULL) { + return NULL; // no entry in this file + } + + if (dir == FORWARD) { + qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr); + } else { + qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr); + } + + return qfp; +} + +/// Get the nth quickfix entry below the specified entry treating multiple +/// entries on a single line as one. Searches forward in the list. +static qfline_T *qf_get_nth_below_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + qfline_T *first_entry = entry; + int first_errornr = *errornr; + + // Treat all the entries on the same line in this file as one + entry = qf_find_last_entry_on_line(entry, errornr); + + if (entry->qf_next == NULL + || entry->qf_next->qf_fnum != entry->qf_fnum) { + // If multiple entries are on the same line, then use the first + // entry + entry = first_entry; + *errornr = first_errornr; + break; + } + + entry = entry->qf_next; + (*errornr)++; + } + + return entry; +} + +/// Get the nth quickfix entry above the specified entry treating multiple +/// entries on a single line as one. Searches backwards in the list. +static qfline_T *qf_get_nth_above_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + if (entry->qf_prev == NULL + || entry->qf_prev->qf_fnum != entry->qf_fnum) { + break; + } + + entry = entry->qf_prev; + (*errornr)--; + + // If multiple entries are on the same line, then use the first entry + entry = qf_find_first_entry_on_line(entry, errornr); + } + + return entry; +} + +/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the +/// specified direction. +/// Returns the error number in the quickfix list or 0 if an entry is not found. +static int qf_find_nth_adj_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + linenr_T n, + int dir) +{ + qfline_T *adj_entry; + int errornr; + + // Find an entry closest to the specified line + adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr); + if (adj_entry == NULL) { + return 0; + } + + if (--n > 0) { + // Go to the n'th entry in the current buffer + if (dir == FORWARD) { + adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n); + } else { + adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n); + } + } + + return errornr; +} + +/// Jump to a quickfix entry in the current file nearest to the current line. +/// ":cabove", ":cbelow", ":labove" and ":lbelow" commands +void ex_cbelow(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + int dir; + int buf_has_flag; + int errornr = 0; + + if (eap->addr_count > 0 && eap->line2 <= 0) { + EMSG(_(e_invrange)); + return; + } + + // Check whether the current buffer has any quickfix entries + if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow) { + buf_has_flag = BUF_HAS_QF_ENTRY; + } else { + buf_has_flag = BUF_HAS_LL_ENTRY; + } + if (!(curbuf->b_has_qf_entry & buf_has_flag)) { + EMSG(_(e_quickfix)); + return; + } + + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; + } + + qfl = qf_get_curlist(qi); + // check if the list has valid errors + if (!qf_list_has_valid_entries(qfl)) { + EMSG(_(e_quickfix)); + return; + } + + if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow) { + dir = FORWARD; + } else { + dir = BACKWARD; + } + + errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum, + eap->addr_count > 0 ? eap->line2 : 0, dir); + + if (errornr > 0) { + qf_jump(qi, 0, errornr, false); + } else { + EMSG(_(e_no_more_items)); + } +} + + +/// Return the autocmd name for the :cfile Ex commands +static char_u * cfile_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cfile: return (char_u *)"cfile"; + case CMD_cgetfile: return (char_u *)"cgetfile"; + case CMD_caddfile: return (char_u *)"caddfile"; + case CMD_lfile: return (char_u *)"lfile"; + case CMD_lgetfile: return (char_u *)"lgetfile"; + case CMD_laddfile: return (char_u *)"laddfile"; + default: return NULL; + } +} + + /* * ":cfile"/":cgetfile"/":caddfile" commands. * ":lfile"/":lgetfile"/":laddfile" commands. @@ -4143,15 +4719,7 @@ void ex_cfile(exarg_T *eap) qf_info_T *qi = &ql_info; char_u *au_name = NULL; - switch (eap->cmdidx) { - case CMD_cfile: au_name = (char_u *)"cfile"; break; - case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break; - case CMD_caddfile: au_name = (char_u *)"caddfile"; break; - case CMD_lfile: au_name = (char_u *)"lfile"; break; - case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break; - case CMD_laddfile: au_name = (char_u *)"laddfile"; break; - default: break; - } + au_name = cfile_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) { if (aborting()) { @@ -4168,6 +4736,8 @@ void ex_cfile(exarg_T *eap) wp = curwin; } + incr_quickfix_busy(); + // This function is used by the :cfile, :cgetfile and :caddfile // commands. // :cfile always creates a new quickfix list and jumps to the @@ -4182,13 +4752,14 @@ void ex_cfile(exarg_T *eap) if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + decr_quickfix_busy(); return; } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf); } @@ -4199,6 +4770,8 @@ void ex_cfile(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + + decr_quickfix_busy(); } /// Return the vimgrep autocmd name. @@ -4319,8 +4892,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, // 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, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -4333,8 +4905,8 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, NULL, // search pattern 0, // nr 0, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; break; } @@ -4396,7 +4968,8 @@ void ex_vimgrep(exarg_T *eap) char_u *s; char_u *p; int fi; - qf_info_T *qi = &ql_info; + qf_info_T *qi; + qf_list_T *qfl; win_T *wp = NULL; buf_T *buf; int duplicate_name = FALSE; @@ -4421,9 +4994,9 @@ void ex_vimgrep(exarg_T *eap) } } - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } if (eap->addr_count > 0) @@ -4473,9 +5046,11 @@ void ex_vimgrep(exarg_T *eap) * ":lcd %:p:h" changes the meaning of short path names. */ os_dirname(dirname_start, MAXPATHL); + incr_quickfix_busy(); + // Remember the current quickfix list identifier, so that we can check for // autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; seconds = (time_t)0; for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { @@ -4504,9 +5079,10 @@ void ex_vimgrep(exarg_T *eap) // buffer above, autocommands might have changed the quickfix list. if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) { FreeWild(fcount, fnames); + decr_quickfix_busy(); goto theend; } - save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + save_qfid = qf_get_curlist(qi)->qf_id; if (buf == NULL) { if (!got_int) @@ -4574,10 +5150,11 @@ void ex_vimgrep(exarg_T *eap) FreeWild(fcount, fnames); - 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); + qfl = qf_get_curlist(qi); + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); qf_update_buffer(qi, NULL); @@ -4587,16 +5164,14 @@ void ex_vimgrep(exarg_T *eap) // The QuickFixCmdPost autocmd may free the quickfix list. Check the list // is still valid. - if (!qflist_valid(wp, save_qfid)) { - goto theend; - } - - if (qf_restore_list(qi, save_qfid) == FAIL) { + if (!qflist_valid(wp, save_qfid) + || qf_restore_list(qi, save_qfid) == FAIL) { + decr_quickfix_busy(); goto theend; } // Jump to first match. - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { + if (!qf_list_empty(qf_get_curlist(qi))) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); @@ -4604,6 +5179,8 @@ void ex_vimgrep(exarg_T *eap) } else EMSG2(_(e_nomatch2), s); + decr_quickfix_busy(); + /* If we loaded a dummy buffer into the current window, the autocommands * may have messed up things, need to redraw and recompute folds. */ if (redraw_for_dummy) { @@ -4780,15 +5357,62 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) } } +/// Copy the specified quickfix entry items into a new dict and appened the dict +/// to 'list'. Returns OK on success. +static int get_qfline_items(qfline_T *qfp, list_T *list) +{ + char_u buf[2]; + int bufnum; + + // Handle entries with a non-existing buffer number. + bufnum = qfp->qf_fnum; + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { + bufnum = 0; + } + + dict_T *const dict = tv_dict_alloc(); + tv_list_append_dict(list, dict); + + buf[0] = qfp->qf_type; + buf[1] = NUL; + if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL + || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) + == FAIL) + || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL) + || (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 ? "" : (const char *)qfp->qf_pattern)) + == FAIL) + || (tv_dict_add_str( + dict, S_LEN("text"), + (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text)) + == FAIL) + || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL) + || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) + == FAIL)) { + // tv_dict_add* fail only if key already exist, but this is a newly + // allocated dictionary which is thus guaranteed to have no existing keys. + assert(false); + } + + return OK; +} + /// Add each quickfix error to list "list" as a dictionary. /// If qf_idx is -1, use the current list. Otherwise, use the specified list. -int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) +int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { - const qf_info_T *qi = qi_arg; - char_u buf[2]; + qf_info_T *qi = qi_arg; + qf_list_T *qfl; qfline_T *qfp; int i; - int bufnum; if (qi == NULL) { qi = &ql_info; @@ -4804,56 +5428,19 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) qf_idx = qi->qf_curlist; } - if (qf_idx >= qi->qf_listcount - || qi->qf_lists[qf_idx].qf_count == 0) { + if (qf_idx >= qi->qf_listcount) { return FAIL; } - qfp = qi->qf_lists[qf_idx].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) { - // Handle entries with a non-existing buffer number. - bufnum = qfp->qf_fnum; - if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) - bufnum = 0; - - dict_T *const dict = tv_dict_alloc(); - tv_list_append_dict(list, dict); - - buf[0] = qfp->qf_type; - buf[1] = NUL; - if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL - || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) - == FAIL) - || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) - == FAIL) - || (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 - ? "" - : (const char *)qfp->qf_pattern)) == FAIL - || tv_dict_add_str(dict, S_LEN("text"), - (qfp->qf_text == NULL - ? "" - : (const char *)qfp->qf_text)) == FAIL - || tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL - || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) - == FAIL)) { - // tv_dict_add* fail only if key already exist, but this is a newly - // allocated dictionary which is thus guaranteed to have no existing keys. - assert(false); - } + qfl = qf_get_list(qi, qf_idx); + if (qf_list_empty(qfl)) { + return FAIL; + } - qfp = qfp->qf_next; - if (qfp == NULL) { - break; - } + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + get_qfline_items(qfp, list); } + return OK; } @@ -4894,12 +5481,12 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) } list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = ll_new_list(); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { (void)get_errorlist(qi, NULL, 0, l); - qf_free(qi, 0); + qf_free(&qi->qf_lists[0]); } xfree(qi); @@ -5021,7 +5608,10 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what) } /// Return default values for quickfix list properties in retdict. -static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) +static int qf_getprop_defaults(qf_info_T *qi, + int flags, + int locstack, + dict_T *retdict) { int status = OK; @@ -5053,7 +5643,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) if ((status == OK) && (flags & QF_GETLIST_TICK)) { status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0); } - if ((status == OK) && (qi != &ql_info) && (flags & QF_GETLIST_FILEWINID)) { + if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); } @@ -5061,10 +5651,10 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) } /// Return the quickfix list title as 'title' in retdict -static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) +static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict) { return tv_dict_add_str(retdict, S_LEN("title"), - (const char *)qi->qf_lists[qf_idx].qf_title); + (const char *)qfl->qf_title); } // Returns the identifier of the window used to display files from a location @@ -5097,13 +5687,13 @@ static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict) } /// 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) +static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict) { int status; - if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + if (qfl->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); + tv_copy(qfl->qf_ctx, &di->di_tv); status = tv_dict_add(retdict, di); if (status == FAIL) { tv_dict_item_free(di); @@ -5115,15 +5705,15 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) 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) +/// Return the current quickfix list index as 'idx' in retdict +static int qf_getprop_idx(qf_list_T *qfl, 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; + int curidx = qfl->qf_index; + if (qf_list_empty(qfl)) { + // For empty lists, current index is set to 0 + curidx = 0; } - return tv_dict_add_nr(retdict, S_LEN("idx"), idx); + return tv_dict_add_nr(retdict, S_LEN("idx"), curidx); } /// Return quickfix/location list details (title) as a dictionary. @@ -5132,9 +5722,10 @@ static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + qf_list_T *qfl; dictitem_T *di = NULL; int status = OK; - int qf_idx; + int qf_idx = INVALID_QFIDX; if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { return qf_get_list_from_lines(what, di, retdict); @@ -5152,11 +5743,13 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) // List is not present or is empty if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) { - return qf_getprop_defaults(qi, flags, retdict); + return qf_getprop_defaults(qi, flags, wp != NULL, retdict); } + qfl = qf_get_list(qi, qf_idx); + if (flags & QF_GETLIST_TITLE) { - status = qf_getprop_title(qi, qf_idx, retdict); + status = qf_getprop_title(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_NR)) { status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1); @@ -5168,21 +5761,21 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = qf_getprop_items(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { - status = qf_getprop_ctx(qi, qf_idx, retdict); + status = qf_getprop_ctx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_ID)) { - status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id); + status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id); } if ((status == OK) && (flags & QF_GETLIST_IDX)) { - status = qf_getprop_idx(qi, qf_idx, retdict); + status = qf_getprop_idx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_SIZE)) { status = tv_dict_add_nr(retdict, S_LEN("size"), - qi->qf_lists[qf_idx].qf_count); + qfl->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); + qfl->qf_changedtick); } if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { status = qf_getprop_filewinid(wp, qi, retdict); @@ -5191,11 +5784,10 @@ 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'. +/// 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, + qf_list_T *qfl, const dict_T *d, bool first_entry) FUNC_ATTR_NONNULL_ALL @@ -5241,17 +5833,16 @@ static int qf_add_entry_from_dict( valid = tv_dict_get_number(d, "valid"); } - const int status = qf_add_entry(qi, - qf_idx, - NULL, // dir + const int status = qf_add_entry(qfl, + NULL, // dir (char_u *)filename, (char_u *)module, bufnum, (char_u *)text, lnum, col, - vcol, // vis_col - (char_u *)pattern, // search pattern + vcol, // vis_col + (char_u *)pattern, // search pattern nr, (char_u)(type == NULL ? NUL : *type), valid); @@ -5269,6 +5860,7 @@ static int qf_add_entry_from_dict( static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { + qf_list_T *qfl = qf_get_list(qi, qf_idx); qfline_T *old_last = NULL; int retval = OK; @@ -5276,12 +5868,13 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // make place for a new list qf_new_list(qi, title); qf_idx = qi->qf_curlist; - } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { + qfl = qf_get_list(qi, qf_idx); + } else if (action == 'a' && !qf_list_empty(qfl)) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qf_idx].qf_last; + old_last = qfl->qf_last; } else if (action == 'r') { - qf_free_items(qi, qf_idx); - qf_store_title(qi, qf_idx, title); + qf_free_items(qfl); + qf_store_title(qfl, title); } TV_LIST_ITER_CONST(list, li, { @@ -5294,22 +5887,22 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; } - retval = qf_add_entry_from_dict(qi, qf_idx, d, li == tv_list_first(list)); - if (retval == FAIL) { + retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list)); + if (retval == QF_FAIL) { break; } }); - if (qi->qf_lists[qf_idx].qf_index == 0) { + if (qfl->qf_index == 0) { // no valid entry - qi->qf_lists[qf_idx].qf_nonevalid = true; + qfl->qf_nonevalid = true; } else { - qi->qf_lists[qf_idx].qf_nonevalid = false; + qfl->qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; - if (qi->qf_lists[qf_idx].qf_count > 0) { - qi->qf_lists[qf_idx].qf_index = 1; + qfl->qf_ptr = qfl->qf_start; + if (!qf_list_empty(qfl)) { + qfl->qf_index = 1; } } @@ -5319,7 +5912,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, return retval; } -// Get the quickfix list index from 'nr' or 'id' +/// Get the quickfix list index from 'nr' or 'id' static int qf_setprop_get_qfidx( const qf_info_T *qi, const dict_T *what, @@ -5379,13 +5972,13 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what, const dictitem_T *di) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl = qf_get_list(qi, qf_idx); if (di->di_tv.v_type != VAR_STRING) { return FAIL; } - xfree(qi->qf_lists[qf_idx].qf_title); - qi->qf_lists[qf_idx].qf_title = - (char_u *)tv_dict_get_string(what, "title", true); + xfree(qfl->qf_title); + qfl->qf_title = (char_u *)tv_dict_get_string(what, "title", true); if (qf_idx == qi->qf_curlist) { qf_update_win_titlevar(qi); } @@ -5439,7 +6032,7 @@ static int qf_setprop_items_from_lines( } if (action == 'r') { - qf_free_items(qi, qf_idx); + qf_free_items(&qi->qf_lists[qf_idx]); } if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat, false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { @@ -5450,24 +6043,25 @@ static int qf_setprop_items_from_lines( } // Set quickfix list context. -static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di) +static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di) FUNC_ATTR_NONNULL_ALL { - tv_free(qi->qf_lists[qf_idx].qf_ctx); + tv_free(qfl->qf_ctx); typval_T *ctx = xcalloc(1, sizeof(typval_T)); tv_copy(&di->di_tv, ctx); - qi->qf_lists[qf_idx].qf_ctx = ctx; + qfl->qf_ctx = ctx; return OK; } -// Set quickfix/location list properties (title, items, context). -// Also used to add items from parsing a list of lines. -// Used by the setqflist() and setloclist() Vim script functions. +/// Set quickfix/location list properties (title, items, context). +/// Also used to add items from parsing a list of lines. +/// Used by the setqflist() and setloclist() Vim script functions. static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char_u *title) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl; dictitem_T *di; int retval = FAIL; bool newlist = action == ' ' || qf_stack_empty(qi); @@ -5482,6 +6076,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, qf_idx = qi->qf_curlist; } + qfl = qf_get_list(qi, qf_idx); if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) { retval = qf_setprop_title(qi, qf_idx, what, di); } @@ -5492,17 +6087,18 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action); } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - retval = qf_setprop_context(qi, qf_idx, di); + retval = qf_setprop_context(qfl, di); } if (retval == OK) { - qf_list_changed(qi, qf_idx); + qf_list_changed(qfl); } return retval; } -// Find the non-location list window with the specified location list. +/// Find the non-location list window with the specified location list stack in +/// the current tabpage. static win_T * find_win_with_ll(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -5523,7 +6119,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) if (qfwin != NULL) { // If the quickfix/location list window is open, then clear it if (qi->qf_curlist < qi->qf_listcount) { - qf_free(qi, qi->qf_curlist); + qf_free(qf_get_curlist(qi)); } qf_update_buffer(qi, NULL); } @@ -5547,15 +6143,14 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) } else if (IS_LL_WINDOW(orig_wp)) { // If the location list window is open, then create a new empty location // list - qf_info_T *new_ll = ll_new_list(); + qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); // first free the list reference in the location list window ll_free_all(&orig_wp->w_llist_ref); orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { - llwin->w_llist = new_ll; - new_ll->qf_refcount++; + win_set_loclist(wp, new_ll); } } } @@ -5576,15 +6171,22 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, if (action == 'f') { // Free the entire quickfix or location list stack qf_free_stack(wp, qi); - } else if (what != NULL) { + return OK; + } + + incr_quickfix_busy(); + + if (what != NULL) { 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); + qf_list_changed(qf_get_curlist(qi)); } } + decr_quickfix_busy(); + return retval; } @@ -5635,6 +6237,62 @@ bool set_ref_in_quickfix(int copyID) return abort; } +/// Return the autocmd name for the :cbuffer Ex commands +static char_u * cbuffer_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cbuffer: return (char_u *)"cbuffer"; + case CMD_cgetbuffer: return (char_u *)"cgetbuffer"; + case CMD_caddbuffer: return (char_u *)"caddbuffer"; + case CMD_lbuffer: return (char_u *)"lbuffer"; + case CMD_lgetbuffer: return (char_u *)"lgetbuffer"; + case CMD_laddbuffer: return (char_u *)"laddbuffer"; + default: return NULL; + } +} + +/// Process and validate the arguments passed to the :cbuffer, :caddbuffer, +/// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands. +static int cbuffer_process_args(exarg_T *eap, + buf_T **bufp, + linenr_T *line1, + linenr_T *line2) +{ + buf_T *buf = NULL; + + if (*eap->arg == NUL) + buf = curbuf; + else if (*skipwhite(skipdigits(eap->arg)) == NUL) + buf = buflist_findnr(atoi((char *)eap->arg)); + + if (buf == NULL) { + EMSG(_(e_invarg)); + return FAIL; + } + + if (buf->b_ml.ml_mfp == NULL) { + EMSG(_("E681: Buffer is not loaded")); + return FAIL; + } + + if (eap->addr_count == 0) { + eap->line1 = 1; + 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) { + EMSG(_(e_invrange)); + return FAIL; + } + + *line1 = eap->line1; + *line2 = eap->line2; + *bufp = buf; + + return OK; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. @@ -5646,34 +6304,15 @@ bool set_ref_in_quickfix(int copyID) void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; - qf_info_T *qi = &ql_info; - const char *au_name = NULL; + qf_info_T *qi; + char_u *au_name = NULL; win_T *wp = NULL; + char_u *qf_title; + linenr_T line1; + linenr_T line2; - switch (eap->cmdidx) { - case CMD_cbuffer: - au_name = "cbuffer"; - break; - case CMD_cgetbuffer: - au_name = "cgetbuffer"; - break; - case CMD_caddbuffer: - au_name = "caddbuffer"; - break; - case CMD_lbuffer: - au_name = "lbuffer"; - break; - case CMD_lgetbuffer: - au_name = "lgetbuffer"; - break; - case CMD_laddbuffer: - au_name = "laddbuffer"; - break; - default: - break; - } - - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + au_name = cbuffer_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; @@ -5681,109 +6320,93 @@ void ex_cbuffer(exarg_T *eap) } // Must come after autocommands. - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } - if (*eap->arg == NUL) - buf = curbuf; - else if (*skipwhite(skipdigits(eap->arg)) == NUL) - buf = buflist_findnr(atoi((char *)eap->arg)); - if (buf == NULL) - EMSG(_(e_invarg)); - else if (buf->b_ml.ml_mfp == NULL) - EMSG(_("E681: Buffer is not loaded")); - else { - if (eap->addr_count == 0) { - eap->line1 = 1; - 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) { - EMSG(_(e_invrange)); - } else { - char_u *qf_title = qf_cmdtitle(*eap->cmdlinep); + if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) { + return; + } - if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", - (char *)qf_title, (char *)buf->b_sfname); - qf_title = IObuff; - } + qf_title = qf_cmdtitle(*eap->cmdlinep); - 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); - } - // Remember the current quickfix list identifier, so that we can - // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; - if (au_name != NULL) { - const buf_T *const curbuf_old = curbuf; - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, - curbuf->b_fname, true, curbuf); - if (curbuf != curbuf_old) { - // Autocommands changed buffer, don't jump now, "qi" may - // be invalid. - res = 0; - } - } - // Jump to the first error for new list and if autocmds didn't - // free the list. - if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) - && qflist_valid(wp, save_qfid)) { - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - } + if (buf->b_sfname) { + vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", + (char *)qf_title, (char *)buf->b_sfname); + qf_title = IObuff; + } + + incr_quickfix_busy(); + + 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 (qf_stack_empty(qi)) { + decr_quickfix_busy(); + return; + } + if (res >= 0) { + qf_list_changed(qf_get_curlist(qi)); + } + // Remember the current quickfix list identifier, so that we can + // check for autocommands changing the current quickfix list. + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) { + const buf_T *const curbuf_old = curbuf; + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, true, curbuf); + if (curbuf != curbuf_old) { + // Autocommands changed buffer, don't jump now, "qi" may + // be invalid. + res = 0; } } + // Jump to the first error for new list and if autocmds didn't + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) + && qflist_valid(wp, save_qfid)) { + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + } + + decr_quickfix_busy(); } -/* - * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. - * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. - */ +/// Return the autocmd name for the :cexpr Ex commands. +static char_u * cexpr_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cexpr: return (char_u *)"cexpr"; + case CMD_cgetexpr: return (char_u *)"cgetexpr"; + case CMD_caddexpr: return (char_u *)"caddexpr"; + case CMD_lexpr: return (char_u *)"lexpr"; + case CMD_lgetexpr: return (char_u *)"lgetexpr"; + case CMD_laddexpr: return (char_u *)"laddexpr"; + default: return NULL; + } +} + +/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. +/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. void ex_cexpr(exarg_T *eap) { - qf_info_T *qi = &ql_info; - const char *au_name = NULL; + qf_info_T *qi; + char_u *au_name = NULL; win_T *wp = NULL; - switch (eap->cmdidx) { - case CMD_cexpr: - au_name = "cexpr"; - break; - case CMD_cgetexpr: - au_name = "cgetexpr"; - break; - case CMD_caddexpr: - au_name = "caddexpr"; - break; - case CMD_lexpr: - au_name = "lexpr"; - break; - case CMD_lgetexpr: - au_name = "lgetexpr"; - break; - case CMD_laddexpr: - au_name = "laddexpr"; - break; - default: - break; - } - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + au_name = cexpr_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; } } - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } /* Evaluate the expression. When the result is a string or a list we can @@ -5792,19 +6415,24 @@ void ex_cexpr(exarg_T *eap) if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) || tv.v_type == VAR_LIST) { + incr_quickfix_busy(); 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 (qf_stack_empty(qi)) { + decr_quickfix_busy(); + goto cleanup; + } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); } // Jump to the first error for a new list and if autocmds didn't @@ -5815,9 +6443,11 @@ void ex_cexpr(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + decr_quickfix_busy(); } else { EMSG(_("E777: String or List expected")); } +cleanup: tv_clear(&tv); } } @@ -5844,7 +6474,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) } if (qi == NULL) { // Allocate a new location list for help text matches - qi = ll_new_list(); + qi = qf_alloc_stack(QFLT_LOCATION); *new_ll = true; } @@ -5875,8 +6505,7 @@ static void hgr_search_file( line[--l] = NUL; } - if (qf_add_entry(qi, - qi->qf_curlist, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -5888,8 +6517,8 @@ static void hgr_search_file( NULL, // search pattern 0, // nr 1, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; if (line != IObuff) { xfree(line); @@ -5984,6 +6613,8 @@ void ex_helpgrep(exarg_T *eap) qi = hgr_get_ll(&new_qi); } + incr_quickfix_busy(); + // Check for a specified language char_u *const lang = check_help_lang(eap->arg); regmatch_T regmatch = { @@ -5998,10 +6629,12 @@ void ex_helpgrep(exarg_T *eap) vim_regfree(regmatch.regprog); - 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_T *qfl = qf_get_curlist(qi); + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); + qf_update_buffer(qi, NULL); } if (p_cpo == empty_option) { @@ -6011,23 +6644,24 @@ void ex_helpgrep(exarg_T *eap) 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 && IS_LL_STACK(qi) && qf_find_buf(qi) == NULL) { // autocommands made "qi" invalid + decr_quickfix_busy(); return; } } - /* Jump to first match. */ - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) - qf_jump(qi, 0, 0, FALSE); - else + // Jump to first match. + if (!qf_list_empty(qf_get_curlist(qi))) { + qf_jump(qi, 0, 0, false); + } else { EMSG2(_(e_nomatch2), eap->arg); + } + + decr_quickfix_busy(); if (eap->cmdidx == CMD_lhelpgrep) { // If the help window is not opened or if it already points to the @@ -6042,3 +6676,4 @@ void ex_helpgrep(exarg_T *eap) } } + diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 31b0c0cd2c..8949b3d968 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -37,6 +37,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xhelpgrep helpgrep <args> command! -nargs=0 -count Xcc <count>cc + command! -count=1 -nargs=0 Xbelow <mods><count>cbelow + command! -count=1 -nargs=0 Xabove <mods><count>cabove let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') call setqflist([], 'f') @@ -70,6 +72,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xhelpgrep lhelpgrep <args> command! -nargs=0 -count Xcc <count>ll + command! -count=1 -nargs=0 Xbelow <mods><count>lbelow + command! -count=1 -nargs=0 Xabove <mods><count>labove let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) call setloclist(0, [], 'f') @@ -163,6 +167,12 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + " No location list for the current window + call assert_fails('lolder', 'E776:') + call assert_fails('lnewer', 'E776:') + endif + let list = [{'bufnr': bufnr('%'), 'lnum': 1}] call g:Xsetlist(list) @@ -561,6 +571,8 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') + " Invalid regular expression + call assert_fails('Xhelpgrep \@<!', 'E480:') endfunc func Test_helpgrep() @@ -1066,8 +1078,8 @@ func Test_efm2() set efm=%f:%s cexpr 'Xtestfile:Line search text' let l = getqflist() - call assert_equal(l[0].pattern, '^\VLine search text\$') - call assert_equal(l[0].lnum, 0) + call assert_equal('^\VLine search text\$', l[0].pattern) + call assert_equal(0, l[0].lnum) let l = split(execute('clist', ''), "\n") call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) @@ -3358,7 +3370,28 @@ func Test_lexpr_crash() augroup QF_Test au! augroup END + + enew | only + augroup QF_Test + au! + au BufNew * call setloclist(0, [], 'f') + augroup END + lexpr 'x:1:x' + augroup QF_Test + au! + augroup END + enew | only + lexpr '' + lopen + augroup QF_Test + au! + au FileType * call setloclist(0, [], 'f') + augroup END + lexpr '' + augroup QF_Test + au! + augroup END endfunc " The following test used to crash Vim @@ -3809,4 +3842,110 @@ func Test_viscol() call delete('Xfile1') endfunc +" Test for the :cbelow, :cabove, :lbelow and :labove commands. +func Xtest_below(cchar) + call s:setup_commands(a:cchar) + + " No quickfix/location list + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + " Empty quickfix/location list + call g:Xsetlist([]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + call s:create_test_file('X1') + call s:create_test_file('X2') + call s:create_test_file('X3') + call s:create_test_file('X4') + + " Invalid entries + edit X1 + call g:Xsetlist(["E1", "E2"]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + call assert_fails('3Xbelow', 'E42:') + call assert_fails('4Xabove', 'E42:') + + " Test the commands with various arguments + Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"] + edit +7 X2 + Xabove + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal 2j + Xbelow + call assert_equal(['X2', 10], [bufname(''), line('.')]) + " Last error in this file + Xbelow 99 + call assert_equal(['X2', 15], [bufname(''), line('.')]) + call assert_fails('Xbelow', 'E553:') + " First error in this file + Xabove 99 + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal gg + Xbelow 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + edit X4 + call assert_fails('Xabove', 'E42:') + call assert_fails('Xbelow', 'E42:') + if a:cchar == 'l' + " If a buffer has location list entries from some other window but not + " from the current window, then the commands should fail. + edit X1 | split | call setloclist(0, [], 'f') + call assert_fails('Xabove', 'E776:') + call assert_fails('Xbelow', 'E776:') + close + endif + + " Test for lines with multiple quickfix entries + Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3", + \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3", + \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] + edit +1 X2 + Xbelow 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal gg + Xbelow 99 + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 99 + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xabove + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xbelow + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + + " Invalid range + if a:cchar == 'c' + call assert_fails('-2cbelow', 'E553:') + " TODO: should go to first error in the current line? + 0cabove + else + call assert_fails('-2lbelow', 'E553:') + " TODO: should go to first error in the current line? + 0labove + endif + + call delete('X1') + call delete('X2') + call delete('X3') + call delete('X4') +endfunc + +func Test_cbelow() + call Xtest_below('c') + call Xtest_below('l') +endfunc + " vim: shiftwidth=2 sts=2 expandtab |