diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 08:50:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 08:50:31 +0000 |
commit | aed8ce9da277f5ecffe968b324f242c41c3b752a (patch) | |
tree | d2e538394cb7a8a7c42a4aac6ccf1a8e3256999b /src/quickfix.c | |
parent | Initial commit. (diff) | |
download | vim-aed8ce9da277f5ecffe968b324f242c41c3b752a.tar.xz vim-aed8ce9da277f5ecffe968b324f242c41c3b752a.zip |
Adding upstream version 2:9.0.1378.upstream/2%9.0.1378upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/quickfix.c')
-rw-r--r-- | src/quickfix.c | 8637 |
1 files changed, 8637 insertions, 0 deletions
diff --git a/src/quickfix.c b/src/quickfix.c new file mode 100644 index 0000000..63dd541 --- /dev/null +++ b/src/quickfix.c @@ -0,0 +1,8637 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * quickfix.c: functions for quickfix mode, using a file with error messages + */ + +#include "vim.h" + +#if defined(FEAT_QUICKFIX) || defined(PROTO) + +struct dir_stack_T +{ + struct dir_stack_T *next; + char_u *dirname; +}; + +/* + * For each error the next struct is allocated and linked in a list. + */ +typedef struct qfline_S qfline_T; +struct qfline_S +{ + qfline_T *qf_next; // pointer to next error in the list + qfline_T *qf_prev; // pointer to previous error in the list + linenr_T qf_lnum; // line number where the error occurred + linenr_T qf_end_lnum; // line number when the error has range or zero + int qf_fnum; // file number for the line + int qf_col; // column where the error occurred + int qf_end_col; // column when the error has range or zero + int qf_nr; // error number + char_u *qf_module; // module name for this error + char_u *qf_pattern; // search pattern for the error + char_u *qf_text; // description of the error + char_u qf_viscol; // set to TRUE if qf_col and qf_end_col is + // screen column + char_u qf_cleared; // set to TRUE if line has been deleted + char_u qf_type; // type of the error (mostly 'E'); 1 for + // :helpgrep + char_u qf_valid; // valid error message detected +}; + +/* + * There is a stack of error lists. + */ +#define LISTCOUNT 10 +#define INVALID_QFIDX (-1) +#define INVALID_QFBUFNR (0) + +/* + * 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 + * Contains a list of entries (qfline_T). qf_start points to the first entry + * and qf_last points to the last entry. qf_count contains the list size. + * + * Usually the list contains one or more entries. But an empty list can be + * created using setqflist()/setloclist() with a title and/or user context + * information and entries can be added later using setqflist()/setloclist(). + */ +typedef struct qf_list_S +{ + int_u 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 + int qf_count; // number of errors (0 means empty list) + int qf_index; // current index in the error list + int qf_nonevalid; // TRUE if not a single valid entry found + char_u *qf_title; // title derived from the command that created + // the error list or set by setqflist + typval_T *qf_ctx; // context set by setqflist/setloclist + callback_T qf_qftf_cb; // 'quickfixtextfunc' callback function + + struct dir_stack_T *qf_dir_stack; + char_u *qf_directory; + struct dir_stack_T *qf_file_stack; + char_u *qf_currfile; + int qf_multiline; + int qf_multiignore; + int qf_multiscan; + long qf_changedtick; +} qf_list_T; + +/* + * Quickfix/Location list stack definition + * Contains a list of quickfix/location lists (qf_list_T) + */ +struct qf_info_S +{ + // Count of references to this list. Used only for location lists. + // When a location list window reference this list, qf_refcount + // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount + // reaches 0, the list is freed. + int qf_refcount; + 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 + int qf_bufnr; // quickfix window buffer number +}; + +static qf_info_T ql_info; // global quickfix list +static int_u last_qf_id = 0; // Last used quickfix list id + +#define FMT_PATTERNS 13 // maximum number of % recognized + +/* + * Structure used to hold the info of one part of 'errorformat' + */ +typedef struct efm_S efm_T; +struct efm_S +{ + regprog_T *prog; // pre-formatted part of 'errorformat' + efm_T *next; // pointer to next (NULL if last) + char_u addr[FMT_PATTERNS]; // indices of used % patterns + char_u prefix; // prefix of this format line: + // 'D' enter directory + // 'X' leave directory + // 'A' start of multi-line message + // 'E' error message + // 'W' warning message + // 'I' informational message + // 'N' note message + // 'C' continuation line + // 'Z' end of multi-line message + // 'G' general, unspecific message + // 'P' push file (partial) message + // 'Q' pop/quit file (partial) message + // 'O' overread (partial) message + char_u flags; // additional flags given in prefix + // '-' do not include this line + // '+' include whole line in message + 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; +static qf_delq_T *qf_delq_head = NULL; + +// Counter to prevent autocmds from freeing up location lists when they are +// still being used. +static int quickfix_busy = 0; + +static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls + +// callback function for 'quickfixtextfunc' +static callback_T qftf_cb; + +static void qf_new_list(qf_info_T *qi, char_u *qf_title); +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, long end_lnum, int col, int end_col, int vis_col, char_u *pattern, int nr, int type, int valid); +static void qf_free(qf_list_T *qfl); +static char_u *qf_types(int, int); +static int qf_get_fnum(qf_list_T *qfl, char_u *, char_u *); +static char_u *qf_push_dir(char_u *, struct dir_stack_T **, int is_file_stack); +static char_u *qf_pop_dir(struct dir_stack_T **); +static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *); +static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, int newwin); +static void qf_fmt_text(garray_T *gap, char_u *text); +static void qf_range_text(garray_T *gap, qfline_T *qfp); +static int qf_win_pos_update(qf_info_T *qi, int old_qf_index); +static win_T *qf_find_win(qf_info_T *qi); +static buf_T *qf_find_buf(qf_info_T *qi); +static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last); +static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int qf_winid); +static buf_T *load_dummy_buffer(char_u *fname, char_u *dirname_start, char_u *resulting_dir); +static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start); +static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start); +static qf_info_T *ll_get_or_alloc_list(win_T *); + +// 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)->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' + * For location list window, return the referenced location list + */ +#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; \ + !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; +static bufref_T qf_last_bufref = {NULL, 0, 0}; + +static garray_T qfga; + +/* + * Get a growarray to buffer text in. Shared between various commands to avoid + * many alloc/free calls. + */ + static garray_T * +qfga_get(void) +{ + static int initialized = FALSE; + + if (!initialized) + { + initialized = TRUE; + ga_init2(&qfga, 1, 256); + } + + // Reset the length to zero. Retain ga_data from previous use to avoid + // many alloc/free calls. + qfga.ga_len = 0; + + return &qfga; +} + +/* + * The "qfga" grow array buffer is reused across multiple quickfix commands as + * a temporary buffer to reduce the number of alloc/free calls. But if the + * buffer size is large, then to avoid holding on to that memory, clear the + * grow array. Otherwise just reset the grow array length. + */ + static void +qfga_clear(void) +{ + if (qfga.ga_maxlen > 1000) + ga_clear(&qfga); + else + qfga.ga_len = 0; +} + +/* + * Maximum number of bytes allowed per line while reading a errorfile. + */ +#define LINE_MAXLEN 4096 + +/* + * Patterns used. Keep in sync with qf_parse_fmt[]. + */ +static struct fmtpattern +{ + char_u convchar; + char *pattern; +} fmt_pat[FMT_PATTERNS] = + { + {'f', ".\\+"}, // only used when at end + {'n', "\\d\\+"}, // 1 + {'l', "\\d\\+"}, // 2 + {'e', "\\d\\+"}, // 3 + {'c', "\\d\\+"}, // 4 + {'k', "\\d\\+"}, // 5 + {'t', "."}, // 6 +#define FMT_PATTERN_M 7 + {'m', ".\\+"}, // 7 +#define FMT_PATTERN_R 8 + {'r', ".*"}, // 8 + {'p', "[- .]*"}, // 9 + {'v', "\\d\\+"}, // 10 + {'s', ".\\+"}, // 11 + {'o', ".\\+"} // 12 + }; + +/* + * 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( + char_u *efmpat, + char_u *regpat, + efm_T *efminfo, + int idx, + int round) +{ + char_u *srcptr; + + if (efminfo->addr[idx]) + { + // Each errorformat pattern can occur only once + semsg(_(e_too_many_chr_in_format_string), *efmpat); + return NULL; + } + if ((idx && idx < FMT_PATTERN_R + && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) + || (idx == FMT_PATTERN_R + && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) + { + semsg(_(e_unexpected_chr_in_format_str), *efmpat); + return NULL; + } + efminfo->addr[idx] = (char_u)++round; + *regpat++ = '\\'; + *regpat++ = '('; +#ifdef BACKSLASH_IN_FILENAME + if (*efmpat == 'f') + { + // Also match "c:" in the file name, even when + // checking for a colon next: "%f:". + // "\%(\a:\)\=" + STRCPY(regpat, "\\%(\\a:\\)\\="); + regpat += 10; + } +#endif + 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(regpat, ".\\{-1,}"); + regpat += 7; + } + else + { + // File name followed by '\\' or '%': include as + // many file name chars as possible. + STRCPY(regpat, "\\f\\+"); + regpat += 4; + } + } + else + { + srcptr = (char_u *)fmt_pat[idx].pattern; + while ((*regpat = *srcptr++) != NUL) + ++regpat; + } + *regpat++ = '\\'; + *regpat++ = ')'; + + return 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( + char_u **pefmp, + char_u *efm, + int len, + char_u *regpat) +{ + char_u *efmp = *pefmp; + + if (*efmp == '[' || *efmp == '\\') + { + if ((*regpat++ = *efmp) == '[') // %*[^a-z0-9] etc. + { + if (efmp[1] == '^') + *regpat++ = *++efmp; + if (efmp < efm + len) + { + *regpat++ = *++efmp; // could be ']' + while (efmp < efm + len + && (*regpat++ = *++efmp) != ']') + // skip ; + if (efmp == efm + len) + { + emsg(_(e_missing_rsb_in_format_string)); + return NULL; + } + } + } + else if (efmp < efm + len) // %*\D, %*\s etc. + *regpat++ = *++efmp; + *regpat++ = '\\'; + *regpat++ = '+'; + } + else + { + // TODO: scanf()-like: %*ud, %*3c, %*f, ... ? + semsg(_(e_unsupported_chr_in_format_string), *efmp); + return NULL; + } + + *pefmp = efmp; + + return regpat; +} + +/* + * Analyze/parse an errorformat prefix. + */ + static char_u * +efm_analyze_prefix(char_u *efmp, efm_T *efminfo) +{ + if (vim_strchr((char_u *)"+-", *efmp) != NULL) + efminfo->flags = *efmp++; + if (vim_strchr((char_u *)"DXAEWINCZGOPQ", *efmp) != NULL) + efminfo->prefix = *efmp; + else + { + semsg(_(e_invalid_chr_in_format_string_prefix), *efmp); + return NULL; + } + + return efmp; +} + +/* + * Converts a 'errorformat' string part in 'efm' to a regular expression + * pattern. The resulting regex pattern is returned in "regpat". Additional + * information about the 'erroformat' pattern is returned in "fmt_ptr". + * Returns OK or FAIL. + */ + static int +efm_to_regpat( + char_u *efm, + int len, + efm_T *fmt_ptr, + char_u *regpat) +{ + char_u *ptr; + char_u *efmp; + int round; + int idx = 0; + + // Build a regexp pattern for a 'errorformat' option part + ptr = regpat; + *ptr++ = '^'; + round = 0; + for (efmp = efm; efmp < efm + len; ++efmp) + { + if (*efmp == '%') + { + ++efmp; + for (idx = 0; idx < FMT_PATTERNS; ++idx) + if (fmt_pat[idx].convchar == *efmp) + break; + if (idx < FMT_PATTERNS) + { + ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round); + if (ptr == NULL) + return FAIL; + round++; + } + else if (*efmp == '*') + { + ++efmp; + ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr); + if (ptr == NULL) + return FAIL; + } + else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) + *ptr++ = *efmp; // regexp magic characters + else if (*efmp == '#') + *ptr++ = '*'; + else if (*efmp == '>') + fmt_ptr->conthere = TRUE; + else if (efmp == efm + 1) // analyse prefix + { + // prefix is allowed only at the beginning of the errorformat + // option part + efmp = efm_analyze_prefix(efmp, fmt_ptr); + if (efmp == NULL) + return FAIL; + } + else + { + semsg(_(e_invalid_chr_in_format_string), *efmp); + return FAIL; + } + } + else // copy normal character + { + if (*efmp == '\\' && efmp + 1 < efm + len) + ++efmp; + else if (vim_strchr((char_u *)".*^$~[", *efmp) != NULL) + *ptr++ = '\\'; // escape regexp atoms + if (*efmp) + *ptr++ = *efmp; + } + } + *ptr++ = '$'; + *ptr = NUL; + + return OK; +} + +/* + * Free the 'errorformat' information list + */ + static void +free_efm_list(efm_T **efm_first) +{ + efm_T *efm_ptr; + + for (efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) + { + *efm_first = efm_ptr->next; + vim_regfree(efm_ptr->prog); + vim_free(efm_ptr); + } + fmt_start = NULL; +} + +/* + * Compute the size of the buffer used to convert a 'errorformat' pattern into + * a regular expression pattern. + */ + static int +efm_regpat_bufsz(char_u *efm) +{ + int sz; + int i; + + sz = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2); + for (i = FMT_PATTERNS; i > 0; ) + sz += (int)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; + efm_T *fmt_first = NULL; + efm_T *fmt_last = NULL; + char_u *fmtstr = NULL; + int len; + int sz; + + // Each part of the format string is copied and modified from errorformat + // to regex prog. Only a few % characters are allowed. + + // Get some space to modify the format string into. + sz = efm_regpat_bufsz(efm); + if ((fmtstr = alloc_id(sz, aid_qf_efm_fmtstr)) == NULL) + goto parse_efm_error; + + while (efm[0] != NUL) + { + // Allocate a new eformat structure and put it at the end of the list + fmt_ptr = ALLOC_CLEAR_ONE_ID(efm_T, aid_qf_efm_fmtpart); + if (fmt_ptr == NULL) + goto parse_efm_error; + if (fmt_first == NULL) // first one + fmt_first = fmt_ptr; + else + fmt_last->next = fmt_ptr; + fmt_last = fmt_ptr; + + // Isolate one part in the 'errorformat' option + len = efm_option_part_len(efm); + + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr) == FAIL) + goto parse_efm_error; + if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) + goto parse_efm_error; + // Advance to next part + efm = skip_to_option_part(efm + len); // skip comma and spaces + } + + if (fmt_first == NULL) // nothing found + emsg(_(e_errorformat_contains_no_pattern)); + + goto parse_efm_end; + +parse_efm_error: + free_efm_list(&fmt_first); + +parse_efm_end: + vim_free(fmtstr); + + return fmt_first; +} + +enum { + QF_FAIL = 0, + QF_OK = 1, + QF_END_OF_INPUT = 2, + QF_NOMEM = 3, + QF_IGNORE_LINE = 4, + QF_MULTISCAN = 5, + QF_ABORT = 6 +}; + +/* + * State information used to parse lines and add entries to a quickfix/location + * list. + */ +typedef struct { + char_u *linebuf; + int linelen; + char_u *growbuf; + int growbufsiz; + FILE *fd; + typval_T *tv; + char_u *p_str; + listitem_T *p_li; + buf_T *buf; + linenr_T buflnum; + linenr_T lnumlast; + vimconv_T vc; +} qfstate_T; + +/* + * Allocate more memory for the line buffer used for parsing lines. + */ + static char_u * +qf_grow_linebuf(qfstate_T *state, int newsz) +{ + char_u *p; + + // If the line exceeds LINE_MAXLEN exclude the last + // byte since it's not a NL character. + state->linelen = newsz > LINE_MAXLEN ? LINE_MAXLEN - 1 : newsz; + if (state->growbuf == NULL) + { + state->growbuf = alloc_id(state->linelen + 1, aid_qf_linebuf); + if (state->growbuf == NULL) + return NULL; + state->growbufsiz = state->linelen; + } + else if (state->linelen > state->growbufsiz) + { + if ((p = vim_realloc(state->growbuf, state->linelen + 1)) == NULL) + return NULL; + state->growbuf = p; + state->growbufsiz = state->linelen; + } + return state->growbuf; +} + +/* + * Get the next string (separated by newline) from state->p_str. + */ + static int +qf_get_next_str_line(qfstate_T *state) +{ + // Get the next line from the supplied string + char_u *p_str = state->p_str; + char_u *p; + int len; + + if (*p_str == NUL) // Reached the end of the string + return QF_END_OF_INPUT; + + p = vim_strchr(p_str, '\n'); + if (p != NULL) + len = (int)(p - p_str) + 1; + else + len = (int)STRLEN(p_str); + + if (len > IOSIZE - 2) + { + state->linebuf = qf_grow_linebuf(state, len); + if (state->linebuf == NULL) + return QF_NOMEM; + } + else + { + state->linebuf = IObuff; + state->linelen = len; + } + vim_strncpy(state->linebuf, p_str, state->linelen); + + // Increment using len in order to discard the rest of the + // line if it exceeds LINE_MAXLEN. + p_str += len; + state->p_str = p_str; + + return QF_OK; +} + +/* + * Get the next string from the List item state->p_li. + */ + static int +qf_get_next_list_line(qfstate_T *state) +{ + listitem_T *p_li = state->p_li; + int len; + + while (p_li != NULL + && (p_li->li_tv.v_type != VAR_STRING + || p_li->li_tv.vval.v_string == NULL)) + p_li = p_li->li_next; // Skip non-string items + + if (p_li == NULL) // End of the list + { + state->p_li = NULL; + return QF_END_OF_INPUT; + } + + len = (int)STRLEN(p_li->li_tv.vval.v_string); + if (len > IOSIZE - 2) + { + state->linebuf = qf_grow_linebuf(state, len); + if (state->linebuf == NULL) + return QF_NOMEM; + } + else + { + state->linebuf = IObuff; + state->linelen = len; + } + + vim_strncpy(state->linebuf, p_li->li_tv.vval.v_string, state->linelen); + + state->p_li = p_li->li_next; // next item + return QF_OK; +} + +/* + * Get the next string from state->buf. + */ + static int +qf_get_next_buf_line(qfstate_T *state) +{ + char_u *p_buf = NULL; + int len; + + // Get the next line from the supplied buffer + if (state->buflnum > state->lnumlast) + return QF_END_OF_INPUT; + + p_buf = ml_get_buf(state->buf, state->buflnum, FALSE); + state->buflnum += 1; + + len = (int)STRLEN(p_buf); + if (len > IOSIZE - 2) + { + state->linebuf = qf_grow_linebuf(state, len); + if (state->linebuf == NULL) + return QF_NOMEM; + } + else + { + state->linebuf = IObuff; + state->linelen = len; + } + vim_strncpy(state->linebuf, p_buf, state->linelen); + + return QF_OK; +} + +/* + * Get the next string from file state->fd. + */ + static int +qf_get_next_file_line(qfstate_T *state) +{ + int discard; + int growbuflen; + + if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) + return QF_END_OF_INPUT; + + discard = FALSE; + state->linelen = (int)STRLEN(IObuff); + if (state->linelen == IOSIZE - 1 && !(IObuff[state->linelen - 1] == '\n')) + { + // The current line exceeds IObuff, continue reading using + // growbuf until EOL or LINE_MAXLEN bytes is read. + if (state->growbuf == NULL) + { + state->growbufsiz = 2 * (IOSIZE - 1); + state->growbuf = alloc_id(state->growbufsiz, aid_qf_linebuf); + if (state->growbuf == NULL) + return QF_NOMEM; + } + + // Copy the read part of the line, excluding null-terminator + memcpy(state->growbuf, IObuff, IOSIZE - 1); + growbuflen = state->linelen; + + for (;;) + { + char_u *p; + + if (fgets((char *)state->growbuf + growbuflen, + state->growbufsiz - growbuflen, state->fd) == NULL) + break; + state->linelen = (int)STRLEN(state->growbuf + growbuflen); + growbuflen += state->linelen; + if ((state->growbuf)[growbuflen - 1] == '\n') + break; + if (state->growbufsiz == LINE_MAXLEN) + { + discard = TRUE; + break; + } + + state->growbufsiz = 2 * state->growbufsiz < LINE_MAXLEN + ? 2 * state->growbufsiz : LINE_MAXLEN; + if ((p = vim_realloc(state->growbuf, state->growbufsiz)) == NULL) + return QF_NOMEM; + state->growbuf = p; + } + + while (discard) + { + // The current line is longer than LINE_MAXLEN, continue + // reading but discard everything until EOL or EOF is + // reached. + if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL + || (int)STRLEN(IObuff) < IOSIZE - 1 + || IObuff[IOSIZE - 2] == '\n') + break; + } + + state->linebuf = state->growbuf; + state->linelen = growbuflen; + } + else + state->linebuf = IObuff; + + // Convert a line if it contains a non-ASCII character. + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) + { + char_u *line; + + line = string_convert(&state->vc, state->linebuf, &state->linelen); + if (line != NULL) + { + if (state->linelen < IOSIZE) + { + STRCPY(state->linebuf, line); + vim_free(line); + } + else + { + vim_free(state->growbuf); + state->linebuf = state->growbuf = line; + state->growbufsiz = state->linelen < LINE_MAXLEN + ? state->linelen : LINE_MAXLEN; + } + } + } + + return QF_OK; +} + +/* + * Get the next string from a file/buffer/list/string. + */ + static int +qf_get_nextline(qfstate_T *state) +{ + int status = QF_FAIL; + + if (state->fd == NULL) + { + if (state->tv != NULL) + { + if (state->tv->v_type == VAR_STRING) + // Get the next line from the supplied string + status = qf_get_next_str_line(state); + else if (state->tv->v_type == VAR_LIST) + // Get the next line from the supplied list + status = qf_get_next_list_line(state); + } + else + // Get the next line from the supplied buffer + status = qf_get_next_buf_line(state); + } + else + // Get the next line from the supplied file + status = qf_get_next_file_line(state); + + if (status != QF_OK) + return status; + + // remove newline/CR from the line + if (state->linelen > 0 && state->linebuf[state->linelen - 1] == '\n') + { + state->linebuf[state->linelen - 1] = NUL; +#ifdef USE_CRNL + if (state->linelen > 1 && state->linebuf[state->linelen - 2] == '\r') + state->linebuf[state->linelen - 2] = NUL; +#endif + } + + remove_bom(state->linebuf); + + return QF_OK; +} + +typedef struct { + char_u *namebuf; + char_u *module; + char_u *errmsg; + int errmsglen; + long lnum; + long end_lnum; + int col; + int end_col; + char_u use_viscol; + char_u *pattern; + int enr; + int type; + int valid; +} qffields_T; + +/* + * 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) +{ + int 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 + && mch_getperm(fields->namebuf) == -1) + 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 end line number ('%e') pattern in regmatch. + * Return the matched value in "fields->end_lnum". + */ + static int +qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) + return QF_FAIL; + fields->end_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 end column number ('%k') pattern in regmatch. + * Return the matched value in "fields->end_col". + */ + static int +qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) + return QF_FAIL; + fields->end_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; +} + +/* + * Copy a non-error line into the error string. Return the matched line in + * "fields->errmsg". + */ + static int +copy_nonerror_line(char_u *linebuf, int linelen, qffields_T *fields) +{ + char_u *p; + + if (linelen >= fields->errmsglen) + { + // linelen + null terminator + if ((p = vim_realloc(fields->errmsg, linelen + 1)) == NULL) + return QF_NOMEM; + fields->errmsg = p; + fields->errmsglen = linelen + 1; + } + // copy whole line to error message + vim_strncpy(fields->errmsg, linebuf, linelen); + + 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) +{ + char_u *p; + int len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) + return QF_FAIL; + len = (int)(rmp->endp[midx] - rmp->startp[midx]); + if (len >= fields->errmsglen) + { + // len + null terminator + if ((p = vim_realloc(fields->errmsg, len + 1)) == NULL) + return QF_NOMEM; + fields->errmsg = p; + fields->errmsglen = len + 1; + } + vim_strncpy(fields->errmsg, rmp->startp[midx], len); + 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) +{ + int len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) + return QF_FAIL; + len = (int)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE - 5) + len = CMDBUFFSIZE - 5; + STRCPY(fields->pattern, "^\\V"); + STRNCAT(fields->pattern, rmp->startp[midx], len); + 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) +{ + int len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) + return QF_FAIL; + len = (int)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE) + len = CMDBUFFSIZE; + STRNCAT(fields->module, rmp->startp[midx], len); + 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. + * Keep in sync with fmt_pat[]. + */ +static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = +{ + NULL, // %f + qf_parse_fmt_n, + qf_parse_fmt_l, + qf_parse_fmt_e, + qf_parse_fmt_c, + qf_parse_fmt_k, + qf_parse_fmt_t, + qf_parse_fmt_m, + NULL, // %r + qf_parse_fmt_p, + qf_parse_fmt_v, + qf_parse_fmt_s, + qf_parse_fmt_o +}; + +/* + * Parse the error format pattern matches in "regmatch" and set the values in + * "fields". fmt_ptr contains the 'efm' format specifiers/prefixes that have a + * match. Returns QF_OK if all the matches are successfully parsed. On + * failure, returns QF_FAIL or QF_NOMEM. + */ + static int +qf_parse_match( + char_u *linebuf, + int linelen, + efm_T *fmt_ptr, + regmatch_T *regmatch, + qffields_T *fields, + int qf_multiline, + int qf_multiscan, + char_u **tail) +{ + int idx = fmt_ptr->prefix; + int i; + int midx; + int status; + + if ((idx == 'C' || idx == 'Z') && !qf_multiline) + return QF_FAIL; + if (vim_strchr((char_u *)"EWIN", idx) != NULL) + fields->type = idx; + else + fields->type = 0; + + // Extract error message data from matched line. + // We check for an actual submatch, because "\[" and "\]" in + // the 'errorformat' may cause the wrong submatch to be used. + 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 == FMT_PATTERN_M) + { + if (fmt_ptr->flags == '+' && !qf_multiscan) // %+ + status = copy_nonerror_line(linebuf, linelen, fields); + else if (midx > 0) // %m + status = qf_parse_fmt_m(regmatch, midx, fields); + } + else if (i == FMT_PATTERN_R && midx > 0) // %r + status = qf_parse_fmt_r(regmatch, midx, tail); + else if (midx > 0) // others + status = (qf_parse_fmt[i])(regmatch, midx, fields); + + if (status != QF_OK) + return status; + } + + return QF_OK; +} + +/* + * Parse an error line in 'linebuf' using a single error format string in + * 'fmt_ptr->prog' and return the matching values in 'fields'. + * Returns QF_OK if the efm format matches completely and the fields are + * successfully copied. Otherwise returns QF_FAIL or QF_NOMEM. + */ + static int +qf_parse_get_fields( + char_u *linebuf, + int linelen, + efm_T *fmt_ptr, + qffields_T *fields, + int qf_multiline, + int qf_multiscan, + char_u **tail) +{ + regmatch_T regmatch; + int status = QF_FAIL; + int r; + + if (qf_multiscan && + vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL) + return QF_FAIL; + + fields->namebuf[0] = NUL; + fields->module[0] = NUL; + fields->pattern[0] = NUL; + if (!qf_multiscan) + fields->errmsg[0] = NUL; + fields->lnum = 0; + fields->end_lnum = 0; + fields->col = 0; + fields->end_col = 0; + fields->use_viscol = FALSE; + fields->enr = -1; + fields->type = 0; + *tail = NULL; + + // Always ignore case when looking for a matching error. + regmatch.rm_ic = TRUE; + regmatch.regprog = fmt_ptr->prog; + r = vim_regexec(®match, linebuf, (colnr_T)0); + fmt_ptr->prog = regmatch.regprog; + if (r) + status = qf_parse_match(linebuf, linelen, fmt_ptr, ®match, + fields, qf_multiline, qf_multiscan, tail); + + return status; +} + +/* + * Parse directory error format prefixes (%D and %X). + * Push and pop directories from the directory stack when scanning directory + * names. + */ + static int +qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl) +{ + if (idx == 'D') // enter directory + { + if (*fields->namebuf == NUL) + { + emsg(_(e_missing_or_empty_directory_name)); + return QF_FAIL; + } + qfl->qf_directory = + qf_push_dir(fields->namebuf, &qfl->qf_dir_stack, FALSE); + if (qfl->qf_directory == NULL) + return QF_FAIL; + } + else if (idx == 'X') // leave directory + qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack); + + return QF_OK; +} + +/* + * Parse global file name error format prefixes (%O, %P and %Q). + */ + static int +qf_parse_file_pfx( + int idx, + qffields_T *fields, + qf_list_T *qfl, + char_u *tail) +{ + fields->valid = FALSE; + if (*fields->namebuf == NUL || mch_getperm(fields->namebuf) >= 0) + { + if (*fields->namebuf && idx == 'P') + qfl->qf_currfile = + qf_push_dir(fields->namebuf, &qfl->qf_file_stack, TRUE); + else if (idx == 'Q') + qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack); + *fields->namebuf = NUL; + if (tail && *tail) + { + STRMOVE(IObuff, skipwhite(tail)); + qfl->qf_multiscan = TRUE; + return QF_MULTISCAN; + } + } + + return QF_OK; +} + +/* + * Parse a non-error line (a line which doesn't match any of the error + * format in 'efm'). + */ + static int +qf_parse_line_nomatch(char_u *linebuf, int linelen, qffields_T *fields) +{ + fields->namebuf[0] = NUL; // no match found, remove file name + fields->lnum = 0; // don't jump to this line + fields->valid = FALSE; + + return copy_nonerror_line(linebuf, linelen, fields); +} + +/* + * Parse multi-line error format prefixes (%C and %Z) + */ + static int +qf_parse_multiline_pfx( + int idx, + qf_list_T *qfl, + qffields_T *fields) +{ + char_u *ptr; + int len; + + if (!qfl->qf_multiignore) + { + qfline_T *qfprev = qfl->qf_last; + + if (qfprev == NULL) + return QF_FAIL; + if (*fields->errmsg && !qfl->qf_multiignore) + { + len = (int)STRLEN(qfprev->qf_text); + ptr = alloc_id(len + STRLEN(fields->errmsg) + 2, + aid_qf_multiline_pfx); + if (ptr == NULL) + return QF_FAIL; + STRCPY(ptr, qfprev->qf_text); + vim_free(qfprev->qf_text); + qfprev->qf_text = ptr; + *(ptr += len) = '\n'; + STRCPY(++ptr, fields->errmsg); + } + if (qfprev->qf_nr == -1) + qfprev->qf_nr = fields->enr; + if (vim_isprintc(fields->type) && !qfprev->qf_type) + // only printable chars allowed + qfprev->qf_type = fields->type; + + if (!qfprev->qf_lnum) + qfprev->qf_lnum = fields->lnum; + if (!qfprev->qf_end_lnum) + qfprev->qf_end_lnum = fields->end_lnum; + if (!qfprev->qf_col) + { + qfprev->qf_col = fields->col; + qfprev->qf_viscol = fields->use_viscol; + } + if (!qfprev->qf_end_col) + qfprev->qf_end_col = fields->end_col; + if (!qfprev->qf_fnum) + qfprev->qf_fnum = qf_get_fnum(qfl, + qfl->qf_directory, + *fields->namebuf || qfl->qf_directory != NULL + ? fields->namebuf + : qfl->qf_currfile != NULL && fields->valid + ? qfl->qf_currfile : 0); + } + if (idx == 'Z') + qfl->qf_multiline = qfl->qf_multiignore = FALSE; + line_breakcheck(); + + return QF_IGNORE_LINE; +} + +/* + * Parse a line and get the quickfix fields. + * Return the QF_ status. + */ + static int +qf_parse_line( + qf_list_T *qfl, + char_u *linebuf, + int linelen, + efm_T *fmt_first, + qffields_T *fields) +{ + efm_T *fmt_ptr; + int idx = 0; + char_u *tail = NULL; + int status; + +restofline: + // If there was no %> item start at the first pattern + if (fmt_start == NULL) + fmt_ptr = fmt_first; + else + { + // Otherwise start from the last used pattern + fmt_ptr = fmt_start; + fmt_start = NULL; + } + + // Try to match each part of 'errorformat' until we find a complete + // match or no match. + fields->valid = TRUE; + for ( ; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) + { + idx = fmt_ptr->prefix; + status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, + qfl->qf_multiline, qfl->qf_multiscan, &tail); + if (status == QF_NOMEM) + return status; + if (status == QF_OK) + break; + } + qfl->qf_multiscan = FALSE; + + if (fmt_ptr == NULL || idx == 'D' || idx == 'X') + { + if (fmt_ptr != NULL) + { + // 'D' and 'X' directory specifiers + status = qf_parse_dir_pfx(idx, fields, qfl); + if (status != QF_OK) + return status; + } + + status = qf_parse_line_nomatch(linebuf, linelen, fields); + if (status != QF_OK) + return status; + + if (fmt_ptr == NULL) + qfl->qf_multiline = qfl->qf_multiignore = FALSE; + } + else if (fmt_ptr != NULL) + { + // honor %> item + if (fmt_ptr->conthere) + fmt_start = fmt_ptr; + + if (vim_strchr((char_u *)"AEWIN", idx) != NULL) + { + qfl->qf_multiline = TRUE; // start of a multi-line message + qfl->qf_multiignore = FALSE;// reset continuation + } + else if (vim_strchr((char_u *)"CZ", idx) != NULL) + { // continuation of multi-line msg + status = qf_parse_multiline_pfx(idx, qfl, fields); + if (status != QF_OK) + return status; + } + else if (vim_strchr((char_u *)"OPQ", idx) != NULL) + { // global file names + status = qf_parse_file_pfx(idx, fields, qfl, tail); + if (status == QF_MULTISCAN) + goto restofline; + } + if (fmt_ptr->flags == '-') // generally exclude this line + { + if (qfl->qf_multiline) + // also exclude continuation lines + qfl->qf_multiignore = TRUE; + return QF_IGNORE_LINE; + } + } + + return QF_OK; +} + +/* + * Returns TRUE if the specified quickfix/location stack is empty + */ + static int +qf_stack_empty(qf_info_T *qi) +{ + return qi == NULL || qi->qf_listcount <= 0; +} + +/* + * Returns TRUE if the specified quickfix/location list is empty. + */ + static int +qf_list_empty(qf_list_T *qfl) +{ + return qfl == NULL || qfl->qf_count <= 0; +} + +/* + * Returns TRUE if the specified quickfix/location list is not empty and + * has valid entries. + */ + static int +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]; +} + +/* + * Allocate the fields used for parsing lines and populating a quickfix list. + */ + static int +qf_alloc_fields(qffields_T *pfields) +{ + pfields->namebuf = alloc_id(CMDBUFFSIZE + 1, aid_qf_namebuf); + pfields->module = alloc_id(CMDBUFFSIZE + 1, aid_qf_module); + pfields->errmsglen = CMDBUFFSIZE + 1; + pfields->errmsg = alloc_id(pfields->errmsglen, aid_qf_errmsg); + pfields->pattern = alloc_id(CMDBUFFSIZE + 1, aid_qf_pattern); + if (pfields->namebuf == NULL || pfields->errmsg == NULL + || pfields->pattern == NULL || pfields->module == NULL) + return FAIL; + + return OK; +} + +/* + * Free the fields used for parsing lines and populating a quickfix list. + */ + static void +qf_free_fields(qffields_T *pfields) +{ + vim_free(pfields->namebuf); + vim_free(pfields->module); + vim_free(pfields->errmsg); + vim_free(pfields->pattern); +} + +/* + * Setup the state information used for parsing lines and populating a + * quickfix list. + */ + static int +qf_setup_state( + qfstate_T *pstate, + char_u *enc, + char_u *efile, + typval_T *tv, + buf_T *buf, + linenr_T lnumfirst, + linenr_T lnumlast) +{ + pstate->vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) + convert_setup(&pstate->vc, enc, p_enc); + + if (efile != NULL && (pstate->fd = mch_fopen((char *)efile, "r")) == NULL) + { + semsg(_(e_cant_open_errorfile_str), efile); + return FAIL; + } + + if (tv != NULL) + { + if (tv->v_type == VAR_STRING) + pstate->p_str = tv->vval.v_string; + else if (tv->v_type == VAR_LIST) + pstate->p_li = tv->vval.v_list->lv_first; + pstate->tv = tv; + } + pstate->buf = buf; + pstate->buflnum = lnumfirst; + pstate->lnumlast = lnumlast; + + return OK; +} + +/* + * Cleanup the state information used for parsing lines and populating a + * quickfix list. + */ + static void +qf_cleanup_state(qfstate_T *pstate) +{ + if (pstate->fd != NULL) + fclose(pstate->fd); + + vim_free(pstate->growbuf); + if (pstate->vc.vc_type != CONV_NONE) + convert_setup(&pstate->vc, NULL, 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->end_lnum, + fields->col, + fields->end_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. + * Alternative: when "efile" is NULL read errors from buffer "buf". + * Alternative: when "tv" is not NULL get errors from the string or list. + * Always use 'errorformat' from "buf" if there is a local value. + * Then "lnumfirst" and "lnumlast" specify the range of lines to use. + * Set the title of the list to "qf_title". + * Return -1 for error, number of errors for success. + */ + static int +qf_init_ext( + qf_info_T *qi, + int qf_idx, + char_u *efile, + buf_T *buf, + typval_T *tv, + char_u *errorformat, + int newlist, // TRUE: start a new error list + linenr_T lnumfirst, // first line number to use + linenr_T lnumlast, // last line number to use + char_u *qf_title, + char_u *enc) +{ + qf_list_T *qfl; + qfstate_T state; + qffields_T fields; + qfline_T *old_last = NULL; + int adding = FALSE; + static efm_T *fmt_first = NULL; + char_u *efm; + static char_u *last_efm = NULL; + int retval = -1; // default: return error flag + int status; + + // Do not used the cached buffer, it may have been wiped out. + VIM_CLEAR(qf_last_bufname); + + CLEAR_FIELD(state); + CLEAR_FIELD(fields); + if ((qf_alloc_fields(&fields) == FAIL) || + (qf_setup_state(&state, enc, efile, tv, buf, + lnumfirst, lnumlast) == FAIL)) + goto qf_init_end; + + if (newlist || qf_idx == qi->qf_listcount) + { + // make place for a new list + qf_new_list(qi, qf_title); + qf_idx = qi->qf_curlist; + qfl = qf_get_list(qi, qf_idx); + } + else + { + // Adding to existing list, use last entry. + adding = TRUE; + qfl = qf_get_list(qi, qf_idx); + if (!qf_list_empty(qfl)) + old_last = qfl->qf_last; + } + + // Use the local value of 'errorformat' if it's set. + if (errorformat == p_efm && tv == NULL && *buf->b_p_efm != NUL) + efm = buf->b_p_efm; + else + efm = errorformat; + + // If the errorformat didn't change between calls, then reuse the + // previously parsed values. + if (last_efm == NULL || (STRCMP(last_efm, efm) != 0)) + { + // free the previously parsed data + VIM_CLEAR(last_efm); + free_efm_list(&fmt_first); + + // parse the current 'efm' + fmt_first = parse_efm_option(efm); + if (fmt_first != NULL) + last_efm = vim_strsave(efm); + } + + if (fmt_first == NULL) // nothing found + goto error2; + + // got_int is reset here, because it was probably set when killing the + // ":make" command, but we still want to read the errorfile then. + got_int = FALSE; + + // Read the lines in the error file one by one. + // Try to recognize one of the error formats in each line. + while (!got_int) + { + status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); + if (status == QF_NOMEM) // memory alloc failure + goto qf_init_end; + if (status == QF_END_OF_INPUT) // end of input + break; + if (status == QF_FAIL) + goto error2; + + line_breakcheck(); + } + if (state.fd == NULL || !ferror(state.fd)) + { + if (qfl->qf_index == 0) + { + // no valid entry found + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qfl->qf_nonevalid = TRUE; + } + else + { + qfl->qf_nonevalid = FALSE; + if (qfl->qf_ptr == NULL) + qfl->qf_ptr = qfl->qf_start; + } + // return number of matches + retval = qfl->qf_count; + goto qf_init_end; + } + emsg(_(e_error_while_reading_errorfile)); +error2: + if (!adding) + { + // Error when creating a new list. Free the new list + qf_free(qfl); + qi->qf_listcount--; + if (qi->qf_curlist > 0) + --qi->qf_curlist; + } +qf_init_end: + if (qf_idx == qi->qf_curlist) + qf_update_buffer(qi, old_last); + qf_cleanup_state(&state); + qf_free_fields(&fields); + + return retval; +} + +/* + * Read the errorfile "efile" into memory, line by line, building the error + * list. Set the error list's title to qf_title. + * Return -1 for error, number of errors for success. + */ + int +qf_init(win_T *wp, + char_u *efile, + char_u *errorformat, + int newlist, // TRUE: start a new error list + char_u *qf_title, + char_u *enc) +{ + qf_info_T *qi = &ql_info; + + if (wp != NULL) + { + qi = ll_get_or_alloc_list(wp); + if (qi == NULL) + return FAIL; + } + + return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat, + newlist, (linenr_T)0, (linenr_T)0, qf_title, enc); +} + +/* + * Set the title of the specified quickfix list. Frees the previous title. + * Prepends ':' to the title. + */ + static void +qf_store_title(qf_list_T *qfl, char_u *title) +{ + VIM_CLEAR(qfl->qf_title); + + if (title == NULL) + return; + + char_u *p = alloc_id(STRLEN(title) + 2, aid_qf_title); + + qfl->qf_title = p; + if (p != NULL) + STRCPY(p, title); +} + +/* + * The title of a quickfix/location list is set, by default, to the command + * that created the quickfix list with the ":" prefix. + * Create a quickfix list title string by prepending ":" to a user command. + * Returns a pointer to a static buffer with the title. + */ + static char_u * +qf_cmdtitle(char_u *cmd) +{ + static char_u qftitle_str[IOSIZE]; + + vim_snprintf((char *)qftitle_str, IOSIZE, ":%s", (char *)cmd); + return qftitle_str; +} + +/* + * 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, 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->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->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++; + qfl = qf_get_curlist(qi); + CLEAR_POINTER(qfl); + qf_store_title(qfl, qf_title); + qfl->qfl_type = qi->qfl_type; + qfl->qf_id = ++last_qf_id; +} + +/* + * Queue location list stack delete request. + */ + static void +locstack_queue_delreq(qf_info_T *qi) +{ + qf_delq_T *q; + + q = ALLOC_ONE(qf_delq_T); + if (q == NULL) + return; + + q->qi = qi; + q->next = qf_delq_head; + qf_delq_head = q; +} + +/* + * Return the global quickfix stack window buffer number. + */ + int +qf_stack_get_bufnr(void) +{ + return ql_info.qf_bufnr; +} + +/* + * Wipe the quickfix window buffer (if present) for the specified + * quickfix/location list. + */ + static void +wipe_qf_buffer(qf_info_T *qi) +{ + buf_T *qfbuf; + + if (qi->qf_bufnr == INVALID_QFBUFNR) + return; + + qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL && qfbuf->b_nwindows == 0) + { + // If the quickfix buffer is not loaded in any window, then + // wipe the buffer. + close_buffer(NULL, qfbuf, DOBUF_WIPE, FALSE, FALSE); + qi->qf_bufnr = INVALID_QFBUFNR; + } +} + +/* + * Free a location list stack + */ + static void +ll_free_all(qf_info_T **pqi) +{ + int i; + qf_info_T *qi; + + qi = *pqi; + if (qi == NULL) + return; + *pqi = NULL; // Remove reference to this 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); + return; + } + + qi->qf_refcount--; + if (qi->qf_refcount < 1) + { + // No references to this location list. + // If the quickfix window buffer is loaded, then wipe it + wipe_qf_buffer(qi); + + for (i = 0; i < qi->qf_listcount; ++i) + qf_free(qf_get_list(qi, i)); + vim_free(qi); + } +} + +/* + * Free all the quickfix/location lists in the stack. + */ + void +qf_free_all(win_T *wp) +{ + int i; + qf_info_T *qi = &ql_info; + + if (wp != NULL) + { + // 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(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) +{ + 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); + vim_free(q); + } + } +#ifdef ABORT_ON_INTERNAL_ERROR + if (quickfix_busy < 0) + { + emsg("quickfix_busy has become negative"); + abort(); + } +#endif +} + +#if defined(EXITFREE) || defined(PROTO) + void +check_quickfix_busy(void) +{ + if (quickfix_busy != 0) + { + semsg("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. + * Returns QF_OK on success or QF_FAIL on a memory allocation failure. + */ + static int +qf_add_entry( + qf_list_T *qfl, // quickfix list entry + char_u *dir, // optional directory name + char_u *fname, // file name or NULL + char_u *module, // module name or NULL + int bufnum, // buffer number or zero + char_u *mesg, // message + long lnum, // line number + long end_lnum, // line number for end + int col, // column + int end_col, // column for end + int vis_col, // using visual column + char_u *pattern, // search pattern + int nr, // error number + int type, // type character + int valid) // valid entry +{ + qfline_T *qfp; + qfline_T **lastp; // pointer to qf_last or NULL + + if ((qfp = ALLOC_ONE_ID(qfline_T, aid_qf_qfline)) == NULL) + return QF_FAIL; + if (bufnum != 0) + { + buf_T *buf = buflist_findnr(bufnum); + + qfp->qf_fnum = bufnum; + if (buf != NULL) + buf->b_has_qf_entry |= + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + } + else + qfp->qf_fnum = qf_get_fnum(qfl, dir, fname); + if ((qfp->qf_text = vim_strsave(mesg)) == NULL) + { + vim_free(qfp); + return QF_FAIL; + } + qfp->qf_lnum = lnum; + qfp->qf_end_lnum = end_lnum; + qfp->qf_col = col; + qfp->qf_end_col = end_col; + qfp->qf_viscol = vis_col; + if (pattern == NULL || *pattern == NUL) + qfp->qf_pattern = NULL; + else if ((qfp->qf_pattern = vim_strsave(pattern)) == NULL) + { + vim_free(qfp->qf_text); + vim_free(qfp); + return QF_FAIL; + } + if (module == NULL || *module == NUL) + qfp->qf_module = NULL; + else if ((qfp->qf_module = vim_strsave(module)) == NULL) + { + vim_free(qfp->qf_text); + vim_free(qfp->qf_pattern); + vim_free(qfp); + return QF_FAIL; + } + qfp->qf_nr = nr; + if (type != 1 && !vim_isprintc(type)) // only printable chars allowed + type = 0; + qfp->qf_type = type; + qfp->qf_valid = valid; + + lastp = &qfl->qf_last; + if (qf_list_empty(qfl)) // first element in the list + { + qfl->qf_start = qfp; + qfl->qf_ptr = qfp; + qfl->qf_index = 0; + qfp->qf_prev = NULL; + } + else + { + qfp->qf_prev = *lastp; + (*lastp)->qf_next = qfp; + } + qfp->qf_next = NULL; + qfp->qf_cleared = FALSE; + *lastp = qfp; + ++qfl->qf_count; + if (qfl->qf_index == 0 && qfp->qf_valid) // first valid entry + { + qfl->qf_index = qfl->qf_count; + qfl->qf_ptr = qfp; + } + + return QF_OK; +} + +/* + * Allocate a new quickfix/location list stack + */ + static qf_info_T * +qf_alloc_stack(qfltype_T qfltype) +{ + qf_info_T *qi; + + qi = ALLOC_CLEAR_ONE_ID(qf_info_T, aid_qf_qfinfo); + if (qi == NULL) + return NULL; + + qi->qf_refcount++; + qi->qfl_type = qfltype; + qi->qf_bufnr = INVALID_QFBUFNR; + return qi; +} + +/* + * 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)) + // For a location list window, use the referenced location list + return wp->w_llist_ref; + + // For a non-location list window, w_llist_ref should not point to a + // location list. + ll_free_all(&wp->w_llist_ref); + + if (wp->w_llist == NULL) + wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list + return wp->w_llist; +} + +/* + * 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_no_location_list)); + 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(qf_list_T *from_qfl, qf_list_T *to_qfl) +{ + int i; + qfline_T *from_qfp; + qfline_T *prevp; + + // copy all the location entries in this list + FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) + { + if (qf_add_entry(to_qfl, + NULL, + NULL, + from_qfp->qf_module, + 0, + from_qfp->qf_text, + from_qfp->qf_lnum, + from_qfp->qf_end_lnum, + from_qfp->qf_col, + from_qfp->qf_end_col, + from_qfp->qf_viscol, + from_qfp->qf_pattern, + from_qfp->qf_nr, + 0, + from_qfp->qf_valid) == QF_FAIL) + return FAIL; + + // qf_add_entry() will not set the qf_num field, as the + // directory and file names are not supplied. So the qf_fnum + // field is copied here. + prevp = to_qfl->qf_last; + prevp->qf_fnum = from_qfp->qf_fnum; // file number + prevp->qf_type = from_qfp->qf_type; // error type + if (from_qfl->qf_ptr == from_qfp) + to_qfl->qf_ptr = prevp; // current location + } + + return OK; +} + +/* + * Copy the specified location list 'from_qfl' to 'to_qfl'. + */ + static int +copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl) +{ + // 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; + to_qfl->qf_start = NULL; + to_qfl->qf_last = NULL; + to_qfl->qf_ptr = NULL; + if (from_qfl->qf_title != NULL) + to_qfl->qf_title = vim_strsave(from_qfl->qf_title); + else + to_qfl->qf_title = NULL; + if (from_qfl->qf_ctx != NULL) + { + to_qfl->qf_ctx = alloc_tv(); + if (to_qfl->qf_ctx != NULL) + copy_tv(from_qfl->qf_ctx, to_qfl->qf_ctx); + } + else + to_qfl->qf_ctx = NULL; + if (from_qfl->qf_qftf_cb.cb_name != NULL) + copy_callback(&to_qfl->qf_qftf_cb, &from_qfl->qf_qftf_cb); + else + to_qfl->qf_qftf_cb.cb_name = NULL; + + if (from_qfl->qf_count) + if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) + return FAIL; + + to_qfl->qf_index = from_qfl->qf_index; // current index in the list + + // Assign a new ID for the location list + to_qfl->qf_id = ++last_qf_id; + to_qfl->qf_changedtick = 0L; + + // When no valid entries are present in the list, qf_ptr points to + // the first item in the list + if (to_qfl->qf_nonevalid) + { + to_qfl->qf_ptr = to_qfl->qf_start; + to_qfl->qf_index = 1; + } + + return OK; +} + +/* + * Copy the location list stack 'from' window to 'to' window. + */ + void +copy_loclist_stack(win_T *from, win_T *to) +{ + qf_info_T *qi; + int idx; + + // When copying from a location list window, copy the referenced + // location list. For other windows, copy the location list for + // that window. + if (IS_LL_WINDOW(from)) + qi = from->w_llist_ref; + else + qi = from->w_llist; + + if (qi == NULL) // no location list to copy + return; + + // allocate a new location list + if ((to->w_llist = qf_alloc_stack(QFLT_LOCATION)) == NULL) + return; + + to->w_llist->qf_listcount = qi->qf_listcount; + + // Copy the location lists one at a time + for (idx = 0; idx < qi->qf_listcount; ++idx) + { + to->w_llist->qf_curlist = idx; + + if (copy_loclist(qf_get_list(qi, idx), + qf_get_list(to->w_llist, idx)) == FAIL) + { + qf_free_all(to); + return; + } + } + + 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_list_T *qfl, char_u *directory, char_u *fname) +{ + char_u *ptr = NULL; + buf_T *buf; + char_u *bufname; + + if (fname == NULL || *fname == NUL) // no file name + return 0; + +#ifdef VMS + vms_remove_version(fname); +#endif +#ifdef BACKSLASH_IN_FILENAME + if (directory != NULL) + slash_adjust(directory); + slash_adjust(fname); +#endif + if (directory != NULL && !vim_isAbsName(fname) + && (ptr = concat_fnames(directory, fname, TRUE)) != NULL) + { + // Here we check if the file really exists. + // This should normally be true, but if make works without + // "leaving directory"-messages we might have missed a + // directory change. + if (mch_getperm(ptr) < 0) + { + vim_free(ptr); + directory = qf_guess_filepath(qfl, fname); + if (directory) + ptr = concat_fnames(directory, fname, TRUE); + else + ptr = vim_strsave(fname); + } + // Use concatenated directory name and file name + bufname = ptr; + } + else + bufname = fname; + + if (qf_last_bufname != NULL && STRCMP(bufname, qf_last_bufname) == 0 + && bufref_valid(&qf_last_bufref)) + { + buf = qf_last_bufref.br_buf; + vim_free(ptr); + } + else + { + vim_free(qf_last_bufname); + buf = buflist_new(bufname, NULL, (linenr_T)0, BLN_NOOPT); + if (bufname == ptr) + qf_last_bufname = bufname; + else + qf_last_bufname = vim_strsave(bufname); + set_bufref(&qf_last_bufref, buf); + } + if (buf == NULL) + return 0; + + buf->b_has_qf_entry = + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + return buf->b_fnum; +} + +/* + * Push dirbuf onto the directory stack and return pointer to actual dir or + * NULL on error. + */ + static char_u * +qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, int is_file_stack) +{ + struct dir_stack_T *ds_new; + struct dir_stack_T *ds_ptr; + + // allocate new stack element and hook it in + ds_new = ALLOC_ONE_ID(struct dir_stack_T, aid_qf_dirstack); + if (ds_new == NULL) + return NULL; + + ds_new->next = *stackptr; + *stackptr = ds_new; + + // store directory on the stack + if (vim_isAbsName(dirbuf) + || (*stackptr)->next == NULL + || is_file_stack) + (*stackptr)->dirname = vim_strsave(dirbuf); + else + { + // Okay we don't have an absolute path. + // dirbuf must be a subdir of one of the directories on the stack. + // Let's search... + ds_new = (*stackptr)->next; + (*stackptr)->dirname = NULL; + while (ds_new) + { + vim_free((*stackptr)->dirname); + (*stackptr)->dirname = concat_fnames(ds_new->dirname, dirbuf, + TRUE); + if (mch_isdir((*stackptr)->dirname) == TRUE) + break; + + ds_new = ds_new->next; + } + + // clean up all dirs we already left + while ((*stackptr)->next != ds_new) + { + ds_ptr = (*stackptr)->next; + (*stackptr)->next = (*stackptr)->next->next; + vim_free(ds_ptr->dirname); + vim_free(ds_ptr); + } + + // Nothing found -> it must be on top level + if (ds_new == NULL) + { + vim_free((*stackptr)->dirname); + (*stackptr)->dirname = vim_strsave(dirbuf); + } + } + + if ((*stackptr)->dirname != NULL) + return (*stackptr)->dirname; + else + { + ds_ptr = *stackptr; + *stackptr = (*stackptr)->next; + vim_free(ds_ptr); + return NULL; + } +} + +/* + * pop dirbuf from the directory stack and return previous directory or NULL if + * stack is empty + */ + static char_u * +qf_pop_dir(struct dir_stack_T **stackptr) +{ + struct dir_stack_T *ds_ptr; + + // TODO: Should we check if dirbuf is the directory on top of the stack? + // What to do if it isn't? + + // pop top element and free it + if (*stackptr != NULL) + { + ds_ptr = *stackptr; + *stackptr = (*stackptr)->next; + vim_free(ds_ptr->dirname); + vim_free(ds_ptr); + } + + // return NEW top element as current dir or NULL if stack is empty + return *stackptr ? (*stackptr)->dirname : NULL; +} + +/* + * clean up directory stack + */ + static void +qf_clean_dir_stack(struct dir_stack_T **stackptr) +{ + struct dir_stack_T *ds_ptr; + + while ((ds_ptr = *stackptr) != NULL) + { + *stackptr = (*stackptr)->next; + vim_free(ds_ptr->dirname); + vim_free(ds_ptr); + } +} + +/* + * 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: 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; + + // no dirs on the stack - there's nothing we can do + if (qfl->qf_dir_stack == NULL) + return NULL; + + ds_ptr = qfl->qf_dir_stack->next; + fullname = NULL; + while (ds_ptr) + { + vim_free(fullname); + fullname = concat_fnames(ds_ptr->dirname, filename, TRUE); + + // If concat_fnames failed, just go on. The worst thing that can happen + // is that we delete the entire stack. + if ((fullname != NULL) && (mch_getperm(fullname) >= 0)) + break; + + ds_ptr = ds_ptr->next; + } + + vim_free(fullname); + + // clean up all dirs we already left + while (qfl->qf_dir_stack->next != ds_ptr) + { + ds_tmp = qfl->qf_dir_stack->next; + qfl->qf_dir_stack->next = qfl->qf_dir_stack->next->next; + vim_free(ds_tmp->dirname); + vim_free(ds_tmp); + } + + return ds_ptr == NULL ? NULL : ds_ptr->dirname; +} + +/* + * Returns TRUE if a quickfix/location list with the given identifier exists. + */ + static int +qflist_valid(win_T *wp, int_u qf_id) +{ + qf_info_T *qi = &ql_info; + int i; + + if (wp != NULL) + { + if (!win_valid(wp)) + return FALSE; + qi = GET_LOC_LIST(wp); // Location list + if (qi == NULL) + return FALSE; + } + + for (i = 0; i < qi->qf_listcount; ++i) + if (qi->qf_lists[i].qf_id == qf_id) + return TRUE; + + return FALSE; +} + +/* + * When loading a file from the quickfix, the autocommands may modify it. + * 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 int +is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) +{ + qfline_T *qfp; + int i; + + // Search for the entry in the current list + FOR_ALL_QFL_ITEMS(qfl, qfp, i) + if (qfp == qf_ptr) + break; + + if (i > qfl->qf_count) // Entry is not found + return FALSE; + + return TRUE; +} + +/* + * 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_list_T *qfl, + qfline_T *qf_ptr, + int *qf_index, + int dir) +{ + int idx; + int old_qf_fnum; + + idx = *qf_index; + old_qf_fnum = qf_ptr->qf_fnum; + + do + { + if (idx == qfl->qf_count || qf_ptr->qf_next == NULL) + return NULL; + ++idx; + qf_ptr = qf_ptr->qf_next; + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) + || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); + + *qf_index = idx; + return 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_list_T *qfl, + qfline_T *qf_ptr, + int *qf_index, + int dir) +{ + int idx; + int old_qf_fnum; + + idx = *qf_index; + old_qf_fnum = qf_ptr->qf_fnum; + + do + { + if (idx == 1 || qf_ptr->qf_prev == NULL) + return NULL; + --idx; + qf_ptr = qf_ptr->qf_prev; + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) + || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); + + *qf_index = idx; + return qf_ptr; +} + +/* + * Get the n'th (errornr) previous/next valid entry from the current entry in + * the quickfix list. + * dir == FORWARD or FORWARD_FILE: next valid entry + * dir == BACKWARD or BACKWARD_FILE: previous valid entry + */ + static qfline_T * +get_nth_valid_entry( + qf_list_T *qfl, + int errornr, + int dir, + int *new_qfidx) +{ + qfline_T *qf_ptr = qfl->qf_ptr; + int qf_idx = qfl->qf_index; + qfline_T *prev_qf_ptr; + int prev_index; + char *err = e_no_more_items; + + while (errornr--) + { + prev_qf_ptr = qf_ptr; + prev_index = qf_idx; + + if (dir == FORWARD || dir == FORWARD_FILE) + qf_ptr = get_next_valid_entry(qfl, qf_ptr, &qf_idx, dir); + else + qf_ptr = get_prev_valid_entry(qfl, qf_ptr, &qf_idx, dir); + if (qf_ptr == NULL) + { + qf_ptr = prev_qf_ptr; + qf_idx = prev_index; + if (err != NULL) + { + emsg(_(err)); + return NULL; + } + break; + } + + err = NULL; + } + + *new_qfidx = qf_idx; + return qf_ptr; +} + +/* + * Get n'th (errornr) quickfix entry from the current entry in the quickfix + * list 'qfl'. Returns a pointer to the new entry and the index in 'new_qfidx' + */ + static qfline_T * +get_nth_entry(qf_list_T *qfl, int errornr, int *new_qfidx) +{ + qfline_T *qf_ptr = qfl->qf_ptr; + int qf_idx = qfl->qf_index; + + // New error number is less than the current error number + while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) + { + --qf_idx; + qf_ptr = qf_ptr->qf_prev; + } + // New error number is greater than the current error number + while (errornr > qf_idx && qf_idx < qfl->qf_count && + qf_ptr->qf_next != NULL) + { + ++qf_idx; + qf_ptr = qf_ptr->qf_next; + } + + *new_qfidx = qf_idx; + return qf_ptr; +} + +/* + * Get a entry specified by 'errornr' and 'dir' from the current + * quickfix/location list. 'errornr' specifies the index of the entry and 'dir' + * specifies the direction (FORWARD/BACKWARD/FORWARD_FILE/BACKWARD_FILE). + * Returns a pointer to the entry and the index of the new entry is stored in + * 'new_qfidx'. + */ + static qfline_T * +qf_get_entry( + qf_list_T *qfl, + int errornr, + int dir, + int *new_qfidx) +{ + qfline_T *qf_ptr = qfl->qf_ptr; + int qfidx = qfl->qf_index; + + if (dir != 0) // next/prev valid entry + qf_ptr = get_nth_valid_entry(qfl, errornr, dir, &qfidx); + else if (errornr != 0) // go to specified number + qf_ptr = get_nth_entry(qfl, errornr, &qfidx); + + *new_qfidx = qfidx; + return qf_ptr; +} + +/* + * Find a window displaying a Vim help file in the current tab page. + */ + static win_T * +qf_find_help_win(void) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (bt_help(wp->w_buffer)) + return wp; + + 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. If 'newwin' is TRUE, then open a new help + * window. + */ + static int +jump_to_help_window(qf_info_T *qi, int newwin, int *opened_window) +{ + win_T *wp; + int flags; + + if (cmdmod.cmod_tab != 0 || newwin) + wp = NULL; + else + wp = qf_find_help_win(); + if (wp != NULL && wp->w_buffer->b_nwindows > 0) + win_enter(wp, TRUE); + else + { + // Split off help window; put it at far top if no position + // specified, the current window is vertically split and narrow. + flags = WSP_HELP; + if (cmdmod.cmod_split == 0 && curwin->w_width != Columns + && curwin->w_width < 80) + flags |= WSP_TOP; + // If the user asks to open a new window, then copy the location list. + // Otherwise, don't copy the location list. + if (IS_LL_STACK(qi) && !newwin) + flags |= WSP_NEWLOC; + + if (win_split(0, flags) == FAIL) + return FAIL; + + *opened_window = TRUE; + + if (curwin->w_height < p_hh) + win_setheight((int)p_hh); + + // When using location list, the new window should use the supplied + // location list. If the user asks to open a new window, then the new + // window will get a copy of the location list. + if (IS_LL_STACK(qi) && !newwin) + win_set_loclist(curwin, qi); + } + + if (!p_im) + restart_edit = 0; // don't want insert mode in help file + + return OK; +} + +/* + * Find a non-quickfix window using the given location list stack in the + * current tabpage. + * Returns NULL if a matching window is not found. + */ + static win_T * +qf_find_win_with_loclist(qf_info_T *ll) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_llist == ll && !bt_quickfix(wp->w_buffer)) + return wp; + + return NULL; +} + +/* + * Find a window containing a normal buffer in the current tab page. + */ + static win_T * +qf_find_win_with_normal_buf(void) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (bt_normal(wp->w_buffer)) + return wp; + + return NULL; +} + +/* + * Go to a window in any tabpage containing the specified file. Returns TRUE + * if successfully jumped to the window. Otherwise returns FALSE. + */ + static int +qf_goto_tabwin_with_file(int fnum) +{ + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer->b_fnum == fnum) + { + goto_tabpage_win(tp, wp); + return TRUE; + } + + return FALSE; +} + +/* + * Create a new window to show a file above the quickfix window. Called when + * only the quickfix window is present. + */ + static int +qf_open_new_file_win(qf_info_T *ll_ref) +{ + int flags; + + flags = WSP_ABOVE; + if (ll_ref != NULL) + flags |= WSP_NEWLOC; + if (win_split(0, flags) == FAIL) + return FAIL; // not enough room for window + p_swb = empty_option; // don't split again + swb_flags = 0; + RESET_BINDING(curwin); + if (ll_ref != NULL) + // The new window should use the location list from the + // location list window + win_set_loclist(curwin, ll_ref); + return OK; +} + +/* + * Go to a window that shows the right buffer. If the window is not found, go + * to the window just above the location list window. This is used for opening + * a file from a location window and not from a quickfix window. If some usable + * window is previously found, then it is supplied in 'use_win'. + */ + static void +qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, qf_info_T *ll_ref) +{ + win_T *win = use_win; + + if (win == NULL) + { + // Find the window showing the selected file in the current tab page. + FOR_ALL_WINDOWS(win) + if (win->w_buffer->b_fnum == qf_fnum) + break; + if (win == NULL) + { + // Find a previous usable window + win = curwin; + do + { + if (bt_normal(win->w_buffer)) + break; + if (win->w_prev == NULL) + win = lastwin; // wrap around the top + else + win = win->w_prev; // go to previous window + } while (win != curwin); + } + } + win_goto(win); + + // If the location list for the window is not set, then set it + // to the location list from the location window + if (win->w_llist == NULL && ll_ref != NULL) + win_set_loclist(win, ll_ref); +} + +/* + * Go to a window that contains the specified buffer 'qf_fnum'. If a window is + * not found, then go to the window just above the quickfix window. This is + * used for opening a file from a quickfix window and not from a location + * window. + */ + static void +qf_goto_win_with_qfl_file(int qf_fnum) +{ + win_T *win; + win_T *altwin; + + win = curwin; + altwin = NULL; + for (;;) + { + if (win->w_buffer->b_fnum == qf_fnum) + break; + if (win->w_prev == NULL) + win = lastwin; // wrap around the top + else + win = win->w_prev; // go to previous window + + if (IS_QF_WINDOW(win)) + { + // Didn't find it, go to the window before the quickfix + // window, unless 'switchbuf' contains 'uselast': in this case we + // try to jump to the previously used window first. + if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) + win = prevwin; + else if (altwin != NULL) + win = altwin; + else if (curwin->w_prev != NULL) + win = curwin->w_prev; + else + win = curwin->w_next; + break; + } + + // Remember a usable window. + if (altwin == NULL && !win->w_p_pvw && bt_normal(win->w_buffer)) + altwin = win; + } + + win_goto(win); +} + +/* + * Find a suitable window for opening a file (qf_fnum) from the + * quickfix/location list and jump to it. If the file is already opened in a + * window, jump to it. Otherwise open a new window to display the file. If + * 'newwin' is TRUE, then always open a new window. This is called from either + * a quickfix or a location list window. + */ + static int +qf_jump_to_usable_window(int qf_fnum, int newwin, int *opened_window) +{ + win_T *usable_wp = NULL; + int usable_win = FALSE; + qf_info_T *ll_ref = NULL; + + // If opening a new window, then don't use the location list referred by + // the current window. Otherwise two windows will refer to the same + // location list. + if (!newwin) + ll_ref = curwin->w_llist_ref; + + if (ll_ref != NULL) + { + // Find a non-quickfix window with this location list + usable_wp = qf_find_win_with_loclist(ll_ref); + if (usable_wp != NULL) + usable_win = TRUE; + } + + if (!usable_win) + { + // Locate a window showing a normal buffer + win_T *win = qf_find_win_with_normal_buf(); + if (win != NULL) + usable_win = TRUE; + } + + // If no usable window is found and 'switchbuf' contains "usetab" + // then search in other tabs. + if (!usable_win && (swb_flags & SWB_USETAB)) + usable_win = qf_goto_tabwin_with_file(qf_fnum); + + // If there is only one window and it is the quickfix window, create a + // new one above the quickfix window. + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win || newwin) + { + if (qf_open_new_file_win(ll_ref) != OK) + return FAIL; + *opened_window = TRUE; // close it when fail + } + else + { + if (curwin->w_llist_ref != NULL) // In a location window + qf_goto_win_with_ll_file(usable_wp, qf_fnum, ll_ref); + else // In a quickfix window + qf_goto_win_with_qfl_file(qf_fnum); + } + + return OK; +} + +/* + * Edit the selected file or help file. + * Returns OK if successfully edited the file, FAIL on failing to open the + * buffer and QF_ABORT if the quickfix/location list was freed by an autocmd + * when opening the buffer. + */ + static int +qf_jump_edit_buffer( + qf_info_T *qi, + qfline_T *qf_ptr, + int forceit, + int prev_winid, + int *opened_window) +{ + qf_list_T *qfl = qf_get_curlist(qi); + int old_changedtick = qfl->qf_changedtick; + qfltype_T qfl_type = qfl->qfl_type; + int retval = OK; + int old_qf_curlist = qi->qf_curlist; + int save_qfid = qfl->qf_id; + + if (qf_ptr->qf_type == 1) + { + // Open help file (do_ecmd() will set b_help flag, readfile() will + // set b_p_ro flag). + if (!can_abandon(curbuf, forceit)) + { + no_write_message(); + return FAIL; + } + + retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, + ECMD_HIDE + ECMD_SET_HELP, + prev_winid == curwin->w_id ? curwin : NULL); + } + else + retval = buflist_getfile(qf_ptr->qf_fnum, + (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); + + // If a location list, check whether the associated window is still + // present. + if (qfl_type == QFLT_LOCATION) + { + win_T *wp = win_id2wp(prev_winid); + + if (wp == NULL && curwin->w_llist != qi) + { + emsg(_(e_current_window_was_closed)); + *opened_window = FALSE; + return QF_ABORT; + } + } + + if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) + { + emsg(_(e_current_quickfix_list_was_changed)); + return QF_ABORT; + } + + // Check if the list was changed. The pointers may happen to be identical, + // thus also check qf_changedtick. + if (old_qf_curlist != qi->qf_curlist + || old_changedtick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) + { + if (qfl_type == QFLT_QUICKFIX) + emsg(_(e_current_quickfix_list_was_changed)); + else + emsg(_(e_current_location_list_was_changed)); + return QF_ABORT; + } + + return retval; +} + +/* + * Go to the error line in the current file using either line/column number or + * a search pattern. + */ + static void +qf_jump_goto_line( + linenr_T qf_lnum, + int qf_col, + char_u qf_viscol, + char_u *qf_pattern) +{ + linenr_T i; + + if (qf_pattern == NULL) + { + // Go to line with error, unless qf_lnum is 0. + i = qf_lnum; + if (i > 0) + { + if (i > curbuf->b_ml.ml_line_count) + i = curbuf->b_ml.ml_line_count; + curwin->w_cursor.lnum = i; + } + if (qf_col > 0) + { + curwin->w_cursor.coladd = 0; + if (qf_viscol == TRUE) + coladvance(qf_col - 1); + else + curwin->w_cursor.col = qf_col - 1; + curwin->w_set_curswant = TRUE; + check_cursor(); + } + else + beginline(BL_WHITE | BL_FIX); + } + else + { + pos_T save_cursor; + + // Move the cursor to the first line in the buffer + save_cursor = curwin->w_cursor; + curwin->w_cursor.lnum = 0; + if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) + curwin->w_cursor = save_cursor; + } +} + +/* + * Display quickfix list index and size message + */ + static void +qf_jump_print_msg( + qf_info_T *qi, + int qf_index, + qfline_T *qf_ptr, + buf_T *old_curbuf, + linenr_T old_lnum) +{ + linenr_T i; + garray_T *gap; + + gap = qfga_get(); + + // Update the screen before showing the message, unless the screen + // scrolled up. + if (!msg_scrolled) + update_topline_redraw(); + vim_snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, + 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. + ga_concat(gap, IObuff); + qf_fmt_text(gap, skipwhite(qf_ptr->qf_text)); + + // Output the message. Overwrite to avoid scrolling when the 'O' + // flag is present in 'shortmess'; But when not jumping, print the + // whole message. + i = msg_scroll; + if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) + msg_scroll = TRUE; + else if (!msg_scrolled && shortmess(SHM_OVERALL)) + msg_scroll = FALSE; + msg_attr_keep((char *)gap->ga_data, 0, TRUE); + msg_scroll = i; + + qfga_clear(); +} + +/* + * Find a usable window for opening a file from the quickfix/location list. If + * a window is not found then open a new window. If 'newwin' is TRUE, then open + * a new window. + * Returns OK if successfully jumped or opened a window. Returns FAIL if not + * able to jump/open a window. Returns NOTDONE if a file is not associated + * with the entry. Returns QF_ABORT if the quickfix/location list was modified + * by an autocmd. + */ + static int +qf_jump_open_window( + qf_info_T *qi, + qfline_T *qf_ptr, + int newwin, + int *opened_window) +{ + qf_list_T *qfl = qf_get_curlist(qi); + int old_changedtick = qfl->qf_changedtick; + int old_qf_curlist = qi->qf_curlist; + qfltype_T qfl_type = qfl->qfl_type; + + // For ":helpgrep" find a help window or open one. + if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) + || cmdmod.cmod_tab != 0)) + if (jump_to_help_window(qi, newwin, opened_window) == FAIL) + return FAIL; + if (old_qf_curlist != qi->qf_curlist + || old_changedtick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) + { + if (qfl_type == QFLT_QUICKFIX) + emsg(_(e_current_quickfix_list_was_changed)); + else + emsg(_(e_current_location_list_was_changed)); + return QF_ABORT; + } + + // If currently in the quickfix window, find another window to show the + // file in. + if (bt_quickfix(curbuf) && !*opened_window) + { + // If there is no file specified, we don't know where to go. + // But do advance, otherwise ":cn" gets stuck. + if (qf_ptr->qf_fnum == 0) + return NOTDONE; + + if (qf_jump_to_usable_window(qf_ptr->qf_fnum, newwin, + opened_window) == FAIL) + return FAIL; + } + if (old_qf_curlist != qi->qf_curlist + || old_changedtick != qfl->qf_changedtick + || !is_qf_entry_present(qfl, qf_ptr)) + { + if (qfl_type == QFLT_QUICKFIX) + emsg(_(e_current_quickfix_list_was_changed)); + else + emsg(_(e_current_location_list_was_changed)); + return QF_ABORT; + } + + return OK; +} + +/* + * Edit a selected file from the quickfix/location list and jump to a + * particular line/column, adjust the folds and display a message about the + * jump. + * Returns OK on success and FAIL on failing to open the file/buffer. Returns + * QF_ABORT if the quickfix/location list is freed by an autocmd when opening + * the file. + */ + static int +qf_jump_to_buffer( + qf_info_T *qi, + int qf_index, + qfline_T *qf_ptr, + int forceit, + int prev_winid, + int *opened_window, + int openfold, + int print_message) +{ + buf_T *old_curbuf; + linenr_T old_lnum; + int retval = OK; + + // If there is a file name, read the wanted file if needed, and check + // autowrite etc. + old_curbuf = curbuf; + old_lnum = curwin->w_cursor.lnum; + + if (qf_ptr->qf_fnum != 0) + { + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, prev_winid, + opened_window); + if (retval != OK) + return retval; + } + + // When not switched to another buffer, still need to set pc mark + if (curbuf == old_curbuf) + setpcmark(); + + qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, + qf_ptr->qf_pattern); + +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_QUICKFIX) && openfold) + foldOpenCursor(); +#endif + if (print_message) + qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); + + return retval; +} + +/* + * Jump to a quickfix line and try to use an existing window. + */ + void +qf_jump(qf_info_T *qi, + int dir, + int errornr, + int forceit) +{ + qf_jump_newwin(qi, dir, errornr, forceit, FALSE); +} + +/* + * Jump to a quickfix line. + * If dir == 0 go to entry "errornr". + * If dir == FORWARD go "errornr" valid entries forward. + * If dir == BACKWARD go "errornr" valid entries backward. + * If dir == FORWARD_FILE go "errornr" valid entries files backward. + * If dir == BACKWARD_FILE go "errornr" valid entries files backward + * else if "errornr" is zero, redisplay the same line + * If 'forceit' is TRUE, then can discard changes to the current buffer. + * If 'newwin' is TRUE, then open the file in a new window. + */ + static void +qf_jump_newwin(qf_info_T *qi, + int dir, + int errornr, + int forceit, + int newwin) +{ + qf_list_T *qfl; + qfline_T *qf_ptr; + qfline_T *old_qf_ptr; + int qf_index; + int old_qf_index; + char_u *old_swb = p_swb; + unsigned old_swb_flags = swb_flags; + int prev_winid; + int opened_window = FALSE; + int print_message = TRUE; + int old_KeyTyped = KeyTyped; // getting file may reset it + int retval = OK; + + if (qi == NULL) + qi = &ql_info; + + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) + { + emsg(_(e_no_errors)); + return; + } + + incr_quickfix_busy(); + + qfl = qf_get_curlist(qi); + + qf_ptr = qfl->qf_ptr; + old_qf_ptr = qf_ptr; + qf_index = qfl->qf_index; + old_qf_index = qf_index; + + qf_ptr = qf_get_entry(qfl, errornr, dir, &qf_index); + if (qf_ptr == NULL) + { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + goto theend; + } + + qfl->qf_index = qf_index; + qfl->qf_ptr = qf_ptr; + 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; + + prev_winid = curwin->w_id; + + retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window); + if (retval == FAIL) + goto failed; + if (retval == QF_ABORT) + { + qi = NULL; + qf_ptr = NULL; + goto theend; + } + if (retval == NOTDONE) + goto theend; + + retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, prev_winid, + &opened_window, old_KeyTyped, print_message); + if (retval == QF_ABORT) + { + // Quickfix/location list was modified by an autocmd + qi = NULL; + qf_ptr = NULL; + } + + if (retval != OK) + { + if (opened_window) + win_close(curwin, TRUE); // Close opened window + if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) + { + // Couldn't open file, so put index back where it was. This could + // happen if the file was readonly and we changed something. +failed: + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + } + } +theend: + if (qi != NULL) + { + qfl->qf_ptr = qf_ptr; + qfl->qf_index = qf_index; + } + if (p_swb != old_swb && p_swb == empty_option) + { + // Restore old 'switchbuf' value, but not when an autocommand or + // modeline has changed the value. + p_swb = old_swb; + swb_flags = old_swb_flags; + } + decr_quickfix_busy(); +} + +// 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, int cursel) +{ + char_u *fname; + buf_T *buf; + int filter_entry; + garray_T *gap; + + 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 = gettail(fname); + } + if (fname == NULL) + sprintf((char *)IObuff, "%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. + 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); + gap = qfga_get(); + if (qfp->qf_lnum == 0) + ga_append(gap, NUL); + else + qf_range_text(gap, qfp); + ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr)); + ga_append(gap, NUL); + msg_puts_attr((char *)gap->ga_data, qfLineAttr); + msg_puts_attr(":", qfSepAttr); + if (qfp->qf_pattern != NULL) + { + gap = qfga_get(); + qf_fmt_text(gap, qfp->qf_pattern); + msg_puts((char *)gap->ga_data); + 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 ^^^^. + gap = qfga_get(); + qf_fmt_text(gap, (fname != NULL || qfp->qf_lnum != 0) + ? skipwhite(qfp->qf_text) : qfp->qf_text); + msg_prt_line((char_u *)gap->ga_data, FALSE); + out_flush(); // show one line at a time +} + +/* + * ":clist": list all errors + * ":llist": list all locations + */ + void +qf_list(exarg_T *eap) +{ + qf_list_T *qfl; + qfline_T *qfp; + int i; + int idx1 = 1; + int idx2 = -1; + char_u *arg = eap->arg; + int plus = FALSE; + int all = eap->forceit; // if not :cl!, only show + // recognised errors + qf_info_T *qi; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) + { + emsg(_(e_no_errors)); + return; + } + if (*arg == '+') + { + ++arg; + plus = TRUE; + } + if (!get_list_range(&arg, &idx1, &idx2) || *arg != NUL) + { + semsg(_(e_trailing_characters_str), arg); + return; + } + qfl = qf_get_curlist(qi); + if (plus) + { + i = qfl->qf_index; + idx2 = i + idx1; + idx1 = i; + } + else + { + i = qfl->qf_count; + if (idx1 < 0) + idx1 = (-idx1 > i) ? 0 : idx1 + i + 1; + if (idx2 < 0) + idx2 = (-idx2 > i) ? 0 : idx2 + i + 1; + } + + // Shorten all the file names, so that it is easy to read + shorten_fnames(FALSE); + + // Get the attributes for the different quickfix highlight items. Note + // that this depends on syntax items defined in the qf.vim syntax file + qfFileAttr = syn_name2attr((char_u *)"qfFileName"); + if (qfFileAttr == 0) + qfFileAttr = HL_ATTR(HLF_D); + qfSepAttr = syn_name2attr((char_u *)"qfSeparator"); + if (qfSepAttr == 0) + qfSepAttr = HL_ATTR(HLF_D); + qfLineAttr = syn_name2attr((char_u *)"qfLineNr"); + if (qfLineAttr == 0) + qfLineAttr = HL_ATTR(HLF_N); + + if (qfl->qf_nonevalid) + all = TRUE; + FOR_ALL_QFL_ITEMS(qfl, qfp, i) + { + if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) + qf_list_entry(qfp, i, i == qfl->qf_index); + + ui_breakcheck(); + } + qfga_clear(); +} + +/* + * Remove newlines and leading whitespace from an error message. + * Add the result to the grow array "gap". + */ + static void +qf_fmt_text(garray_T *gap, char_u *text) +{ + char_u *p = text; + + while (*p != NUL) + { + if (*p == '\n') + { + ga_append(gap, ' '); + while (*++p != NUL) + if (!VIM_ISWHITE(*p) && *p != '\n') + break; + } + else + ga_append(gap, *p++); + } + + ga_append(gap, NUL); +} + +/* + * Add the range information from the lnum, col, end_lnum, and end_col values + * of a quickfix entry to the grow array "gap". + */ + static void +qf_range_text(garray_T *gap, qfline_T *qfp) +{ + char_u *buf = IObuff; + int bufsize = IOSIZE; + int len; + + vim_snprintf((char *)buf, bufsize, "%ld", qfp->qf_lnum); + len = (int)STRLEN(buf); + + if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) + { + vim_snprintf((char *)buf + len, bufsize - len, + "-%ld", qfp->qf_end_lnum); + len += (int)STRLEN(buf + len); + } + if (qfp->qf_col > 0) + { + vim_snprintf((char *)buf + len, bufsize - len, " col %d", qfp->qf_col); + len += (int)STRLEN(buf + len); + if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) + { + vim_snprintf((char *)buf + len, bufsize - len, + "-%d", qfp->qf_end_col); + len += (int)STRLEN(buf + len); + } + } + buf[len] = NUL; + + ga_concat_len(gap, buf, len); +} + +/* + * Display information (list number, list size and the title) about a + * quickfix/location list. + */ + static void +qf_msg(qf_info_T *qi, int which, char *lead) +{ + char *title = (char *)qi->qf_lists[which].qf_title; + int count = qi->qf_lists[which].qf_count; + char_u buf[IOSIZE]; + + vim_snprintf((char *)buf, IOSIZE, _("%serror list %d of %d; %d errors "), + lead, + which + 1, + qi->qf_listcount, + count); + + if (title != NULL) + { + size_t len = STRLEN(buf); + + if (len < 34) + { + vim_memset(buf + len, ' ', 34 - len); + buf[34] = NUL; + } + vim_strcat(buf, (char_u *)title, IOSIZE); + } + trunc_string(buf, buf, Columns - 1, IOSIZE); + msg((char *)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. + */ + void +qf_age(exarg_T *eap) +{ + qf_info_T *qi; + int count; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + if (eap->addr_count != 0) + count = eap->line2; + else + count = 1; + while (count--) + { + if (eap->cmdidx == CMD_colder || eap->cmdidx == CMD_lolder) + { + if (qi->qf_curlist == 0) + { + emsg(_(e_at_bottom_of_quickfix_stack)); + break; + } + --qi->qf_curlist; + } + else + { + if (qi->qf_curlist >= qi->qf_listcount - 1) + { + emsg(_(e_at_top_of_quickfix_stack)); + break; + } + ++qi->qf_curlist; + } + } + qf_msg(qi, qi->qf_curlist, ""); + qf_update_buffer(qi, NULL); +} + +/* + * Display the information about all the quickfix/location lists in the stack + */ + void +qf_history(exarg_T *eap) +{ + qf_info_T *qi = qf_cmd_get_stack(eap, FALSE); + int i; + + if (eap->addr_count > 0) + { + if (qi == NULL) + { + emsg(_(e_no_location_list)); + return; + } + + // Jump to the specified quickfix list + if (eap->line2 > 0 && eap->line2 <= qi->qf_listcount) + { + qi->qf_curlist = eap->line2 - 1; + qf_msg(qi, qi->qf_curlist, ""); + qf_update_buffer(qi, NULL); + } + else + emsg(_(e_invalid_range)); + + return; + } + + if (qf_stack_empty(qi)) + msg(_("No entries")); + else + for (i = 0; i < qi->qf_listcount; ++i) + qf_msg(qi, i, i == qi->qf_curlist ? "> " : " "); +} + +/* + * 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_list_T *qfl) +{ + qfline_T *qfp; + qfline_T *qfpnext; + int stop = FALSE; + + while (qfl->qf_count && qfl->qf_start != NULL) + { + qfp = qfl->qf_start; + qfpnext = qfp->qf_next; + if (!stop) + { + vim_free(qfp->qf_module); + vim_free(qfp->qf_text); + vim_free(qfp->qf_pattern); + stop = (qfp == qfpnext); + vim_free(qfp); + if (stop) + // Somehow qf_count may have an incorrect value, set it to 1 + // to avoid crashing when it's wrong. + // TODO: Avoid qf_count being incorrect. + qfl->qf_count = 1; + } + qfl->qf_start = qfpnext; + --qfl->qf_count; + } + + qfl->qf_index = 0; + qfl->qf_start = NULL; + qfl->qf_last = NULL; + qfl->qf_ptr = NULL; + qfl->qf_nonevalid = TRUE; + + qf_clean_dir_stack(&qfl->qf_dir_stack); + qfl->qf_directory = NULL; + qf_clean_dir_stack(&qfl->qf_file_stack); + qfl->qf_currfile = NULL; + qfl->qf_multiline = FALSE; + qfl->qf_multiignore = FALSE; + qfl->qf_multiscan = FALSE; +} + +/* + * Free error list "idx". Frees all the entries in the quickfix list, + * associated context information and the title. + */ + static void +qf_free(qf_list_T *qfl) +{ + qf_free_items(qfl); + + VIM_CLEAR(qfl->qf_title); + free_tv(qfl->qf_ctx); + qfl->qf_ctx = NULL; + free_callback(&qfl->qf_qftf_cb); + qfl->qf_id = 0; + qfl->qf_changedtick = 0L; +} + +/* + * qf_mark_adjust: adjust marks + */ + void +qf_mark_adjust( + win_T *wp, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after) +{ + int i; + qfline_T *qfp; + int idx; + qf_info_T *qi = &ql_info; + int found_one = FALSE; + int buf_has_flag = wp == NULL ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + + if (!(curbuf->b_has_qf_entry & buf_has_flag)) + return; + if (wp != NULL) + { + if (wp->w_llist == NULL) + return; + qi = wp->w_llist; + } + + 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) + { + if (amount == MAXLNUM) + qfp->qf_cleared = TRUE; + else + qfp->qf_lnum += amount; + } + else if (amount_after && qfp->qf_lnum > line2) + qfp->qf_lnum += amount_after; + } + } + + if (!found_one) + curbuf->b_has_qf_entry &= ~buf_has_flag; +} + +/* + * Make a nice message out of the error character and the error number: + * char number message + * e or E 0 " error" + * w or W 0 " warning" + * i or I 0 " info" + * n or N 0 " note" + * 0 0 "" + * other 0 " c" + * e or E n " error n" + * w or W n " warning n" + * i or I n " info n" + * n or N n " note n" + * 0 n " error n" + * other n " c n" + * 1 x "" :helpgrep + */ + static char_u * +qf_types(int c, int nr) +{ + static char_u buf[20]; + static char_u cc[3]; + char_u *p; + + if (c == 'W' || c == 'w') + p = (char_u *)" warning"; + else if (c == 'I' || c == 'i') + p = (char_u *)" info"; + else if (c == 'N' || c == 'n') + p = (char_u *)" note"; + else if (c == 'E' || c == 'e' || (c == 0 && nr > 0)) + p = (char_u *)" error"; + else if (c == 0 || c == 1) + p = (char_u *)""; + else + { + cc[0] = ' '; + cc[1] = c; + cc[2] = NUL; + p = cc; + } + + if (nr <= 0) + return p; + + sprintf((char *)buf, "%s %3d", (char *)p, nr); + return buf; +} + +/* + * When "split" is FALSE: Open the entry/result under the cursor. + * When "split" is TRUE: Open the entry/result under the cursor in a new window. + */ + void +qf_view_result(int split) +{ + qf_info_T *qi = &ql_info; + + if (IS_LL_WINDOW(curwin)) + qi = GET_LOC_LIST(curwin); + + if (qf_list_empty(qf_get_curlist(qi))) + { + emsg(_(e_no_errors)); + return; + } + + if (split) + { + // Open the selected entry in a new window + qf_jump_newwin(qi, 0, (long)curwin->w_cursor.lnum, FALSE, TRUE); + do_cmdline_cmd((char_u *) "clearjumps"); + return; + } + + do_cmdline_cmd((char_u *)(IS_LL_WINDOW(curwin) ? ".ll" : ".cc")); +} + +/* + * ":cwindow": open the quickfix window if we have errors to display, + * close it if not. + * ":lwindow": open the location list window if we have locations to display, + * close it if not. + */ + void +ex_cwindow(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + win_T *win; + + 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); + + // If a quickfix window is open but we have no errors to display, + // close the window. If a quickfix window is not open, then open + // it if we have errors; otherwise, leave it closed. + if (qf_stack_empty(qi) + || qfl->qf_nonevalid + || qf_list_empty(qfl)) + { + if (win != NULL) + ex_cclose(eap); + } + else if (win == NULL) + ex_copen(eap); +} + +/* + * ":cclose": close the window showing the list of errors. + * ":lclose": close the window showing the location list + */ + void +ex_cclose(exarg_T *eap) +{ + win_T *win = NULL; + qf_info_T *qi; + + if ((qi = qf_cmd_get_stack(eap, FALSE)) == NULL) + return; + + // Find existing quickfix window and close it. + win = qf_find_win(qi); + if (win != NULL) + win_close(win, FALSE); +} + +/* + * 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); +} + +/* + * Goto a quickfix or location list window (if present). + * Returns OK if the window is found, FAIL otherwise. + */ + static int +qf_goto_cwindow(qf_info_T *qi, int resize, int sz, int vertsplit) +{ + win_T *win; + + win = qf_find_win(qi); + if (win == NULL) + return FAIL; + + win_goto(win); + if (resize) + { + if (vertsplit) + { + if (sz != win->w_width) + win_setwidth(sz); + } + else if (sz != win->w_height && win->w_height + + win->w_status_height + tabline_height() < cmdline_row) + win_setheight(sz); + } + + return OK; +} + +/* + * Set options for the buffer in the quickfix or location list window. + */ + static void +qf_set_cwindow_options(void) +{ + // switch off 'swapfile' + set_option_value_give_err((char_u *)"swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err((char_u *)"bt", + 0L, (char_u *)"quickfix", OPT_LOCAL); + set_option_value_give_err((char_u *)"bh", 0L, (char_u *)"hide", OPT_LOCAL); + RESET_BINDING(curwin); +#ifdef FEAT_DIFF + curwin->w_p_diff = FALSE; +#endif +#ifdef FEAT_FOLDING + set_option_value_give_err((char_u *)"fdm", 0L, (char_u *)"manual", + OPT_LOCAL); +#endif +} + +/* + * Open a new quickfix or location list window, load the quickfix buffer and + * set the appropriate options for the window. + * Returns FAIL if the window could not be opened. + */ + static int +qf_open_new_cwindow(qf_info_T *qi, int height) +{ + buf_T *qf_buf; + win_T *oldwin = curwin; + tabpage_T *prevtab = curtab; + int flags = 0; + win_T *win; + + qf_buf = qf_find_buf(qi); + + // The current window becomes the previous window afterwards. + win = curwin; + + if (IS_QF_STACK(qi) && cmdmod.cmod_split == 0) + // Create the new quickfix window at the very bottom, except when + // :belowright or :aboveleft is used. + win_goto(lastwin); + // Default is to open the window below the current window + if (cmdmod.cmod_split == 0) + flags = WSP_BELOW; + flags |= WSP_NEWLOC; + if (win_split(height, flags) == FAIL) + return FAIL; // not enough room for window + RESET_BINDING(curwin); + + if (IS_LL_STACK(qi)) + { + // For the location list window, create a reference to the + // location list stack from the window 'win'. + curwin->w_llist_ref = qi; + qi->qf_refcount++; + } + + if (oldwin != curwin) + oldwin = NULL; // don't store info when in another window + if (qf_buf != NULL) + { + // Use the existing quickfix buffer + if (do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, + ECMD_HIDE + ECMD_OLDBUF + ECMD_NOWINENTER, oldwin) == FAIL) + return FAIL; + } + else + { + // Create a new quickfix buffer + if (do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE + ECMD_NOWINENTER, + oldwin) == FAIL) + return FAIL; + + // save the number of the new buffer + qi->qf_bufnr = curbuf->b_fnum; + } + + // Set the options for the quickfix buffer/window (if not already done) + // Do this even if the quickfix buffer was already present, as an autocmd + // might have previously deleted (:bdelete) the quickfix buffer. + if (!bt_quickfix(curbuf)) + qf_set_cwindow_options(); + + // Only set the height when still in the same tab page and there is no + // window to the side. + if (curtab == prevtab && curwin->w_width == Columns) + win_setheight(height); + curwin->w_p_wfh = TRUE; // set 'winfixheight' + if (win_valid(win)) + prevwin = win; + + return OK; +} + +/* + * ":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; + qf_list_T *qfl; + int height; + int status = FAIL; + int lnum; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + incr_quickfix_busy(); + + if (eap->addr_count != 0) + height = eap->line2; + else + height = QF_WINHEIGHT; + + reset_VIsual_and_resel(); // stop Visual mode +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + // Find an existing quickfix window, or open a new one. + if (cmdmod.cmod_tab == 0) + status = qf_goto_cwindow(qi, eap->addr_count != 0, height, + cmdmod.cmod_split & WSP_VERT); + if (status == FAIL) + if (qf_open_new_cwindow(qi, height) == FAIL) + { + decr_quickfix_busy(); + return; + } + + 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(qfl, curbuf, NULL, curwin->w_id); + + decr_quickfix_busy(); + + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + check_cursor(); + update_topline(); // scroll to show the line +} + +/* + * Move the cursor in the quickfix window to "lnum". + */ + static void +qf_win_goto(win_T *win, linenr_T lnum) +{ + win_T *old_curwin = curwin; + + curwin = win; + curbuf = win->w_buffer; + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_curswant = 0; + update_topline(); // scroll to show the line + redraw_later(UPD_VALID); + curwin->w_redr_status = TRUE; // update ruler + curwin = old_curwin; + curbuf = curwin->w_buffer; +} + +/* + * :cbottom/:lbottom commands. + */ + void +ex_cbottom(exarg_T *eap) +{ + qf_info_T *qi; + win_T *win; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + win = qf_find_win(qi); + if (win != NULL && win->w_cursor.lnum != win->w_buffer->b_ml.ml_line_count) + qf_win_goto(win, win->w_buffer->b_ml.ml_line_count); +} + +/* + * Return the number of the current entry (line number in the quickfix + * window). + */ + linenr_T +qf_current_entry(win_T *wp) +{ + qf_info_T *qi = &ql_info; + + if (IS_LL_WINDOW(wp)) + // In the location list window, use the referenced location list + qi = wp->w_llist_ref; + + return qf_get_curlist(qi)->qf_index; +} + +/* + * Update the cursor position in the quickfix window to the current error. + * Return TRUE if there is a quickfix window. + */ + static int +qf_win_pos_update( + qf_info_T *qi, + int old_qf_index) // previous qf_index or zero +{ + win_T *win; + int qf_index = qf_get_curlist(qi)->qf_index; + + // Put the cursor on the current error in the quickfix window, so that + // it's viewable. + win = qf_find_win(qi); + if (win != NULL + && qf_index <= win->w_buffer->b_ml.ml_line_count + && old_qf_index != qf_index) + { + if (qf_index > old_qf_index) + { + win->w_redraw_top = old_qf_index; + win->w_redraw_bot = qf_index; + } + else + { + win->w_redraw_top = qf_index; + win->w_redraw_bot = old_qf_index; + } + qf_win_goto(win, qf_index); + } + return win != NULL; +} + +/* + * Check whether the given window is displaying the specified quickfix/location + * stack. + */ + static int +is_qf_win(win_T *win, qf_info_T *qi) +{ + // A window displaying the quickfix buffer will have the w_llist_ref field + // set to NULL. + // A window displaying a location list buffer will have the w_llist_ref + // pointing to the location list. + if (bt_quickfix(win->w_buffer)) + if ((IS_QF_STACK(qi) && win->w_llist_ref == NULL) + || (IS_LL_STACK(qi) && win->w_llist_ref == qi)) + return TRUE; + + return FALSE; +} + +/* + * Find a window displaying the quickfix/location stack 'qi' in the current tab + * page. + */ + static win_T * +qf_find_win(qf_info_T *qi) +{ + win_T *win; + + FOR_ALL_WINDOWS(win) + if (is_qf_win(win, qi)) + return win; + return NULL; +} + +/* + * Find a quickfix buffer. + * Searches in windows opened in all the tab pages. + */ + static buf_T * +qf_find_buf(qf_info_T *qi) +{ + tabpage_T *tp; + win_T *win; + + if (qi->qf_bufnr != INVALID_QFBUFNR) + { + buf_T *qfbuf; + qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL) + return qfbuf; + // buffer is no longer present + qi->qf_bufnr = INVALID_QFBUFNR; + } + + FOR_ALL_TAB_WINDOWS(tp, win) + if (is_qf_win(win, qi)) + return win->w_buffer; + + return NULL; +} + +/* + * Process the 'quickfixtextfunc' option value. + * Returns OK or FAIL. + */ + char * +did_set_quickfixtextfunc(optset_T *args UNUSED) +{ + if (option_set_callback_func(p_qftf, &qftf_cb) == FAIL) + return e_invalid_argument; + + return NULL; +} + +/* + * Update the w:quickfix_title variable in the quickfix/location list window in + * all the tab pages. + */ + static void +qf_update_win_titlevar(qf_info_T *qi) +{ + qf_list_T *qfl = qf_get_curlist(qi); + tabpage_T *tp; + win_T *win; + win_T *save_curwin = curwin; + + FOR_ALL_TAB_WINDOWS(tp, win) + { + if (is_qf_win(win, qi)) + { + curwin = win; + qf_set_title_var(qfl); + } + } + curwin = save_curwin; +} + +/* + * Find the quickfix buffer. If it exists, update the contents. + */ + static void +qf_update_buffer(qf_info_T *qi, qfline_T *old_last) +{ + buf_T *buf; + win_T *win; + aco_save_T aco; + + // Check if a buffer for the quickfix list exists. Update it. + buf = qf_find_buf(qi); + if (buf == NULL) + return; + + linenr_T old_line_count = buf->b_ml.ml_line_count; + int qf_winid = 0; + + if (IS_LL_STACK(qi)) + { + if (curwin->w_llist == qi) + win = curwin; + else + { + // Find the file window (non-quickfix) with this location list + win = qf_find_win_with_loclist(qi); + if (win == NULL) + // File window is not found. Find the location list window. + win = qf_find_win(qi); + if (win == NULL) + return; + } + qf_winid = win->w_id; + } + + // autocommands may cause trouble + incr_quickfix_busy(); + + int do_fill = TRUE; + if (old_last == NULL) + { + // set curwin/curbuf to buf and save a few things + aucmd_prepbuf(&aco, buf); + if (curbuf != buf) + do_fill = FALSE; // failed to find a window for "buf" + } + + if (do_fill) + { + qf_update_win_titlevar(qi); + + qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); + ++CHANGEDTICK(buf); + + if (old_last == NULL) + { + (void)qf_win_pos_update(qi, 0); + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } + } + + // Only redraw when added lines are visible. This avoids flickering + // when the added lines are not visible. + if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) + redraw_buf_later(buf, UPD_NOT_VALID); + + // always called after incr_quickfix_busy() + decr_quickfix_busy(); +} + +/* + * Add an error line to the quickfix buffer. + */ + static int +qf_buf_add_line( + buf_T *buf, // quickfix window buffer + linenr_T lnum, + qfline_T *qfp, + char_u *dirname, + int first_bufline, + char_u *qftf_str) +{ + buf_T *errbuf; + garray_T *gap; + + gap = qfga_get(); + + // If the 'quickfixtextfunc' function returned a non-empty custom string + // for this entry, then use it. + if (qftf_str != NULL && *qftf_str != NUL) + { + ga_concat(gap, qftf_str); + ga_append(gap, NUL); + } + else + { + if (qfp->qf_module != NULL) + ga_concat(gap, qfp->qf_module); + else if (qfp->qf_fnum != 0 + && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL + && errbuf->b_fname != NULL) + { + if (qfp->qf_type == 1) // :helpgrep + ga_concat(gap, gettail(errbuf->b_fname)); + else + { + // Shorten the file name if not done already. + // For optimization, do this only for the first entry in a + // buffer. + if (first_bufline && (errbuf->b_sfname == NULL + || mch_isFullName(errbuf->b_sfname))) + { + if (*dirname == NUL) + mch_dirname(dirname, MAXPATHL); + shorten_buf_fname(errbuf, dirname, FALSE); + } + ga_concat(gap, errbuf->b_fname); + } + } + + ga_append(gap, '|'); + + if (qfp->qf_lnum > 0) + { + qf_range_text(gap, qfp); + ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr)); + } + else if (qfp->qf_pattern != NULL) + qf_fmt_text(gap, qfp->qf_pattern); + ga_append(gap, '|'); + ga_append(gap, ' '); + + // 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(gap, gap->ga_len > 3 ? skipwhite(qfp->qf_text) + : qfp->qf_text); + } + + if (ml_append_buf(buf, lnum, gap->ga_data, gap->ga_len, FALSE) == FAIL) + return FAIL; + + return OK; +} + +/* + * Call the 'quickfixtextfunc' function to get the list of lines to display in + * the quickfix window for the entries 'start_idx' to 'end_idx'. + */ + static list_T * +call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long end_idx) +{ + callback_T *cb = &qftf_cb; + list_T *qftf_list = NULL; + static int recursive = FALSE; + + if (recursive) + return NULL; // this doesn't work properly recursively + recursive = TRUE; + + // If 'quickfixtextfunc' is set, then use the user-supplied function to get + // the text to display. Use the local value of 'quickfixtextfunc' if it is + // set. + if (qfl->qf_qftf_cb.cb_name != NULL) + cb = &qfl->qf_qftf_cb; + if (cb->cb_name != NULL) + { + typval_T args[1]; + dict_T *d; + typval_T rettv; + + // create the dict argument + if ((d = dict_alloc_lock(VAR_FIXED)) == NULL) + { + recursive = FALSE; + return NULL; + } + dict_add_number(d, "quickfix", (long)IS_QF_LIST(qfl)); + dict_add_number(d, "winid", (long)qf_winid); + dict_add_number(d, "id", (long)qfl->qf_id); + dict_add_number(d, "start_idx", start_idx); + dict_add_number(d, "end_idx", end_idx); + ++d->dv_refcount; + args[0].v_type = VAR_DICT; + args[0].vval.v_dict = d; + + qftf_list = NULL; + if (call_callback(cb, 0, &rettv, 1, args) != FAIL) + { + if (rettv.v_type == VAR_LIST) + { + qftf_list = rettv.vval.v_list; + qftf_list->lv_refcount++; + } + clear_tv(&rettv); + } + dict_unref(d); + } + + recursive = FALSE; + return qftf_list; +} + +/* + * 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, int qf_winid) +{ + linenr_T lnum; + qfline_T *qfp; + int old_KeyTyped = KeyTyped; + list_T *qftf_list = NULL; + listitem_T *qftf_li = NULL; + + if (old_last == NULL) + { + if (buf != curbuf) + { + internal_error("qf_fill_buffer()"); + return; + } + + // delete all existing lines + while ((curbuf->b_ml.ml_flags & ML_EMPTY) == 0) + (void)ml_delete((linenr_T)1); + } + + // Check if there is anything to display + if (qfl != NULL && qfl->qf_start != NULL) + { + char_u dirname[MAXPATHL]; + int invalid_val = FALSE; + int prev_bufnr = -1; + + *dirname = NUL; + + // Add one line for each error + if (old_last == NULL) + { + qfp = qfl->qf_start; + lnum = 0; + } + else + { + if (old_last->qf_next != NULL) + qfp = old_last->qf_next; + else + qfp = old_last; + lnum = buf->b_ml.ml_line_count; + } + + qftf_list = call_qftf_func(qfl, qf_winid, (long)(lnum + 1), + (long)qfl->qf_count); + if (qftf_list != NULL) + qftf_li = qftf_list->lv_first; + + while (lnum < qfl->qf_count) + { + char_u *qftf_str = NULL; + + // Use the text supplied by the user defined function (if any). + // If the returned value is not string, then ignore the rest + // of the returned values and use the default. + if (qftf_li != NULL && !invalid_val) + { + qftf_str = tv_get_string_chk(&qftf_li->li_tv); + if (qftf_str == NULL) + invalid_val = TRUE; + } + + if (qf_buf_add_line(buf, lnum, qfp, dirname, + prev_bufnr != qfp->qf_fnum, qftf_str) == FAIL) + break; + + prev_bufnr = qfp->qf_fnum; + ++lnum; + qfp = qfp->qf_next; + if (qfp == NULL) + break; + + if (qftf_li != NULL) + qftf_li = qftf_li->li_next; + } + + if (old_last == NULL) + // Delete the empty line which is now at the end + (void)ml_delete(lnum + 1); + + qfga_clear(); + } + + // correct cursor position + check_lnums(TRUE); + + if (old_last == NULL) + { + // Set the 'filetype' to "qf" each time after filling the buffer. + // This resembles reading a file into a buffer, it's more logical when + // using autocommands. + ++curbuf_lock; + set_option_value_give_err((char_u *)"ft", + 0L, (char_u *)"qf", OPT_LOCAL); + curbuf->b_p_ma = FALSE; + + keep_filetype = TRUE; // don't detect 'filetype' + apply_autocmds(EVENT_BUFREADPOST, (char_u *)"quickfix", NULL, + FALSE, curbuf); + apply_autocmds(EVENT_BUFWINENTER, (char_u *)"quickfix", NULL, + FALSE, curbuf); + keep_filetype = FALSE; + --curbuf_lock; + + // make sure it will be redrawn + redraw_curbuf_later(UPD_NOT_VALID); + } + + // Restore KeyTyped, setting 'filetype' may reset it. + KeyTyped = old_KeyTyped; +} + +/* + * For every change made to the quickfix list, update the changed tick. + */ + static void +qf_list_changed(qf_list_T *qfl) +{ + qfl->qf_changedtick++; +} + +/* + * Return the quickfix/location list number with the given identifier. + * Returns -1 if list is not found. + */ + static int +qf_id2nr(qf_info_T *qi, int_u qfid) +{ + int qf_idx; + + for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) + if (qi->qf_lists[qf_idx].qf_id == qfid) + return qf_idx; + 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. + */ + static int +qf_restore_list(qf_info_T *qi, int_u save_qfid) +{ + int curlist; + + if (qf_get_curlist(qi)->qf_id == save_qfid) + return OK; + + curlist = qf_id2nr(qi, save_qfid); + if (curlist < 0) + // list is not present + return FAIL; + qi->qf_curlist = curlist; + return OK; +} + +/* + * Jump to the first entry if there is one. + */ + static void +qf_jump_first(qf_info_T *qi, int_u save_qfid, int forceit) +{ + if (qf_restore_list(qi, save_qfid) == FAIL) + return; + + // Autocommands might have cleared the list, check for that. + if (!qf_list_empty(qf_get_curlist(qi))) + qf_jump(qi, 0, 0, forceit); +} + +/* + * Return TRUE when using ":vimgrep" for ":grep". + */ + int +grep_internal(cmdidx_T cmdidx) +{ + return ((cmdidx == CMD_grep + || cmdidx == CMD_lgrep + || cmdidx == CMD_grepadd + || cmdidx == CMD_lgrepadd) + && STRCMP("internal", + *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0); +} + +/* + * Return the make/grep autocmd name. + */ + static char_u * +make_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) + { + case CMD_make: return (char_u *)"make"; + case CMD_lmake: return (char_u *)"lmake"; + case CMD_grep: return (char_u *)"grep"; + case CMD_lgrep: return (char_u *)"lgrep"; + case CMD_grepadd: return (char_u *)"grepadd"; + case CMD_lgrepadd: return (char_u *)"lgrepadd"; + default: return NULL; + } +} + +/* + * Return the name for the errorfile, in allocated memory. + * Find a new unique name when 'makeef' contains "##". + * Returns NULL for error. + */ + static char_u * +get_mef_name(void) +{ + char_u *p; + char_u *name; + static int start = -1; + static int off = 0; +#ifdef HAVE_LSTAT + stat_T sb; +#endif + + if (*p_mef == NUL) + { + name = vim_tempname('e', FALSE); + if (name == NULL) + emsg(_(e_cant_get_temp_file_name)); + return name; + } + + for (p = p_mef; *p; ++p) + if (p[0] == '#' && p[1] == '#') + break; + + if (*p == NUL) + return vim_strsave(p_mef); + + // Keep trying until the name doesn't exist yet. + for (;;) + { + if (start == -1) + start = mch_get_pid(); + else + off += 19; + + name = alloc_id(STRLEN(p_mef) + 30, aid_qf_mef_name); + if (name == NULL) + break; + STRCPY(name, p_mef); + sprintf((char *)name + (p - p_mef), "%d%d", start, off); + STRCAT(name, p + 2); + if (mch_getperm(name) < 0 +#ifdef HAVE_LSTAT + // Don't accept a symbolic link, it's a security risk. + && mch_lstat((char *)name, &sb) < 0 +#endif + ) + break; + vim_free(name); + } + return name; +} + +/* + * Form the complete command line to invoke 'make'/'grep'. Quote the command + * using 'shellquote' and append 'shellpipe'. Echo the fully formed command. + */ + static char_u * +make_get_fullcmd(char_u *makecmd, char_u *fname) +{ + char_u *cmd; + unsigned len; + + len = (unsigned)STRLEN(p_shq) * 2 + (unsigned)STRLEN(makecmd) + 1; + if (*p_sp != NUL) + len += (unsigned)STRLEN(p_sp) + (unsigned)STRLEN(fname) + 3; + cmd = alloc_id(len, aid_qf_makecmd); + if (cmd == NULL) + return NULL; + sprintf((char *)cmd, "%s%s%s", (char *)p_shq, (char *)makecmd, + (char *)p_shq); + + // If 'shellpipe' empty: don't redirect to 'errorfile'. + if (*p_sp != NUL) + append_redir(cmd, len, p_sp, fname); + + // Display the fully formed command. Output a newline if there's something + // else than the :make command that was typed (in which case the cursor is + // in column 0). + if (msg_col == 0) + msg_didout = FALSE; + msg_start(); + msg_puts(":!"); + msg_outtrans(cmd); // show what we are doing + + return cmd; +} + +/* + * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" + */ + void +ex_make(exarg_T *eap) +{ + char_u *fname; + char_u *cmd; + char_u *enc = NULL; + win_T *wp = NULL; + qf_info_T *qi = &ql_info; + int res; + char_u *au_name = NULL; + int_u save_qfid; + char_u *errorformat = p_efm; + int newlist = TRUE; + + // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". + if (grep_internal(eap->cmdidx)) + { + ex_vimgrep(eap); + return; + } + + au_name = make_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { +#ifdef FEAT_EVAL + if (aborting()) + return; +#endif + } + enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; + + if (is_loclist_cmd(eap->cmdidx)) + wp = curwin; + + autowrite_all(); + fname = get_mef_name(); + if (fname == NULL) + return; + mch_remove(fname); // in case it's not unique + + cmd = make_get_fullcmd(eap->arg, fname); + if (cmd == NULL) + { + vim_free(fname); + return; + } + + // let the shell know if we are redirecting output or not + do_shell(cmd, *p_sp != NUL ? SHELL_DOOUT : 0); + +#ifdef AMIGA + out_flush(); + // read window status report and redraw before message + (void)char_avail(); +#endif + + incr_quickfix_busy(); + + if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) + errorformat = p_gefm; + if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) + newlist = FALSE; + + res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), + enc); + if (wp != NULL) + { + qi = GET_LOC_LIST(wp); + if (qi == NULL) + goto cleanup; + } + 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. + save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, TRUE, curbuf); + if (res > 0 && !eap->forceit && qflist_valid(wp, save_qfid)) + // display the first error + qf_jump_first(qi, save_qfid, FALSE); + +cleanup: + decr_quickfix_busy(); + mch_remove(fname); + vim_free(fname); + vim_free(cmd); +} + +/* + * Returns the number of entries in the current quickfix/location list. + */ + int +qf_get_size(exarg_T *eap) +{ + qf_info_T *qi; + + if ((qi = qf_cmd_get_stack(eap, FALSE)) == NULL) + return 0; + return qf_get_curlist(qi)->qf_count; +} + +/* + * Returns the number of valid entries in the current quickfix/location list. + */ + int +qf_get_valid_size(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + qfline_T *qfp; + int i, sz = 0; + int prev_fnum = 0; + + if ((qi = qf_cmd_get_stack(eap, FALSE)) == NULL) + return 0; + + qfl = qf_get_curlist(qi); + FOR_ALL_QFL_ITEMS(qfl, qfp, i) + { + if (qfp->qf_valid) + { + if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo) + sz++; // Count all valid entries + else if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) + { + // Count the number of files + sz++; + prev_fnum = qfp->qf_fnum; + } + } + } + + return sz; +} + +/* + * Returns the current index of the quickfix/location list. + * Returns 0 if there is an error. + */ + int +qf_get_cur_idx(exarg_T *eap) +{ + qf_info_T *qi; + + if ((qi = qf_cmd_get_stack(eap, FALSE)) == NULL) + return 0; + + return qf_get_curlist(qi)->qf_index; +} + +/* + * Returns the current index in the quickfix/location list (counting only valid + * entries). If no valid entries are in the list, then returns 1. + */ + int +qf_get_cur_valid_idx(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + qfline_T *qfp; + int i, eidx = 0; + int prev_fnum = 0; + + if ((qi = qf_cmd_get_stack(eap, FALSE)) == NULL) + return 1; + + qfl = qf_get_curlist(qi); + qfp = qfl->qf_start; + + // check if the list has valid errors + if (!qf_list_has_valid_entries(qfl)) + return 1; + + for (i = 1; i <= qfl->qf_index && qfp!= NULL; i++, qfp = qfp->qf_next) + { + if (qfp->qf_valid) + { + if (eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) + { + if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) + { + // Count the number of files + eidx++; + prev_fnum = qfp->qf_fnum; + } + } + else + eidx++; + } + } + + return eidx ? eidx : 1; +} + +/* + * Get the 'n'th valid error entry in the quickfix or location list. + * 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 int +qf_get_nth_valid_entry(qf_list_T *qfl, int n, int fdo) +{ + qfline_T *qfp; + int i, eidx; + int prev_fnum = 0; + + // check if the list has valid errors + if (!qf_list_has_valid_entries(qfl)) + return 1; + + eidx = 0; + FOR_ALL_QFL_ITEMS(qfl, qfp, i) + { + if (qfp->qf_valid) + { + if (fdo) + { + if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) + { + // Count the number of files + eidx++; + prev_fnum = qfp->qf_fnum; + } + } + else + eidx++; + } + + if (eidx == n) + break; + } + + if (i <= qfl->qf_count) + return i; + else + return 1; +} + +/* + * ":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; + int errornr; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + if (eap->addr_count > 0) + errornr = (int)eap->line2; + else + { + switch (eap->cmdidx) + { + case CMD_cc: case CMD_ll: + errornr = 0; + break; + case CMD_crewind: case CMD_lrewind: case CMD_cfirst: + case CMD_lfirst: + errornr = 1; + break; + default: + errornr = 32767; + } + } + + // For cdo and ldo commands, jump to the nth valid error. + // For cfdo and lfdo commands, jump to the nth valid file entry. + if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo + || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) + errornr = qf_get_nth_valid_entry(qf_get_curlist(qi), + eap->addr_count > 0 ? (int)eap->line1 : 1, + eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); + + qf_jump(qi, 0, errornr, eap->forceit); +} + +/* + * ":cnext", ":cnfile", ":cNext" and ":cprevious". + * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". + * Also, used by ":cdo", ":ldo", ":cfdo" and ":lfdo" commands. + */ + void +ex_cnext(exarg_T *eap) +{ + qf_info_T *qi; + int errornr; + int dir; + + if ((qi = qf_cmd_get_stack(eap, TRUE)) == NULL) + return; + + if (eap->addr_count > 0 + && (eap->cmdidx != CMD_cdo && eap->cmdidx != CMD_ldo + && eap->cmdidx != CMD_cfdo && eap->cmdidx != CMD_lfdo)) + errornr = (int)eap->line2; + else + errornr = 1; + + // Depending on the command jump to either next or previous entry/file. + switch (eap->cmdidx) + { + case CMD_cnext: case CMD_lnext: case CMD_cdo: case CMD_ldo: + dir = FORWARD; + break; + case CMD_cprevious: case CMD_lprevious: case CMD_cNext: + case CMD_lNext: + dir = BACKWARD; + break; + case CMD_cnfile: case CMD_lnfile: case CMD_cfdo: case CMD_lfdo: + dir = FORWARD_FILE; + break; + case CMD_cpfile: case CMD_lpfile: case CMD_cNfile: case CMD_lNfile: + dir = BACKWARD_FILE; + break; + default: + dir = FORWARD; + break; + } + + 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; +} + +/* + * Returns TRUE if the specified quickfix entry is + * after the given line (linewise is TRUE) + * or after the line and column. + */ + static int +qf_entry_after_pos(qfline_T *qfp, pos_T *pos, int linewise) +{ + if (linewise) + return qfp->qf_lnum > pos->lnum; + else + return (qfp->qf_lnum > pos->lnum || + (qfp->qf_lnum == pos->lnum && qfp->qf_col > pos->col)); +} + +/* + * Returns TRUE if the specified quickfix entry is + * before the given line (linewise is TRUE) + * or before the line and column. + */ + static int +qf_entry_before_pos(qfline_T *qfp, pos_T *pos, int linewise) +{ + if (linewise) + return qfp->qf_lnum < pos->lnum; + else + return (qfp->qf_lnum < pos->lnum || + (qfp->qf_lnum == pos->lnum && qfp->qf_col < pos->col)); +} + +/* + * Returns TRUE if the specified quickfix entry is + * on or after the given line (linewise is TRUE) + * or on or after the line and column. + */ + static int +qf_entry_on_or_after_pos(qfline_T *qfp, pos_T *pos, int linewise) +{ + if (linewise) + return qfp->qf_lnum >= pos->lnum; + else + return (qfp->qf_lnum > pos->lnum || + (qfp->qf_lnum == pos->lnum && qfp->qf_col >= pos->col)); +} + +/* + * Returns TRUE if the specified quickfix entry is + * on or before the given line (linewise is TRUE) + * or on or before the line and column. + */ + static int +qf_entry_on_or_before_pos(qfline_T *qfp, pos_T *pos, int linewise) +{ + if (linewise) + return qfp->qf_lnum <= pos->lnum; + else + return (qfp->qf_lnum < pos->lnum || + (qfp->qf_lnum == pos->lnum && qfp->qf_col <= pos->col)); +} + +/* + * Find the first quickfix entry after position 'pos' in buffer 'bnr'. + * If 'linewise' is TRUE, returns the entry after the specified line and treats + * multiple entries on a single line as one. Otherwise returns the entry after + * the specified line and column. + * '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 'pos'. + */ + static qfline_T * +qf_find_entry_after_pos( + int bnr, + pos_T *pos, + int linewise, + qfline_T *qfp, + int *errornr) +{ + if (qf_entry_after_pos(qfp, pos, linewise)) + // First entry is after position 'pos' + return qfp; + + // Find the entry just before or at the position 'pos' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qf_entry_on_or_before_pos(qfp->qf_next, pos, linewise)) + { + qfp = qfp->qf_next; + ++*errornr; + } + + if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) + // No entries found after position 'pos' + return NULL; + + // Use the entry just after position 'pos' + qfp = qfp->qf_next; + ++*errornr; + + return qfp; +} + +/* + * Find the first quickfix entry before position 'pos' in buffer 'bnr'. + * If 'linewise' is TRUE, returns the entry before the specified line and + * treats multiple entries on a single line as one. Otherwise returns the entry + * before the specified line and column. + * '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 'pos'. + */ + static qfline_T * +qf_find_entry_before_pos( + int bnr, + pos_T *pos, + int linewise, + qfline_T *qfp, + int *errornr) +{ + // Find the entry just before the position 'pos' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qf_entry_before_pos(qfp->qf_next, pos, linewise)) + { + qfp = qfp->qf_next; + ++*errornr; + } + + if (qf_entry_on_or_after_pos(qfp, pos, linewise)) + return NULL; + + if (linewise) + // 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 position 'pos' in buffer 'bnr' in + * the direction 'dir'. + */ + static qfline_T * +qf_find_closest_entry( + qf_list_T *qfl, + int bnr, + pos_T *pos, + int dir, + int linewise, + 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_after_pos(bnr, pos, linewise, qfp, errornr); + else + qfp = qf_find_entry_before_pos(bnr, pos, linewise, qfp, errornr); + + return qfp; +} + +/* + * Get the nth quickfix entry below the specified entry. Searches forward in + * the list. If linewise is TRUE, then treat multiple entries on a single line + * as one. + */ + static void +qf_get_nth_below_entry(qfline_T *entry_arg, int n, int linewise, int *errornr) +{ + qfline_T *entry = entry_arg; + + while (n-- > 0 && !got_int) + { + int first_errornr = *errornr; + + if (linewise) + // 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 (linewise) + *errornr = first_errornr; + break; + } + + entry = entry->qf_next; + ++*errornr; + } +} + +/* + * Get the nth quickfix entry above the specified entry. Searches backwards in + * the list. If linewise is TRUE, then treat multiple entries on a single line + * as one. + */ + static void +qf_get_nth_above_entry(qfline_T *entry, int n, int linewise, int *errornr) +{ + 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 + if (linewise) + entry = qf_find_first_entry_on_line(entry, errornr); + } +} + +/* + * Find the n'th quickfix entry adjacent to position 'pos' 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, + pos_T *pos, + int n, + int dir, + int linewise) +{ + qfline_T *adj_entry; + int errornr; + + // Find an entry closest to the specified position + adj_entry = qf_find_closest_entry(qfl, bnr, pos, dir, linewise, &errornr); + if (adj_entry == NULL) + return 0; + + if (--n > 0) + { + // Go to the n'th entry in the current buffer + if (dir == FORWARD) + qf_get_nth_below_entry(adj_entry, n, linewise, &errornr); + else + qf_get_nth_above_entry(adj_entry, n, linewise, &errornr); + } + + return errornr; +} + +/* + * Jump to a quickfix entry in the current file nearest to the current line or + * current line/col. + * ":cabove", ":cbelow", ":labove", ":lbelow", ":cafter", ":cbefore", + * ":lafter" and ":lbefore" commands + */ + void +ex_cbelow(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + int dir; + int buf_has_flag; + int errornr = 0; + pos_T pos; + + if (eap->addr_count > 0 && eap->line2 <= 0) + { + emsg(_(e_invalid_range)); + return; + } + + // Check whether the current buffer has any quickfix entries + if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow + || eap->cmdidx == CMD_cbefore || eap->cmdidx == CMD_cafter) + 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_no_errors)); + 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_no_errors)); + return; + } + + if (eap->cmdidx == CMD_cbelow + || eap->cmdidx == CMD_lbelow + || eap->cmdidx == CMD_cafter + || eap->cmdidx == CMD_lafter) + // Forward motion commands + dir = FORWARD; + else + dir = BACKWARD; + + pos = curwin->w_cursor; + // A quickfix entry column number is 1 based whereas cursor column + // number is 0 based. Adjust the column number. + pos.col++; + errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, &pos, + eap->addr_count > 0 ? eap->line2 : 0, dir, + eap->cmdidx == CMD_cbelow + || eap->cmdidx == CMD_lbelow + || eap->cmdidx == CMD_cabove + || eap->cmdidx == CMD_labove); + + 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. + */ + void +ex_cfile(exarg_T *eap) +{ + char_u *enc = NULL; + win_T *wp = NULL; + qf_info_T *qi = &ql_info; + char_u *au_name = NULL; + int_u save_qfid = 0; // init for gcc + int res; + + au_name = cfile_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + NULL, FALSE, curbuf)) + { +#ifdef FEAT_EVAL + if (aborting()) + return; +#endif + } + + enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; +#ifdef FEAT_BROWSE + if (cmdmod.cmod_flags & CMOD_BROWSE) + { + char_u *browse_file = do_browse(0, (char_u *)_("Error file"), eap->arg, + NULL, NULL, + (char_u *)_(BROWSE_FILTER_ALL_FILES), NULL); + if (browse_file == NULL) + return; + set_string_option_direct((char_u *)"ef", -1, browse_file, OPT_FREE, 0); + vim_free(browse_file); + } + else +#endif + if (*eap->arg != NUL) + set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + + if (is_loclist_cmd(eap->cmdidx)) + 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 + // first error. + // :cgetfile creates a new quickfix list but doesn't jump to the + // first error. + // :caddfile adds to an existing quickfix list. If there is no + // quickfix list then a new list is created. + res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile + && eap->cmdidx != CMD_laddfile), + qf_cmdtitle(*eap->cmdlinep), enc); + if (wp != NULL) + { + qi = GET_LOC_LIST(wp); + if (qi == NULL) + { + decr_quickfix_busy(); + return; + } + } + if (res >= 0) + qf_list_changed(qf_get_curlist(qi)); + save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf); + + // Jump to the first error for a new list and if autocmds didn't + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile) + && qflist_valid(wp, save_qfid)) + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + + decr_quickfix_busy(); +} + +/* + * Return the vimgrep autocmd name. + */ + static char_u * +vgr_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) + { + case CMD_vimgrep: return (char_u *)"vimgrep"; + case CMD_lvimgrep: return (char_u *)"lvimgrep"; + case CMD_vimgrepadd: return (char_u *)"vimgrepadd"; + case CMD_lvimgrepadd: return (char_u *)"lvimgrepadd"; + case CMD_grep: return (char_u *)"grep"; + case CMD_lgrep: return (char_u *)"lgrep"; + case CMD_grepadd: return (char_u *)"grepadd"; + case CMD_lgrepadd: return (char_u *)"lgrepadd"; + default: return NULL; + } +} + +/* + * Initialize the regmatch used by vimgrep for pattern "s". + */ + static void +vgr_init_regmatch(regmmatch_T *regmatch, char_u *s) +{ + // Get the search pattern: either white-separated or enclosed in // + regmatch->regprog = NULL; + + if (s == NULL || *s == NUL) + { + // Pattern is empty, use last search pattern. + if (last_search_pat() == NULL) + { + emsg(_(e_no_previous_regular_expression)); + return; + } + regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC); + } + else + regmatch->regprog = vim_regcomp(s, RE_MAGIC); + + regmatch->rmm_ic = p_ic; + regmatch->rmm_maxcol = 0; +} + +/* + * Display a file name when vimgrep is running. + */ + static void +vgr_display_fname(char_u *fname) +{ + char_u *p; + + msg_start(); + p = msg_strtrunc(fname, TRUE); + if (p == NULL) + msg_outtrans(fname); + else + { + msg_outtrans(p); + vim_free(p); + } + msg_clr_eos(); + msg_didout = FALSE; // overwrite this message + msg_nowait = TRUE; // don't wait for this message + msg_col = 0; + out_flush(); +} + +/* + * Load a dummy buffer to search for a pattern using vimgrep. + */ + static buf_T * +vgr_load_dummy_buf( + char_u *fname, + char_u *dirname_start, + char_u *dirname_now) +{ + int save_mls; +#if defined(FEAT_SYN_HL) + char_u *save_ei = NULL; +#endif + buf_T *buf; + +#if defined(FEAT_SYN_HL) + // Don't do Filetype autocommands to avoid loading syntax and + // indent scripts, a great speed improvement. + save_ei = au_event_disable(",Filetype"); +#endif + // Don't use modelines here, it's useless. + save_mls = p_mls; + p_mls = 0; + + // Load file into a buffer, so that 'fileencoding' is detected, + // autocommands applied, etc. + buf = load_dummy_buffer(fname, dirname_start, dirname_now); + + p_mls = save_mls; +#if defined(FEAT_SYN_HL) + au_event_restore(save_ei); +#endif + + return buf; +} + +/* + * Check whether a quickfix/location list is valid. Autocmds may remove or + * change a quickfix list when vimgrep is running. If the list is not found, + * create a new list. + */ + static int +vgr_qflist_valid( + win_T *wp, + qf_info_T *qi, + int_u qfid, + char_u *title) +{ + // Verify that the quickfix/location list was not freed by an autocmd + if (!qflist_valid(wp, qfid)) + { + if (wp != NULL) + { + // An autocmd has freed the location list. + emsg(_(e_current_location_list_was_changed)); + return FALSE; + } + else + { + // Quickfix list is not found, create a new one. + qf_new_list(qi, title); + return TRUE; + } + } + + if (qf_restore_list(qi, qfid) == FAIL) + return FALSE; + + return TRUE; +} + +/* + * Search for a pattern in all the lines in a buffer and add the matching lines + * to a quickfix list. + */ + static int +vgr_match_buflines( + qf_list_T *qfl, + char_u *fname, + buf_T *buf, + char_u *spat, + regmmatch_T *regmatch, + long *tomatch, + int duplicate_name, + int flags) +{ + int found_match = FALSE; + long lnum; + colnr_T col; + int pat_len = (int)STRLEN(spat); + + for (lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; ++lnum) + { + col = 0; + if (!(flags & VGR_FUZZY)) + { + // Regular expression match + while (vim_regexec_multi(regmatch, curwin, buf, lnum, + col, NULL) > 0) + { + // Pass the buffer number so that it gets used even for a + // dummy buffer, unless duplicate_name is set, then the + // buffer will be wiped out below. + if (qf_add_entry(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + ml_get_buf(buf, + regmatch->startpos[0].lnum + lnum, FALSE), + regmatch->startpos[0].lnum + lnum, + regmatch->endpos[0].lnum + lnum, + regmatch->startpos[0].col + 1, + regmatch->endpos[0].col + 1, + FALSE, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + TRUE // valid + ) == QF_FAIL) + { + got_int = TRUE; + break; + } + found_match = TRUE; + if (--*tomatch == 0) + break; + if ((flags & VGR_GLOBAL) == 0 + || regmatch->endpos[0].lnum > 0) + break; + col = regmatch->endpos[0].col + + (col == regmatch->endpos[0].col); + if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, FALSE))) + break; + } + } + else + { + char_u *str = ml_get_buf(buf, lnum, FALSE); + int score; + int_u matches[MAX_FUZZY_MATCHES]; + int_u sz = ARRAY_LENGTH(matches); + + // Fuzzy string match + while (fuzzy_match(str + col, spat, FALSE, &score, matches, sz) > 0) + { + // Pass the buffer number so that it gets used even for a + // dummy buffer, unless duplicate_name is set, then the + // buffer will be wiped out below. + if (qf_add_entry(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + str, + lnum, + 0, + matches[0] + col + 1, + 0, + FALSE, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + TRUE // valid + ) == QF_FAIL) + { + got_int = TRUE; + break; + } + found_match = TRUE; + if (--*tomatch == 0) + break; + if ((flags & VGR_GLOBAL) == 0) + break; + col = matches[pat_len - 1] + col + 1; + if (col > (colnr_T)STRLEN(str)) + break; + } + } + line_breakcheck(); + if (got_int) + break; + } + + return found_match; +} + +/* + * Jump to the first match and update the directory. + */ + static void +vgr_jump_to_match( + qf_info_T *qi, + int forceit, + int *redraw_for_dummy, + buf_T *first_match_buf, + char_u *target_dir) +{ + buf_T *buf; + + buf = curbuf; + qf_jump(qi, 0, 0, forceit); + if (buf != curbuf) + // If we jumped to another buffer redrawing will already be + // taken care of. + *redraw_for_dummy = FALSE; + + // Jump to the directory used after loading the buffer. + if (curbuf == first_match_buf && target_dir != NULL) + { + exarg_T ea; + + CLEAR_FIELD(ea); + ea.arg = target_dir; + ea.cmdidx = CMD_lcd; + ex_cd(&ea); + } +} + +/* + * :vimgrep command arguments + */ +typedef struct +{ + long tomatch; // maximum number of matches to find + char_u *spat; // search pattern + int flags; // search modifier + char_u **fnames; // list of files to search + int fcount; // number of files + regmmatch_T regmatch; // compiled search pattern + char_u *qf_title; // quickfix list title +} vgr_args_T; + +/* + * Process :vimgrep command arguments. The command syntax is: + * + * :{count}vimgrep /{pattern}/[g][j] {file} ... + */ + static int +vgr_process_args( + exarg_T *eap, + vgr_args_T *args) +{ + char_u *p; + + vim_memset(args, 0, sizeof(*args)); + + args->regmatch.regprog = NULL; + args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); + + if (eap->addr_count > 0) + args->tomatch = eap->line2; + else + args->tomatch = MAXLNUM; + + // Get the search pattern: either white-separated or enclosed in // + p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags); + if (p == NULL) + { + emsg(_(e_invalid_search_pattern_or_delimiter)); + return FAIL; + } + + vgr_init_regmatch(&args->regmatch, args->spat); + if (args->regmatch.regprog == NULL) + return FAIL; + + p = skipwhite(p); + if (*p == NUL) + { + emsg(_(e_file_name_missing_or_invalid_pattern)); + return FAIL; + } + + // Parse the list of arguments, wildcards have already been expanded. + if ((get_arglist_exp(p, &args->fcount, &args->fnames, TRUE) == FAIL) || + args->fcount == 0) + { + emsg(_(e_no_match)); + return FAIL; + } + + return OK; +} + +/* + * Return TRUE if "buf" had an existing swap file, the current swap file does + * not end in ".swp". + */ + static int +existing_swapfile(buf_T *buf) +{ + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) + { + char_u *fname = buf->b_ml.ml_mfp->mf_fname; + size_t len = STRLEN(fname); + + return fname[len - 1] != 'p' || fname[len - 2] != 'w'; + } + return FALSE; +} + +/* + * Search for a pattern in a list of files and populate the quickfix list with + * the matches. + */ + static int +vgr_process_files( + win_T *wp, + qf_info_T *qi, + vgr_args_T *cmd_args, + int *redraw_for_dummy, + buf_T **first_match_buf, + char_u **target_dir) +{ + int status = FAIL; + int_u save_qfid = qf_get_curlist(qi)->qf_id; + time_t seconds = 0; + char_u *fname; + int fi; + buf_T *buf; + int duplicate_name = FALSE; + int using_dummy; + char_u *dirname_start = NULL; + char_u *dirname_now = NULL; + int found_match; + aco_save_T aco; + + dirname_start = alloc_id(MAXPATHL, aid_qf_dirname_start); + dirname_now = alloc_id(MAXPATHL, aid_qf_dirname_now); + if (dirname_start == NULL || dirname_now == NULL) + goto theend; + + // Remember the current directory, because a BufRead autocommand that does + // ":lcd %:p:h" changes the meaning of short path names. + mch_dirname(dirname_start, MAXPATHL); + + seconds = (time_t)0; + for (fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; + ++fi) + { + fname = shorten_fname1(cmd_args->fnames[fi]); + if (time(NULL) > seconds) + { + // Display the file name every second or so, show the user we are + // working on it. + seconds = time(NULL); + vgr_display_fname(fname); + } + + buf = buflist_findname_exp(cmd_args->fnames[fi]); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + { + // Remember that a buffer with this name already exists. + duplicate_name = (buf != NULL); + using_dummy = TRUE; + *redraw_for_dummy = TRUE; + + buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now); + } + else + // Use existing, loaded buffer. + using_dummy = FALSE; + + // Check whether the quickfix list is still valid. When loading a + // buffer above, autocommands might have changed the quickfix list. + if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) + goto theend; + + save_qfid = qf_get_curlist(qi)->qf_id; + + if (buf == NULL) + { + if (!got_int) + smsg(_("Cannot open file \"%s\""), fname); + } + else + { + // Try for a match in all lines of the buffer. + // For ":1vimgrep" look for first match only. + found_match = vgr_match_buflines(qf_get_curlist(qi), + fname, buf, cmd_args->spat, &cmd_args->regmatch, + &cmd_args->tomatch, duplicate_name, cmd_args->flags); + + if (using_dummy) + { + if (found_match && *first_match_buf == NULL) + *first_match_buf = buf; + if (duplicate_name) + { + // Never keep a dummy buffer if there is another buffer + // with the same name. + wipe_dummy_buffer(buf, dirname_start); + buf = NULL; + } + else if ((cmdmod.cmod_flags & CMOD_HIDE) == 0 + || buf->b_p_bh[0] == 'u' // "unload" + || buf->b_p_bh[0] == 'w' // "wipe" + || buf->b_p_bh[0] == 'd') // "delete" + { + // When no match was found we don't need to remember the + // buffer, wipe it out. If there was a match and it + // wasn't the first one or we won't jump there: only + // unload the buffer. + // Ignore 'hidden' here, because it may lead to having too + // many swap files. + if (!found_match) + { + wipe_dummy_buffer(buf, dirname_start); + buf = NULL; + } + else if (buf != *first_match_buf + || (cmd_args->flags & VGR_NOJUMP) + || existing_swapfile(buf)) + { + unload_dummy_buffer(buf, dirname_start); + // Keeping the buffer, remove the dummy flag. + buf->b_flags &= ~BF_DUMMY; + buf = NULL; + } + } + + if (buf != NULL) + { + // Keeping the buffer, remove the dummy flag. + buf->b_flags &= ~BF_DUMMY; + + // If the buffer is still loaded we need to use the + // directory we jumped to below. + if (buf == *first_match_buf + && *target_dir == NULL + && STRCMP(dirname_start, dirname_now) != 0) + *target_dir = vim_strsave(dirname_now); + + // The buffer is still loaded, the Filetype autocommands + // need to be done now, in that buffer. And the modelines + // need to be done (again). But not the window-local + // options! + aucmd_prepbuf(&aco, buf); + if (curbuf == buf) + { +#if defined(FEAT_SYN_HL) + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, + buf->b_fname, TRUE, buf); +#endif + do_modelines(OPT_NOWIN); + aucmd_restbuf(&aco); + } + } + } + } + } + + status = OK; + +theend: + vim_free(dirname_now); + vim_free(dirname_start); + return status; +} + +/* + * ":vimgrep {pattern} file(s)" + * ":vimgrepadd {pattern} file(s)" + * ":lvimgrep {pattern} file(s)" + * ":lvimgrepadd {pattern} file(s)" + */ + void +ex_vimgrep(exarg_T *eap) +{ + vgr_args_T args; + qf_info_T *qi; + qf_list_T *qfl; + int_u save_qfid; + win_T *wp = NULL; + int redraw_for_dummy = FALSE; + buf_T *first_match_buf = NULL; + char_u *target_dir = NULL; + char_u *au_name = NULL; + int status; + + au_name = vgr_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { +#ifdef FEAT_EVAL + if (aborting()) + return; +#endif + } + + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) + return; + + if (vgr_process_args(eap, &args) == FAIL) + goto theend; + + if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd + && eap->cmdidx != CMD_vimgrepadd + && eap->cmdidx != CMD_lvimgrepadd) + || qf_stack_empty(qi)) + // make place for a new list + qf_new_list(qi, args.qf_title); + + incr_quickfix_busy(); + + status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, + &first_match_buf, &target_dir); + if (status != OK) + { + FreeWild(args.fcount, args.fnames); + decr_quickfix_busy(); + goto theend; + } + + FreeWild(args.fcount, args.fnames); + + 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); + + // Remember the current quickfix list identifier, so that we can check for + // autocommands changing the current quickfix list. + save_qfid = qf_get_curlist(qi)->qf_id; + + if (au_name != NULL) + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, TRUE, curbuf); + // The QuickFixCmdPost autocmd may free the quickfix list. Check the list + // is still valid. + if (!qflist_valid(wp, save_qfid) + || qf_restore_list(qi, save_qfid) == FAIL) + { + decr_quickfix_busy(); + goto theend; + } + + // Jump to first match. + if (!qf_list_empty(qf_get_curlist(qi))) + { + if ((args.flags & VGR_NOJUMP) == 0) + vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, + first_match_buf, target_dir); + } + else + semsg(_(e_no_match_str_2), args.spat); + + 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) + { +#ifdef FEAT_FOLDING + foldUpdateAll(curwin); +#else + redraw_later(UPD_NOT_VALID); +#endif + } + +theend: + vim_free(args.qf_title); + vim_free(target_dir); + vim_regfree(args.regmatch.regprog); +} + +/* + * Restore current working directory to "dirname_start" if they differ, taking + * into account whether it is set locally or globally. + */ + static void +restore_start_dir(char_u *dirname_start) +{ + char_u *dirname_now = alloc(MAXPATHL); + + if (dirname_now == NULL) + return; + + mch_dirname(dirname_now, MAXPATHL); + if (STRCMP(dirname_start, dirname_now) != 0) + { + // If the directory has changed, change it back by building up an + // appropriate ex command and executing it. + exarg_T ea; + + CLEAR_FIELD(ea); + ea.arg = dirname_start; + ea.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd; + ex_cd(&ea); + } + vim_free(dirname_now); +} + +/* + * Load file "fname" into a dummy buffer and return the buffer pointer, + * placing the directory resulting from the buffer load into the + * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller + * prior to calling this function. Restores directory to "dirname_start" prior + * to returning, if autocmds or the 'autochdir' option have changed it. + * + * If creating the dummy buffer does not fail, must call unload_dummy_buffer() + * or wipe_dummy_buffer() later! + * + * Returns NULL if it fails. + */ + static buf_T * +load_dummy_buffer( + char_u *fname, + char_u *dirname_start, // in: old directory + char_u *resulting_dir) // out: new directory +{ + buf_T *newbuf; + bufref_T newbufref; + bufref_T newbuf_to_wipe; + int failed = TRUE; + aco_save_T aco; + int readfile_result; + + // Allocate a buffer without putting it in the buffer list. + newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); + if (newbuf == NULL) + return NULL; + set_bufref(&newbufref, newbuf); + + // Init the options. + buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP); + + // need to open the memfile before putting the buffer in a window + if (ml_open(newbuf) == OK) + { + // Make sure this buffer isn't wiped out by autocommands. + ++newbuf->b_locked; + + // set curwin/curbuf to buf and save a few things + aucmd_prepbuf(&aco, newbuf); + if (curbuf == newbuf) + { + // Need to set the filename for autocommands. + (void)setfname(curbuf, fname, NULL, FALSE); + + // Create swap file now to avoid the ATTENTION message. + check_need_swap(TRUE); + + // Remove the "dummy" flag, otherwise autocommands may not + // work. + curbuf->b_flags &= ~BF_DUMMY; + + newbuf_to_wipe.br_buf = NULL; + readfile_result = readfile(fname, NULL, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, + NULL, READ_NEW | READ_DUMMY); + --newbuf->b_locked; + if (readfile_result == OK + && !got_int + && !(curbuf->b_flags & BF_NEW)) + { + failed = FALSE; + if (curbuf != newbuf) + { + // Bloody autocommands changed the buffer! Can happen when + // using netrw and editing a remote file. Use the current + // buffer instead, delete the dummy one after restoring the + // window stuff. + set_bufref(&newbuf_to_wipe, newbuf); + newbuf = curbuf; + } + } + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + + if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) + wipe_buffer(newbuf_to_wipe.br_buf, FALSE); + } + + // Add back the "dummy" flag, otherwise buflist_findname_stat() won't + // skip it. + newbuf->b_flags |= BF_DUMMY; + } + + // When autocommands/'autochdir' option changed directory: go back. + // Let the caller know what the resulting dir was first, in case it is + // important. + mch_dirname(resulting_dir, MAXPATHL); + restore_start_dir(dirname_start); + + if (!bufref_valid(&newbufref)) + return NULL; + if (failed) + { + wipe_dummy_buffer(newbuf, dirname_start); + return NULL; + } + return newbuf; +} + +/* + * Wipe out the dummy buffer that load_dummy_buffer() created. Restores + * directory to "dirname_start" prior to returning, if autocmds or the + * 'autochdir' option have changed it. + */ + static void +wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) +{ + // If any autocommand opened a window on the dummy buffer, close that + // window. If we can't close them all then give up. + while (buf->b_nwindows > 0) + { + int did_one = FALSE; + win_T *wp; + + if (firstwin->w_next != NULL) + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + { + if (win_close(wp, FALSE) == OK) + did_one = TRUE; + break; + } + if (!did_one) + return; + } + + if (curbuf != buf && buf->b_nwindows == 0) // safety check + { +#if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that aborting() + // returns FALSE when wiping out the buffer. Otherwise it doesn't + // work when got_int is set. + enter_cleanup(&cs); +#endif + + wipe_buffer(buf, TRUE); + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + // When autocommands/'autochdir' option changed directory: go back. + restore_start_dir(dirname_start); + } +} + +/* + * Unload the dummy buffer that load_dummy_buffer() created. Restores + * directory to "dirname_start" prior to returning, if autocmds or the + * 'autochdir' option have changed it. + */ + static void +unload_dummy_buffer(buf_T *buf, char_u *dirname_start) +{ + if (curbuf == buf) // safety check + return; + + close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE, TRUE); + + // When autocommands/'autochdir' option changed directory: go back. + restore_start_dir(dirname_start); +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Copy the specified quickfix entry items into a new dict and append the dict + * to 'list'. Returns OK on success. + */ + static int +get_qfline_items(qfline_T *qfp, list_T *list) +{ + int bufnum; + dict_T *dict; + char_u buf[2]; + + // Handle entries with a non-existing buffer number. + bufnum = qfp->qf_fnum; + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) + bufnum = 0; + + if ((dict = dict_alloc()) == NULL) + return FAIL; + if (list_append_dict(list, dict) == FAIL) + return FAIL; + + buf[0] = qfp->qf_type; + buf[1] = NUL; + if (dict_add_number(dict, "bufnr", (long)bufnum) == FAIL + || dict_add_number(dict, "lnum", (long)qfp->qf_lnum) == FAIL + || dict_add_number(dict, "end_lnum", (long)qfp->qf_end_lnum) == FAIL + || dict_add_number(dict, "col", (long)qfp->qf_col) == FAIL + || dict_add_number(dict, "end_col", (long)qfp->qf_end_col) == FAIL + || dict_add_number(dict, "vcol", (long)qfp->qf_viscol) == FAIL + || dict_add_number(dict, "nr", (long)qfp->qf_nr) == FAIL + || dict_add_string(dict, "module", qfp->qf_module) == FAIL + || dict_add_string(dict, "pattern", qfp->qf_pattern) == FAIL + || dict_add_string(dict, "text", qfp->qf_text) == FAIL + || dict_add_string(dict, "type", buf) == FAIL + || dict_add_number(dict, "valid", (long)qfp->qf_valid) == FAIL) + return FAIL; + + 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. + * If eidx is not 0, then return only the specified entry. Otherwise return + * all the entries. + */ + static int +get_errorlist( + qf_info_T *qi_arg, + win_T *wp, + int qf_idx, + int eidx, + list_T *list) +{ + qf_info_T *qi = qi_arg; + qf_list_T *qfl; + qfline_T *qfp; + int i; + + if (qi == NULL) + { + qi = &ql_info; + if (wp != NULL) + { + qi = GET_LOC_LIST(wp); + if (qi == NULL) + return FAIL; + } + } + + if (eidx < 0) + return OK; + + if (qf_idx == INVALID_QFIDX) + qf_idx = qi->qf_curlist; + + if (qf_idx >= qi->qf_listcount) + return FAIL; + + qfl = qf_get_list(qi, qf_idx); + if (qf_list_empty(qfl)) + return FAIL; + + FOR_ALL_QFL_ITEMS(qfl, qfp, i) + { + if (eidx > 0) + { + if (eidx == i) + return get_qfline_items(qfp, list); + } + else if (get_qfline_items(qfp, list) == FAIL) + return FAIL; + } + + return OK; +} + +// Flags used by getqflist()/getloclist() to determine which fields to return. +enum { + QF_GETLIST_NONE = 0x0, + QF_GETLIST_TITLE = 0x1, + QF_GETLIST_ITEMS = 0x2, + QF_GETLIST_NR = 0x4, + QF_GETLIST_WINID = 0x8, + QF_GETLIST_CONTEXT = 0x10, + QF_GETLIST_ID = 0x20, + QF_GETLIST_IDX = 0x40, + QF_GETLIST_SIZE = 0x80, + QF_GETLIST_TICK = 0x100, + QF_GETLIST_FILEWINID = 0x200, + QF_GETLIST_QFBUFNR = 0x400, + QF_GETLIST_QFTF = 0x800, + QF_GETLIST_ALL = 0xFFF, +}; + +/* + * Parse text from 'di' and return the quickfix list items. + * Existing quickfix lists are not modified. + */ + static int +qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) +{ + int status = FAIL; + qf_info_T *qi; + char_u *errorformat = p_efm; + dictitem_T *efm_di; + list_T *l; + + // Only a List value is supported + if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL) + return FAIL; + + // If errorformat is supplied then use it, otherwise use the 'efm' + // option setting + if ((efm_di = dict_find(what, (char_u *)"efm", -1)) != NULL) + { + if (efm_di->di_tv.v_type != VAR_STRING || + efm_di->di_tv.vval.v_string == NULL) + return FAIL; + errorformat = efm_di->di_tv.vval.v_string; + } + + l = list_alloc(); + if (l == NULL) + return FAIL; + + qi = qf_alloc_stack(QFLT_INTERNAL); + if (qi != NULL) + { + 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, 0, l); + qf_free(&qi->qf_lists[0]); + } + free(qi); + } + dict_add_list(retdict, "items", l); + status = OK; + + return status; +} + +/* + * Return the quickfix/location list window identifier in the current tabpage. + */ + static int +qf_winid(qf_info_T *qi) +{ + win_T *win; + + // The quickfix window can be opened even if the quickfix list is not set + // using ":copen". This is not true for location lists. + if (qi == NULL) + return 0; + win = qf_find_win(qi); + if (win != NULL) + return win->w_id; + return 0; +} + +/* + * Returns the number of the buffer displayed in the quickfix/location list + * window. If there is no buffer associated with the list or the buffer is + * wiped out, then returns 0. + */ + static int +qf_getprop_qfbufnr(qf_info_T *qi, dict_T *retdict) +{ + int bufnum = 0; + + if (qi != NULL && buflist_findnr(qi->qf_bufnr) != NULL) + bufnum = qi->qf_bufnr; + + return dict_add_number(retdict, "qfbufnr", bufnum); +} + +/* + * Convert the keys in 'what' to quickfix list property flags. + */ + static int +qf_getprop_keys2flags(dict_T *what, int loclist) +{ + int flags = QF_GETLIST_NONE; + + if (dict_has_key(what, "all")) + { + flags |= QF_GETLIST_ALL; + if (!loclist) + // File window ID is applicable only to location list windows + flags &= ~ QF_GETLIST_FILEWINID; + } + + if (dict_has_key(what, "title")) + flags |= QF_GETLIST_TITLE; + + if (dict_has_key(what, "nr")) + flags |= QF_GETLIST_NR; + + if (dict_has_key(what, "winid")) + flags |= QF_GETLIST_WINID; + + if (dict_has_key(what, "context")) + flags |= QF_GETLIST_CONTEXT; + + if (dict_has_key(what, "id")) + flags |= QF_GETLIST_ID; + + if (dict_has_key(what, "items")) + flags |= QF_GETLIST_ITEMS; + + if (dict_has_key(what, "idx")) + flags |= QF_GETLIST_IDX; + + if (dict_has_key(what, "size")) + flags |= QF_GETLIST_SIZE; + + if (dict_has_key(what, "changedtick")) + flags |= QF_GETLIST_TICK; + + if (loclist && dict_has_key(what, "filewinid")) + flags |= QF_GETLIST_FILEWINID; + + if (dict_has_key(what, "qfbufnr")) + flags |= QF_GETLIST_QFBUFNR; + + if (dict_has_key(what, "quickfixtextfunc")) + flags |= QF_GETLIST_QFTF; + + return flags; +} + +/* + * Return the quickfix list index based on 'nr' or 'id' in 'what'. + * If 'nr' and 'id' are not present in 'what' then return the current + * quickfix list index. + * If 'nr' is zero then return the current quickfix list index. + * If 'nr' is '$' then return the last quickfix list index. + * If 'id' is present then return the index of the quickfix list with that id. + * If 'id' is zero then return the quickfix list index specified by 'nr'. + * Return -1, if quickfix list is not present or if the stack is empty. + */ + static int +qf_getprop_qfidx(qf_info_T *qi, dict_T *what) +{ + int qf_idx; + dictitem_T *di; + + qf_idx = qi->qf_curlist; // default is the current list + if ((di = dict_find(what, (char_u *)"nr", -1)) != NULL) + { + // Use the specified quickfix/location list + if (di->di_tv.v_type == VAR_NUMBER) + { + // for zero use the current list + if (di->di_tv.vval.v_number != 0) + { + qf_idx = di->di_tv.vval.v_number - 1; + if (qf_idx < 0 || qf_idx >= qi->qf_listcount) + qf_idx = INVALID_QFIDX; + } + } + else if (di->di_tv.v_type == VAR_STRING + && di->di_tv.vval.v_string != NULL + && STRCMP(di->di_tv.vval.v_string, "$") == 0) + // Get the last quickfix list number + qf_idx = qi->qf_listcount - 1; + else + qf_idx = INVALID_QFIDX; + } + + if ((di = dict_find(what, (char_u *)"id", -1)) != NULL) + { + // Look for a list with the specified id + if (di->di_tv.v_type == VAR_NUMBER) + { + // For zero, use the current list or the list specified by 'nr' + if (di->di_tv.vval.v_number != 0) + qf_idx = qf_id2nr(qi, di->di_tv.vval.v_number); + } + else + qf_idx = INVALID_QFIDX; + } + + return qf_idx; +} + +/* + * Return default values for quickfix list properties in retdict. + */ + static int +qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *retdict) +{ + int status = OK; + + if (flags & QF_GETLIST_TITLE) + status = dict_add_string(retdict, "title", (char_u *)""); + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) + { + list_T *l = list_alloc(); + if (l != NULL) + status = dict_add_list(retdict, "items", l); + else + status = FAIL; + } + if ((status == OK) && (flags & QF_GETLIST_NR)) + status = dict_add_number(retdict, "nr", 0); + if ((status == OK) && (flags & QF_GETLIST_WINID)) + status = dict_add_number(retdict, "winid", qf_winid(qi)); + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) + status = dict_add_string(retdict, "context", (char_u *)""); + if ((status == OK) && (flags & QF_GETLIST_ID)) + status = dict_add_number(retdict, "id", 0); + if ((status == OK) && (flags & QF_GETLIST_IDX)) + status = dict_add_number(retdict, "idx", 0); + if ((status == OK) && (flags & QF_GETLIST_SIZE)) + status = dict_add_number(retdict, "size", 0); + if ((status == OK) && (flags & QF_GETLIST_TICK)) + status = dict_add_number(retdict, "changedtick", 0); + if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) + status = dict_add_number(retdict, "filewinid", 0); + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) + status = qf_getprop_qfbufnr(qi, retdict); + if ((status == OK) && (flags & QF_GETLIST_QFTF)) + status = dict_add_string(retdict, "quickfixtextfunc", (char_u *)""); + + return status; +} + +/* + * Return the quickfix list title as 'title' in retdict + */ + static int +qf_getprop_title(qf_list_T *qfl, dict_T *retdict) +{ + return dict_add_string(retdict, "title", qfl->qf_title); +} + +/* + * Returns the identifier of the window used to display files from a location + * list. If there is no associated window, then returns 0. Useful only when + * called from a location list window. + */ + static int +qf_getprop_filewinid(win_T *wp, qf_info_T *qi, dict_T *retdict) +{ + int winid = 0; + + if (wp != NULL && IS_LL_WINDOW(wp)) + { + win_T *ll_wp = qf_find_win_with_loclist(qi); + if (ll_wp != NULL) + winid = ll_wp->w_id; + } + + return dict_add_number(retdict, "filewinid", winid); +} + +/* + * Return the quickfix list items/entries as 'items' in retdict. + * If eidx is not 0, then return the item at the specified index. + */ + static int +qf_getprop_items(qf_info_T *qi, int qf_idx, int eidx, dict_T *retdict) +{ + int status = OK; + list_T *l = list_alloc(); + if (l != NULL) + { + (void)get_errorlist(qi, NULL, qf_idx, eidx, l); + dict_add_list(retdict, "items", l); + } + else + status = FAIL; + + return status; +} + +/* + * Return the quickfix list context (if any) as 'context' in retdict. + */ + static int +qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict) +{ + int status; + dictitem_T *di; + + if (qfl->qf_ctx != NULL) + { + di = dictitem_alloc((char_u *)"context"); + if (di != NULL) + { + copy_tv(qfl->qf_ctx, &di->di_tv); + status = dict_add(retdict, di); + if (status == FAIL) + dictitem_free(di); + } + else + status = FAIL; + } + else + status = dict_add_string(retdict, "context", (char_u *)""); + + return status; +} + +/* + * Return the current quickfix list index as 'idx' in retdict. + * If a specific entry index (eidx) is supplied, then use that. + */ + static int +qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict) +{ + if (eidx == 0) + { + eidx = qfl->qf_index; + if (qf_list_empty(qfl)) + // For empty lists, current index is set to 0 + eidx = 0; + } + return dict_add_number(retdict, "idx", eidx); +} + +/* + * Return the 'quickfixtextfunc' function of a quickfix/location list + */ + static int +qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) +{ + int status; + + if (qfl->qf_qftf_cb.cb_name != NULL) + { + typval_T tv; + + put_callback(&qfl->qf_qftf_cb, &tv); + status = dict_add_tv(retdict, "quickfixtextfunc", &tv); + clear_tv(&tv); + } + else + status = dict_add_string(retdict, "quickfixtextfunc", (char_u *)""); + + return status; +} + +/* + * Return quickfix/location list details (title) as a + * dictionary. 'what' contains the details to return. If 'list_idx' is -1, + * then current list is used. Otherwise the specified list is used. + */ + static int +qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) +{ + qf_info_T *qi = &ql_info; + qf_list_T *qfl; + int status = OK; + int qf_idx = INVALID_QFIDX; + int eidx = 0; + dictitem_T *di; + int flags = QF_GETLIST_NONE; + + if ((di = dict_find(what, (char_u *)"lines", -1)) != NULL) + return qf_get_list_from_lines(what, di, retdict); + + if (wp != NULL) + qi = GET_LOC_LIST(wp); + + flags = qf_getprop_keys2flags(what, (wp != NULL)); + + if (!qf_stack_empty(qi)) + qf_idx = qf_getprop_qfidx(qi, what); + + // List is not present or is empty + if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) + return qf_getprop_defaults(qi, flags, wp != NULL, retdict); + + qfl = qf_get_list(qi, qf_idx); + + // If an entry index is specified, use that + if ((di = dict_find(what, (char_u *)"idx", -1)) != NULL) + { + if (di->di_tv.v_type != VAR_NUMBER) + return FAIL; + eidx = di->di_tv.vval.v_number; + } + + if (flags & QF_GETLIST_TITLE) + status = qf_getprop_title(qfl, retdict); + if ((status == OK) && (flags & QF_GETLIST_NR)) + status = dict_add_number(retdict, "nr", qf_idx + 1); + if ((status == OK) && (flags & QF_GETLIST_WINID)) + status = dict_add_number(retdict, "winid", qf_winid(qi)); + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) + status = qf_getprop_items(qi, qf_idx, eidx, retdict); + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) + status = qf_getprop_ctx(qfl, retdict); + if ((status == OK) && (flags & QF_GETLIST_ID)) + status = dict_add_number(retdict, "id", qfl->qf_id); + if ((status == OK) && (flags & QF_GETLIST_IDX)) + status = qf_getprop_idx(qfl, eidx, retdict); + if ((status == OK) && (flags & QF_GETLIST_SIZE)) + status = dict_add_number(retdict, "size", qfl->qf_count); + if ((status == OK) && (flags & QF_GETLIST_TICK)) + status = dict_add_number(retdict, "changedtick", qfl->qf_changedtick); + if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) + status = qf_getprop_filewinid(wp, qi, retdict); + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) + status = qf_getprop_qfbufnr(qi, retdict); + if ((status == OK) && (flags & QF_GETLIST_QFTF)) + status = qf_getprop_qftf(qfl, retdict); + + return status; +} + +/* + * Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the + * items in the dict 'd'. If it is a valid error entry, then set 'valid_entry' + * to TRUE. + */ + static int +qf_add_entry_from_dict( + qf_list_T *qfl, + dict_T *d, + int first_entry, + int *valid_entry) +{ + static int did_bufnr_emsg; + char_u *filename, *module, *pattern, *text, *type; + int bufnum, valid, status, col, end_col, vcol, nr; + long lnum, end_lnum; + + if (first_entry) + did_bufnr_emsg = FALSE; + + filename = dict_get_string(d, "filename", TRUE); + module = dict_get_string(d, "module", TRUE); + bufnum = (int)dict_get_number(d, "bufnr"); + lnum = (int)dict_get_number(d, "lnum"); + end_lnum = (int)dict_get_number(d, "end_lnum"); + col = (int)dict_get_number(d, "col"); + end_col = (int)dict_get_number(d, "end_col"); + vcol = (int)dict_get_number(d, "vcol"); + nr = (int)dict_get_number(d, "nr"); + type = dict_get_string(d, "type", TRUE); + pattern = dict_get_string(d, "pattern", TRUE); + text = dict_get_string(d, "text", TRUE); + if (text == NULL) + text = vim_strsave((char_u *)""); + + valid = TRUE; + if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) + valid = FALSE; + + // Mark entries with non-existing buffer number as not valid. Give the + // error message only once. + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) + { + if (!did_bufnr_emsg) + { + did_bufnr_emsg = TRUE; + semsg(_(e_buffer_nr_not_found), bufnum); + } + valid = FALSE; + bufnum = 0; + } + + // If the 'valid' field is present it overrules the detected value. + if (dict_has_key(d, "valid")) + valid = (int)dict_get_bool(d, "valid", FALSE); + + status = qf_add_entry(qfl, + NULL, // dir + filename, + module, + bufnum, + text, + lnum, + end_lnum, + col, + end_col, + vcol, // vis_col + pattern, // search pattern + nr, + type == NULL ? NUL : *type, + valid); + + vim_free(filename); + vim_free(module); + vim_free(pattern); + vim_free(text); + vim_free(type); + + if (valid) + *valid_entry = TRUE; + + return status; +} + +/* + * Add list of entries to quickfix/location list. Each list entry is + * a dictionary with item information. + */ + static int +qf_add_entries( + qf_info_T *qi, + int qf_idx, + list_T *list, + char_u *title, + int action) +{ + qf_list_T *qfl = qf_get_list(qi, qf_idx); + listitem_T *li; + dict_T *d; + qfline_T *old_last = NULL; + int retval = OK; + int valid_entry = FALSE; + + if (action == ' ' || qf_idx == qi->qf_listcount) + { + // make place for a new list + qf_new_list(qi, title); + qf_idx = qi->qf_curlist; + qfl = qf_get_list(qi, qf_idx); + } + else if (action == 'a' && !qf_list_empty(qfl)) + // Adding to existing list, use last entry. + old_last = qfl->qf_last; + else if (action == 'r') + { + qf_free_items(qfl); + qf_store_title(qfl, title); + } + + FOR_ALL_LIST_ITEMS(list, li) + { + if (li->li_tv.v_type != VAR_DICT) + continue; // Skip non-dict items + + d = li->li_tv.vval.v_dict; + if (d == NULL) + continue; + + retval = qf_add_entry_from_dict(qfl, d, li == list->lv_first, + &valid_entry); + if (retval == QF_FAIL) + break; + } + + // Check if any valid error entries are added to the list. + if (valid_entry) + qfl->qf_nonevalid = FALSE; + else if (qfl->qf_index == 0) + // no valid entry + qfl->qf_nonevalid = TRUE; + + // If not appending to the list, set the current error to the first entry + if (action != 'a') + qfl->qf_ptr = qfl->qf_start; + + // Update the current error index if not appending to the list or if the + // list was empty before and it is not empty now. + if ((action != 'a' || qfl->qf_index == 0) && !qf_list_empty(qfl)) + qfl->qf_index = 1; + + // Don't update the cursor in quickfix window when appending entries + qf_update_buffer(qi, old_last); + + return retval; +} + +/* + * Get the quickfix list index from 'nr' or 'id' + */ + static int +qf_setprop_get_qfidx( + qf_info_T *qi, + dict_T *what, + int action, + int *newlist) +{ + dictitem_T *di; + int qf_idx = qi->qf_curlist; // default is the current list + + if ((di = dict_find(what, (char_u *)"nr", -1)) != NULL) + { + // Use the specified quickfix/location list + if (di->di_tv.v_type == VAR_NUMBER) + { + // for zero use the current list + if (di->di_tv.vval.v_number != 0) + qf_idx = di->di_tv.vval.v_number - 1; + + if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) + { + // When creating a new list, accept qf_idx pointing to the next + // non-available list and add the new list at the end of the + // stack. + *newlist = TRUE; + qf_idx = qf_stack_empty(qi) ? 0 : qi->qf_listcount - 1; + } + else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) + return INVALID_QFIDX; + else if (action != ' ') + *newlist = FALSE; // use the specified list + } + else if (di->di_tv.v_type == VAR_STRING + && di->di_tv.vval.v_string != NULL + && STRCMP(di->di_tv.vval.v_string, "$") == 0) + { + if (!qf_stack_empty(qi)) + qf_idx = qi->qf_listcount - 1; + else if (*newlist) + qf_idx = 0; + else + return INVALID_QFIDX; + } + else + return INVALID_QFIDX; + } + + if (!*newlist && (di = dict_find(what, (char_u *)"id", -1)) != NULL) + { + // Use the quickfix/location list with the specified id + if (di->di_tv.v_type != VAR_NUMBER) + return INVALID_QFIDX; + + return qf_id2nr(qi, di->di_tv.vval.v_number); + } + + return qf_idx; +} + +/* + * Set the quickfix list title. + */ + static int +qf_setprop_title(qf_info_T *qi, int qf_idx, dict_T *what, dictitem_T *di) +{ + qf_list_T *qfl = qf_get_list(qi, qf_idx); + + if (di->di_tv.v_type != VAR_STRING) + return FAIL; + + vim_free(qfl->qf_title); + qfl->qf_title = dict_get_string(what, "title", TRUE); + if (qf_idx == qi->qf_curlist) + qf_update_win_titlevar(qi); + + return OK; +} + +/* + * Set quickfix list items/entries. + */ + static int +qf_setprop_items(qf_info_T *qi, int qf_idx, dictitem_T *di, int action) +{ + int retval = FAIL; + char_u *title_save; + + if (di->di_tv.v_type != VAR_LIST) + return FAIL; + + title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title); + retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, + title_save, action == ' ' ? 'a' : action); + vim_free(title_save); + + return retval; +} + +/* + * Set quickfix list items/entries from a list of lines. + */ + static int +qf_setprop_items_from_lines( + qf_info_T *qi, + int qf_idx, + dict_T *what, + dictitem_T *di, + int action) +{ + char_u *errorformat = p_efm; + dictitem_T *efm_di; + int retval = FAIL; + + // Use the user supplied errorformat settings (if present) + if ((efm_di = dict_find(what, (char_u *)"efm", -1)) != NULL) + { + if (efm_di->di_tv.v_type != VAR_STRING || + efm_di->di_tv.vval.v_string == NULL) + return FAIL; + errorformat = efm_di->di_tv.vval.v_string; + } + + // Only a List value is supported + if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL) + return FAIL; + + if (action == 'r') + 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) + retval = OK; + + return retval; +} + +/* + * Set quickfix list context. + */ + static int +qf_setprop_context(qf_list_T *qfl, dictitem_T *di) +{ + typval_T *ctx; + + free_tv(qfl->qf_ctx); + ctx = alloc_tv(); + if (ctx != NULL) + copy_tv(&di->di_tv, ctx); + qfl->qf_ctx = ctx; + + return OK; +} + +/* + * Set the current index in the specified quickfix list + */ + static int +qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl, dictitem_T *di) +{ + int denote = FALSE; + int newidx; + int old_qfidx; + qfline_T *qf_ptr; + + // If the specified index is '$', then use the last entry + if (di->di_tv.v_type == VAR_STRING + && di->di_tv.vval.v_string != NULL + && STRCMP(di->di_tv.vval.v_string, "$") == 0) + newidx = qfl->qf_count; + else + { + // Otherwise use the specified index + newidx = tv_get_number_chk(&di->di_tv, &denote); + if (denote) + return FAIL; + } + + if (newidx < 1) // sanity check + return FAIL; + if (newidx > qfl->qf_count) + newidx = qfl->qf_count; + + old_qfidx = qfl->qf_index; + qf_ptr = get_nth_entry(qfl, newidx, &newidx); + if (qf_ptr == NULL) + return FAIL; + qfl->qf_ptr = qf_ptr; + qfl->qf_index = newidx; + + // If the current list is modified and it is displayed in the quickfix + // window, then Update it. + if (qf_get_curlist(qi)->qf_id == qfl->qf_id) + qf_win_pos_update(qi, old_qfidx); + + return OK; +} + +/* + * Set the current index in the specified quickfix list + */ + static int +qf_setprop_qftf(qf_info_T *qi UNUSED, qf_list_T *qfl, dictitem_T *di) +{ + callback_T cb; + + free_callback(&qfl->qf_qftf_cb); + cb = get_callback(&di->di_tv); + if (cb.cb_name == NULL || *cb.cb_name == NUL) + return OK; + + set_callback(&qfl->qf_qftf_cb, &cb); + if (cb.cb_free_name) + vim_free(cb.cb_name); + + 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. + */ + static int +qf_set_properties(qf_info_T *qi, dict_T *what, int action, char_u *title) +{ + dictitem_T *di; + int retval = FAIL; + int qf_idx; + int newlist = FALSE; + qf_list_T *qfl; + + if (action == ' ' || qf_stack_empty(qi)) + newlist = TRUE; + + qf_idx = qf_setprop_get_qfidx(qi, what, action, &newlist); + if (qf_idx == INVALID_QFIDX) // List not found + return FAIL; + + if (newlist) + { + qi->qf_curlist = qf_idx; + qf_new_list(qi, title); + qf_idx = qi->qf_curlist; + } + + qfl = qf_get_list(qi, qf_idx); + if ((di = dict_find(what, (char_u *)"title", -1)) != NULL) + retval = qf_setprop_title(qi, qf_idx, what, di); + if ((di = dict_find(what, (char_u *)"items", -1)) != NULL) + retval = qf_setprop_items(qi, qf_idx, di, action); + if ((di = dict_find(what, (char_u *)"lines", -1)) != NULL) + retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action); + if ((di = dict_find(what, (char_u *)"context", -1)) != NULL) + retval = qf_setprop_context(qfl, di); + if ((di = dict_find(what, (char_u *)"idx", -1)) != NULL) + retval = qf_setprop_curidx(qi, qfl, di); + if ((di = dict_find(what, (char_u *)"quickfixtextfunc", -1)) != NULL) + retval = qf_setprop_qftf(qi, qfl, di); + + if (newlist || retval == OK) + qf_list_changed(qfl); + if (newlist) + qf_update_buffer(qi, NULL); + + return retval; +} + +/* + * Free the entire quickfix/location list stack. + * If the quickfix/location list window is open, then clear it. + */ + static void +qf_free_stack(win_T *wp, qf_info_T *qi) +{ + win_T *qfwin = qf_find_win(qi); + win_T *llwin = NULL; + + if (qfwin != NULL) + { + // If the quickfix/location list window is open, then clear it + if (qi->qf_curlist < qi->qf_listcount) + qf_free(qf_get_curlist(qi)); + qf_update_buffer(qi, NULL); + } + + if (wp != NULL && IS_LL_WINDOW(wp)) + { + // If in the location list window, then use the non-location list + // window with this location list (if present) + llwin = qf_find_win_with_loclist(qi); + if (llwin != NULL) + wp = llwin; + } + + qf_free_all(wp); + if (wp == NULL) + { + // quickfix list + qi->qf_curlist = 0; + qi->qf_listcount = 0; + } + else if (qfwin != NULL) + { + // If the location list window is open, then create a new empty + // location list + qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); + + if (new_ll != NULL) + { + new_ll->qf_bufnr = qfwin->w_buffer->b_fnum; + + // first free the list reference in the location list window + ll_free_all(&qfwin->w_llist_ref); + + qfwin->w_llist_ref = new_ll; + if (wp != qfwin) + win_set_loclist(wp, new_ll); + } + } +} + +/* + * Populate the quickfix list with the items supplied in the list + * of dictionaries. "title" will be copied to w:quickfix_title. + * "action" is 'a' for add, 'r' for replace. Otherwise create a new list. + * When "what" is not NULL then only set some properties. + */ + int +set_errorlist( + win_T *wp, + list_T *list, + int action, + char_u *title, + dict_T *what) +{ + qf_info_T *qi = &ql_info; + int retval = OK; + + if (wp != NULL) + { + qi = ll_get_or_alloc_list(wp); + if (qi == NULL) + return FAIL; + } + + if (action == 'f') + { + // Free the entire quickfix or location list stack + qf_free_stack(wp, qi); + return OK; + } + + // A dict argument cannot be specified with a non-empty list argument + if (list->lv_len != 0 && what != NULL) + { + semsg(_(e_invalid_argument_str), + _("cannot have both a list and a \"what\" argument")); + return FAIL; + } + + 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(qf_get_curlist(qi)); + } + + decr_quickfix_busy(); + + return retval; +} + +/* + * Mark the quickfix context and callback function as in use for all the lists + * in a quickfix stack. + */ + static int +mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ + int i; + int abort = FALSE; + typval_T *ctx; + callback_T *cb; + + for (i = 0; i < LISTCOUNT && !abort; ++i) + { + ctx = qi->qf_lists[i].qf_ctx; + if (ctx != NULL && ctx->v_type != VAR_NUMBER + && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) + abort = abort || set_ref_in_item(ctx, copyID, NULL, NULL); + + cb = &qi->qf_lists[i].qf_qftf_cb; + abort = abort || set_ref_in_callback(cb, copyID); + } + + return abort; +} + +/* + * Mark the context of the quickfix list and the location lists (if present) as + * "in use". So that garbage collection doesn't free the context. + */ + int +set_ref_in_quickfix(int copyID) +{ + int abort = FALSE; + tabpage_T *tp; + win_T *win; + + abort = mark_quickfix_ctx(&ql_info, copyID); + if (abort) + return abort; + + abort = set_ref_in_callback(&qftf_cb, copyID); + if (abort) + return abort; + + FOR_ALL_TAB_WINDOWS(tp, win) + { + if (win->w_llist != NULL) + { + abort = mark_quickfix_ctx(win->w_llist, copyID); + if (abort) + return abort; + } + if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) + { + // In a location list window and none of the other windows is + // referring to this location list. Mark the location list + // context as still in use. + abort = mark_quickfix_ctx(win->w_llist_ref, copyID); + if (abort) + return abort; + } + } + + return abort; +} +#endif + +/* + * 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_invalid_argument)); + return FAIL; + } + + if (buf->b_ml.ml_mfp == NULL) + { + emsg(_(e_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_invalid_range)); + return FAIL; + } + + *line1 = eap->line1; + *line2 = eap->line2; + *bufp = buf; + + return OK; +} + +/* + * ":[range]cbuffer [bufnr]" command. + * ":[range]caddbuffer [bufnr]" command. + * ":[range]cgetbuffer [bufnr]" command. + * ":[range]lbuffer [bufnr]" command. + * ":[range]laddbuffer [bufnr]" command. + * ":[range]lgetbuffer [bufnr]" command. + */ + void +ex_cbuffer(exarg_T *eap) +{ + buf_T *buf = NULL; + qf_info_T *qi; + char_u *au_name = NULL; + int res; + int_u save_qfid; + win_T *wp = NULL; + char_u *qf_title; + linenr_T line1; + linenr_T line2; + + au_name = cbuffer_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { +#ifdef FEAT_EVAL + if (aborting()) + return; +#endif + } + + // Must come after autocommands. + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) + return; + + if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) + return; + + qf_title = qf_cmdtitle(*eap->cmdlinep); + + if (buf->b_sfname) + { + vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", + (char *)qf_title, (char *)buf->b_sfname); + qf_title = IObuff; + } + + incr_quickfix_busy(); + + res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, + (eap->cmdidx != CMD_caddbuffer + && eap->cmdidx != CMD_laddbuffer), + line1, 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. + save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) + { + buf_T *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 a 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(); +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return the autocmd name for the :cexpr Ex commands. + */ + 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; + } +} + + int +trigger_cexpr_autocmd(int cmdidx) +{ + char_u *au_name = cexpr_get_auname(cmdidx); + + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { + if (aborting()) + return FAIL; + } + return OK; +} + + int +cexpr_core(exarg_T *eap, typval_T *tv) +{ + qf_info_T *qi; + win_T *wp = NULL; + + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) + return FAIL; + + if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) + || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)) + { + int res; + int_u save_qfid; + char_u *au_name = cexpr_get_auname(eap->cmdidx); + + incr_quickfix_busy(); + 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(); + return FAIL; + } + 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. + save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) + 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 + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qflist_valid(wp, save_qfid)) + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + decr_quickfix_busy(); + return OK; + } + + emsg(_(e_string_or_list_expected)); + return FAIL; +} + +/* + * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. + * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. + * Also: ":caddexpr", ":cgetexpr", "laddexpr" and "laddexpr". + */ + void +ex_cexpr(exarg_T *eap) +{ + typval_T *tv; + + if (trigger_cexpr_autocmd(eap->cmdidx) == FAIL) + return; + + // Evaluate the expression. When the result is a string or a list we can + // use it to fill the errorlist. + tv = eval_expr(eap->arg, eap); + if (tv == NULL) + return; + + (void)cexpr_core(eap, tv); + free_tv(tv); +} +#endif + +/* + * Get the location list for ":lhelpgrep" + */ + static qf_info_T * +hgr_get_ll(int *new_ll) +{ + win_T *wp; + qf_info_T *qi; + + // If the current window is a help window, then use it + if (bt_help(curwin->w_buffer)) + wp = curwin; + else + // Find an existing help window + wp = qf_find_help_win(); + + if (wp == NULL) // Help window not found + qi = NULL; + else + qi = wp->w_llist; + + if (qi == NULL) + { + // Allocate a new location list for help text matches + if ((qi = qf_alloc_stack(QFLT_LOCATION)) == NULL) + return NULL; + *new_ll = TRUE; + } + + return qi; +} + +/* + * Search for a pattern in a help file. + */ + static void +hgr_search_file( + qf_list_T *qfl, + char_u *fname, + vimconv_T *p_vc, + regmatch_T *p_regmatch) +{ + FILE *fd; + long lnum; + + fd = mch_fopen((char *)fname, "r"); + if (fd == NULL) + return; + + lnum = 1; + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) + { + char_u *line = IObuff; + + // Convert a line if 'encoding' is not utf-8 and + // the line contains a non-ASCII character. + if (p_vc->vc_type != CONV_NONE && has_non_ascii(IObuff)) + { + line = string_convert(p_vc, IObuff, NULL); + if (line == NULL) + line = IObuff; + } + + if (vim_regexec(p_regmatch, line, (colnr_T)0)) + { + int l = (int)STRLEN(line); + + // remove trailing CR, LF, spaces, etc. + while (l > 0 && line[l - 1] <= ' ') + line[--l] = NUL; + + if (qf_add_entry(qfl, + NULL, // dir + fname, + NULL, + 0, + line, + lnum, + 0, + (int)(p_regmatch->startp[0] - line) + + 1, // col + (int)(p_regmatch->endp[0] - line) + + 1, // end_col + FALSE, // vis_col + NULL, // search pattern + 0, // nr + 1, // type + TRUE // valid + ) == QF_FAIL) + { + got_int = TRUE; + if (line != IObuff) + vim_free(line); + break; + } + } + if (line != IObuff) + vim_free(line); + ++lnum; + line_breakcheck(); + } + fclose(fd); +} + +/* + * Search for a pattern in all the help files in the doc directory under + * the given directory. + */ + static void +hgr_search_files_in_dir( + qf_list_T *qfl, + char_u *dirname, + regmatch_T *p_regmatch, + vimconv_T *p_vc +#ifdef FEAT_MULTI_LANG + , char_u *lang +#endif + ) +{ + int fcount; + char_u **fnames; + int fi; + + // Find all "*.txt" and "*.??x" files in the "doc" directory. + add_pathsep(dirname); + STRCAT(dirname, "doc/*.\\(txt\\|??x\\)"); + if (gen_expand_wildcards(1, &dirname, &fcount, + &fnames, EW_FILE|EW_SILENT) == OK + && fcount > 0) + { + for (fi = 0; fi < fcount && !got_int; ++fi) + { +#ifdef FEAT_MULTI_LANG + // Skip files for a different language. + if (lang != NULL + && STRNICMP(lang, fnames[fi] + + STRLEN(fnames[fi]) - 3, 2) != 0 + && !(STRNICMP(lang, "en", 2) == 0 + && STRNICMP("txt", fnames[fi] + + STRLEN(fnames[fi]) - 3, 3) == 0)) + continue; +#endif + + hgr_search_file(qfl, fnames[fi], p_vc, p_regmatch); + } + FreeWild(fcount, fnames); + } +} + +/* + * Search for a pattern in all the help files in the 'runtimepath' + * and add the matches to a quickfix list. + * 'lang' is the language specifier. If supplied, then only matches in the + * specified language are found. + */ + static void +hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, char_u *lang) +{ + char_u *p; + + vimconv_T vc; + + // Help files are in utf-8 or latin1, convert lines when 'encoding' + // differs. + vc.vc_type = CONV_NONE; + if (!enc_utf8) + convert_setup(&vc, (char_u *)"utf-8", p_enc); + + // Go through all the directories in 'runtimepath' + p = p_rtp; + while (*p != NUL && !got_int) + { + copy_option_part(&p, NameBuff, MAXPATHL, ","); + + hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, &vc +#ifdef FEAT_MULTI_LANG + , lang +#endif + ); + } + + if (vc.vc_type != CONV_NONE) + convert_setup(&vc, NULL, NULL); +} + +/* + * ":helpgrep {pattern}" + */ + void +ex_helpgrep(exarg_T *eap) +{ + regmatch_T regmatch; + char_u *save_cpo; + int save_cpo_allocated; + qf_info_T *qi = &ql_info; + int new_qi = FALSE; + char_u *au_name = NULL; + char_u *lang = NULL; + int updated = FALSE; + + switch (eap->cmdidx) + { + case CMD_helpgrep: au_name = (char_u *)"helpgrep"; break; + case CMD_lhelpgrep: au_name = (char_u *)"lhelpgrep"; break; + default: break; + } + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, TRUE, curbuf)) + { +#ifdef FEAT_EVAL + if (aborting()) + return; +#endif + } + + if (is_loclist_cmd(eap->cmdidx)) + { + qi = hgr_get_ll(&new_qi); + if (qi == NULL) + return; + } + + // Make 'cpoptions' empty, the 'l' flag should not be used here. + save_cpo = p_cpo; + save_cpo_allocated = is_option_allocated("cpo"); + p_cpo = empty_option; + + incr_quickfix_busy(); + +#ifdef FEAT_MULTI_LANG + // Check for a specified language + lang = check_help_lang(eap->arg); +#endif + regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); + regmatch.rm_ic = FALSE; + if (regmatch.regprog != NULL) + { + qf_list_T *qfl; + + // create a new quickfix list + qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep)); + qfl = qf_get_curlist(qi); + + hgr_search_in_rtp(qfl, ®match, lang); + + vim_regfree(regmatch.regprog); + + qfl->qf_nonevalid = FALSE; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); + updated = TRUE; + } + + if (p_cpo == empty_option) + p_cpo = save_cpo; + else + { + // Darn, some plugin changed the value. If it's still empty it was + // changed and restored, need to restore in the complicated way. + if (*p_cpo == NUL) + set_option_value_give_err((char_u *)"cpo", 0L, save_cpo, 0); + if (save_cpo_allocated) + free_string_option(save_cpo); + } + + if (updated) + // This may open a window and source scripts, do this after 'cpo' was + // restored. + qf_update_buffer(qi, NULL); + + if (au_name != NULL) + { + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, TRUE, curbuf); + // When adding a location list to an existing location list stack, + // if the autocmd made the stack invalid, then just return. + if (!new_qi && IS_LL_STACK(qi) && qf_find_win_with_loclist(qi) == NULL) + { + decr_quickfix_busy(); + return; + } + } + + // Jump to first match. + if (!qf_list_empty(qf_get_curlist(qi))) + qf_jump(qi, 0, 0, FALSE); + else + semsg(_(e_no_match_str_2), eap->arg); + + decr_quickfix_busy(); + + if (eap->cmdidx == CMD_lhelpgrep) + { + // If the help window is not opened or if it already points to the + // correct location list, then free the new location list. + if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) + { + if (new_qi) + ll_free_all(&qi); + } + else if (curwin->w_llist == NULL && new_qi) + // current window didn't have a location list associated with it + // before. Associate the new location list now. + curwin->w_llist = qi; + } +} + +# if defined(EXITFREE) || defined(PROTO) + void +free_quickfix(void) +{ + win_T *win; + tabpage_T *tab; + + qf_free_all(NULL); + // Free all location lists + FOR_ALL_TAB_WINDOWS(tab, win) + qf_free_all(win); + + ga_clear(&qfga); +} +# endif + +#endif // FEAT_QUICKFIX + +#if defined(FEAT_EVAL) || defined(PROTO) +# ifdef FEAT_QUICKFIX + static void +get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) +{ + if (what_arg->v_type == VAR_UNKNOWN) + { + if (rettv_list_alloc(rettv) == OK) + if (is_qf || wp != NULL) + (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list); + } + else + { + if (rettv_dict_alloc(rettv) == OK) + if (is_qf || (wp != NULL)) + { + if (what_arg->v_type == VAR_DICT) + { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) + qf_get_properties(wp, d, rettv->vval.v_dict); + } + else + emsg(_(e_dictionary_required)); + } + } +} +# endif + +/* + * "getloclist()" function + */ + void +f_getloclist(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +# ifdef FEAT_QUICKFIX + win_T *wp; + + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 1) == FAIL)) + return; + + wp = find_win_by_nr_or_id(&argvars[0]); + get_qf_loc_list(FALSE, wp, &argvars[1], rettv); +# endif +} + +/* + * "getqflist()" function + */ + void +f_getqflist(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +# ifdef FEAT_QUICKFIX + if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL) + return; + + get_qf_loc_list(TRUE, NULL, &argvars[0], rettv); +# endif +} + +/* + * Used by "setqflist()" and "setloclist()" functions + */ + static void +set_qf_ll_list( + win_T *wp UNUSED, + typval_T *list_arg UNUSED, + typval_T *action_arg UNUSED, + typval_T *what_arg UNUSED, + typval_T *rettv) +{ +# ifdef FEAT_QUICKFIX + char_u *act; + int action = 0; + static int recursive = 0; +# endif + + rettv->vval.v_number = -1; + +# ifdef FEAT_QUICKFIX + if (list_arg->v_type != VAR_LIST) + emsg(_(e_list_required)); + else if (recursive != 0) + emsg(_(e_autocommand_caused_recursive_behavior)); + else + { + list_T *l = list_arg->vval.v_list; + dict_T *what = NULL; + int valid_dict = TRUE; + + if (action_arg->v_type == VAR_STRING) + { + act = tv_get_string_chk(action_arg); + if (act == NULL) + return; // type error; errmsg already given + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') && + act[1] == NUL) + action = *act; + else + semsg(_(e_invalid_action_str_1), act); + } + else if (action_arg->v_type == VAR_UNKNOWN) + action = ' '; + else + emsg(_(e_string_required)); + + if (action_arg->v_type != VAR_UNKNOWN + && what_arg->v_type != VAR_UNKNOWN) + { + if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) + what = what_arg->vval.v_dict; + else + { + emsg(_(e_dictionary_required)); + valid_dict = FALSE; + } + } + + ++recursive; + if (l != NULL && action && valid_dict + && set_errorlist(wp, l, action, + (char_u *)(wp == NULL ? ":setqflist()" : ":setloclist()"), + what) == OK) + rettv->vval.v_number = 0; + --recursive; + } +# endif +} + +/* + * "setloclist()" function + */ + void +f_setloclist(typval_T *argvars, typval_T *rettv) +{ + win_T *win; + + rettv->vval.v_number = -1; + + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || check_for_list_arg(argvars, 1) == FAIL + || check_for_opt_string_arg(argvars, 2) == FAIL + || (argvars[2].v_type != VAR_UNKNOWN + && check_for_opt_dict_arg(argvars, 3) == FAIL))) + return; + + win = find_win_by_nr_or_id(&argvars[0]); + if (win != NULL) + set_qf_ll_list(win, &argvars[1], &argvars[2], &argvars[3], rettv); +} + +/* + * "setqflist()" function + */ + void +f_setqflist(typval_T *argvars, typval_T *rettv) +{ + if (in_vim9script() + && (check_for_list_arg(argvars, 0) == FAIL + || check_for_opt_string_arg(argvars, 1) == FAIL + || (argvars[1].v_type != VAR_UNKNOWN + && check_for_opt_dict_arg(argvars, 2) == FAIL))) + return; + + set_qf_ll_list(NULL, &argvars[0], &argvars[1], &argvars[2], rettv); +} +#endif |