diff options
-rw-r--r-- | src/nvim/quickfix.c | 1042 | ||||
-rw-r--r-- | src/nvim/testdir/test_quickfix.vim | 24 | ||||
-rw-r--r-- | src/nvim/version.c | 6 |
3 files changed, 610 insertions, 462 deletions
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a4c3ec2430..efa1401498 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -137,6 +137,40 @@ struct efm_S { int conthere; /* %> used */ }; +enum { + QF_FAIL = 0, + QF_OK = 1, + QF_END_OF_INPUT = 2, + QF_NOMEM = 3, + QF_IGNORE_LINE = 4 +}; + +typedef struct { + char_u *linebuf; + size_t linelen; + char_u *growbuf; + size_t growbufsiz; + FILE *fd; + typval_T *tv; + char_u *p_str; + listitem_T *p_li; + buf_T *buf; + linenr_T buflnum; + linenr_T lnumlast; +} qfstate_T; + +typedef struct { + char_u *namebuf; + char_u *errmsg; + size_t errmsglen; + long lnum; + int col; + bool use_viscol; + char_u *pattern; + int enr; + char_u type; + bool valid; +} qffields_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.c.generated.h" @@ -179,22 +213,6 @@ qf_init ( // Maximum number of bytes allowed per line while reading an errorfile. static const size_t LINE_MAXLEN = 4096; -static char_u *qf_grow_linebuf(char_u **growbuf, size_t *growbufsiz, - size_t newsz, size_t *allocsz) -{ - // If the line exceeds LINE_MAXLEN exclude the last - // byte since it's not a NL character. - *allocsz = newsz > LINE_MAXLEN ? LINE_MAXLEN - 1 : newsz; - if (*growbuf == NULL) { - *growbuf = xmalloc(*allocsz + 1); - *growbufsiz = *allocsz; - } else if (*allocsz > *growbufsiz) { - *growbuf = xrealloc(*growbuf, *allocsz + 1); - *growbufsiz = *allocsz; - } - return *growbuf; -} - static struct fmtpattern { char_u convchar; @@ -428,6 +446,505 @@ parse_efm_end: return fmt_first; } +static char_u *qf_grow_linebuf(qfstate_T *state, size_t 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 = xmalloc(state->linelen + 1); + state->growbufsiz = state->linelen; + } else if (state->linelen > state->growbufsiz) { + state->growbuf = xrealloc(state->growbuf, state->linelen + 1); + 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; + size_t 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 = (size_t)(p - p_str) + 1; + } else { + len = STRLEN(p_str); + } + + if (len > IOSIZE - 2) { + state->linebuf = qf_grow_linebuf(state, len); + } else { + state->linebuf = IObuff; + state->linelen = len; + } + STRLCPY(state->linebuf, p_str, state->linelen + 1); + + // 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; + size_t len; + + // Get the next line from the supplied list + 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 = STRLEN(p_li->li_tv.vval.v_string); + if (len > IOSIZE - 2) { + state->linebuf = qf_grow_linebuf(state, len); + } else { + state->linebuf = IObuff; + state->linelen = len; + } + + STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1); + + 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; + size_t 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 = STRLEN(p_buf); + if (len > IOSIZE - 2) { + state->linebuf = qf_grow_linebuf(state, len); + } else { + state->linebuf = IObuff; + state->linelen = len; + } + STRLCPY(state->linebuf, p_buf, state->linelen + 1); + + return QF_OK; +} + +/// Get the next string from file state->fd. +static int qf_get_next_file_line(qfstate_T *state) +{ + size_t growbuflen; + + if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) { + return QF_END_OF_INPUT; + } + + bool discard = false; + state->linelen = STRLEN(IObuff); + if (state->linelen == IOSIZE - 1 && !(IObuff[state->linelen - 1] == '\n' +#ifdef USE_CRNL + || IObuff[state->linelen - 1] == '\r' +#endif + )) { // NOLINT(whitespace/parens) + // 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 = xmalloc(state->growbufsiz); + } + + // 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, + (int)(state->growbufsiz - growbuflen), state->fd) == NULL) { + break; + } + state->linelen = STRLEN(state->growbuf + growbuflen); + growbuflen += state->linelen; + if (state->growbuf[growbuflen - 1] == '\n' +#ifdef USE_CRNL + || state->growbuf[growbuflen - 1] == '\r' +#endif + ) { + break; + } + if (state->growbufsiz == LINE_MAXLEN) { + discard = true; + break; + } + + state->growbufsiz = (2 * state->growbufsiz < LINE_MAXLEN) + ? 2 * state->growbufsiz : LINE_MAXLEN; + state->growbuf = xrealloc(state->growbuf, state->growbufsiz); + } + + 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 + || STRLEN(IObuff) < IOSIZE - 1 + || IObuff[IOSIZE - 1] == '\n' +#ifdef USE_CRNL + || IObuff[IOSIZE - 1] == '\r' +#endif + ) { + break; + } + } + + state->linebuf = state->growbuf; + state->linelen = growbuflen; + } else { + state->linebuf = IObuff; + } + 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; + } + + if (state->linelen > 0 && state->linebuf[state->linelen - 1] == '\n') { + state->linebuf[state->linelen - 1] = NUL; + } +#ifdef USE_CRNL + if (state->linelen > 0 && state->linebuf[state->linelen - 1] == '\r') { + state->linebuf[state->linelen - 1] = NUL; + } +#endif + + remove_bom(state->linebuf); + + return QF_OK; +} + + +/// Parse a line and get the quickfix fields. +/// Return the QF_ status. +static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen, + efm_T *fmt_first, qffields_T *fields) +{ + efm_T *fmt_ptr; + static efm_T *fmt_start = NULL; // cached across calls + size_t len; + int i; + int idx = 0; + char_u *tail = NULL; + regmatch_T regmatch; + + + // 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) { + idx = fmt_ptr->prefix; + if (qi->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) { + continue; + } + fields->namebuf[0] = NUL; + fields->pattern[0] = NUL; + if (!qi->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; + int r = vim_regexec(®match, linebuf, (colnr_T)0); + fmt_ptr->prog = regmatch.regprog; + if (r) { + if ((idx == 'C' || idx == 'Z') && !qi->qf_multiline) { + continue; + } + if (vim_strchr((char_u *)"EWI", idx) != NULL) { + fields->type = (char_u)idx; + } else { + fields->type = 0; + } + // Extract error message data from matched line. + // We check for an actual submatch, because "\[" and "\]" in + // the 'errorformat' may cause the wrong submatch to be used. + if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f + if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { + continue; + } + // Expand ~/file and $HOME/file to full path. + char_u c = *regmatch.endp[i]; + *regmatch.endp[i] = NUL; + expand_env(regmatch.startp[i], fields->namebuf, CMDBUFFSIZE); + *regmatch.endp[i] = c; + + if (vim_strchr((char_u *)"OPQ", idx) != NULL + && !os_path_exists(fields->namebuf)) { + continue; + } + } + if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n + if (regmatch.startp[i] == NULL) { + continue; + } + fields->enr = (int)atol((char *)regmatch.startp[i]); + } + if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l + if (regmatch.startp[i] == NULL) { + continue; + } + fields->lnum = atol((char *)regmatch.startp[i]); + } + if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c + if (regmatch.startp[i] == NULL) { + continue; + } + fields->col = (int)atol((char *)regmatch.startp[i]); + } + if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t + if (regmatch.startp[i] == NULL) { + continue; + } + fields->type = *regmatch.startp[i]; + } + if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ + if (linelen > fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + STRLCPY(fields->errmsg, linebuf, linelen + 1); + } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m + if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { + continue; + } + len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); + if (len > fields->errmsglen) { + // len + null terminator + fields->errmsg = xrealloc(fields->errmsg, len + 1); + fields->errmsglen = len + 1; + } + STRLCPY(fields->errmsg, regmatch.startp[i], len + 1); + } + if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r + if (regmatch.startp[i] == NULL) { + continue; + } + tail = regmatch.startp[i]; + } + if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p + char_u *match_ptr; + + if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { + continue; + } + fields->col = 0; + for (match_ptr = regmatch.startp[i]; + match_ptr != regmatch.endp[i]; match_ptr++) { + fields->col++; + if (*match_ptr == TAB) { + fields->col += 7; + fields->col -= fields->col % 8; + } + } + fields->col++; + fields->use_viscol = true; + } + if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v + if (regmatch.startp[i] == NULL) { + continue; + } + fields->col = (int)atol((char *)regmatch.startp[i]); + fields->use_viscol = true; + } + if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s + if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { + continue; + } + len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); + if (len > CMDBUFFSIZE - 5) { + len = CMDBUFFSIZE - 5; + } + STRCPY(fields->pattern, "^\\V"); + xstrlcat((char *)fields->pattern, (char *)regmatch.startp[i], + CMDBUFFSIZE+1); + fields->pattern[len + 3] = '\\'; + fields->pattern[len + 4] = '$'; + fields->pattern[len + 5] = NUL; + } + break; + } + } + qi->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; + } + qi->qf_directory = qf_push_dir(fields->namebuf, &qi->qf_dir_stack, + false); + if (qi->qf_directory == NULL) { + return QF_FAIL; + } + } else if (idx == 'X') { // leave directory + qi->qf_directory = qf_pop_dir(&qi->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 + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + // copy whole line to error message + STRLCPY(fields->errmsg, linebuf, linelen + 1); + if (fmt_ptr == NULL) { + qi->qf_multiline = qi->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) { + qi->qf_multiline = true; // start of a multi-line message + qi->qf_multiignore = false; // reset continuation + } else if (vim_strchr((char_u *)"CZ", idx) + != NULL) { // continuation of multi-line msg + qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; + if (qfprev == NULL) { + return QF_FAIL; + } + if (*fields->errmsg && !qi->qf_multiignore) { + size_t len = STRLEN(qfprev->qf_text); + qfprev->qf_text = xrealloc(qfprev->qf_text, + len + STRLEN(fields->errmsg) + 2); + qfprev->qf_text[len] = '\n'; + STRCPY(qfprev->qf_text + len + 1, fields->errmsg); + } + if (qfprev->qf_nr == -1) { + qfprev->qf_nr = fields->enr; + } + if (vim_isprintc(fields->type) && !qfprev->qf_type) { + qfprev->qf_type = fields->type; // only printable chars allowed + } + if (!qfprev->qf_lnum) { + qfprev->qf_lnum = fields->lnum; + } + if (!qfprev->qf_col) { + qfprev->qf_col = fields->col; + } + qfprev->qf_viscol = fields->use_viscol; + if (!qfprev->qf_fnum) { + qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, + *fields->namebuf || qi->qf_directory + ? fields->namebuf + : qi->qf_currfile && fields->valid + ? qi->qf_currfile : 0); + } + if (idx == 'Z') { + qi->qf_multiline = qi->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 || os_path_exists(fields->namebuf)) { + if (*fields->namebuf && idx == 'P') { + qi->qf_currfile = qf_push_dir(fields->namebuf, &qi->qf_file_stack, + true); + } else if (idx == 'Q') { + qi->qf_currfile = qf_pop_dir(&qi->qf_file_stack); + } + *fields->namebuf = NUL; + if (tail && *tail) { + STRMOVE(IObuff, skipwhite(tail)); + qi->qf_multiscan = true; + goto restofline; + } + } + } + if (fmt_ptr->flags == '-') { // generally exclude this line + if (qi->qf_multiline) { + // also exclude continuation lines + qi->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". @@ -449,45 +966,22 @@ qf_init_ext( char_u *qf_title ) { - char_u *namebuf; - char_u *errmsg; - size_t errmsglen; - char_u *pattern; - char_u *growbuf = NULL; - size_t growbuflen; - size_t growbufsiz = 0; - char_u *linebuf = NULL; - size_t linelen = 0; - bool discard; - int col = 0; - bool use_viscol = false; - char_u type = 0; - linenr_T buflnum = lnumfirst; - long lnum = 0L; - int enr = 0; - FILE *fd = NULL; + qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, + NULL, 0, 0 }; + qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; static efm_T *fmt_first = NULL; - efm_T *fmt_ptr; - efm_T *fmt_start = NULL; char_u *efm; static char_u *last_efm = NULL; - size_t len; - int i; - int idx = 0; int retval = -1; // default: return error flag - char_u *tail = NULL; - char_u *p_buf = NULL; - char_u *p_str = NULL; - listitem_T *p_li = NULL; - regmatch_T regmatch; + int status; - namebuf = xmalloc(CMDBUFFSIZE + 1); - errmsglen = CMDBUFFSIZE + 1; - errmsg = xmalloc(errmsglen); - pattern = xmalloc(CMDBUFFSIZE + 1); + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); + fields.errmsglen = CMDBUFFSIZE + 1; + fields.errmsg = xmalloc(fields.errmsglen); + fields.pattern = xmalloc(CMDBUFFSIZE + 1); - if (efile != NULL && (fd = mch_fopen((char *)efile, "r")) == NULL) { + if (efile != NULL && (state.fd = mch_fopen((char *)efile, "r")) == NULL) { EMSG2(_(e_openerrf), efile); goto qf_init_end; } @@ -500,15 +994,12 @@ qf_init_ext( old_last = qi->qf_lists[qi->qf_curlist].qf_last; } - /* - * Each part of the format string is copied and modified from errorformat to - * regex prog. Only a few % characters are allowed. - */ - /* Use the local value of 'errorformat' if it's set. */ - if (errorformat == p_efm && tv == NULL && *buf->b_p_efm != NUL) + // 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 + } else { efm = errorformat; + } // If we are not adding or adding to another list: clear the state. if (newlist || qi->qf_curlist != qi->qf_dir_curlist) { @@ -547,424 +1038,57 @@ qf_init_ext( */ got_int = FALSE; - /* Always ignore case when looking for a matching error. */ - regmatch.rm_ic = TRUE; - if (tv != NULL) { - if (tv->v_type == VAR_STRING) - p_str = tv->vval.v_string; - else if (tv->v_type == VAR_LIST) - p_li = tv->vval.v_list->lv_first; + 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. */ - if (fd == NULL) { - if (tv != NULL) { - if (tv->v_type == VAR_STRING) { - /* Get the next line from the supplied string */ - char_u *p; - - if (*p_str == NUL) { // Reached the end of the string - break; - } - - p = vim_strchr(p_str, '\n'); - if (p != NULL) { - len = (size_t)(p - p_str) + 1; - } else { - len = STRLEN(p_str); - } - - if (len > IOSIZE - 2) { - linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen); - } else { - linebuf = IObuff; - linelen = len; - } - STRLCPY(linebuf, p_str, linelen + 1); - - // Increment using len in order to discard the rest of the line if it - // exceeds LINE_MAXLEN. - p_str += len; - } else if (tv->v_type == VAR_LIST) { - // Get the next line from the supplied list - 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 - break; - } - - len = STRLEN(p_li->li_tv.vval.v_string); - if (len > IOSIZE - 2) { - linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen); - } else { - linebuf = IObuff; - linelen = len; - } - - STRLCPY(linebuf, p_li->li_tv.vval.v_string, linelen + 1); - - p_li = p_li->li_next; /* next item */ - } - } else { - /* Get the next line from the supplied buffer */ - if (buflnum > lnumlast) - break; - p_buf = ml_get_buf(buf, buflnum++, false); - len = STRLEN(p_buf); - if (len > IOSIZE - 2) { - linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen); - } else { - linebuf = IObuff; - linelen = len; - } - STRLCPY(linebuf, p_buf, linelen + 1); - } - } else { - if (fgets((char *)IObuff, IOSIZE, fd) == NULL) { - break; - } - - discard = false; - linelen = STRLEN(IObuff); - if (linelen == IOSIZE - 1 && !(IObuff[linelen - 1] == '\n' -#ifdef USE_CRNL - || IObuff[linelen - 1] == '\r' -#endif - )) { // NOLINT(whitespace/parens) - // The current line exceeds IObuff, continue reading using growbuf - // until EOL or LINE_MAXLEN bytes is read. - if (growbuf == NULL) { - growbufsiz = 2 * (IOSIZE - 1); - growbuf = xmalloc(growbufsiz); - } - - // Copy the read part of the line, excluding null-terminator - memcpy(growbuf, IObuff, IOSIZE - 1); - growbuflen = linelen; - - for (;;) { - if (fgets((char *)growbuf + growbuflen, - (int)(growbufsiz - growbuflen), fd) == NULL) { - break; - } - linelen = STRLEN(growbuf + growbuflen); - growbuflen += linelen; - if (growbuf[growbuflen - 1] == '\n' -#ifdef USE_CRNL - || growbuf[growbuflen - 1] == '\r' -#endif - ) { - break; - } - if (growbufsiz == LINE_MAXLEN) { - discard = true; - break; - } - - growbufsiz = (2 * growbufsiz < LINE_MAXLEN) - ? 2 * growbufsiz : LINE_MAXLEN; - growbuf = xrealloc(growbuf, 2 * growbufsiz); - } - - 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, fd) == NULL - || STRLEN(IObuff) < IOSIZE - 1 - || IObuff[IOSIZE - 1] == '\n' -#ifdef USE_CRNL - || IObuff[IOSIZE - 1] == '\r' -#endif - ) { - break; - } - } - - linebuf = growbuf; - linelen = growbuflen; - } else { - linebuf = IObuff; - } - } - - if (linelen > 0 && linebuf[linelen - 1] == '\n') { - linebuf[linelen - 1] = NUL; - } -#ifdef USE_CRNL - if (linelen > 0 && linebuf[linelen - 1] == '\r') { - linebuf[linelen - 1] = NUL; - } -#endif - - remove_bom(linebuf); - - /* 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; + // Get the next line from a file/buffer/list/string + status = qf_get_nextline(&state); + if (status == QF_END_OF_INPUT) { // end of input + break; } - // Try to match each part of 'errorformat' until we find a complete - // match or no match. - bool valid = true; -restofline: - for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) { - idx = fmt_ptr->prefix; - if (qi->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) { - continue; - } - namebuf[0] = NUL; - pattern[0] = NUL; - if (!qi->qf_multiscan) { - errmsg[0] = NUL; - } - lnum = 0; - col = 0; - use_viscol = false; - enr = -1; - type = 0; - tail = NULL; - - regmatch.regprog = fmt_ptr->prog; - int r = vim_regexec(®match, linebuf, (colnr_T)0); - fmt_ptr->prog = regmatch.regprog; - if (r) { - if ((idx == 'C' || idx == 'Z') && !qi->qf_multiline) { - continue; - } - if (vim_strchr((char_u *)"EWI", idx) != NULL) { - type = (char_u)idx; - } else { - type = 0; - } - // Extract error message data from matched line. - // We check for an actual submatch, because "\[" and "\]" in - // the 'errorformat' may cause the wrong submatch to be used. - if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f - if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { - continue; - } - // Expand ~/file and $HOME/file to full path. - char_u c = *regmatch.endp[i]; - *regmatch.endp[i] = NUL; - expand_env(regmatch.startp[i], namebuf, CMDBUFFSIZE); - *regmatch.endp[i] = c; - - if (vim_strchr((char_u *)"OPQ", idx) != NULL - && !os_path_exists(namebuf)) { - continue; - } - } - if ((i = (int)fmt_ptr->addr[1]) > 0) { /* %n */ - if (regmatch.startp[i] == NULL) - continue; - enr = (int)atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[2]) > 0) { /* %l */ - if (regmatch.startp[i] == NULL) - continue; - lnum = atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[3]) > 0) { /* %c */ - if (regmatch.startp[i] == NULL) - continue; - col = (int)atol((char *)regmatch.startp[i]); - } - if ((i = (int)fmt_ptr->addr[4]) > 0) { /* %t */ - if (regmatch.startp[i] == NULL) - continue; - type = *regmatch.startp[i]; - } - if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ - if (linelen > errmsglen) { - // linelen + null terminator - errmsg = xrealloc(errmsg, linelen + 1); - errmsglen = linelen + 1; - } - STRLCPY(errmsg, linebuf, linelen + 1); - } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m - if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) { - continue; - } - len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > errmsglen) { - // len + null terminator - errmsg = xrealloc(errmsg, len + 1); - errmsglen = len + 1; - } - STRLCPY(errmsg, regmatch.startp[i], len + 1); - } - if ((i = (int)fmt_ptr->addr[6]) > 0) { /* %r */ - if (regmatch.startp[i] == NULL) - continue; - tail = regmatch.startp[i]; - } - if ((i = (int)fmt_ptr->addr[7]) > 0) { /* %p */ - char_u *match_ptr; - - if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) - continue; - col = 0; - for (match_ptr = regmatch.startp[i]; - match_ptr != regmatch.endp[i]; ++match_ptr) { - ++col; - if (*match_ptr == TAB) { - col += 7; - col -= col % 8; - } - } - col++; - use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[8]) > 0) { /* %v */ - if (regmatch.startp[i] == NULL) - continue; - col = (int)atol((char *)regmatch.startp[i]); - use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[9]) > 0) { /* %s */ - if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) - continue; - len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > CMDBUFFSIZE - 5) { - len = CMDBUFFSIZE - 5; - } - STRCPY(pattern, "^\\V"); - STRNCAT(pattern, regmatch.startp[i], len); - pattern[len + 3] = '\\'; - pattern[len + 4] = '$'; - pattern[len + 5] = NUL; - } - break; - } + status = qf_parse_line(qi, state.linebuf, state.linelen, fmt_first, + &fields); + if (status == QF_FAIL) { + goto error2; } - qi->qf_multiscan = false; - - if (fmt_ptr == NULL || idx == 'D' || idx == 'X') { - if (fmt_ptr != NULL) { - if (idx == 'D') { /* enter directory */ - if (*namebuf == NUL) { - EMSG(_("E379: Missing or empty directory name")); - goto error2; - } - qi->qf_directory = qf_push_dir(namebuf, &qi->qf_dir_stack, false); - if (qi->qf_directory == NULL) { - goto error2; - } - } else if (idx == 'X') { // leave directory - qi->qf_directory = qf_pop_dir(&qi->qf_dir_stack); - } - } - namebuf[0] = NUL; // no match found, remove file name - lnum = 0; // don't jump to this line - valid = false; - if (linelen > errmsglen) { - // linelen + null terminator - errmsg = xrealloc(errmsg, linelen + 1); - } - // copy whole line to error message - STRLCPY(errmsg, linebuf, linelen + 1); - if (fmt_ptr == NULL) { - qi->qf_multiline = qi->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) { - qi->qf_multiline = true; // start of a multi-line message - qi->qf_multiignore = false; // reset continuation - } else if (vim_strchr((char_u *)"CZ", idx) - != NULL) { // continuation of multi-line msg - qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; - if (qfprev == NULL) { - goto error2; - } - if (*errmsg && !qi->qf_multiignore) { - size_t len = STRLEN(qfprev->qf_text); - qfprev->qf_text = xrealloc(qfprev->qf_text, len + STRLEN(errmsg) + 2); - qfprev->qf_text[len] = '\n'; - STRCPY(qfprev->qf_text + len + 1, errmsg); - } - if (qfprev->qf_nr == -1) - qfprev->qf_nr = enr; - if (vim_isprintc(type) && !qfprev->qf_type) - qfprev->qf_type = type; /* only printable chars allowed */ - if (!qfprev->qf_lnum) - qfprev->qf_lnum = lnum; - if (!qfprev->qf_col) - qfprev->qf_col = col; - qfprev->qf_viscol = use_viscol; - if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, - *namebuf - || qi->qf_directory - ? namebuf : qi->qf_currfile - && valid ? qi->qf_currfile : 0); - } - if (idx == 'Z') { - qi->qf_multiline = qi->qf_multiignore = false; - } - line_breakcheck(); - continue; - } else if (vim_strchr((char_u *)"OPQ", idx) != NULL) { - // global file names - valid = false; - if (*namebuf == NUL || os_path_exists(namebuf)) { - if (*namebuf && idx == 'P') { - qi->qf_currfile = qf_push_dir(namebuf, &qi->qf_file_stack, true); - } else if (idx == 'Q') { - qi->qf_currfile = qf_pop_dir(&qi->qf_file_stack); - } - *namebuf = NUL; - if (tail && *tail) { - STRMOVE(IObuff, skipwhite(tail)); - qi->qf_multiscan = true; - goto restofline; - } - } - } - if (fmt_ptr->flags == '-') { // generally exclude this line - if (qi->qf_multiline) { - // also exclude continuation lines - qi->qf_multiignore = true; - } - continue; - } + if (status == QF_IGNORE_LINE) { + continue; } if (qf_add_entry(qi, qi->qf_directory, - (*namebuf || qi->qf_directory) - ? namebuf : ((qi->qf_currfile && valid) - ? qi->qf_currfile : (char_u *)NULL), + (*fields.namebuf || qi->qf_directory) + ? fields.namebuf : ((qi->qf_currfile && fields.valid) + ? qi->qf_currfile : (char_u *)NULL), 0, - errmsg, - lnum, - col, - use_viscol, - pattern, - enr, - type, - valid) == FAIL) { + fields.errmsg, + fields.lnum, + fields.col, + fields.use_viscol, + fields.pattern, + fields.enr, + fields.type, + fields.valid) == FAIL) { goto error2; } line_breakcheck(); } - if (fd == NULL || !ferror(fd)) { + if (state.fd == NULL || !ferror(state.fd)) { if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { /* no valid entry found */ qi->qf_lists[qi->qf_curlist].qf_ptr = @@ -989,13 +1113,13 @@ error2: qi->qf_curlist--; } qf_init_end: - if (fd != NULL) { - fclose(fd); + if (state.fd != NULL) { + fclose(state.fd); } - xfree(namebuf); - xfree(errmsg); - xfree(pattern); - xfree(growbuf); + xfree(fields.namebuf); + xfree(fields.errmsg); + xfree(fields.pattern); + xfree(state.growbuf); qf_update_buffer(qi, old_last); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 0d6e5e1830..02f8da8b23 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -904,6 +904,30 @@ function! Test_efm2() call assert_equal('E', l[0].type) call assert_equal("\nunknown variable 'i'", l[0].text) + " Test for %A, %C and other formats + let lines = [ + \"==============================================================", + \"FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)", + \"--------------------------------------------------------------", + \"Traceback (most recent call last):", + \' File "unittests/dbfacadeTest.py", line 89, in testFoo', + \" self.assertEquals(34, dtid)", + \' File "/usr/lib/python2.2/unittest.py", line 286, in', + \" failUnlessEqual", + \" raise self.failureException, \\", + \"AssertionError: 34 != 33", + \"", + \"--------------------------------------------------------------", + \"Ran 27 tests in 0.063s" + \] + set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m + cgetexpr lines + let l = getqflist() + call assert_equal(8, len(l)) + call assert_equal(89, l[4].lnum) + call assert_equal(1, l[4].valid) + call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + let &efm = save_efm endfunction diff --git a/src/nvim/version.c b/src/nvim/version.c index 0374e02e02..fe72ee11f2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -379,7 +379,7 @@ static int included_patches[] = { 2064, // 2063 NA 2062, - // 2061, + 2061, // 2060 NA // 2059 NA // 2058, @@ -393,8 +393,8 @@ static int included_patches[] = { 2050, 2049, // 2048 NA - // 2047, - // 2046, + 2047, + 2046, // 2045 NA 2044, 2043, |