Mercurial > vim
view src/quickfix.c @ 13016:e47e70300f30 v8.0.1384
patch 8.0.1384: not enough quickfix help; confusing winid
commit https://github.com/vim/vim/commit/74240d3febd1e3bc7cf086c647c9348b20716c33
Author: Bram Moolenaar <Bram@vim.org>
Date: Sun Dec 10 15:26:15 2017 +0100
patch 8.0.1384: not enough quickfix help; confusing winid
Problem: Not enough quickfix help; confusing winid.
Solution: Add more examples in the help. When the quickfix window is not
present, return zero for getqflist() with 'winid'. Add more tests
for jumping to quickfix list entries. (Yegappan Lakshmanan, closes
#2427)
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sun, 10 Dec 2017 15:30:06 +0100 |
parents | 49e136457c66 |
children | 7c0e0e923537 |
line wrap: on
line source
/* 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 */ int qf_fnum; /* file number for the line */ int qf_col; /* column where the error occurred */ int qf_nr; /* error number */ char_u *qf_pattern; /* search pattern for the error */ char_u *qf_text; /* description of the error */ char_u qf_viscol; /* set to TRUE if qf_col is screen column */ char_u qf_cleared; /* set to TRUE if line has been deleted */ char_u qf_type; /* type of the error (mostly 'E'); 1 for :helpgrep */ char_u qf_valid; /* valid error message detected */ }; /* * There is a stack of error lists. */ #define LISTCOUNT 10 /* * 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 */ 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 */ 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; } 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]; }; static qf_info_T ql_info; /* global quickfix list */ static int_u last_qf_id = 0; /* Last used quickfix list id */ #define FMT_PATTERNS 10 /* 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 */ /* '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 */ }; static efm_T *fmt_start = NULL; /* cached across qf_parse_line() calls */ 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, linenr_T lnumfirst, linenr_T lnumlast, char_u *qf_title, char_u *enc); static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title); static void qf_new_list(qf_info_T *qi, char_u *qf_title); static void ll_free_all(qf_info_T **pqi); static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, int bufnum, char_u *mesg, long lnum, int col, int vis_col, char_u *pattern, int nr, int type, int valid); static qf_info_T *ll_new_list(void); static void qf_free(qf_info_T *qi, int idx); static char_u *qf_types(int, int); static int qf_get_fnum(qf_info_T *qi, int qf_idx, 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_info_T *qi, int qf_idx, char_u *); static void qf_fmt_text(char_u *text, char_u *buf, int bufsize); static void qf_clean_dir_stack(struct dir_stack_T **); static int qf_win_pos_update(qf_info_T *qi, int old_qf_index); static int is_qf_win(win_T *win, qf_info_T *qi); 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_set_title_var(qf_info_T *qi); static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last); static char_u *get_mef_name(void); static void restore_start_dir(char_u *dirname_start); 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) /* * 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) /* * 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}; /* * 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); } /* * Maximum number of bytes allowed per line while reading a errorfile. */ #define LINE_MAXLEN 4096 static struct fmtpattern { char_u convchar; char *pattern; } fmt_pat[FMT_PATTERNS] = { {'f', ".\\+"}, /* only used when at end */ {'n', "\\d\\+"}, {'l', "\\d\\+"}, {'c', "\\d\\+"}, {'t', "."}, {'m', ".\\+"}, {'r', ".*"}, {'p', "[- .]*"}, {'v', "\\d\\+"}, {'s', ".\\+"} }; /* * Converts a 'errorformat' string to regular expression pattern */ static int efm_to_regpat( char_u *efm, int len, efm_T *fmt_ptr, char_u *regpat, char_u *errmsg) { char_u *ptr; char_u *efmp; char_u *srcptr; int round; int idx = 0; /* * Build regexp pattern from current 'errorformat' option */ 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) { if (fmt_ptr->addr[idx]) { sprintf((char *)errmsg, _("E372: Too many %%%c in format string"), *efmp); EMSG(errmsg); return -1; } if ((idx && idx < 6 && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) || (idx == 6 && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { sprintf((char *)errmsg, _("E373: Unexpected %%%c in format string"), *efmp); EMSG(errmsg); return -1; } fmt_ptr->addr[idx] = (char_u)++round; *ptr++ = '\\'; *ptr++ = '('; #ifdef BACKSLASH_IN_FILENAME if (*efmp == 'f') { /* Also match "c:" in the file name, even when * checking for a colon next: "%f:". * "\%(\a:\)\=" */ STRCPY(ptr, "\\%(\\a:\\)\\="); ptr += 10; } #endif if (*efmp == 'f' && efmp[1] != NUL) { if (efmp[1] != '\\' && efmp[1] != '%') { /* A file name may contain spaces, but this isn't * in "\f". For "%f:%l:%m" there may be a ":" in * the file name. Use ".\{-1,}x" instead (x is * the next character), the requirement that :999: * follows should work. */ STRCPY(ptr, ".\\{-1,}"); ptr += 7; } else { /* File name followed by '\\' or '%': include as * many file name chars as possible. */ STRCPY(ptr, "\\f\\+"); ptr += 4; } } else { srcptr = (char_u *)fmt_pat[idx].pattern; while ((*ptr = *srcptr++) != NUL) ++ptr; } *ptr++ = '\\'; *ptr++ = ')'; } else if (*efmp == '*') { if (*++efmp == '[' || *efmp == '\\') { if ((*ptr++ = *efmp) == '[') /* %*[^a-z0-9] etc. */ { if (efmp[1] == '^') *ptr++ = *++efmp; if (efmp < efm + len) { *ptr++ = *++efmp; /* could be ']' */ while (efmp < efm + len && (*ptr++ = *++efmp) != ']') /* skip */; if (efmp == efm + len) { EMSG(_("E374: Missing ] in format string")); return -1; } } } else if (efmp < efm + len) /* %*\D, %*\s etc. */ *ptr++ = *++efmp; *ptr++ = '\\'; *ptr++ = '+'; } else { /* TODO: scanf()-like: %*ud, %*3c, %*f, ... ? */ sprintf((char *)errmsg, _("E375: Unsupported %%%c in format string"), *efmp); EMSG(errmsg); return -1; } } 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 */ { if (vim_strchr((char_u *)"+-", *efmp) != NULL) fmt_ptr->flags = *efmp++; if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) fmt_ptr->prefix = *efmp; else { sprintf((char *)errmsg, _("E376: Invalid %%%c in format string prefix"), *efmp); EMSG(errmsg); return -1; } } else { sprintf((char *)errmsg, _("E377: Invalid %%%c in format string"), *efmp); EMSG(errmsg); return -1; } } 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 0; } 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; } /* Parse 'errorformat' option */ static efm_T * parse_efm_option(char_u *efm) { char_u *errmsg = NULL; int errmsglen; efm_T *fmt_ptr = NULL; efm_T *fmt_first = NULL; efm_T *fmt_last = NULL; char_u *fmtstr = NULL; int len; int i; int round; errmsglen = CMDBUFFSIZE + 1; errmsg = alloc_id(errmsglen, aid_qf_errmsg); if (errmsg == NULL) goto parse_efm_end; /* * 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. */ i = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2); for (round = FMT_PATTERNS; round > 0; ) i += (int)STRLEN(fmt_pat[--round].pattern); #ifdef BACKSLASH_IN_FILENAME i += 12; /* "%f" can become twelve chars longer (see efm_to_regpat) */ #else i += 2; /* "%f" can become two chars longer */ #endif if ((fmtstr = alloc(i)) == 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 = (efm_T *)alloc_clear((unsigned)sizeof(efm_T)); 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 */ for (len = 0; efm[len] != NUL && efm[len] != ','; ++len) if (efm[len] == '\\' && efm[len + 1] != NUL) ++len; if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) 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(_("E378: 'errorformat' contains no pattern")); goto parse_efm_end; parse_efm_error: free_efm_list(&fmt_first); parse_efm_end: vim_free(fmtstr); vim_free(errmsg); return fmt_first; } enum { QF_FAIL = 0, QF_OK = 1, QF_END_OF_INPUT = 2, QF_NOMEM = 3, QF_IGNORE_LINE = 4 }; 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; static char_u * qf_grow_linebuf(qfstate_T *state, int newsz) { /* * 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(state->linelen + 1); if (state->growbuf == NULL) return NULL; state->growbufsiz = state->linelen; } else if (state->linelen > state->growbufsiz) { state->growbuf = vim_realloc(state->growbuf, state->linelen + 1); if (state->growbuf == NULL) return NULL; 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 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(state->growbufsiz); 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 (;;) { 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; state->growbuf = vim_realloc(state->growbuf, state->growbufsiz); if (state->growbuf == NULL) return QF_NOMEM; } 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 - 1] == '\n') break; } state->linebuf = state->growbuf; state->linelen = growbuflen; } else state->linebuf = IObuff; #ifdef FEAT_MBYTE /* 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; } } } #endif 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 } #ifdef FEAT_MBYTE remove_bom(state->linebuf); #endif return QF_OK; } typedef struct { char_u *namebuf; char_u *errmsg; int errmsglen; long lnum; int col; char_u use_viscol; char_u *pattern; int enr; int type; int valid; } qffields_T; /* * Parse a line and get the quickfix fields. * Return the QF_ status. */ static int qf_parse_line( qf_info_T *qi, int qf_idx, char_u *linebuf, int linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; char_u *ptr; int len; int i; int idx = 0; char_u *tail = NULL; regmatch_T regmatch; qf_list_T *qfl = &qi->qf_lists[qf_idx]; /* Always ignore case when looking for a matching error. */ regmatch.rm_ic = TRUE; /* If there was no %> item start at the first pattern */ if (fmt_start == NULL) fmt_ptr = fmt_first; else { 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; restofline: for ( ; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) { int r; idx = fmt_ptr->prefix; if (qfl->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) continue; fields->namebuf[0] = NUL; fields->pattern[0] = NUL; if (!qfl->qf_multiscan) fields->errmsg[0] = NUL; fields->lnum = 0; fields->col = 0; fields->use_viscol = FALSE; fields->enr = -1; fields->type = 0; tail = NULL; regmatch.regprog = fmt_ptr->prog; r = vim_regexec(®match, linebuf, (colnr_T)0); fmt_ptr->prog = regmatch.regprog; if (r) { if ((idx == 'C' || idx == 'Z') && !qfl->qf_multiline) continue; if (vim_strchr((char_u *)"EWI", idx) != NULL) fields->type = idx; else fields->type = 0; /* * Extract error message data from matched line. * We check for an actual submatch, because "\[" and "\]" in * the 'errorformat' may cause the wrong submatch to be used. */ if ((i = (int)fmt_ptr->addr[0]) > 0) /* %f */ { int c; if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) continue; /* Expand ~/file and $HOME/file to full path. */ c = *regmatch.endp[i]; *regmatch.endp[i] = NUL; expand_env(regmatch.startp[i], fields->namebuf, CMDBUFFSIZE); *regmatch.endp[i] = c; if (vim_strchr((char_u *)"OPQ", idx) != NULL && mch_getperm(fields->namebuf) == -1) continue; } if ((i = (int)fmt_ptr->addr[1]) > 0) /* %n */ { if (regmatch.startp[i] == NULL) continue; fields->enr = (int)atol((char *)regmatch.startp[i]); } if ((i = (int)fmt_ptr->addr[2]) > 0) /* %l */ { if (regmatch.startp[i] == NULL) continue; fields->lnum = atol((char *)regmatch.startp[i]); } if ((i = (int)fmt_ptr->addr[3]) > 0) /* %c */ { if (regmatch.startp[i] == NULL) continue; fields->col = (int)atol((char *)regmatch.startp[i]); } if ((i = (int)fmt_ptr->addr[4]) > 0) /* %t */ { if (regmatch.startp[i] == NULL) continue; fields->type = *regmatch.startp[i]; } if (fmt_ptr->flags == '+' && !qfl->qf_multiscan) /* %+ */ { if (linelen >= fields->errmsglen) { /* linelen + null terminator */ if ((fields->errmsg = vim_realloc(fields->errmsg, linelen + 1)) == NULL) return QF_NOMEM; fields->errmsglen = linelen + 1; } vim_strncpy(fields->errmsg, linebuf, linelen); } else if ((i = (int)fmt_ptr->addr[5]) > 0) /* %m */ { if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) continue; len = (int)(regmatch.endp[i] - regmatch.startp[i]); if (len >= fields->errmsglen) { /* len + null terminator */ if ((fields->errmsg = vim_realloc(fields->errmsg, len + 1)) == NULL) return QF_NOMEM; fields->errmsglen = len + 1; } vim_strncpy(fields->errmsg, regmatch.startp[i], len); } if ((i = (int)fmt_ptr->addr[6]) > 0) /* %r */ { if (regmatch.startp[i] == NULL) continue; tail = regmatch.startp[i]; } if ((i = (int)fmt_ptr->addr[7]) > 0) /* %p */ { char_u *match_ptr; if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) continue; fields->col = 0; for (match_ptr = regmatch.startp[i]; match_ptr != regmatch.endp[i]; ++match_ptr) { ++fields->col; if (*match_ptr == TAB) { fields->col += 7; fields->col -= fields->col % 8; } } ++fields->col; fields->use_viscol = TRUE; } if ((i = (int)fmt_ptr->addr[8]) > 0) /* %v */ { if (regmatch.startp[i] == NULL) continue; fields->col = (int)atol((char *)regmatch.startp[i]); fields->use_viscol = TRUE; } if ((i = (int)fmt_ptr->addr[9]) > 0) /* %s */ { if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) continue; len = (int)(regmatch.endp[i] - regmatch.startp[i]); if (len > CMDBUFFSIZE - 5) len = CMDBUFFSIZE - 5; STRCPY(fields->pattern, "^\\V"); STRNCAT(fields->pattern, regmatch.startp[i], len); fields->pattern[len + 3] = '\\'; fields->pattern[len + 4] = '$'; fields->pattern[len + 5] = NUL; } break; } } qfl->qf_multiscan = FALSE; if (fmt_ptr == NULL || idx == 'D' || idx == 'X') { if (fmt_ptr != NULL) { if (idx == 'D') /* enter directory */ { if (*fields->namebuf == NUL) { EMSG(_("E379: Missing or empty directory name")); return QF_FAIL; } qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack, FALSE); if (qfl->qf_directory == NULL) return QF_FAIL; } else if (idx == 'X') /* leave directory */ qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack); } fields->namebuf[0] = NUL; /* no match found, remove file name */ fields->lnum = 0; /* don't jump to this line */ fields->valid = FALSE; if (linelen >= fields->errmsglen) { /* linelen + null terminator */ if ((fields->errmsg = vim_realloc(fields->errmsg, linelen + 1)) == NULL) return QF_NOMEM; fields->errmsglen = linelen + 1; } /* copy whole line to error message */ vim_strncpy(fields->errmsg, linebuf, linelen); 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 *)"AEWI", idx) != NULL) { qfl->qf_multiline = TRUE; /* start of a multi-line message */ qfl->qf_multiignore = FALSE;/* reset continuation */ } else if (vim_strchr((char_u *)"CZ", idx) != NULL) { /* continuation of multi-line msg */ if (!qfl->qf_multiignore) { qfline_T *qfprev = qfl->qf_last; if (qfprev == NULL) return QF_FAIL; if (*fields->errmsg && !qfl->qf_multiignore) { len = (int)STRLEN(qfprev->qf_text); if ((ptr = alloc((unsigned)(len + STRLEN(fields->errmsg) + 2))) == 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_col) qfprev->qf_col = fields->col; qfprev->qf_viscol = fields->use_viscol; if (!qfprev->qf_fnum) qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, *fields->namebuf || qfl->qf_directory != 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; } else if (vim_strchr((char_u *)"OPQ", idx) != NULL) { /* global file names */ 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; 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; } /* * 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_free(qf_last_bufname); qf_last_bufname = NULL; vim_memset(&state, 0, sizeof(state)); vim_memset(&fields, 0, sizeof(fields)); #ifdef FEAT_MBYTE state.vc.vc_type = CONV_NONE; if (enc != NULL && *enc != NUL) convert_setup(&state.vc, enc, p_enc); #endif fields.namebuf = alloc_id(CMDBUFFSIZE + 1, aid_qf_namebuf); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = alloc_id(fields.errmsglen, aid_qf_errmsg); fields.pattern = alloc_id(CMDBUFFSIZE + 1, aid_qf_pattern); if (fields.namebuf == NULL || fields.errmsg == NULL || fields.pattern == NULL) goto qf_init_end; if (efile != NULL && (state.fd = mch_fopen((char *)efile, "r")) == NULL) { EMSG2(_(e_openerrf), efile); 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; } else { /* Adding to existing list, use last entry. */ adding = TRUE; if (qi->qf_lists[qf_idx].qf_count > 0) old_last = qi->qf_lists[qf_idx].qf_last; } qfl = &qi->qf_lists[qf_idx]; /* 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_free(last_efm); last_efm = NULL; 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; if (tv != NULL) { if (tv->v_type == VAR_STRING) state.p_str = tv->vval.v_string; else if (tv->v_type == VAR_LIST) state.p_li = tv->vval.v_list->lv_first; state.tv = tv; } state.buf = buf; state.buflnum = lnumfirst; state.lnumlast = lnumlast; /* * Read the lines in the error file one by one. * Try to recognize one of the error formats in each line. */ while (!got_int) { /* Get the next line from a file/buffer/list/string */ status = qf_get_nextline(&state); if (status == QF_NOMEM) /* memory alloc failure */ goto qf_init_end; if (status == QF_END_OF_INPUT) /* end of input */ break; status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen, fmt_first, &fields); if (status == QF_FAIL) goto error2; if (status == QF_NOMEM) goto qf_init_end; if (status == QF_IGNORE_LINE) continue; if (qf_add_entry(qi, qf_idx, qfl->qf_directory, (*fields.namebuf || qfl->qf_directory != NULL) ? fields.namebuf : ((qfl->qf_currfile != NULL && fields.valid) ? qfl->qf_currfile : (char_u *)NULL), 0, fields.errmsg, fields.lnum, fields.col, fields.use_viscol, fields.pattern, fields.enr, fields.type, fields.valid) == FAIL) goto error2; line_breakcheck(); } if (state.fd == NULL || !ferror(state.fd)) { 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_readerrf)); error2: if (!adding) { /* Error when creating a new list. Free the new list */ qf_free(qi, qi->qf_curlist); qi->qf_listcount--; if (qi->qf_curlist > 0) --qi->qf_curlist; } qf_init_end: if (state.fd != NULL) fclose(state.fd); vim_free(fields.namebuf); vim_free(fields.errmsg); vim_free(fields.pattern); vim_free(state.growbuf); if (qf_idx == qi->qf_curlist) qf_update_buffer(qi, old_last); #ifdef FEAT_MBYTE if (state.vc.vc_type != CONV_NONE) convert_setup(&state.vc, NULL, NULL); #endif return retval; } static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { vim_free(qi->qf_lists[qf_idx].qf_title); qi->qf_lists[qf_idx].qf_title = NULL; if (title != NULL) { char_u *p = alloc((int)STRLEN(title) + 2); qi->qf_lists[qf_idx].qf_title = p; if (p != NULL) sprintf((char *)p, ":%s", (char *)title); } } /* * 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; /* * If the current entry is not the last entry, delete entries beyond * the current entry. This makes it possible to browse in a tree-like * way with ":grep'. */ while (qi->qf_listcount > qi->qf_curlist + 1) qf_free(qi, --qi->qf_listcount); /* * When the stack is full, remove to oldest entry * Otherwise, add a new entry. */ if (qi->qf_listcount == LISTCOUNT) { qf_free(qi, 0); for (i = 1; i < LISTCOUNT; ++i) qi->qf_lists[i - 1] = qi->qf_lists[i]; qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; vim_memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); qf_store_title(qi, qi->qf_curlist, qf_title); qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } /* * Free a location list */ 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 */ qi->qf_refcount--; if (qi->qf_refcount < 1) { /* No references to this location list */ for (i = 0; i < qi->qf_listcount; ++i) qf_free(qi, i); vim_free(qi); } } 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(qi, i); } /* * Add an entry to the end of the list of errors. * Returns OK or FAIL. */ static int qf_add_entry( qf_info_T *qi, /* quickfix list */ int qf_idx, /* list index */ char_u *dir, /* optional directory name */ char_u *fname, /* file name or NULL */ int bufnum, /* buffer number or zero */ char_u *mesg, /* message */ long lnum, /* line number */ int col, /* column */ 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 = (qfline_T *)alloc((unsigned)sizeof(qfline_T))) == NULL) return FAIL; if (bufnum != 0) { buf_T *buf = buflist_findnr(bufnum); qfp->qf_fnum = bufnum; if (buf != NULL) buf->b_has_qf_entry |= (qi == &ql_info) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; } else qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname); if ((qfp->qf_text = vim_strsave(mesg)) == NULL) { vim_free(qfp); return FAIL; } qfp->qf_lnum = lnum; qfp->qf_col = 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 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 = &qi->qf_lists[qf_idx].qf_last; if (qi->qf_lists[qf_idx].qf_count == 0) /* first element in the list */ { qi->qf_lists[qf_idx].qf_start = qfp; qi->qf_lists[qf_idx].qf_ptr = qfp; qi->qf_lists[qf_idx].qf_index = 0; qfp->qf_prev = NULL; } else { qfp->qf_prev = *lastp; (*lastp)->qf_next = qfp; } qfp->qf_next = NULL; qfp->qf_cleared = FALSE; *lastp = qfp; ++qi->qf_lists[qf_idx].qf_count; if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) /* first valid entry */ { qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; qi->qf_lists[qf_idx].qf_ptr = qfp; } return OK; } /* * Allocate a new location list */ static qf_info_T * ll_new_list(void) { qf_info_T *qi; qi = (qf_info_T *)alloc((unsigned)sizeof(qf_info_T)); if (qi != NULL) { vim_memset(qi, 0, (size_t)(sizeof(qf_info_T))); qi->qf_refcount++; } return qi; } /* * Return the location list for window 'wp'. * If not present, allocate a location list */ 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 = ll_new_list(); /* new location list */ return wp->w_llist; } /* * Copy the location list from window "from" to window "to". */ void copy_loclist(win_T *from, win_T *to) { qf_info_T *qi; int idx; int i; /* * 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 = ll_new_list()) == 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++) { qf_list_T *from_qfl; qf_list_T *to_qfl; to->w_llist->qf_curlist = idx; from_qfl = &qi->qf_lists[idx]; to_qfl = &to->w_llist->qf_lists[idx]; /* Some of the fields are populated by qf_add_entry() */ 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_count) { qfline_T *from_qfp; qfline_T *prevp; /* copy all the location entries in this list */ for (i = 0, from_qfp = from_qfl->qf_start; i < from_qfl->qf_count && from_qfp != NULL; ++i, from_qfp = from_qfp->qf_next) { if (qf_add_entry(to->w_llist, to->w_llist->qf_curlist, NULL, NULL, 0, from_qfp->qf_text, from_qfp->qf_lnum, from_qfp->qf_col, from_qfp->qf_viscol, from_qfp->qf_pattern, from_qfp->qf_nr, 0, from_qfp->qf_valid) == FAIL) { qf_free_all(to); return; } /* * 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->w_llist->qf_lists[to->w_llist->qf_curlist].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 */ } } 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; /* 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; } } to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ } /* * Get buffer number for file "directory/fname". * Also sets the b_has_qf_entry flag. */ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, char_u *fname) { 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(qi, qf_idx, 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 = (qi == &ql_info) ? 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 = (struct dir_stack_T *)alloc((unsigned)sizeof(struct dir_stack_T)); 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 || (*stackptr && 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 the this directory tree: * ./ * ./aa * ./aa/bb * ./bb * ./bb/x.c * and make says: * making all in aa * making all in bb * x.c:9: Error * Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. * qf_guess_filepath will return NULL. */ static char_u * qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) { struct dir_stack_T *ds_ptr; struct dir_stack_T *ds_tmp; char_u *fullname; qf_list_T *qfl = &qi->qf_lists[qf_idx]; /* no dirs on the stack - there's nothing we can do */ if (qfl->qf_dir_stack == NULL) 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; } /* * When loading a file from the quickfix, the auto commands may modify it. * This may invalidate the current quickfix entry. This function checks * whether a entry is still present in the quickfix. * Similar to location list. */ static int is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) { qf_list_T *qfl; qfline_T *qfp; int i; qfl = &qi->qf_lists[qi->qf_curlist]; /* Search for the entry in the current list */ for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; ++i, qfp = qfp->qf_next) if (qfp == NULL || qfp == qf_ptr) 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_info_T *qi, 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 == qi->qf_lists[qi->qf_curlist].qf_count || qf_ptr->qf_next == NULL) return NULL; ++idx; qf_ptr = qf_ptr->qf_next; } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *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_info_T *qi, 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 ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; return qf_ptr; } /* * Get the n'th (errornr) previous/next valid entry from the current entry in * the quickfix list. * dir == FORWARD or FORWARD_FILE: next valid entry * dir == BACKWARD or BACKWARD_FILE: previous valid entry */ static qfline_T * get_nth_valid_entry( qf_info_T *qi, int errornr, qfline_T *qf_ptr, int *qf_index, int dir) { qfline_T *prev_qf_ptr; int prev_index; static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); char_u *err = e_no_more_items; while (errornr--) { prev_qf_ptr = qf_ptr; prev_index = *qf_index; if (dir == FORWARD || dir == FORWARD_FILE) qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); else qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); if (qf_ptr == NULL) { qf_ptr = prev_qf_ptr; *qf_index = prev_index; if (err != NULL) { EMSG(_(err)); return NULL; } break; } err = NULL; } return qf_ptr; } /* * Get n'th (errornr) quickfix entry */ static qfline_T * get_nth_entry( qf_info_T *qi, int errornr, qfline_T *qf_ptr, int *cur_qfidx) { int qf_idx = *cur_qfidx; /* New error number is less than the current error number */ while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) { --qf_idx; qf_ptr = qf_ptr->qf_prev; } /* New error number is greater than the current error number */ while (errornr > qf_idx && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count && qf_ptr->qf_next != NULL) { ++qf_idx; qf_ptr = qf_ptr->qf_next; } *cur_qfidx = qf_idx; return qf_ptr; } /* * Find a help window or open one. */ static int jump_to_help_window(qf_info_T *qi, int *opened_window) { win_T *wp; int flags; if (cmdmod.tab != 0) wp = NULL; else FOR_ALL_WINDOWS(wp) if (bt_help(wp->w_buffer)) break; if (wp != NULL && wp->w_buffer->b_nwindows > 0) win_enter(wp, TRUE); else { /* * Split off help window; put it at far top if no position * specified, the current window is vertically split and narrow. */ flags = WSP_HELP; if (cmdmod.split == 0 && curwin->w_width != Columns && curwin->w_width < 80) flags |= WSP_TOP; if (qi != &ql_info) flags |= WSP_NEWLOC; /* don't copy the location list */ if (win_split(0, flags) == FAIL) return FAIL; *opened_window = TRUE; if (curwin->w_height < p_hh) win_setheight((int)p_hh); if (qi != &ql_info) /* not a quickfix list */ { /* The new window should use the supplied location list */ curwin->w_llist = qi; qi->qf_refcount++; } } if (!p_im) restart_edit = 0; /* don't want insert mode in help file */ return OK; } /* * Find a suitable window for opening a file (qf_fnum) and jump to it. * If the file is already opened in a window, jump to it. */ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) { win_T *usable_win_ptr = NULL; int usable_win; qf_info_T *ll_ref; int flags; win_T *win; win_T *altwin; usable_win = 0; ll_ref = curwin->w_llist_ref; if (ll_ref != NULL) { /* Find a window using the same location list that is not a * quickfix window. */ FOR_ALL_WINDOWS(usable_win_ptr) if (usable_win_ptr->w_llist == ll_ref && !bt_quickfix(usable_win_ptr->w_buffer)) { usable_win = 1; break; } } if (!usable_win) { /* Locate a window showing a normal buffer */ FOR_ALL_WINDOWS(win) if (win->w_buffer->b_p_bt[0] == NUL) { usable_win = 1; break; } } /* * If no usable window is found and 'switchbuf' contains "usetab" * then search in other tabs. */ if (!usable_win && (swb_flags & SWB_USETAB)) { tabpage_T *tp; win_T *wp; FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer->b_fnum == qf_fnum) { goto_tabpage_win(tp, wp); usable_win = 1; goto win_found; } } } win_found: /* * If there is only one window and it is the quickfix window, create a * new one above the quickfix window. */ if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { flags = WSP_ABOVE; if (ll_ref != NULL) flags |= WSP_NEWLOC; if (win_split(0, flags) == FAIL) return FAIL; /* not enough room for window */ *opened_window = TRUE; /* close it when fail */ p_swb = empty_option; /* don't split again */ swb_flags = 0; RESET_BINDING(curwin); if (ll_ref != NULL) { /* The new window should use the location list from the * location list window */ curwin->w_llist = ll_ref; ll_ref->qf_refcount++; } } else { if (curwin->w_llist_ref != NULL) { /* In a location window */ win = usable_win_ptr; if (win == NULL) { /* Find the window showing the selected file */ FOR_ALL_WINDOWS(win) if (win->w_buffer->b_fnum == qf_fnum) break; if (win == NULL) { /* Find a previous usable window */ win = curwin; do { if (win->w_buffer->b_p_bt[0] == NUL) break; if (win->w_prev == NULL) win = lastwin; /* wrap around the top */ else win = win->w_prev; /* go to previous window */ } while (win != curwin); } } win_goto(win); /* If the location list for the window is not set, then set it * to the location list from the location window */ if (win->w_llist == NULL) { win->w_llist = ll_ref; ll_ref->qf_refcount++; } } else { /* * Try to find a window that shows the right buffer. * Default to the window just above the quickfix buffer. */ win = curwin; altwin = NULL; for (;;) { if (win->w_buffer->b_fnum == qf_fnum) break; if (win->w_prev == NULL) win = lastwin; /* wrap around the top */ else win = win->w_prev; /* go to previous window */ if (IS_QF_WINDOW(win)) { /* Didn't find it, go to the window before the quickfix * window. */ if (altwin != NULL) win = altwin; else if (curwin->w_prev != NULL) win = curwin->w_prev; else win = curwin->w_next; break; } /* Remember a usable window. */ if (altwin == NULL && !win->w_p_pvw && win->w_buffer->b_p_bt[0] == NUL) altwin = win; } win_goto(win); } } return OK; } /* * Edit the selected file or help file. */ static int qf_jump_edit_buffer( qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { int retval = OK; if (qf_ptr->qf_type == 1) { /* Open help file (do_ecmd() will set b_help flag, readfile() will * set b_p_ro flag). */ if (!can_abandon(curbuf, forceit)) { no_write_message(); retval = FALSE; } else retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, ECMD_HIDE + ECMD_SET_HELP, oldwin == curwin ? curwin : NULL); } else { int old_qf_curlist = qi->qf_curlist; retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); if (qi != &ql_info && !win_valid_any_tab(oldwin)) { EMSG(_("E924: Current window was closed")); *abort = TRUE; *opened_window = FALSE; } else if (old_qf_curlist != qi->qf_curlist || !is_qf_entry_present(qi, qf_ptr)) { if (qi == &ql_info) EMSG(_("E925: Current quickfix was changed")); else EMSG(_("E926: Current location list was changed")); *abort = TRUE; } if (*abort) retval = FALSE; } return retval; } /* * Goto the error line in the current file using either line/column number or a * search pattern. */ static void qf_jump_goto_line( linenr_T qf_lnum, int qf_col, char_u qf_viscol, char_u *qf_pattern) { linenr_T i; char_u *line; colnr_T screen_col; colnr_T char_col; if (qf_pattern == NULL) { /* * Go to line with error, unless qf_lnum is 0. */ i = qf_lnum; if (i > 0) { if (i > curbuf->b_ml.ml_line_count) i = curbuf->b_ml.ml_line_count; curwin->w_cursor.lnum = i; } if (qf_col > 0) { curwin->w_cursor.col = qf_col - 1; #ifdef FEAT_VIRTUALEDIT curwin->w_cursor.coladd = 0; #endif if (qf_viscol == TRUE) { /* * Check each character from the beginning of the error * line up to the error column. For each tab character * found, reduce the error column value by the length of * a tab character. */ line = ml_get_curline(); screen_col = 0; for (char_col = 0; char_col < curwin->w_cursor.col; ++char_col) { if (*line == NUL) break; if (*line++ == '\t') { curwin->w_cursor.col -= 7 - (screen_col % 8); screen_col += 8 - (screen_col % 8); } else ++screen_col; } } check_cursor(); } else beginline(BL_WHITE | BL_FIX); } else { pos_T save_cursor; /* Move the cursor to the first line in the buffer */ save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL, NULL)) curwin->w_cursor = save_cursor; } } /* * Display quickfix list index and size message */ static void qf_jump_print_msg( qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf_T *old_curbuf, linenr_T old_lnum) { linenr_T i; int len; /* Update the screen before showing the message, unless the screen * scrolled up. */ if (!msg_scrolled) update_topline_redraw(); sprintf((char *)IObuff, _("(%d of %d)%s%s: "), qf_index, qi->qf_lists[qi->qf_curlist].qf_count, qf_ptr->qf_cleared ? _(" (line deleted)") : "", (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); /* Add the message, skipping leading whitespace and newlines. */ len = (int)STRLEN(IObuff); qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); /* Output the message. Overwrite to avoid scrolling when the 'O' * flag is present in 'shortmess'; But when not jumping, print the * whole message. */ i = msg_scroll; if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) msg_scroll = TRUE; else if (!msg_scrolled && shortmess(SHM_OVERALL)) msg_scroll = FALSE; msg_attr_keep(IObuff, 0, TRUE); msg_scroll = i; } /* * jump to a quickfix line * if dir == FORWARD go "errornr" valid entries forward * if dir == BACKWARD go "errornr" valid entries backward * if dir == FORWARD_FILE go "errornr" valid entries files backward * if dir == BACKWARD_FILE go "errornr" valid entries files backward * else if "errornr" is zero, redisplay the same line * else go to entry "errornr" */ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { qfline_T *qf_ptr; qfline_T *old_qf_ptr; int qf_index; int old_qf_index; buf_T *old_curbuf; linenr_T old_lnum; char_u *old_swb = p_swb; unsigned old_swb_flags = swb_flags; int opened_window = FALSE; win_T *oldwin = curwin; int print_message = TRUE; #ifdef FEAT_FOLDING int old_KeyTyped = KeyTyped; /* getting file may reset it */ #endif int retval = OK; if (qi == NULL) qi = &ql_info; if (qi->qf_curlist >= qi->qf_listcount || qi->qf_lists[qi->qf_curlist].qf_count == 0) { EMSG(_(e_quickfix)); return; } qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; old_qf_ptr = qf_ptr; qf_index = qi->qf_lists[qi->qf_curlist].qf_index; old_qf_index = qf_index; if (dir != 0) /* next/prev valid entry */ { qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir); if (qf_ptr == NULL) { qf_ptr = old_qf_ptr; qf_index = old_qf_index; goto theend; } } else if (errornr != 0) /* go to specified number */ qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index); qi->qf_lists[qi->qf_curlist].qf_index = qf_index; if (qf_win_pos_update(qi, old_qf_index)) /* No need to print the error message if it's visible in the error * window */ print_message = FALSE; /* * For ":helpgrep" find a help window or open one. */ if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) if (jump_to_help_window(qi, &opened_window) == FAIL) goto theend; /* * If currently in the quickfix window, find another window to show the * file in. */ if (bt_quickfix(curbuf) && !opened_window) { /* * If there is no file specified, we don't know where to go. * But do advance, otherwise ":cn" gets stuck. */ if (qf_ptr->qf_fnum == 0) goto theend; if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) goto failed; } /* * 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) { int abort = FALSE; retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window, &abort); if (abort) { qi = NULL; qf_ptr = NULL; } } if (retval == OK) { /* 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) && old_KeyTyped) foldOpenCursor(); #endif if (print_message) qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); } else { 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) { qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr; qi->qf_lists[qi->qf_curlist].qf_index = qf_index; } if (p_swb != old_swb && opened_window) { /* Restore old 'switchbuf' value, but not when an autocommand or * modeline has changed the value. */ if (p_swb == empty_option) { p_swb = old_swb; swb_flags = old_swb_flags; } else free_string_option(old_swb); } } /* * ":clist": list all errors * ":llist": list all locations */ void qf_list(exarg_T *eap) { buf_T *buf; char_u *fname; qfline_T *qfp; int i; int idx1 = 1; int idx2 = -1; char_u *arg = eap->arg; int plus = FALSE; int qfFileAttr; int qfSepAttr; int qfLineAttr; int all = eap->forceit; /* if not :cl!, only show recognised errors */ qf_info_T *qi = &ql_info; if (eap->cmdidx == CMD_llist) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); return; } } if (qi->qf_curlist >= qi->qf_listcount || qi->qf_lists[qi->qf_curlist].qf_count == 0) { EMSG(_(e_quickfix)); return; } if (*arg == '+') { ++arg; plus = TRUE; } if (!get_list_range(&arg, &idx1, &idx2) || *arg != NUL) { EMSG(_(e_trailing)); return; } if (plus) { i = qi->qf_lists[qi->qf_curlist].qf_index; idx2 = i + idx1; idx1 = i; } else { i = qi->qf_lists[qi->qf_curlist].qf_count; if (idx1 < 0) idx1 = (-idx1 > i) ? 0 : idx1 + i + 1; if (idx2 < 0) idx2 = (-idx2 > i) ? 0 : idx2 + i + 1; } /* * 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 (qi->qf_lists[qi->qf_curlist].qf_nonevalid) all = TRUE; qfp = qi->qf_lists[qi->qf_curlist].qf_start; for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { msg_putchar('\n'); if (got_int) break; fname = NULL; if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) { fname = buf->b_fname; if (qfp->qf_type == 1) /* :helpgrep */ fname = gettail(fname); } if (fname == NULL) sprintf((char *)IObuff, "%2d", i); else vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index ? HL_ATTR(HLF_QFL) : qfFileAttr); if (qfp->qf_lnum != 0) msg_puts_attr((char_u *)":", qfSepAttr); if (qfp->qf_lnum == 0) IObuff[0] = NUL; else if (qfp->qf_col == 0) sprintf((char *)IObuff, "%ld", qfp->qf_lnum); else sprintf((char *)IObuff, "%ld col %d", qfp->qf_lnum, qfp->qf_col); sprintf((char *)IObuff + STRLEN(IObuff), "%s", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); msg_puts_attr(IObuff, qfLineAttr); msg_puts_attr((char_u *)":", qfSepAttr); if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); msg_puts(IObuff); msg_puts_attr((char_u *)":", qfSepAttr); } msg_puts((char_u *)" "); /* Remove newlines and leading whitespace from the text. For an * unrecognized line keep the indent, the compiler may mark a word * with ^^^^. */ qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) ? skipwhite(qfp->qf_text) : qfp->qf_text, IObuff, IOSIZE); msg_prt_line(IObuff, FALSE); out_flush(); /* show one line at a time */ } qfp = qfp->qf_next; if (qfp == NULL) break; ++i; ui_breakcheck(); } } /* * Remove newlines and leading whitespace from an error message. * Put the result in "buf[bufsize]". */ static void qf_fmt_text(char_u *text, char_u *buf, int bufsize) { int i; char_u *p = text; for (i = 0; *p != NUL && i < bufsize - 1; ++i) { if (*p == '\n') { buf[i] = ' '; while (*++p != NUL) if (!VIM_ISWHITE(*p) && *p != '\n') break; } else buf[i] = *p++; } buf[i] = NUL; } 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(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 = &ql_info; int count; if (eap->cmdidx == CMD_lolder || eap->cmdidx == CMD_lnewer) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); 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(_("E380: At bottom of quickfix stack")); break; } --qi->qf_curlist; } else { if (qi->qf_curlist >= qi->qf_listcount - 1) { EMSG(_("E381: At top of quickfix stack")); break; } ++qi->qf_curlist; } } qf_msg(qi, qi->qf_curlist, ""); qf_update_buffer(qi, NULL); } void qf_history(exarg_T *eap) { qf_info_T *qi = &ql_info; int i; if (eap->cmdidx == CMD_lhistory) qi = GET_LOC_LIST(curwin); if (qi == NULL || (qi->qf_listcount == 0 && qi->qf_lists[qi->qf_curlist].qf_count == 0)) 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_info_T *qi, int idx) { qfline_T *qfp; qfline_T *qfpnext; int stop = FALSE; qf_list_T *qfl = &qi->qf_lists[idx]; while (qfl->qf_count && qfl->qf_start != NULL) { qfp = qfl->qf_start; qfpnext = qfp->qf_next; if (!stop) { vim_free(qfp->qf_text); stop = (qfp == qfpnext); vim_free(qfp->qf_pattern); 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_info_T *qi, int idx) { qf_list_T *qfl = &qi->qf_lists[idx]; qf_free_items(qi, idx); vim_free(qfl->qf_title); qfl->qf_title = NULL; free_tv(qfl->qf_ctx); qfl->qf_ctx = NULL; qfl->qf_id = 0; } /* * 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) if (qi->qf_lists[idx].qf_count) for (i = 0, qfp = qi->qf_lists[idx].qf_start; i < qi->qf_lists[idx].qf_count && qfp != NULL; ++i, qfp = qfp->qf_next) 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" * 0 0 "" * other 0 " c" * e or E n " error n" * w or W n " warning n" * i or I n " info 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 == '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; } /* * ":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 = &ql_info; win_T *win; if (eap->cmdidx == CMD_lwindow) { qi = GET_LOC_LIST(curwin); if (qi == NULL) return; } /* 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 (qi->qf_lists[qi->qf_curlist].qf_nonevalid || qi->qf_lists[qi->qf_curlist].qf_count == 0 || qi->qf_curlist >= qi->qf_listcount) { 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 = &ql_info; if (eap->cmdidx == CMD_lclose || eap->cmdidx == CMD_lwindow) { qi = GET_LOC_LIST(curwin); if (qi == NULL) return; } /* Find existing quickfix window and close it. */ win = qf_find_win(qi); if (win != NULL) win_close(win, FALSE); } /* * ":copen": open a window that shows the list of errors. * ":lopen": open a window that shows the location list. */ void ex_copen(exarg_T *eap) { qf_info_T *qi = &ql_info; int height; win_T *win; tabpage_T *prevtab = curtab; buf_T *qf_buf; win_T *oldwin = curwin; if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); return; } } 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 existing quickfix window, or open a new one. */ win = qf_find_win(qi); if (win != NULL && cmdmod.tab == 0) { win_goto(win); if (eap->addr_count != 0) { if (cmdmod.split & WSP_VERT) { if (height != win->w_width) win_setwidth(height); } else if (height != win->w_height) win_setheight(height); } } else { qf_buf = qf_find_buf(qi); /* The current window becomes the previous window afterwards. */ win = curwin; if ((eap->cmdidx == CMD_copen || eap->cmdidx == CMD_cwindow) && cmdmod.split == 0) /* Create the new window at the very bottom, except when * :belowright or :aboveleft is used. */ win_goto(lastwin); if (win_split(height, WSP_BELOW | WSP_NEWLOC) == FAIL) return; /* not enough room for window */ RESET_BINDING(curwin); if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { /* * For the location list window, create a reference to the * location list from the window 'win'. */ curwin->w_llist_ref = win->w_llist; win->w_llist->qf_refcount++; } if (oldwin != curwin) oldwin = NULL; /* don't store info when in another window */ if (qf_buf != NULL) /* Use the existing quickfix buffer */ (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE + ECMD_OLDBUF, oldwin); else { /* Create a new quickfix buffer */ (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); /* switch off 'swapfile' */ set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); set_option_value((char_u *)"bt", 0L, (char_u *)"quickfix", OPT_LOCAL); set_option_value((char_u *)"bh", 0L, (char_u *)"wipe", OPT_LOCAL); RESET_BINDING(curwin); #ifdef FEAT_DIFF curwin->w_p_diff = FALSE; #endif #ifdef FEAT_FOLDING set_option_value((char_u *)"fdm", 0L, (char_u *)"manual", OPT_LOCAL); #endif } /* 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; } qf_set_title_var(qi); /* * Fill the buffer with the quickfix list. */ qf_fill_buffer(qi, curbuf, NULL); curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index; 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; #ifdef FEAT_VIRTUALEDIT curwin->w_cursor.coladd = 0; #endif curwin->w_curswant = 0; update_topline(); /* scroll to show the line */ redraw_later(VALID); curwin->w_redr_status = TRUE; /* update ruler */ curwin = old_curwin; curbuf = curwin->w_buffer; } /* * :cbottom/:lbottom commands. */ void ex_cbottom(exarg_T *eap UNUSED) { qf_info_T *qi = &ql_info; win_T *win; if (eap->cmdidx == CMD_lbottom) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); 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 qi->qf_lists[qi->qf_curlist].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 = qi->qf_lists[qi->qf_curlist].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 * list buffer */ 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 ((qi == &ql_info && win->w_llist_ref == NULL) || (qi != &ql_info && win->w_llist_ref == qi)) return TRUE; return FALSE; } /* * Find a window displaying the quickfix/location list 'qi' * Searches in only the windows opened in the current tab. */ static win_T * qf_find_win(qf_info_T *qi) { win_T *win; FOR_ALL_WINDOWS(win) if (is_qf_win(win, qi)) break; return win; } /* * Find a quickfix buffer. * Searches in windows opened in all the tabs. */ static buf_T * qf_find_buf(qf_info_T *qi) { tabpage_T *tp; win_T *win; FOR_ALL_TAB_WINDOWS(tp, win) if (is_qf_win(win, qi)) return win->w_buffer; return NULL; } /* * Update the w:quickfix_title variable in the quickfix/location list window */ static void qf_update_win_titlevar(qf_info_T *qi) { win_T *win; win_T *curwin_save; if ((win = qf_find_win(qi)) != NULL) { curwin_save = curwin; curwin = win; qf_set_title_var(qi); curwin = curwin_save; } } /* * 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) { linenr_T old_line_count = buf->b_ml.ml_line_count; if (old_last == NULL) /* set curwin/curbuf to buf and save a few things */ aucmd_prepbuf(&aco, buf); qf_update_win_titlevar(qi); qf_fill_buffer(qi, buf, old_last); ++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, NOT_VALID); } } /* * Set "w:quickfix_title" if "qi" has a title. */ static void qf_set_title_var(qf_info_T *qi) { if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) set_internal_string_var((char_u *)"w:quickfix_title", qi->qf_lists[qi->qf_curlist].qf_title); } /* * Fill current buffer with quickfix errors, replacing any previous contents. * curbuf must be the quickfix buffer! * If "old_last" is not NULL append the items after this one. * When "old_last" is NULL then "buf" must equal "curbuf"! Because * ml_delete() is used and autocommands will be triggered. */ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) { linenr_T lnum; qfline_T *qfp; buf_T *errbuf; int len; int old_KeyTyped = KeyTyped; 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, FALSE); } /* Check if there is anything to display */ if (qi->qf_curlist < qi->qf_listcount) { /* Add one line for each error */ if (old_last == NULL) { qfp = qi->qf_lists[qi->qf_curlist].qf_start; lnum = 0; } else { qfp = old_last->qf_next; lnum = buf->b_ml.ml_line_count; } while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) /* :helpgrep */ STRCPY(IObuff, gettail(errbuf->b_fname)); else STRCPY(IObuff, errbuf->b_fname); len = (int)STRLEN(IObuff); } else len = 0; IObuff[len++] = '|'; if (qfp->qf_lnum > 0) { sprintf((char *)IObuff + len, "%ld", qfp->qf_lnum); len += (int)STRLEN(IObuff + len); if (qfp->qf_col > 0) { sprintf((char *)IObuff + len, " col %d", qfp->qf_col); len += (int)STRLEN(IObuff + len); } sprintf((char *)IObuff + len, "%s", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); len += (int)STRLEN(IObuff + len); } else if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); len += (int)STRLEN(IObuff + len); } IObuff[len++] = '|'; IObuff[len++] = ' '; /* 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(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, IObuff + len, IOSIZE - len); if (ml_append_buf(buf, lnum, IObuff, (colnr_T)STRLEN(IObuff) + 1, FALSE) == FAIL) break; ++lnum; qfp = qfp->qf_next; if (qfp == NULL) break; } if (old_last == NULL) /* Delete the empty line which is now at the end */ (void)ml_delete(lnum + 1, FALSE); } /* 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. */ #ifdef FEAT_AUTOCMD ++curbuf_lock; #endif set_option_value((char_u *)"ft", 0L, (char_u *)"qf", OPT_LOCAL); curbuf->b_p_ma = FALSE; #ifdef FEAT_AUTOCMD 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; #endif /* make sure it will be redrawn */ redraw_curbuf_later(NOT_VALID); } /* Restore KeyTyped, setting 'filetype' may reset it. */ KeyTyped = old_KeyTyped; } /* * 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); } /* * 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; unsigned len; win_T *wp = NULL; qf_info_T *qi = &ql_info; int res; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ if (grep_internal(eap->cmdidx)) { ex_vimgrep(eap); return; } switch (eap->cmdidx) { case CMD_make: au_name = (char_u *)"make"; break; case CMD_lmake: au_name = (char_u *)"lmake"; break; case CMD_grep: au_name = (char_u *)"grep"; break; case CMD_lgrep: au_name = (char_u *)"lgrep"; break; case CMD_grepadd: au_name = (char_u *)"grepadd"; break; case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break; default: break; } if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, TRUE, curbuf)) { # ifdef FEAT_EVAL if (aborting()) return; # endif } #endif #ifdef FEAT_MBYTE enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; #endif if (eap->cmdidx == CMD_lmake || eap->cmdidx == CMD_lgrep || eap->cmdidx == CMD_lgrepadd) wp = curwin; autowrite_all(); fname = get_mef_name(); if (fname == NULL) return; mch_remove(fname); /* in case it's not unique */ /* * If 'shellpipe' empty: don't redirect to 'errorfile'. */ len = (unsigned)STRLEN(p_shq) * 2 + (unsigned)STRLEN(eap->arg) + 1; if (*p_sp != NUL) len += (unsigned)STRLEN(p_sp) + (unsigned)STRLEN(fname) + 3; cmd = alloc(len); if (cmd == NULL) return; sprintf((char *)cmd, "%s%s%s", (char *)p_shq, (char *)eap->arg, (char *)p_shq); if (*p_sp != NUL) append_redir(cmd, len, p_sp, fname); /* * 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 */ /* 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 res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), *eap->cmdlinep, enc); if (wp != NULL) qi = GET_LOC_LIST(wp); #ifdef FEAT_AUTOCMD if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); if (qi->qf_curlist < qi->qf_listcount) res = qi->qf_lists[qi->qf_curlist].qf_count; else res = 0; } #endif if (res > 0 && !eap->forceit) qf_jump(qi, 0, 0, FALSE); /* display first error */ mch_remove(fname); vim_free(fname); vim_free(cmd); } /* * 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_notmp)); 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((unsigned)STRLEN(p_mef) + 30); 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; } /* * Returns the number of valid entries in the current quickfix/location list. */ int qf_get_size(exarg_T *eap) { qf_info_T *qi = &ql_info; qfline_T *qfp; int i, sz = 0; int prev_fnum = 0; if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { /* Location list */ qi = GET_LOC_LIST(curwin); if (qi == NULL) return 0; } for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start; i < qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL; ++i, qfp = qfp->qf_next) { 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 = &ql_info; if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { /* Location list */ qi = GET_LOC_LIST(curwin); if (qi == NULL) return 0; } return qi->qf_lists[qi->qf_curlist].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 = &ql_info; qf_list_T *qfl; qfline_T *qfp; int i, eidx = 0; int prev_fnum = 0; if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { /* Location list */ qi = GET_LOC_LIST(curwin); if (qi == NULL) return 1; } qfl = &qi->qf_lists[qi->qf_curlist]; qfp = qfl->qf_start; /* check if the list has valid errors */ if (qfl->qf_count <= 0 || qfl->qf_nonevalid) 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_info_T *qi, int n, int fdo) { qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; qfline_T *qfp = qfl->qf_start; int i, eidx; int prev_fnum = 0; /* check if the list has valid errors */ if (qfl->qf_count <= 0 || qfl->qf_nonevalid) return 1; for (i = 1, eidx = 0; i <= qfl->qf_count && qfp != NULL; i++, qfp = qfp->qf_next) { 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 = &ql_info; int errornr; if (eap->cmdidx == CMD_ll || eap->cmdidx == CMD_lrewind || eap->cmdidx == CMD_lfirst || eap->cmdidx == CMD_llast || eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); return; } } if (eap->addr_count > 0) errornr = (int)eap->line2; else { if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) errornr = 0; else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) errornr = 1; else 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(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 = &ql_info; int errornr; if (eap->cmdidx == CMD_lnext || eap->cmdidx == CMD_lNext || eap->cmdidx == CMD_lprevious || eap->cmdidx == CMD_lnfile || eap->cmdidx == CMD_lNfile || eap->cmdidx == CMD_lpfile || eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); 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; qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo) ? FORWARD : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) ? FORWARD_FILE : (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile || eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile) ? BACKWARD_FILE : BACKWARD, errornr, eap->forceit); } /* * ":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; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; #endif int res; if (eap->cmdidx == CMD_lfile || eap->cmdidx == CMD_lgetfile || eap->cmdidx == CMD_laddfile) wp = curwin; #ifdef FEAT_AUTOCMD switch (eap->cmdidx) { case CMD_cfile: au_name = (char_u *)"cfile"; break; case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break; case CMD_caddfile: au_name = (char_u *)"caddfile"; break; case CMD_lfile: au_name = (char_u *)"lfile"; break; case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break; case CMD_laddfile: au_name = (char_u *)"laddfile"; break; default: break; } if (au_name != NULL) apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, FALSE, curbuf); #endif #ifdef FEAT_MBYTE enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; #endif #ifdef FEAT_BROWSE if (cmdmod.browse) { char_u *browse_file = do_browse(0, (char_u *)_("Error file"), eap->arg, NULL, NULL, 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); /* * 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), *eap->cmdlinep, enc); #ifdef FEAT_AUTOCMD if (au_name != NULL) apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf); #endif if (res > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { if (wp != NULL) qi = GET_LOC_LIST(wp); qf_jump(qi, 0, 0, eap->forceit); /* display first error */ } } /* * ":vimgrep {pattern} file(s)" * ":vimgrepadd {pattern} file(s)" * ":lvimgrep {pattern} file(s)" * ":lvimgrepadd {pattern} file(s)" */ void ex_vimgrep(exarg_T *eap) { regmmatch_T regmatch; int fcount; char_u **fnames; char_u *fname; char_u *title; char_u *s; char_u *p; int fi; qf_info_T *qi = &ql_info; #ifdef FEAT_AUTOCMD qfline_T *cur_qf_start; #endif long lnum; buf_T *buf; int duplicate_name = FALSE; int using_dummy; int redraw_for_dummy = FALSE; int found_match; buf_T *first_match_buf = NULL; time_t seconds = 0; int save_mls; #if defined(FEAT_AUTOCMD) && defined(FEAT_SYN_HL) char_u *save_ei = NULL; #endif aco_save_T aco; int flags = 0; colnr_T col; long tomatch; char_u *dirname_start = NULL; char_u *dirname_now = NULL; char_u *target_dir = NULL; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; switch (eap->cmdidx) { case CMD_vimgrep: au_name = (char_u *)"vimgrep"; break; case CMD_lvimgrep: au_name = (char_u *)"lvimgrep"; break; case CMD_vimgrepadd: au_name = (char_u *)"vimgrepadd"; break; case CMD_lvimgrepadd: au_name = (char_u *)"lvimgrepadd"; break; case CMD_grep: au_name = (char_u *)"grep"; break; case CMD_lgrep: au_name = (char_u *)"lgrep"; break; case CMD_grepadd: au_name = (char_u *)"grepadd"; break; case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break; default: break; } if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, TRUE, curbuf)) { # ifdef FEAT_EVAL if (aborting()) return; # endif } #endif if (eap->cmdidx == CMD_lgrep || eap->cmdidx == CMD_lvimgrep || eap->cmdidx == CMD_lgrepadd || eap->cmdidx == CMD_lvimgrepadd) { qi = ll_get_or_alloc_list(curwin); if (qi == NULL) return; } if (eap->addr_count > 0) tomatch = eap->line2; else tomatch = MAXLNUM; /* Get the search pattern: either white-separated or enclosed in // */ regmatch.regprog = NULL; title = vim_strsave(*eap->cmdlinep); p = skip_vimgrep_pat(eap->arg, &s, &flags); if (p == NULL) { EMSG(_(e_invalpat)); goto theend; } if (s != NULL && *s == NUL) { /* Pattern is empty, use last search pattern. */ if (last_search_pat() == NULL) { EMSG(_(e_noprevre)); goto theend; } regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else regmatch.regprog = vim_regcomp(s, RE_MAGIC); if (regmatch.regprog == NULL) goto theend; regmatch.rmm_ic = p_ic; regmatch.rmm_maxcol = 0; p = skipwhite(p); if (*p == NUL) { EMSG(_("E683: File name missing or invalid pattern")); goto theend; } if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) || qi->qf_curlist == qi->qf_listcount) /* make place for a new list */ qf_new_list(qi, title != NULL ? title : *eap->cmdlinep); /* parse the list of arguments */ if (get_arglist_exp(p, &fcount, &fnames, TRUE) == FAIL) goto theend; if (fcount == 0) { EMSG(_(e_nomatch)); goto theend; } 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) { FreeWild(fcount, fnames); 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); #ifdef FEAT_AUTOCMD /* Remember the value of qf_start, so that we can check for autocommands * changing the current quickfix list. */ cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; #endif seconds = (time_t)0; for (fi = 0; fi < fcount && !got_int && tomatch > 0; ++fi) { fname = shorten_fname1(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); 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(); } buf = buflist_findname_exp(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; #if defined(FEAT_AUTOCMD) && 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_AUTOCMD) && defined(FEAT_SYN_HL) au_event_restore(save_ei); #endif } else /* Use existing, loaded buffer. */ using_dummy = FALSE; #ifdef FEAT_AUTOCMD if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) { int idx; /* Autocommands changed the quickfix list. Find the one we were * using and restore it. */ for (idx = 0; idx < LISTCOUNT; ++idx) if (cur_qf_start == qi->qf_lists[idx].qf_start) { qi->qf_curlist = idx; break; } if (idx == LISTCOUNT) { /* List cannot be found, create a new one. */ qf_new_list(qi, *eap->cmdlinep); cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; } } #endif if (buf == NULL) { if (!got_int) smsg((char_u *)_("Cannot open file \"%s\""), fname); } else { /* Try for a match in all lines of the buffer. * For ":1vimgrep" look for first match only. */ found_match = FALSE; for (lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; ++lnum) { col = 0; while (vim_regexec_multi(®match, curwin, buf, lnum, col, NULL, NULL) > 0) { /* Pass the buffer number so that it gets used even for a * dummy buffer, unless duplicate_name is set, then the * buffer will be wiped out below. */ if (qf_add_entry(qi, qi->qf_curlist, NULL, /* dir */ fname, duplicate_name ? 0 : buf->b_fnum, ml_get_buf(buf, regmatch.startpos[0].lnum + lnum, FALSE), regmatch.startpos[0].lnum + lnum, regmatch.startpos[0].col + 1, FALSE, /* vis_col */ NULL, /* search pattern */ 0, /* nr */ 0, /* type */ TRUE /* valid */ ) == FAIL) { got_int = TRUE; break; } found_match = TRUE; if (--tomatch == 0) break; if ((flags & VGR_GLOBAL) == 0 || regmatch.endpos[0].lnum > 0) break; col = regmatch.endpos[0].col + (col == regmatch.endpos[0].col); if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, FALSE))) break; } line_breakcheck(); if (got_int) break; } #ifdef FEAT_AUTOCMD cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; #endif 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.hide || 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 || (flags & VGR_NOJUMP)) { 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 defined(FEAT_AUTOCMD) && 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); } } } } FreeWild(fcount, fnames); qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; qi->qf_lists[qi->qf_curlist].qf_index = 1; qf_update_buffer(qi, NULL); #ifdef FEAT_AUTOCMD if (au_name != NULL) apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); #endif /* Jump to first match. */ if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { if ((flags & VGR_NOJUMP) == 0) { buf = curbuf; qf_jump(qi, 0, 0, eap->forceit); if (buf != curbuf) /* If we jumped to another buffer redrawing will already be * taken care of. */ redraw_for_dummy = FALSE; /* Jump to the directory used after loading the buffer. */ if (curbuf == first_match_buf && target_dir != NULL) { exarg_T ea; ea.arg = target_dir; ea.cmdidx = CMD_lcd; ex_cd(&ea); } } } else EMSG2(_(e_nomatch2), s); /* 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(NOT_VALID); #endif } theend: vim_free(title); vim_free(dirname_now); vim_free(dirname_start); vim_free(target_dir); vim_regfree(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 (NULL != dirname_now) { 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; 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; /* 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) { /* set curwin/curbuf to buf and save a few things */ aucmd_prepbuf(&aco, 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; if (readfile(fname, NULL, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW | READ_DUMMY) == 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 (curbuf != buf) /* safety check */ { #if defined(FEAT_AUTOCMD) && 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, FALSE); #if defined(FEAT_AUTOCMD) && 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 */ { close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE); /* When autocommands/'autochdir' option changed directory: go back. */ restore_start_dir(dirname_start); } } #if defined(FEAT_EVAL) || defined(PROTO) /* * Add each quickfix error to list "list" as a dictionary. * If qf_idx is -1, use the current list. Otherwise, use the specified list. */ int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { qf_info_T *qi = qi_arg; dict_T *dict; char_u buf[2]; qfline_T *qfp; int i; int bufnum; if (qi == NULL) { qi = &ql_info; if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) return FAIL; } } if (qf_idx == -1) qf_idx = qi->qf_curlist; if (qf_idx >= qi->qf_listcount || qi->qf_lists[qf_idx].qf_count == 0) return FAIL; qfp = qi->qf_lists[qf_idx].qf_start; for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; ++i) { /* Handle entries with a non-existing buffer number. */ bufnum = qfp->qf_fnum; if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) bufnum = 0; 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_nr_str(dict, "bufnr", (long)bufnum, NULL) == FAIL || dict_add_nr_str(dict, "lnum", (long)qfp->qf_lnum, NULL) == FAIL || dict_add_nr_str(dict, "col", (long)qfp->qf_col, NULL) == FAIL || dict_add_nr_str(dict, "vcol", (long)qfp->qf_viscol, NULL) == FAIL || dict_add_nr_str(dict, "nr", (long)qfp->qf_nr, NULL) == FAIL || dict_add_nr_str(dict, "pattern", 0L, qfp->qf_pattern == NULL ? (char_u *)"" : qfp->qf_pattern) == FAIL || dict_add_nr_str(dict, "text", 0L, qfp->qf_text == NULL ? (char_u *)"" : qfp->qf_text) == FAIL || dict_add_nr_str(dict, "type", 0L, buf) == FAIL || dict_add_nr_str(dict, "valid", (long)qfp->qf_valid, NULL) == FAIL) return FAIL; qfp = qfp->qf_next; if (qfp == NULL) break; } 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_ALL = 0xFF }; /* * Parse text from 'di' and return the quickfix list items */ 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) { /* 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_info_T *)alloc((unsigned)sizeof(qf_info_T)); if (qi != NULL) { vim_memset(qi, 0, (size_t)(sizeof(qf_info_T))); qi->qf_refcount++; if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, TRUE, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { (void)get_errorlist(qi, NULL, 0, l); qf_free(qi, 0); } free(qi); } dict_add_list(retdict, "items", l); status = OK; } return status; } /* * 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 -1; } /* * Return quickfix/location list details (title) as a * dictionary. 'what' contains the details to return. If 'list_idx' is -1, * then current list is used. Otherwise the specified list is used. */ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; int status = OK; int qf_idx; 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); /* List is not present or is empty */ if (qi == NULL || qi->qf_listcount == 0) { /* If querying for the size of the list, return 0 */ if (((di = dict_find(what, (char_u *)"nr", -1)) != NULL) && (di->di_tv.v_type == VAR_STRING) && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) return dict_add_nr_str(retdict, "nr", 0, NULL); return FAIL; } 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) return FAIL; } } else if ((di->di_tv.v_type == VAR_STRING) && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) /* Get the last quickfix list number */ qf_idx = qi->qf_listcount - 1; else return FAIL; flags |= QF_GETLIST_NR; } 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 specifed by 'nr' */ if (di->di_tv.vval.v_number != 0) { qf_idx = qf_id2nr(qi, di->di_tv.vval.v_number); if (qf_idx == -1) return FAIL; /* List not found */ } flags |= QF_GETLIST_ID; } else return FAIL; } if (dict_find(what, (char_u *)"all", -1) != NULL) flags |= QF_GETLIST_ALL; if (dict_find(what, (char_u *)"title", -1) != NULL) flags |= QF_GETLIST_TITLE; if (dict_find(what, (char_u *)"winid", -1) != NULL) flags |= QF_GETLIST_WINID; if (dict_find(what, (char_u *)"context", -1) != NULL) flags |= QF_GETLIST_CONTEXT; if (dict_find(what, (char_u *)"items", -1) != NULL) flags |= QF_GETLIST_ITEMS; if (dict_find(what, (char_u *)"idx", -1) != NULL) flags |= QF_GETLIST_IDX; if (dict_find(what, (char_u *)"size", -1) != NULL) flags |= QF_GETLIST_SIZE; if (flags & QF_GETLIST_TITLE) { char_u *t; t = qi->qf_lists[qf_idx].qf_title; if (t == NULL) t = (char_u *)""; status = dict_add_nr_str(retdict, "title", 0L, t); } if ((status == OK) && (flags & QF_GETLIST_NR)) status = dict_add_nr_str(retdict, "nr", qf_idx + 1, NULL); if ((status == OK) && (flags & QF_GETLIST_WINID)) { win_T *win; int win_id = 0; win = qf_find_win(qi); if (win != NULL) win_id = win->w_id; status = dict_add_nr_str(retdict, "winid", win_id, NULL); } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { list_T *l = list_alloc(); if (l != NULL) { (void)get_errorlist(qi, NULL, qf_idx, l); dict_add_list(retdict, "items", l); } else status = FAIL; } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { if (qi->qf_lists[qf_idx].qf_ctx != NULL) { di = dictitem_alloc((char_u *)"context"); if (di != NULL) { copy_tv(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); status = dict_add(retdict, di); if (status == FAIL) dictitem_free(di); } else status = FAIL; } else status = dict_add_nr_str(retdict, "context", 0L, (char_u *)""); } if ((status == OK) && (flags & QF_GETLIST_ID)) status = dict_add_nr_str(retdict, "id", qi->qf_lists[qf_idx].qf_id, NULL); if ((status == OK) && (flags & QF_GETLIST_IDX)) { int idx = qi->qf_lists[qf_idx].qf_index; if (qi->qf_lists[qf_idx].qf_count == 0) /* For empty lists, qf_index is set to 1 */ idx = 0; status = dict_add_nr_str(retdict, "idx", idx, NULL); } if ((status == OK) && (flags & QF_GETLIST_SIZE)) status = dict_add_nr_str(retdict, "size", qi->qf_lists[qf_idx].qf_count, NULL); 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) { listitem_T *li; dict_T *d; char_u *filename, *pattern, *text, *type; int bufnum; long lnum; int col, nr; int vcol; qfline_T *old_last = NULL; int valid, status; int retval = OK; int did_bufnr_emsg = FALSE; if (action == ' ' || qf_idx == qi->qf_listcount) { /* make place for a new list */ qf_new_list(qi, title); qf_idx = qi->qf_curlist; } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) /* Adding to existing list, use last entry. */ old_last = qi->qf_lists[qf_idx].qf_last; else if (action == 'r') { qf_free_items(qi, qf_idx); qf_store_title(qi, qf_idx, title); } for (li = list->lv_first; li != NULL; li = li->li_next) { if (li->li_tv.v_type != VAR_DICT) continue; /* Skip non-dict items */ d = li->li_tv.vval.v_dict; if (d == NULL) continue; filename = get_dict_string(d, (char_u *)"filename", TRUE); bufnum = (int)get_dict_number(d, (char_u *)"bufnr"); lnum = (int)get_dict_number(d, (char_u *)"lnum"); col = (int)get_dict_number(d, (char_u *)"col"); vcol = (int)get_dict_number(d, (char_u *)"vcol"); nr = (int)get_dict_number(d, (char_u *)"nr"); type = get_dict_string(d, (char_u *)"type", TRUE); pattern = get_dict_string(d, (char_u *)"pattern", TRUE); text = get_dict_string(d, (char_u *)"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; EMSGN(_("E92: Buffer %ld not found"), bufnum); } valid = FALSE; bufnum = 0; } /* If the 'valid' field is present it overrules the detected value. */ if ((dict_find(d, (char_u *)"valid", -1)) != NULL) valid = (int)get_dict_number(d, (char_u *)"valid"); status = qf_add_entry(qi, qf_idx, NULL, /* dir */ filename, bufnum, text, lnum, col, vcol, /* vis_col */ pattern, /* search pattern */ nr, type == NULL ? NUL : *type, valid); vim_free(filename); vim_free(pattern); vim_free(text); vim_free(type); if (status == FAIL) { retval = FAIL; break; } } if (qi->qf_lists[qf_idx].qf_index == 0) /* no valid entry */ qi->qf_lists[qf_idx].qf_nonevalid = TRUE; else qi->qf_lists[qf_idx].qf_nonevalid = FALSE; if (action != 'a') { qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; if (qi->qf_lists[qf_idx].qf_count > 0) qi->qf_lists[qf_idx].qf_index = 1; } /* Don't update the cursor in quickfix window when appending entries */ qf_update_buffer(qi, old_last); return retval; } 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; char_u *errorformat = p_efm; if (action == ' ' || qi->qf_curlist == qi->qf_listcount) newlist = TRUE; 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 = qi->qf_listcount - 1; } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) return FAIL; else if (action != ' ') newlist = FALSE; /* use the specified list */ } else if (di->di_tv.v_type == VAR_STRING && STRCMP(di->di_tv.vval.v_string, "$") == 0) { if (qi->qf_listcount > 0) qf_idx = qi->qf_listcount - 1; else if (newlist) qf_idx = 0; else return FAIL; } else return FAIL; } 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) { qf_idx = qf_id2nr(qi, di->di_tv.vval.v_number); if (qf_idx == -1) return FAIL; /* List not found */ } else return FAIL; } if (newlist) { qi->qf_curlist = qf_idx; qf_new_list(qi, title); qf_idx = qi->qf_curlist; } if ((di = dict_find(what, (char_u *)"title", -1)) != NULL) { if (di->di_tv.v_type == VAR_STRING) { vim_free(qi->qf_lists[qf_idx].qf_title); qi->qf_lists[qf_idx].qf_title = get_dict_string(what, (char_u *)"title", TRUE); if (qf_idx == qi->qf_curlist) qf_update_win_titlevar(qi); retval = OK; } } if ((di = dict_find(what, (char_u *)"items", -1)) != NULL) { if (di->di_tv.v_type == VAR_LIST) { char_u *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); if (action == 'r') { /* * When replacing the quickfix list entries using * qf_add_entries(), the title is set with a ':' prefix. * Restore the title with the saved title. */ vim_free(qi->qf_lists[qf_idx].qf_title); qi->qf_lists[qf_idx].qf_title = vim_strsave(title_save); } vim_free(title_save); } } if ((di = dict_find(what, (char_u *)"efm", -1)) != NULL) { if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL) return FAIL; errorformat = di->di_tv.vval.v_string; } if ((di = dict_find(what, (char_u *)"lines", -1)) != NULL) { /* Only a List value is supported */ if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) { if (action == 'r') qf_free_items(qi, 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; } else return FAIL; } if ((di = dict_find(what, (char_u *)"context", -1)) != NULL) { typval_T *ctx; free_tv(qi->qf_lists[qf_idx].qf_ctx); ctx = alloc_tv(); if (ctx != NULL) copy_tv(&di->di_tv, ctx); qi->qf_lists[qf_idx].qf_ctx = ctx; retval = OK; } return retval; } /* * Find the non-location list window with the specified location list. */ static win_T * find_win_with_ll(qf_info_T *qi) { win_T *wp = NULL; FOR_ALL_WINDOWS(wp) if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) return wp; return NULL; } /* * 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; win_T *orig_wp = wp; if (qfwin != NULL) { /* If the quickfix/location list window is open, then clear it */ if (qi->qf_curlist < qi->qf_listcount) qf_free(qi, qi->qf_curlist); qf_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 = find_win_with_ll(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 (IS_LL_WINDOW(orig_wp)) { /* If the location list window is open, then create a new empty * location list */ qf_info_T *new_ll = ll_new_list(); /* first free the list reference in the location list window */ ll_free_all(&orig_wp->w_llist_ref); orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { llwin->w_llist = new_ll; new_ll->qf_refcount++; } } } /* * 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. */ 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); } else if (what != NULL) retval = qf_set_properties(qi, what, action, title); else retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); return retval; } static int mark_quickfix_ctx(qf_info_T *qi, int copyID) { int i; int abort = FALSE; typval_T *ctx; 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 = set_ref_in_item(ctx, copyID, NULL, NULL); } return abort; } /* * Mark the context of the quickfix list and the location lists (if present) as * "in use". So that garabage collection doesn't free the context. */ 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; FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_llist != NULL) { abort = mark_quickfix_ctx(win->w_llist, copyID); if (abort) return abort; } } return abort; } #endif /* * ":[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 = &ql_info; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; #endif int res; if (eap->cmdidx == CMD_lbuffer || eap->cmdidx == CMD_lgetbuffer || eap->cmdidx == CMD_laddbuffer) { qi = ll_get_or_alloc_list(curwin); if (qi == NULL) return; } #ifdef FEAT_AUTOCMD switch (eap->cmdidx) { case CMD_cbuffer: au_name = (char_u *)"cbuffer"; break; case CMD_cgetbuffer: au_name = (char_u *)"cgetbuffer"; break; case CMD_caddbuffer: au_name = (char_u *)"caddbuffer"; break; case CMD_lbuffer: au_name = (char_u *)"lbuffer"; break; case CMD_lgetbuffer: au_name = (char_u *)"lgetbuffer"; break; case CMD_laddbuffer: au_name = (char_u *)"laddbuffer"; 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 } #endif if (*eap->arg == NUL) buf = curbuf; else if (*skipwhite(skipdigits(eap->arg)) == NUL) buf = buflist_findnr(atoi((char *)eap->arg)); if (buf == NULL) EMSG(_(e_invarg)); else if (buf->b_ml.ml_mfp == NULL) EMSG(_("E681: Buffer is not loaded")); else { if (eap->addr_count == 0) { eap->line1 = 1; eap->line2 = buf->b_ml.ml_line_count; } if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) EMSG(_(e_invrange)); else { char_u *qf_title = *eap->cmdlinep; if (buf->b_sfname) { vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", (char *)qf_title, (char *)buf->b_sfname); qf_title = IObuff; } res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), eap->line1, eap->line2, qf_title, NULL); #ifdef FEAT_AUTOCMD if (au_name != NULL) apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); #endif if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)) qf_jump(qi, 0, 0, eap->forceit); /* display first error */ } } } #if defined(FEAT_EVAL) || defined(PROTO) /* * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. */ void ex_cexpr(exarg_T *eap) { typval_T *tv; qf_info_T *qi = &ql_info; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; #endif int res; if (eap->cmdidx == CMD_lexpr || eap->cmdidx == CMD_lgetexpr || eap->cmdidx == CMD_laddexpr) { qi = ll_get_or_alloc_list(curwin); if (qi == NULL) return; } #ifdef FEAT_AUTOCMD switch (eap->cmdidx) { case CMD_cexpr: au_name = (char_u *)"cexpr"; break; case CMD_cgetexpr: au_name = (char_u *)"cgetexpr"; break; case CMD_caddexpr: au_name = (char_u *)"caddexpr"; break; case CMD_lexpr: au_name = (char_u *)"lexpr"; break; case CMD_lgetexpr: au_name = (char_u *)"lgetexpr"; break; case CMD_laddexpr: au_name = (char_u *)"laddexpr"; 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 } #endif /* 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, NULL); if (tv != NULL) { if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)) { 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, *eap->cmdlinep, NULL); #ifdef FEAT_AUTOCMD if (au_name != NULL) apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); #endif if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) qf_jump(qi, 0, 0, eap->forceit); /* display first error */ } else EMSG(_("E777: String or List expected")); free_tv(tv); } } #endif /* * ":helpgrep {pattern}" */ void ex_helpgrep(exarg_T *eap) { regmatch_T regmatch; char_u *save_cpo; char_u *p; int fcount; char_u **fnames; FILE *fd; int fi; long lnum; #ifdef FEAT_MULTI_LANG char_u *lang; #endif qf_info_T *qi = &ql_info; qf_info_T *save_qi; int new_qi = FALSE; win_T *wp; #ifdef FEAT_AUTOCMD char_u *au_name = NULL; #endif #ifdef FEAT_MULTI_LANG /* Check for a specified language */ lang = check_help_lang(eap->arg); #endif #ifdef FEAT_AUTOCMD 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 } #endif /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ save_cpo = p_cpo; p_cpo = empty_option; if (eap->cmdidx == CMD_lhelpgrep) { /* 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 */ FOR_ALL_WINDOWS(wp) if (bt_help(wp->w_buffer)) break; 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 = ll_new_list()) == NULL) return; new_qi = TRUE; } } /* Autocommands may change the list. Save it for later comparison */ save_qi = qi; regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { #ifdef FEAT_MBYTE 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); #endif /* create a new quickfix list */ qf_new_list(qi, *eap->cmdlinep); /* Go through all directories in 'runtimepath' */ p = p_rtp; while (*p != NUL && !got_int) { copy_option_part(&p, NameBuff, MAXPATHL, ","); /* Find all "*.txt" and "*.??x" files in the "doc" directory. */ add_pathsep(NameBuff); STRCAT(NameBuff, "doc/*.\\(txt\\|??x\\)"); if (gen_expand_wildcards(1, &NameBuff, &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 fd = mch_fopen((char *)fnames[fi], "r"); if (fd != NULL) { lnum = 1; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { char_u *line = IObuff; #ifdef FEAT_MBYTE /* Convert a line if 'encoding' is not utf-8 and * the line contains a non-ASCII character. */ if (vc.vc_type != CONV_NONE && has_non_ascii(IObuff)) { line = string_convert(&vc, IObuff, NULL); if (line == NULL) line = IObuff; } #endif if (vim_regexec(®match, 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(qi, qi->qf_curlist, NULL, /* dir */ fnames[fi], 0, line, lnum, (int)(regmatch.startp[0] - line) + 1, /* col */ FALSE, /* vis_col */ NULL, /* search pattern */ 0, /* nr */ 1, /* type */ TRUE /* valid */ ) == FAIL) { got_int = TRUE; #ifdef FEAT_MBYTE if (line != IObuff) vim_free(line); #endif break; } } #ifdef FEAT_MBYTE if (line != IObuff) vim_free(line); #endif ++lnum; line_breakcheck(); } fclose(fd); } } FreeWild(fcount, fnames); } } vim_regfree(regmatch.regprog); #ifdef FEAT_MBYTE if (vc.vc_type != CONV_NONE) convert_setup(&vc, NULL, NULL); #endif qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; qi->qf_lists[qi->qf_curlist].qf_index = 1; } if (p_cpo == empty_option) p_cpo = save_cpo; else /* Darn, some plugin changed the value. */ free_string_option(save_cpo); qf_update_buffer(qi, NULL); #ifdef FEAT_AUTOCMD if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) /* autocommands made "qi" invalid */ return; } #endif /* Jump to first match. */ if (qi->qf_lists[qi->qf_curlist].qf_count > 0) qf_jump(qi, 0, 0, FALSE); else EMSG2(_(e_nomatch2), eap->arg); if (eap->cmdidx == CMD_lhelpgrep) { /* If the help window is not opened or if it already points to the * correct location list, then free the new location list. */ if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) { if (new_qi) ll_free_all(&qi); } else if (curwin->w_llist == NULL) curwin->w_llist = qi; } } #endif /* FEAT_QUICKFIX */