diff options
-rw-r--r-- | runtime/doc/news.txt | 1 | ||||
-rw-r--r-- | runtime/doc/options.txt | 22 | ||||
-rw-r--r-- | runtime/doc/quickfix.txt | 16 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 28 | ||||
-rw-r--r-- | runtime/optwin.vim | 9 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/main.c | 3 | ||||
-rw-r--r-- | src/nvim/option.c | 32 | ||||
-rw-r--r-- | src/nvim/option_vars.h | 1 | ||||
-rw-r--r-- | src/nvim/options.lua | 37 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 282 | ||||
-rw-r--r-- | test/old/testdir/gen_opt_test.vim | 2 | ||||
-rw-r--r-- | test/old/testdir/test_quickfix.vim | 181 |
14 files changed, 554 insertions, 64 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index df33c2b6b8..5b614c1b47 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -78,6 +78,7 @@ LUA OPTIONS +• 'chistory' and 'lhistory' set size of the |quickfix-stack|. • 'diffopt' `inline:` configures diff highlighting for changes within a line. • 'pummaxwidth' sets maximum width for the completion popup menu. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 18261a3c51..7e20ef43d7 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1326,6 +1326,17 @@ A jump table for the options with a short description can be found at |Q_op|. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. + *'chistory'* *'chi'* +'chistory' 'chi' number (default 10) + global + Number of quickfix lists that should be remembered for the quickfix + stack. Must be between 1 and 100. If the option is set to a value + that is lower than the amount of entries in the quickfix list stack, + entries will be removed starting from the oldest one. If the current + quickfix list was removed, then the quickfix list at top of the stack + (the most recently created) will be used in its place. For additional + info, see |quickfix-stack|. + *'cindent'* *'cin'* *'nocindent'* *'nocin'* 'cindent' 'cin' boolean (default off) local to buffer @@ -3780,6 +3791,17 @@ A jump table for the options with a short description can be found at |Q_op|. temporarily when performing an operation where redrawing may cause flickering or cause a slowdown. + *'lhistory'* *'lhi'* +'lhistory' 'lhi' number (default 10) + local to window + Like 'chistory', but for the location list stack associated with the + current window. If the option is changed in either the location list + window itself or the the window that is associated with the location + list stack, the new value will also be applied to the other one. This + means this value will always be the same for a given location list + window and its corresponding window. See |quickfix-stack| for + additional info. + *'linebreak'* *'lbr'* *'nolinebreak'* *'nolbr'* 'linebreak' 'lbr' boolean (default off) local to window diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index c2a8c15b10..2702feb70f 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -31,12 +31,12 @@ From inside Vim an easy way to run a command and handle the output is with the The 'errorformat' option should be set to match the error messages from your compiler (see |errorformat| below). - *quickfix-ID* + *quickfix-stack* *quickfix-ID* Each quickfix list has a unique identifier called the quickfix ID and this number will not change within a Vim session. The |getqflist()| function can be used to get the identifier assigned to a list. There is also a quickfix list -number which may change whenever more than ten lists are added to a quickfix -stack. +number which may change whenever more than 'chistory' lists are added to a +quickfix stack. *location-list* *E776* A location list is a window-local quickfix list. You get one after commands @@ -848,10 +848,12 @@ using these functions are below: ============================================================================= 3. Using more than one list of errors *quickfix-error-lists* -So far has been assumed that there is only one list of errors. Actually the -ten last used lists are remembered. When starting a new list, the previous -ones are automatically kept. Two commands can be used to access older error -lists. They set one of the existing error lists as the current one. +So far it has been assumed that there is only one list of errors. Actually +there can be multiple used lists that are remembered; see 'chistory' and +'lhistory'. +When starting a new list, the previous ones are automatically kept. Two +commands can be used to access older error lists. They set one of the +existing error lists as the current one. *:colder* *:col* *E380* :col[der] [count] Go to older error list. When [count] is given, do diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 163f74da22..8a7cf499c6 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -651,6 +651,7 @@ Short explanation of each option: *option-list* 'cdpath' 'cd' list of directories searched with ":cd" 'cedit' key used to open the command-line window 'charconvert' 'ccv' expression for character encoding conversion +'chistory' 'chi' maximum number of quickfix lists in history 'cindent' 'cin' do C program indenting 'cinkeys' 'cink' keys that trigger indent when 'cindent' is set 'cinoptions' 'cino' how to do indenting when 'cindent' is set @@ -768,6 +769,7 @@ Short explanation of each option: *option-list* 'langremap' 'lrm' do apply 'langmap' to mapped characters 'laststatus' 'ls' tells when last window has status lines 'lazyredraw' 'lz' don't redraw while executing macros +'lhistory' 'lhi' maximum number of location lists in history 'linebreak' 'lbr' wrap long lines at a blank 'lines' number of lines in the display 'linespace' 'lsp' number of pixel lines to use between characters diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index b681321d75..54c8c0c6b3 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -799,6 +799,20 @@ vim.o.ccv = vim.o.charconvert vim.go.charconvert = vim.o.charconvert vim.go.ccv = vim.go.charconvert +--- Number of quickfix lists that should be remembered for the quickfix +--- stack. Must be between 1 and 100. If the option is set to a value +--- that is lower than the amount of entries in the quickfix list stack, +--- entries will be removed starting from the oldest one. If the current +--- quickfix list was removed, then the quickfix list at top of the stack +--- (the most recently created) will be used in its place. For additional +--- info, see `quickfix-stack`. +--- +--- @type integer +vim.o.chistory = 10 +vim.o.chi = vim.o.chistory +vim.go.chistory = vim.o.chistory +vim.go.chi = vim.go.chistory + --- Enables automatic C program indenting. See 'cinkeys' to set the keys --- that trigger reindenting in insert mode and 'cinoptions' to set your --- preferred indent style. @@ -3730,6 +3744,20 @@ vim.o.lz = vim.o.lazyredraw vim.go.lazyredraw = vim.o.lazyredraw vim.go.lz = vim.go.lazyredraw +--- Like 'chistory', but for the location list stack associated with the +--- current window. If the option is changed in either the location list +--- window itself or the the window that is associated with the location +--- list stack, the new value will also be applied to the other one. This +--- means this value will always be the same for a given location list +--- window and its corresponding window. See `quickfix-stack` for +--- additional info. +--- +--- @type integer +vim.o.lhistory = 10 +vim.o.lhi = vim.o.lhistory +vim.wo.lhistory = vim.o.lhistory +vim.wo.lhi = vim.wo.lhistory + --- If on, Vim will wrap long lines at a character in 'breakat' rather --- than at the last character that fits on the screen. Unlike --- 'wrapmargin' and 'textwidth', this does not insert <EOL>s in the file, diff --git a/runtime/optwin.vim b/runtime/optwin.vim index ce4590f01b..79715460ac 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2025 Mar 28 +" Last Change: 2025 Apr 06 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " If there already is an option window, jump to that one. @@ -367,6 +367,13 @@ if has("linebreak") call append("$", "\t" .. s:local_to_window) call <SID>OptionL("nuw") endif +if has("quickfix") + call <SID>AddOption("chistory", gettext("maximum number of quickfix lists that can be stored in history")) + call <SID>OptionL("chi") + call <SID>AddOption("lhistory", gettext("maximum number of location lists that can be stored in history")) + call append("$", "\t" .. s:local_to_window) + call <SID>OptionL("lhi") +endif if has("conceal") call <SID>AddOption("conceallevel", gettext("controls whether concealable text is hidden")) call append("$", "\t" .. s:local_to_window) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f226ce9c42..05d1829a31 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -149,6 +149,8 @@ typedef struct { #define w_p_wfw w_onebuf_opt.wo_wfw // 'winfixwidth' int wo_pvw; #define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' + OptInt wo_lhi; +#define w_p_lhi w_onebuf_opt.wo_lhi // 'lhistory' int wo_rl; #define w_p_rl w_onebuf_opt.wo_rl // 'rightleft' char *wo_rlc; diff --git a/src/nvim/main.c b/src/nvim/main.c index 50677607eb..bc960f239a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -238,6 +238,9 @@ void early_init(mparm_T *paramp) TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype + + // initialize quickfix list + qf_init_stack(); } #ifdef MAKE_LIB diff --git a/src/nvim/option.c b/src/nvim/option.c index 3d4c2e65f9..4bc05ccea3 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -126,6 +126,10 @@ static const char e_number_required_after_equal[] = N_("E521: Number required after ="); static const char e_preview_window_already_exists[] = N_("E590: A preview window already exists"); +static const char e_cannot_have_negative_or_zero_number_of_quickfix[] + = N_("E1542: Cannot have a negative or zero number of quickfix/location lists"); +static const char e_cannot_have_more_than_hundred_quickfix[] + = N_("E1543: Cannot have more than a hundred quickfix/location lists"); static char *p_term = NULL; static char *p_ttytype = NULL; @@ -2701,6 +2705,23 @@ static const char *did_set_wrap(optset_T *args) return NULL; } +/// Process the new 'chistory' or 'lhistory' option value. 'chistory' will +/// be used if args->os_varp is the same as p_chi, else 'lhistory'. +static const char *did_set_xhistory(optset_T *args) +{ + win_T *win = (win_T *)args->os_win; + bool is_p_chi = (OptInt *)args->os_varp == &p_chi; + OptInt *arg = is_p_chi ? &p_chi : (OptInt *)args->os_varp; + + if (is_p_chi) { + qf_resize_stack((int)(*arg)); + } else { + ll_resize_stack(win, (int)(*arg)); + } + + return NULL; +} + // When 'syntax' is set, load the syntax of that name static void do_syntax_autocmd(buf_T *buf, bool value_changed) { @@ -2942,6 +2963,14 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e return e_invarg; } break; + case kOptChistory: + case kOptLhistory: + if (value < 1) { + return e_cannot_have_negative_or_zero_number_of_quickfix; + } else if (value > 100) { + return e_cannot_have_more_than_hundred_quickfix; + } + break; default: break; } @@ -4604,6 +4633,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return &(win->w_p_wfw); case kOptPreviewwindow: return &(win->w_p_pvw); + case kOptLhistory: + return &(win->w_p_lhi); case kOptRightleft: return &(win->w_p_rl); case kOptRightleftcmd: @@ -4883,6 +4914,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_fdt = copy_option_val(from->wo_fdt); to->wo_fmr = copy_option_val(from->wo_fmr); to->wo_scl = copy_option_val(from->wo_scl); + to->wo_lhi = from->wo_lhi; to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; to->wo_stc = copy_option_val(from->wo_stc); diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index e624ab80ef..d8d3e1e124 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -444,6 +444,7 @@ EXTERN OptInt p_rdt; ///< 'redrawtime' EXTERN OptInt p_re; ///< 'regexpengine' EXTERN OptInt p_report; ///< 'report' EXTERN OptInt p_pvh; ///< 'previewheight' +EXTERN OptInt p_chi; ///< 'chistory' EXTERN int p_ari; ///< 'allowrevins' EXTERN int p_ri; ///< 'revins' EXTERN int p_ru; ///< 'ruler' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index cfd92137d2..e9fa18c98f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1123,6 +1123,25 @@ local options = { varname = 'p_ccv', }, { + abbreviation = 'chi', + cb = 'did_set_xhistory', + defaults = 10, + desc = [=[ + Number of quickfix lists that should be remembered for the quickfix + stack. Must be between 1 and 100. If the option is set to a value + that is lower than the amount of entries in the quickfix list stack, + entries will be removed starting from the oldest one. If the current + quickfix list was removed, then the quickfix list at top of the stack + (the most recently created) will be used in its place. For additional + info, see |quickfix-stack|. + ]=], + full_name = 'chistory', + scope = { 'global' }, + short_desc = N_('number of quickfix lists stored in history'), + type = 'number', + varname = 'p_chi', + }, + { abbreviation = 'cin', defaults = false, desc = [=[ @@ -5023,6 +5042,24 @@ local options = { varname = 'p_lz', }, { + abbreviation = 'lhi', + cb = 'did_set_xhistory', + defaults = 10, + desc = [=[ + Like 'chistory', but for the location list stack associated with the + current window. If the option is changed in either the location list + window itself or the the window that is associated with the location + list stack, the new value will also be applied to the other one. This + means this value will always be the same for a given location list + window and its corresponding window. See |quickfix-stack| for + additional info. + ]=], + full_name = 'lhistory', + scope = { 'win' }, + short_desc = N_('number of location lists stored in history'), + type = 'number', + }, + { abbreviation = 'lbr', defaults = false, desc = [=[ diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 5f197bc84f..0e6b7b9ce4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -101,7 +101,6 @@ struct qfline_S { }; // There is a stack of error lists. -#define LISTCOUNT 10 #define INVALID_QFIDX (-1) #define INVALID_QFBUFNR (0) @@ -153,12 +152,14 @@ struct qf_info_S { int qf_refcount; int qf_listcount; // current number of lists int qf_curlist; // current error list - qf_list_T qf_lists[LISTCOUNT]; + int qf_maxcount; // maximum number of lists + qf_list_T *qf_lists; qfltype_T qfl_type; // type of list int qf_bufnr; // quickfix window buffer number }; -static qf_info_T ql_info; // global quickfix list +static qf_info_T ql_info_actual; // global quickfix list +static qf_info_T *ql_info; // points to ql_info_actual after allocation static unsigned last_qf_id = 0; // Last Used quickfix list id #define FMT_PATTERNS 14 // maximum number of % recognized @@ -382,7 +383,8 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T int qf_init(win_T *wp, const char *restrict efile, char *restrict errorformat, int newlist, const char *restrict qf_title, char *restrict enc) { - qf_info_T *qi = wp == NULL ? &ql_info : ll_get_or_alloc_list(wp); + qf_info_T *qi = wp == NULL ? ql_info : ll_get_or_alloc_list(wp); + assert(qi != NULL); return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat, newlist, 0, 0, qf_title, enc); @@ -1260,6 +1262,31 @@ static qf_list_T *qf_get_curlist(qf_info_T *qi) return qf_get_list(qi, qi->qf_curlist); } +/// Pop a quickfix list from the quickfix/location list stack +/// Automatically adjust qf_curlist so that it stays pointed +/// to the same list, unless it is deleted, if so then use the +/// newest created list instead. qf_listcount will be set correctly. +/// The above will only happen if <adjust> is true. +static void qf_pop_stack(qf_info_T *qi, bool adjust) +{ + qf_free(&qi->qf_lists[0]); + for (int i = 1; i < qi->qf_listcount; i++) { + qi->qf_lists[i - 1] = qi->qf_lists[i]; + } + + // fill with zeroes now unused list at the top + memset(qi->qf_lists + qi->qf_listcount - 1, 0, sizeof(*qi->qf_lists)); + + if (adjust) { + qi->qf_listcount--; + if (qi->qf_curlist == 0) { + qi->qf_curlist = qi->qf_listcount - 1; + } else { + qi->qf_curlist--; + } + } +} + /// Prepare for adding a new quickfix list. If the current list is in the /// middle of the stack, then all the following lists are freed and then /// the new list is added. @@ -1274,15 +1301,13 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title) // When the stack is full, remove to oldest entry // Otherwise, add a new entry. - if (qi->qf_listcount == LISTCOUNT) { - qf_free(&qi->qf_lists[0]); - for (int i = 1; i < LISTCOUNT; i++) { - qi->qf_lists[i - 1] = qi->qf_lists[i]; - } - qi->qf_curlist = LISTCOUNT - 1; + if (qi->qf_listcount == qi->qf_maxcount) { + qf_pop_stack(qi, false); + qi->qf_curlist = qi->qf_listcount - 1; // point to new empty list } else { qi->qf_curlist = qi->qf_listcount++; } + qf_list_T *qfl = qf_get_curlist(qi); CLEAR_POINTER(qfl); qf_store_title(qfl, qf_title); @@ -1731,7 +1756,8 @@ static void locstack_queue_delreq(qf_info_T *qi) /// Return the global quickfix stack window buffer number. int qf_stack_get_bufnr(void) { - return ql_info.qf_bufnr; + assert(ql_info != NULL); + return ql_info->qf_bufnr; } /// Wipe the quickfix window buffer (if present) for the specified @@ -1745,13 +1771,44 @@ static void wipe_qf_buffer(qf_info_T *qi) buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); if (qfbuf != NULL && qfbuf->b_nwindows == 0) { + bool buf_was_null = false; + // can happen when curwin is going to be closed e.g. curwin->w_buffer + // was already closed in win_close(), and we are now closing the + // window related location list buffer from win_free_mem() + // but close_buffer() calls CHECK_CURBUF() macro and requires + // curwin->w_buffer == curbuf + if (curwin->w_buffer == NULL) { + curwin->w_buffer = curbuf; + buf_was_null = true; + } + // If the quickfix buffer is not loaded in any window, then // wipe the buffer. close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); qi->qf_bufnr = INVALID_QFBUFNR; + if (buf_was_null) { + curwin->w_buffer = NULL; + } + } +} + +/// Free all lists in the stack (not including the stack) +static void qf_free_list_stack_items(qf_info_T *qi) +{ + for (int i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); } } +/// Free a qf_info_T struct completely +static void qf_free_lists(qf_info_T *qi) +{ + qf_free_list_stack_items(qi); + + xfree(qi->qf_lists); + xfree(qi); +} + /// Free a location list stack static void ll_free_all(qf_info_T **pqi) { @@ -1774,26 +1831,21 @@ static void ll_free_all(qf_info_T **pqi) // If the quickfix window buffer is loaded, then wipe it wipe_qf_buffer(qi); - for (int i = 0; i < qi->qf_listcount; i++) { - qf_free(qf_get_list(qi, i)); - } - xfree(qi); + qf_free_lists(qi); } } /// Free all the quickfix/location lists in the stack. void qf_free_all(win_T *wp) { + 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 - qf_info_T *qi = &ql_info; - for (int i = 0; i < qi->qf_listcount; i++) { - qf_free(qf_get_list(qi, i)); - } + } else if (qi != NULL) { + qf_free_list_stack_items(qi); // quickfix list } } @@ -1949,18 +2001,119 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in return QF_OK; } -/// Allocate a new quickfix/location list stack -static qf_info_T *qf_alloc_stack(qfltype_T qfltype) +/// Resize quickfix stack to be able to hold n amount of lists. +void qf_resize_stack(int n) +{ + assert(ql_info != NULL); + qf_resize_stack_base(ql_info, n); +} + +/// Resize location list stack for window 'wp' to be able to hold n amount of lists. +void ll_resize_stack(win_T *wp, int n) +{ + // check if current window is a location list window; + // if so then sync its 'lhistory' to the parent window or vice versa + if (IS_LL_WINDOW(curwin)) { + qf_sync_llw_to_win(wp); + } else { + qf_sync_win_to_llw(wp); + } + + qf_info_T *qi = ll_get_or_alloc_list(wp); + qf_resize_stack_base(qi, n); +} + +/// Resize quickfix/location lists stack to be able to hold n amount of lists. +static void qf_resize_stack_base(qf_info_T *qi, int n) + FUNC_ATTR_NONNULL_ALL +{ + int amount_to_rm = 0; + size_t lsz = sizeof(*qi->qf_lists); + + if (n == qi->qf_maxcount) { + return; + } else if (n < qi->qf_maxcount && n < qi->qf_listcount) { + // We have too many lists to store them all in the new stack, + // pop lists until we can fit them all in the newly resized stack + amount_to_rm = qi->qf_listcount - n; + + for (int i = 0; i < amount_to_rm; i++) { + qf_pop_stack(qi, true); + } + } + + qf_list_T *new = xrealloc(qi->qf_lists, lsz * (size_t)n); + + // fill with zeroes any newly allocated memory + if (n > qi->qf_maxcount) { + memset(new + qi->qf_maxcount, 0, lsz * (size_t)(n - qi->qf_maxcount)); + } + + qi->qf_lists = new; + qi->qf_maxcount = n; + + qf_update_buffer(qi, NULL); +} + +/// Initialize quickfix list, should only be called once. +void qf_init_stack(void) +{ + ql_info = qf_alloc_stack(QFLT_QUICKFIX, (int)p_chi); +} + +/// Sync a location list window's 'lhistory' value to the parent window +static void qf_sync_llw_to_win(win_T *llw) +{ + win_T *wp = qf_find_win_with_loclist(llw->w_llist_ref); + + if (wp != NULL) { + wp->w_p_lhi = llw->w_p_lhi; + } +} + +/// Sync a window's 'lhistory' value to its location list window, if any +static void qf_sync_win_to_llw(win_T *pwp) +{ + qf_info_T *llw = pwp->w_llist; + + if (llw != NULL) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_llist_ref == llw && bt_quickfix(wp->w_buffer)) { + wp->w_p_lhi = pwp->w_p_lhi; + return; + } + } + } +} + +/// Allocate a new quickfix/location list stack that is able to hold +/// up to n amount of lists +static qf_info_T *qf_alloc_stack(qfltype_T qfltype, int n) FUNC_ATTR_NONNULL_RET { - qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); - qi->qf_refcount++; + qf_info_T *qi; + if (qfltype == QFLT_QUICKFIX) { + qi = &ql_info_actual; + } else { + qi = xcalloc(1, sizeof(qf_info_T)); + qi->qf_refcount++; + } qi->qfl_type = qfltype; qi->qf_bufnr = INVALID_QFBUFNR; + qi->qf_lists = qf_alloc_list_stack(n); + qi->qf_maxcount = n; return qi; } +/// Allocate memory for qf_lists member of qf_info_T struct. +/// 'actual' is the actual amount of lists that have been allocated for +static qf_list_T *qf_alloc_list_stack(int n) + FUNC_ATTR_NONNULL_RET +{ + return xcalloc((size_t)n, sizeof(qf_list_T)); +} + /// Return the location list stack for window 'wp'. /// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) @@ -1976,8 +2129,10 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) ll_free_all(&wp->w_llist_ref); if (wp->w_llist == NULL) { - wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list + // new location list + wp->w_llist = qf_alloc_stack(QFLT_LOCATION, (int)wp->w_p_lhi); } + return wp->w_llist; } @@ -1987,7 +2142,8 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) /// message if 'print_emsg' is true. static qf_info_T *qf_cmd_get_stack(exarg_T *eap, bool print_emsg) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); @@ -2009,7 +2165,7 @@ static qf_info_T *qf_cmd_get_stack(exarg_T *eap, bool print_emsg) static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; if (is_loclist_cmd(eap->cmdidx)) { qi = ll_get_or_alloc_list(curwin); @@ -2122,8 +2278,10 @@ void copy_loclist_stack(win_T *from, win_T *to) return; } - // allocate a new location list - to->w_llist = qf_alloc_stack(QFLT_LOCATION); + // allocate a new location list, set size of stack to 'from' window value + to->w_llist = qf_alloc_stack(QFLT_LOCATION, (int)from->w_p_lhi); + // set 'to' lhi to reflect new value + to->w_p_lhi = to->w_llist->qf_maxcount; to->w_llist->qf_listcount = qi->qf_listcount; @@ -2340,16 +2498,16 @@ static char *qf_guess_filepath(qf_list_T *qfl, char *filename) /// Returns true, if a quickfix/location list with the given identifier exists. static bool qflist_valid(win_T *wp, unsigned qf_id) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; if (wp) { if (!win_valid(wp)) { return false; } qi = GET_LOC_LIST(wp); // Location list - if (!qi) { - return false; - } + } + if (!qi) { + return false; } for (int i = 0; i < qi->qf_listcount; i++) { @@ -3063,7 +3221,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo const bool old_KeyTyped = KeyTyped; // getting file may reset it if (qi == NULL) { - qi = &ql_info; + assert(ql_info != NULL); + qi = ql_info; } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { @@ -3504,7 +3663,8 @@ static void qf_free(qf_list_T *qfl) bool qf_mark_adjust(buf_T *buf, win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); int buf_has_flag = wp == NULL ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; if (!(buf->b_has_qf_entry & buf_has_flag)) { @@ -3593,7 +3753,8 @@ static char *qf_types(int c, int nr) // When "split" is true: Open the entry/result under the cursor in a new window. void qf_view_result(bool split) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); @@ -3869,7 +4030,8 @@ void ex_cbottom(exarg_T *eap) // window). linenr_T qf_current_entry(win_T *wp) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); if (IS_LL_WINDOW(wp)) { // In the location list window, use the referenced location list @@ -4445,7 +4607,8 @@ void ex_make(exarg_T *eap) int res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), enc); - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { @@ -5132,7 +5295,8 @@ static char *cfile_get_auname(cmdidx_T cmdidx) void ex_cfile(exarg_T *eap) { win_T *wp = NULL; - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); char *au_name = NULL; au_name = cfile_get_auname(eap->cmdidx); @@ -5921,12 +6085,12 @@ static int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx, lis qf_info_T *qi = qi_arg; if (qi == NULL) { - qi = &ql_info; + qi = ql_info; if (wp != NULL) { qi = GET_LOC_LIST(wp); - if (qi == NULL) { - return FAIL; - } + } + if (qi == NULL) { + return FAIL; } } @@ -6004,14 +6168,15 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) } list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL, 1); if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, true, 0, 0, NULL, NULL) > 0) { get_errorlist(qi, NULL, 0, 0, l); qf_free(&qi->qf_lists[0]); } - xfree(qi); + + qf_free_lists(qi); tv_dict_add_list(retdict, S_LEN("items"), l); status = OK; @@ -6288,7 +6453,8 @@ static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) /// then current list is used. Otherwise the specified list is used. static int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); dictitem_T *di = NULL; int status = OK; int qf_idx = INVALID_QFIDX; @@ -6866,7 +7032,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) } else if (qfwin != NULL) { // If the location list window is open, then create a new empty location // list - qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); + qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION, (int)wp->w_p_lhi); new_ll->qf_bufnr = qfwin->w_buffer->b_fnum; // first free the list reference in the location list window @@ -6886,11 +7052,13 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) /// When "what" is not NULL then only set some properties. int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what) { - qf_info_T *qi = &ql_info; - + qf_info_T *qi; if (wp != NULL) { qi = ll_get_or_alloc_list(wp); + } else { + qi = ql_info; } + assert(qi != NULL); if (action == 'f') { // Free the entire quickfix or location list stack @@ -6924,7 +7092,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what static bool mark_quickfix_user_data(qf_info_T *qi, int copyID) { bool abort = false; - for (int i = 0; i < LISTCOUNT && !abort; i++) { + for (int i = 0; i < qi->qf_maxcount && !abort; i++) { qf_list_T *qfl = &qi->qf_lists[i]; if (!qfl->qf_has_user_data) { continue; @@ -6948,7 +7116,7 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) { bool abort = false; - for (int i = 0; i < LISTCOUNT && !abort; i++) { + for (int i = 0; i < qi->qf_maxcount && !abort; i++) { typval_T *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) { @@ -6966,8 +7134,9 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) /// "in use". So that garbage collection doesn't free the context. bool set_ref_in_quickfix(int copyID) { - if (mark_quickfix_ctx(&ql_info, copyID) - || mark_quickfix_user_data(&ql_info, copyID) + assert(ql_info != NULL); + if (mark_quickfix_ctx(ql_info, copyID) + || mark_quickfix_user_data(ql_info, copyID) || set_ref_in_callback(&qftf_cb, copyID, NULL, NULL)) { return true; } @@ -7215,7 +7384,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) qf_info_T *qi = wp == NULL ? NULL : wp->w_llist; if (qi == NULL) { // Allocate a new location list for help text matches - qi = qf_alloc_stack(QFLT_LOCATION); + qi = qf_alloc_stack(QFLT_LOCATION, 1); *new_ll = true; } @@ -7326,7 +7495,8 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char // ":helpgrep {pattern}" void ex_helpgrep(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = ql_info; + assert(qi != NULL); char *au_name = NULL; switch (eap->cmdidx) { diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index fb0af3de0a..01c7d88464 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -106,6 +106,7 @@ let test_values = { \ []], \ "\ number options + \ 'chistory': [[1, 2, 10, 50], [1000, -1]], \ 'cmdheight': [[0, 1, 2, 10], [-1]], \ 'cmdwinheight': [[1, 2, 10], [-1, 0]], \ 'columns': [[12, 80, 10000], [-1, 0, 10]], @@ -116,6 +117,7 @@ let test_values = { "\ 'iminsert': [[0, 1, 2], [-1, 3, 999]], "\ 'imsearch': [[-1, 0, 1, 2], [-2, 3, 999]], "\ 'imstyle': [[0, 1], [-1, 2, 999]], + \ 'lhistory': [[1, 2, 10, 50], [1000, -1]], \ 'lines': [[2, 24, 1000], [-1, 0, 1]], \ 'linespace': [[-1, 0, 2, 4, 999], ['']], \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]], diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim index ca48812e7d..ced6c8d7bd 100644 --- a/test/old/testdir/test_quickfix.vim +++ b/test/old/testdir/test_quickfix.vim @@ -43,6 +43,8 @@ func s:setup_commands(cchar) command! -count=1 -nargs=0 Xabove <mods><count>cabove command! -count=1 -nargs=0 Xbefore <mods><count>cbefore command! -count=1 -nargs=0 Xafter <mods><count>cafter + command! -nargs=1 Xsethist <mods>set chistory=<args> + command! -nargs=0 Xsethistdefault <mods>set chistory& let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') call setqflist([], 'f') @@ -80,6 +82,9 @@ func s:setup_commands(cchar) command! -count=1 -nargs=0 Xabove <mods><count>labove command! -count=1 -nargs=0 Xbefore <mods><count>lbefore command! -count=1 -nargs=0 Xafter <mods><count>lafter + command! -nargs=1 Xsethist <mods>set lhistory=<args> + command! -nargs=1 Xsetlocalhist <mods>setlocal lhistory=<args> + command! -nargs=0 Xsethistdefault <mods>set lhistory& let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) call setloclist(0, [], 'f') @@ -6596,4 +6601,180 @@ func Test_hardlink_fname() call Xtest_hardlink_fname('l') endfunc +" Test for checking if correct number of tests are deleted +" and current list stays the same after setting Xhistory +" to a smaller number. Do roughly the same for growing the stack. +func Xtest_resize_list_stack(cchar) + call s:setup_commands(a:cchar) + Xsethist 100 + + for i in range(1, 100) + Xexpr string(i) + endfor + Xopen + call assert_equal(g:Xgetlist({'nr': '$'}).nr, 100) + call assert_equal("|| 100", getline(1)) + Xsethist 8 + call assert_equal("|| 100", getline(1)) + Xolder 5 + call assert_equal("|| 95", getline(1)) + Xsethist 6 + call assert_equal("|| 95", getline(1)) + Xsethist 1 + call assert_equal("|| 100", getline(1)) + + " grow array again + Xsethist 100 + for i in range(1, 99) + Xexpr string(i) + endfor + call assert_equal("|| 99", getline(1)) + Xolder 99 + call assert_equal("|| 100", getline(1)) + + Xsethistdefault +endfunc + +func Test_resize_list_stack() + call Xtest_resize_list_stack('c') + call Xtest_resize_list_stack('l') +endfunc + +" Test to check if order of lists is from +" oldest at the bottom to newest at the top +func Xtest_Xhistory_check_order(cchar) + + Xsethist 100 + + for i in range(1, 100) + Xexpr string(i) + endfor + + Xopen + for i in range(100, 1, -1) + let l:ret = assert_equal("|| " .. i, getline(1)) + + if ret == 1 || i == 1 + break + endif + Xolder + endfor + + for i in range(1, 50) + Xexpr string(i) + endfor + + for i in range(50, 1, -1) + let l:ret = assert_equal("|| " .. i, getline(1)) + + if ret == 1 || i == 50 + break + endif + Xolder + endfor + + for i in range(50, 1, -1) + let l:ret = assert_equal("|| " .. i, getline(1)) + + if ret == 1 || i == 50 + break + endif + Xolder + endfor + + Xsethistdefault +endfunc + +func Test_set_history_to_check_order() + call Xtest_Xhistory_check_order('c') + call Xtest_Xhistory_check_order('l') +endfunc + +" Check if 'lhistory' is the same between the location list window +" and associated normal window +func Test_win_and_loc_synced() + new + set lhistory=2 + lexpr "Text" + lopen + + " check if lhistory is synced when modified inside the + " location list window + setlocal lhistory=1 + wincmd k + call assert_equal(&lhistory, 1) + + " check if lhistory is synced when modified inside the + " normal window + setlocal lhistory=10 + lopen + call assert_equal(&lhistory, 10) + + wincmd k + lclose + wincmd q + + set lhistory& +endfunc + +" Test if setting the lhistory of one window doesn't affect the other +func Test_two_win_are_independent_of_history() + setlocal lhistory=10 + new + setlocal lhistory=20 + wincmd w + call assert_equal(&lhistory, 10) + wincmd w + wincmd q + + set lhistory& +endfunc + +" Test if lhistory is copied over to a new window +func Test_lhistory_copied_over() + setlocal lhistory=3 + split + call assert_equal(&lhistory, 3) + wincmd q + + set lhistory& +endfunc + +" Test if error occurs when given invalid history number +func Xtest_invalid_history_num(cchar) + call s:setup_commands(a:cchar) + + call assert_fails('Xsethist -10000', "E1542:") + call assert_fails('Xsethist 10000', "E1543:") + Xsethistdefault +endfunc + +func Test_invalid_history_num() + call Xtest_invalid_history_num('c') + call Xtest_invalid_history_num('l') +endfunc + +" Test if chistory and lhistory don't affect each other +func Test_chi_and_lhi_are_independent() + set chistory=100 + set lhistory=100 + + set chistory=10 + call assert_equal(&lhistory, 100) + + set lhistory=1 + call assert_equal(&chistory, 10) + + set chistory& + set lhistory& +endfunc + +func Test_quickfix_close_buffer_crash() + new + lexpr 'test' | lopen + wincmd k + lclose + wincmd q +endfunc + " vim: shiftwidth=2 sts=2 expandtab |