vim-patch:9.0.0736: quickfix listing does not handle very long messages

Problem:    Quickfix listing does not handle very long messages.
Solution:   Use a growarray instead of a fixed size buffer. (Yegappan
            Lakshmanan, closes vim/vim#11357)

f8412c9d7c

Override Test_very_long_error_line() with a rewrite that doesn't use
deferred delete and string interpolation.

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq 2023-03-05 14:47:51 +08:00
parent ed05d38d9f
commit 1cfe83c2a2
2 changed files with 130 additions and 77 deletions

View File

@ -2807,13 +2807,15 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
update_screen(); update_screen();
} }
} }
snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, vim_snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
qf_get_curlist(qi)->qf_count, qf_get_curlist(qi)->qf_count,
qf_ptr->qf_cleared ? _(" (line deleted)") : "", qf_ptr->qf_cleared ? _(" (line deleted)") : "",
qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
// Add the message, skipping leading whitespace and newlines. // Add the message, skipping leading whitespace and newlines.
int len = (int)strlen(IObuff); garray_T ga;
qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); ga_init(&ga, 1, 256);
ga_concat(&ga, IObuff);
qf_fmt_text(&ga, skipwhite(qf_ptr->qf_text));
// Output the message. Overwrite to avoid scrolling when the 'O' // Output the message. Overwrite to avoid scrolling when the 'O'
// flag is present in 'shortmess'; But when not jumping, print the // flag is present in 'shortmess'; But when not jumping, print the
@ -2825,8 +2827,9 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
msg_scroll = false; msg_scroll = false;
} }
msg_ext_set_kind("quickfix"); msg_ext_set_kind("quickfix");
msg_attr_keep(IObuff, 0, true, false); msg_attr_keep(ga.ga_data, 0, true, false);
msg_scroll = (int)i; msg_scroll = (int)i;
ga_clear(&ga);
} }
/// Find a usable window for opening a file from the quickfix/location list. If /// Find a usable window for opening a file from the quickfix/location list. If
@ -3086,41 +3089,32 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
if (qfp->qf_lnum != 0) { if (qfp->qf_lnum != 0) {
msg_puts_attr(":", qfSepAttr); msg_puts_attr(":", qfSepAttr);
} }
garray_T ga;
ga_init(&ga, 1, 256);
if (qfp->qf_lnum == 0) { if (qfp->qf_lnum == 0) {
IObuff[0] = NUL; ga_append(&ga, NUL);
} else { } else {
qf_range_text(qfp, IObuff, IOSIZE); qf_range_text(&ga, qfp);
} }
vim_snprintf(IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr)); ga_concat(&ga, qf_types(qfp->qf_type, qfp->qf_nr));
msg_puts_attr((const char *)IObuff, qfLineAttr); ga_append(&ga, NUL);
msg_puts_attr(ga.ga_data, qfLineAttr);
ga_clear(&ga);
msg_puts_attr(":", qfSepAttr); msg_puts_attr(":", qfSepAttr);
if (qfp->qf_pattern != NULL) { if (qfp->qf_pattern != NULL) {
qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); qf_fmt_text(&ga, qfp->qf_pattern);
msg_puts((const char *)IObuff); msg_puts(ga.ga_data);
ga_clear(&ga);
msg_puts_attr(":", qfSepAttr); msg_puts_attr(":", qfSepAttr);
} }
msg_puts(" "); msg_puts(" ");
char *tbuf = IObuff;
size_t tbuflen = IOSIZE;
size_t len = strlen(qfp->qf_text) + 3;
if (len > IOSIZE) {
tbuf = xmalloc(len);
tbuflen = len;
}
// Remove newlines and leading whitespace from the text. For an // Remove newlines and leading whitespace from the text. For an
// unrecognized line keep the indent, the compiler may mark a word // unrecognized line keep the indent, the compiler may mark a word
// with ^^^^. // with ^^^^.
qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) qf_fmt_text(&ga, (fname != NULL || qfp->qf_lnum != 0) ? skipwhite(qfp->qf_text) : qfp->qf_text);
? skipwhite(qfp->qf_text) : qfp->qf_text, msg_prt_line(ga.ga_data, false);
tbuf, (int)tbuflen); ga_clear(&ga);
msg_prt_line(tbuf, false);
if (tbuf != IObuff) {
xfree(tbuf);
}
} }
// ":clist": list all errors // ":clist": list all errors
@ -3197,49 +3191,54 @@ void qf_list(exarg_T *eap)
} }
} }
// Remove newlines and leading whitespace from an error message. /// Remove newlines and leading whitespace from an error message.
// Put the result in "buf[bufsize]". /// Add the result to the grow array "gap".
static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsize) static void qf_fmt_text(garray_T *gap, const char *restrict text)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
int i;
const char *p = (char *)text; const char *p = (char *)text;
for (i = 0; *p != NUL && i < bufsize - 1; i++) { while (*p != NUL) {
if (*p == '\n') { if (*p == '\n') {
buf[i] = ' '; ga_append(gap, ' ');
while (*++p != NUL) { while (*++p != NUL) {
if (!ascii_iswhite(*p) && *p != '\n') { if (!ascii_iswhite(*p) && *p != '\n') {
break; break;
} }
} }
} else { } else {
buf[i] = *p++; ga_append(gap, (uint8_t)(*p++));
} }
} }
buf[i] = NUL;
ga_append(gap, NUL);
} }
// Range information from lnum, col, end_lnum, and end_col. /// Add the range information from the lnum, col, end_lnum, and end_col values
// Put the result in "buf[bufsize]". /// of a quickfix entry to the grow array "gap".
static void qf_range_text(const qfline_T *qfp, char *buf, int bufsize) static void qf_range_text(garray_T *gap, const qfline_T *qfp)
{ {
vim_snprintf(buf, (size_t)bufsize, "%" PRIdLINENR, qfp->qf_lnum); char *const buf = IObuff;
int len = (int)strlen(buf); const size_t bufsize = IOSIZE;
vim_snprintf(buf, bufsize, "%" PRIdLINENR, qfp->qf_lnum);
size_t len = strlen(buf);
if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) { if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) {
vim_snprintf(buf + len, (size_t)(bufsize - len), "-%" PRIdLINENR, qfp->qf_end_lnum); vim_snprintf(buf + len, bufsize - len, "-%" PRIdLINENR, qfp->qf_end_lnum);
len += (int)strlen(buf + len); len += strlen(buf + len);
} }
if (qfp->qf_col > 0) { if (qfp->qf_col > 0) {
vim_snprintf(buf + len, (size_t)(bufsize - len), " col %d", qfp->qf_col); vim_snprintf(buf + len, bufsize - len, " col %d", qfp->qf_col);
len += (int)strlen(buf + len); len += strlen(buf + len);
if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) { if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) {
vim_snprintf(buf + len, (size_t)(bufsize - len), "-%d", qfp->qf_end_col); vim_snprintf(buf + len, bufsize - len, "-%d", qfp->qf_end_col);
len += (int)strlen(buf + len); len += strlen(buf + len);
} }
} }
buf[len] = NUL; buf[len] = NUL;
ga_concat_len(gap, buf, len);
} }
/// Display information (list number, list size and the title) about a /// Display information (list number, list size and the title) about a
@ -3945,21 +3944,22 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
char *dirname, char *qftf_str, bool first_bufline) char *dirname, char *qftf_str, bool first_bufline)
FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5) FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5)
{ {
garray_T ga;
ga_init(&ga, 1, 256);
// If the 'quickfixtextfunc' function returned a non-empty custom string // If the 'quickfixtextfunc' function returned a non-empty custom string
// for this entry, then use it. // for this entry, then use it.
if (qftf_str != NULL && *qftf_str != NUL) { if (qftf_str != NULL && *qftf_str != NUL) {
xstrlcpy(IObuff, qftf_str, IOSIZE); ga_concat(&ga, qftf_str);
} else { } else {
buf_T *errbuf; buf_T *errbuf;
int len;
if (qfp->qf_module != NULL) { if (qfp->qf_module != NULL) {
xstrlcpy(IObuff, qfp->qf_module, IOSIZE); ga_concat(&ga, qfp->qf_module);
len = (int)strlen(IObuff);
} else if (qfp->qf_fnum != 0 } else if (qfp->qf_fnum != 0
&& (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
&& errbuf->b_fname != NULL) { && errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep if (qfp->qf_type == 1) { // :helpgrep
xstrlcpy(IObuff, path_tail(errbuf->b_fname), IOSIZE); ga_concat(&ga, path_tail(errbuf->b_fname));
} else { } else {
// Shorten the file name if not done already. // Shorten the file name if not done already.
// For optimization, do this only for the first entry in a // For optimization, do this only for the first entry in a
@ -3972,42 +3972,35 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
} }
shorten_buf_fname(errbuf, dirname, false); shorten_buf_fname(errbuf, dirname, false);
} }
xstrlcpy(IObuff, errbuf->b_fname, IOSIZE); ga_concat(&ga, errbuf->b_fname);
} }
len = (int)strlen(IObuff);
} else {
len = 0;
} }
if (len < IOSIZE - 1) {
IObuff[len++] = '|';
}
if (qfp->qf_lnum > 0) {
qf_range_text(qfp, IObuff + len, IOSIZE - len);
len += (int)strlen(IObuff + len);
snprintf(IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type, ga_append(&ga, '|');
qfp->qf_nr));
len += (int)strlen(IObuff + len); if (qfp->qf_lnum > 0) {
qf_range_text(&ga, qfp);
ga_concat(&ga, qf_types(qfp->qf_type, qfp->qf_nr));
} else if (qfp->qf_pattern != NULL) { } else if (qfp->qf_pattern != NULL) {
qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); qf_fmt_text(&ga, qfp->qf_pattern);
len += (int)strlen(IObuff + len);
}
if (len < IOSIZE - 2) {
IObuff[len++] = '|';
IObuff[len++] = ' ';
} }
ga_append(&ga, '|');
ga_append(&ga, ' ');
// Remove newlines and leading whitespace from the text. // Remove newlines and leading whitespace from the text.
// For an unrecognized line keep the indent, the compiler may // For an unrecognized line keep the indent, the compiler may
// mark a word with ^^^^. // mark a word with ^^^^.
qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, qf_fmt_text(&ga, ga.ga_len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text);
IObuff + len, IOSIZE - len);
} }
if (ml_append_buf(buf, lnum, IObuff, ga_append(&ga, NUL);
(colnr_T)strlen(IObuff) + 1, false) == FAIL) {
if (ml_append_buf(buf, lnum, ga.ga_data, ga.ga_len + 1, false) == FAIL) {
return FAIL; return FAIL;
} }
ga_clear(&ga);
return OK; return OK;
} }

View File

@ -6220,6 +6220,66 @@ func Test_loclist_replace_autocmd()
call setloclist(0, [], 'f') call setloclist(0, [], 'f')
endfunc endfunc
" Test for a very long error line and a very long information line
func Test_very_long_error_line()
let msg = repeat('abcdefghijklmn', 146)
let emsg = 'Xlonglines.c:1:' . msg
call writefile([msg, emsg], 'Xerror', 'D')
cfile Xerror
cwindow
call assert_equal($'|| {msg}', getline(1))
call assert_equal($'Xlonglines.c|1| {msg}', getline(2))
cclose
let l = execute('clist!')->split("\n")
call assert_equal([$' 1: {msg}', $' 2 Xlonglines.c:1: {msg}'], l)
let l = execute('cc')->split("\n")
call assert_equal([$'(2 of 2): {msg}'], l)
call setqflist([], 'f')
endfunc
" The test depends on deferred delete and string interpolation, which haven't
" been ported, so override it with a rewrite that doesn't use these features.
func! Test_very_long_error_line()
let msg = repeat('abcdefghijklmn', 146)
let emsg = 'Xlonglines.c:1:' . msg
call writefile([msg, emsg], 'Xerror')
cfile Xerror
call delete('Xerror')
cwindow
call assert_equal('|| ' .. msg, getline(1))
call assert_equal('Xlonglines.c|1| ' .. msg, getline(2))
cclose
let l = execute('clist!')->split("\n")
call assert_equal([' 1: ' .. msg, ' 2 Xlonglines.c:1: ' .. msg], l)
let l = execute('cc')->split("\n")
call assert_equal(['(2 of 2): ' .. msg], l)
call setqflist([], 'f')
endfunc
" In the quickfix window, spaces at the beginning of an informational line
" should not be removed but should be removed from an error line.
func Test_info_line_with_space()
cexpr ["a.c:20:12: error: expected ';' before ':' token",
\ ' 20 | Afunc():', '', ' | ^']
copen
call assert_equal(["a.c|20 col 12| error: expected ';' before ':' token",
\ '|| 20 | Afunc():', '|| ',
\ '|| | ^'], getline(1, '$'))
cclose
let l = execute('clist!')->split("\n")
call assert_equal([" 1 a.c:20 col 12: error: expected ';' before ':' token",
\ ' 2: 20 | Afunc():', ' 3: ', ' 4: | ^'], l)
call setqflist([], 'f')
endfunc
func s:QfTf(_) func s:QfTf(_)
endfunc endfunc