diff options
30 files changed, 551 insertions, 295 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 47f4271091..86006497d9 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -16,6 +16,12 @@ function! s:selection.on_exit(jobid, data, event) abort endif endfunction +function! s:selection.on_stderr(jobid, data, event) abort + echohl WarningMsg + echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(a:data) + echohl None +endfunction + let s:selections = { '*': s:selection, '+': copy(s:selection)} function! s:try_cmd(cmd, ...) abort @@ -135,24 +141,17 @@ function! s:clipboard.set(lines, regtype, reg) abort end let selection.data = [a:lines, a:regtype] let argv = split(s:copy[a:reg], " ") + let selection.argv = argv let selection.detach = s:cache_enabled let selection.cwd = "/" - call extend(selection, { - \ 'on_stdout': function('s:set_errhandler'), - \ 'on_stderr': function('s:set_errhandler'), - \ }) let jobid = jobstart(argv, selection) if jobid > 0 call jobsend(jobid, a:lines) call jobclose(jobid, 'stdin') let selection.owner = jobid - endif -endfunction - -function! s:set_errhandler(job_id, data, event) abort - if a:job_id <= 0 + else echohl WarningMsg - echo 'clipboard: error when invoking provider: ' . join(a:data) + echomsg 'clipboard: failed to execute: '.(s:copy[a:reg]) echohl None endif endfunction diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cca62f1469..8882043162 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4273,7 +4273,8 @@ getqflist([{what}]) *getqflist()* If the optional {what} dictionary argument is supplied, then returns only the items listed in {what} as a dictionary. The following string items are supported in {what}: - nr get information for this quickfix list + nr get information for this quickfix list; zero + means the current quickfix list title get the list title winid get the |window-ID| (if opened) all all of the above quickfix properties diff --git a/src/nvim/README.md b/src/nvim/README.md index 3032913500..1c1c3c364e 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,14 +1,26 @@ -## Source code overview +Nvim core source +================ -This document is an overview of how Nvim works internally, focusing on parts -that are different from Vim. Since Nvim inherited from Vim, some information in -[its README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) -still applies. +Module-specific details are documented at the top of each module (`terminal.c`, +`screen.c`, ...). -For module-specific details, read the source code. Some files are extensively -commented at the top (e.g. terminal.c, screen.c). +See `:help development` for more guidelines. -### Source file name conventions +Logs +---- + +Low-level log messages sink to `$NVIM_LOG_FILE`. + +You can use `LOG_CALLSTACK()` anywhere in the source to log the current +stacktrace. (Currently Linux-only.) + +UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). + + rm -rf build/ + make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" + +Filename conventions +-------------------- The source files use extensions to hint about their purpose. @@ -19,10 +31,12 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. -### Top-level program loops +Nvim lifecycle +-------------- + +Following describes how Nvim processes input. -Let's understand what a Vim-like program does by analyzing the workflow of -a typical editing session: +Consider a typical Vim-like editing session: 01. Vim dispays the welcome screen 02. User types: `:` @@ -154,7 +168,8 @@ modes managed by the `state_enter` loop: - insert mode: `insert_{enter,check,execute}()`(`edit.c`) - terminal mode: `terminal_{enter,execute}()`(`terminal.c`) -### Async event support +Async event support +------------------- One of the features Nvim added is the support for handling arbitrary asynchronous events, which can include: diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 90a564bb6a..724a8578ac 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1203,8 +1203,8 @@ do_buffer ( */ while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, FALSE) == FAIL) + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { + if (win_close(curwin, false) == FAIL) break; } @@ -4428,15 +4428,17 @@ do_arg_all ( continue; } } - /* don't close last window */ - if (firstwin == lastwin - && (first_tabpage->tp_next == NULL || !had_tab)) - use_firstwin = TRUE; - else { + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); - /* check if autocommands removed the next window */ - if (!win_valid(wpnext)) - wpnext = firstwin; /* start all over... */ + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } } } } @@ -4593,7 +4595,7 @@ void ex_buffer_all(exarg_T *eap) - tabline_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) - && firstwin != lastwin + && !ONE_WINDOW && !(wp->w_closing || wp->w_buffer->b_locked > 0) ) { win_close(wp, FALSE); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e5bb7f1b38..3ec8deb666 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4239,11 +4239,17 @@ static int eval7( // use its contents. s = deref_func_name((const char *)s, &len, &partial, !evaluate); + // Need to make a copy, in case evaluating the arguments makes + // the name invalid. + s = xmemdupz(s, len); + // Invoke the function. ret = get_func_tv(s, len, rettv, arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, partial, NULL); + xfree(s); + // If evaluate is false rettv->v_type was not set in // get_func_tv, but it's needed in handle_subscript() to parse // what follows. So set it here. @@ -15135,7 +15141,8 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *error = NULL; - uint64_t id = channel_connect(tcp, address, 50, &error); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + uint64_t id = channel_connect(tcp, address, 50, (char *)IObuff, &error); if (error) { EMSG2(_("connection failed: %s"), error); @@ -22443,8 +22450,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) if (data->rpc) { - // the rpc channel takes over the in and out streams - channel_from_process(proc, data->id); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + // RPC channel takes over the in/out streams. + channel_from_process(proc, data->id, (char *)IObuff); } else { wstream_init(proc->in, 0); if (proc->out) { @@ -22769,3 +22777,11 @@ bool eval_has_provider(const char *name) return false; } + +/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. +void eval_format_source_name_line(char *buf, size_t bufsize) +{ + snprintf(buf, bufsize, "%s:%" PRIdLINENR, + (sourcing_name ? sourcing_name : (char_u *)"?"), + (sourcing_name ? sourcing_lnum : 0)); +} diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0987cb3915..a555fb77e8 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2802,16 +2802,18 @@ void ex_z(exarg_T *eap) int j; linenr_T lnum = eap->line2; - /* Vi compatible: ":z!" uses display height, without a count uses - * 'scroll' */ - if (eap->forceit) + // Vi compatible: ":z!" uses display height, without a count uses + // 'scroll' + if (eap->forceit) { bigness = curwin->w_height; - else if (firstwin == lastwin) + } else if (ONE_WINDOW) { bigness = curwin->w_p_scr * 2; - else + } else { bigness = curwin->w_height - 3; - if (bigness < 1) + } + if (bigness < 1) { bigness = 1; + } x = eap->arg; kind = x; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5d7246581c..d2eb881bc6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5976,7 +5976,7 @@ static void ex_quit(exarg_T *eap) // specified. Example: // :h|wincmd w|1q - don't quit // :h|wincmd w|q - quit - if (only_one_window() && (firstwin == lastwin || eap->addr_count == 0)) { + if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { getout(0); } /* close window; may free buffer */ @@ -6174,12 +6174,14 @@ static void ex_tabonly(exarg_T *eap) */ void tabpage_close(int forceit) { - /* First close all the windows but the current one. If that worked then - * close the last window in this tab, that will close it. */ - if (lastwin != firstwin) - close_others(TRUE, forceit); - if (lastwin == firstwin) + // First close all the windows but the current one. If that worked then + // close the last window in this tab, that will close it. + if (!ONE_WINDOW) { + close_others(true, forceit); + } + if (ONE_WINDOW) { ex_win_close(forceit, curwin, NULL); + } } /* diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index acdb25ca67..d2b90db707 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -119,7 +119,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') end end @@ -128,6 +128,7 @@ for i = 1, #events do call_output:write('\n{\n') if ev.remote_only then write_arglist(call_output, ev, false) + call_output:write(' UI_LOG('..ev.name..', 0);\n') call_output:write(' ui_event("'..ev.name..'", args);\n') else call_output:write(' UI_CALL') diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 6d1bd1de12..f08812600f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -494,6 +494,7 @@ EXTERN int updating_screen INIT(= FALSE); EXTERN win_T *firstwin; /* first window */ EXTERN win_T *lastwin; /* last window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ +# define ONE_WINDOW (firstwin == lastwin) /* * When using this macro "break" only breaks out of the inner loop. Use "goto" * to break out of the tabpage loop. diff --git a/src/nvim/log.c b/src/nvim/log.c index f1dbe61dda..252fe5438d 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -173,6 +173,53 @@ FILE *open_log_file(void) return stderr; } +#if defined(__linux__) +# include <execinfo.h> +void log_callstack(const char *const func_name, const int line_num) +{ + void *trace[100]; + int trace_size = backtrace(trace, ARRAY_SIZE(trace)); + + char exepath[MAXPATHL] = { 0 }; + size_t exepathlen = MAXPATHL; + if (os_exepath(exepath, &exepathlen) != 0) { + abort(); + } + assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below. + + do_log(DEBUG_LOG_LEVEL, func_name, line_num, true, "trace:"); + + char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace))]; + snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath); + for (int i = 1; i < trace_size; i++) { + char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space. + snprintf(buf, sizeof(buf), " %p", trace[i]); + xstrlcat(cmdbuf, buf, sizeof(cmdbuf)); + } + // Now we have a command string like: + // addr2line -e /path/to/exe -f -p 0x123 0x456 ... + + log_lock(); + FILE *log_file = open_log_file(); + if (log_file == NULL) { + goto end; + } + + FILE *fp = popen(cmdbuf, "r"); + char linebuf[IOSIZE]; + while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) { + fprintf(log_file, " %s", linebuf); + } + pclose(fp); + + if (log_file != stderr && log_file != stdout) { + fclose(log_file); + } +end: + log_unlock(); +} +#endif + static bool do_log_to_file(FILE *log_file, int log_level, const char *func_name, int line_num, bool eol, const char* fmt, ...) diff --git a/src/nvim/log.h b/src/nvim/log.h index 221f0bbaf6..2bd18f5776 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -61,6 +61,10 @@ __VA_ARGS__) #endif +#if defined(__linux__) +# define LOG_CALLSTACK() log_callstack(__func__, __LINE__) +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "log.h.generated.h" #endif diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 74c58fb203..328b96fd5c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -585,7 +585,7 @@ void free_all_mem(void) p_ea = false; if (first_tabpage->tp_next != NULL) do_cmdline_cmd("tabonly!"); - if (firstwin != lastwin) + if (!ONE_WINDOW) do_cmdline_cmd("only!"); /* Free all spell info. */ diff --git a/src/nvim/move.c b/src/nvim/move.c index d5be4cb8c3..81d46a7f17 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1763,7 +1763,7 @@ int onepage(int dir, long count) loff.fill = 0; if (dir == FORWARD) { - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling */ if (p_window <= 2) ++curwin->w_topline; @@ -1797,7 +1797,7 @@ int onepage(int dir, long count) max_topfill(); continue; } - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling (sort of) */ if (p_window <= 2) --curwin->w_topline; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 68ac35bc4e..6fd1af1ba6 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -117,12 +117,15 @@ void channel_teardown(void) /// Creates an API channel by starting a process and connecting to its /// stdin/stdout. stderr is handled by the job infrastructure. /// -/// @param argv The argument vector for the process. [consumed] -/// @return The channel id (> 0), on success. -/// 0, on error. -uint64_t channel_from_process(Process *proc, uint64_t id) +/// @param proc process object +/// @param id (optional) channel id +/// @param source description of source function, rplugin name, TCP addr, etc +/// +/// @return Channel id (> 0), on success. 0, on error. +uint64_t channel_from_process(Process *proc, uint64_t id, char *source) { - Channel *channel = register_channel(kChannelTypeProc, id, proc->events); + Channel *channel = register_channel(kChannelTypeProc, id, proc->events, + source); incref(channel); // process channels are only closed by the exit_cb channel->data.proc = proc; @@ -138,7 +141,8 @@ uint64_t channel_from_process(Process *proc, uint64_t id) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, + watcher->addr); socket_watcher_accept(watcher, &channel->data.stream); incref(channel); // close channel only after the stream is closed channel->data.stream.internal_close_cb = close_cb; @@ -148,8 +152,9 @@ void channel_from_connection(SocketWatcher *watcher) rstream_start(&channel->data.stream, receive_msgpack, channel); } -uint64_t channel_connect(bool tcp, const char *address, - int timeout, const char **error) +/// @param source description of source function, rplugin name, TCP addr, etc +uint64_t channel_connect(bool tcp, const char *address, int timeout, + char *source, const char **error) { if (!tcp) { char *path = fix_fname(address); @@ -161,7 +166,7 @@ uint64_t channel_connect(bool tcp, const char *address, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, source); if (!socket_connect(&main_loop, &channel->data.stream, tcp, address, timeout, error)) { decref(channel); @@ -329,11 +334,10 @@ bool channel_close(uint64_t id) return true; } -/// Creates an API channel from stdin/stdout. This is used when embedding -/// Neovim +/// Creates an API channel from stdin/stdout. Used to embed Nvim. void channel_from_stdio(void) { - Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); + Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL); incref(channel); // stdio channels are only closed on exit // read stream rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); @@ -346,7 +350,7 @@ void channel_from_stdio(void) /// when an instance connects to its own named pipe. uint64_t channel_create_internal(void) { - Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); + Channel *channel = register_channel(kChannelTypeInternal, 0, NULL, NULL); incref(channel); // internal channel lives until process exit return channel->id; } @@ -745,9 +749,12 @@ static void close_cb(Stream *stream, void *data) decref(data); } +/// @param source description of source function, rplugin name, TCP addr, etc static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events) + MultiQueue *events, char *source) { + // Jobs and channels share the same id namespace. + assert(id == 0 || !pmap_get(uint64_t)(channels, id)); Channel *rv = xmalloc(sizeof(Channel)); rv->events = events ? events : multiqueue_new_child(main_loop.events); rv->type = type; @@ -761,6 +768,14 @@ static Channel *register_channel(ChannelType type, uint64_t id, kv_init(rv->call_stack); kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); + + ILOG("new channel %" PRIu64 " (%s): %s", rv->id, + (type == kChannelTypeProc ? "proc" + : (type == kChannelTypeSocket ? "socket" + : (type == kChannelTypeStdio ? "stdio" + : (type == kChannelTypeInternal ? "internal" : "?")))), + (source ? source : "?")); + return rv; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 37b37e2859..a6ae022e79 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4078,7 +4078,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window height NOW */ - if (lastwin != firstwin) { + if (!ONE_WINDOW) { if (pp == &p_wh && curwin->w_height < p_wh) win_setheight((int)p_wh); if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) @@ -4107,7 +4107,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) + if (!ONE_WINDOW && curwin->w_width < p_wiw) win_setwidth((int)p_wiw); } /* 'winminwidth' */ @@ -5239,7 +5239,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) void comp_col(void) { - int last_has_status = (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)); + int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW)); sc_col = 0; ru_col = 0; @@ -5718,7 +5718,22 @@ void buf_copy_options(buf_T *buf, int flags) free_buf_options(buf, TRUE); buf->b_p_ro = FALSE; /* don't copy readonly */ buf->b_p_fenc = vim_strsave(p_fenc); - buf->b_p_ff = vim_strsave(p_ff); + switch (*p_ffs) { + case 'm': + buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + break; + case 'd': + buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + break; + case 'u': + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + break; + default: + buf->b_p_ff = vim_strsave(p_ff); + } + if (buf->b_p_ff != NULL) { + buf->b_start_ffc = *buf->b_p_ff; + } buf->b_p_bh = empty_option; buf->b_p_bt = empty_option; } else @@ -7039,8 +7054,11 @@ dict_T *get_winbuf_options(const int bufopt) if (opt->flags & P_STRING) { tv_dict_add_str(d, opt->fullname, strlen(opt->fullname), *(const char **)varp); + } else if (opt->flags & P_NUM) { + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), + *(long *)varp); } else { - tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *varp); + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *(int *)varp); } } } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bd5dfa92cc..4997209556 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -383,6 +383,8 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, return 0; } +static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls + static void free_efm_list(efm_T **efm_first) { for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) { @@ -390,6 +392,8 @@ static void free_efm_list(efm_T **efm_first) vim_regfree(efm_ptr->prog); xfree(efm_ptr); } + + fmt_start = NULL; } // Parse 'errorformat' option @@ -671,7 +675,6 @@ 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; @@ -875,36 +878,38 @@ restofline: 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 (!qi->qf_multiignore) { + 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; @@ -967,6 +972,7 @@ qf_init_ext( NULL, 0, 0 }; qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; + bool adding = false; static efm_T *fmt_first = NULL; char_u *efm; static char_u *last_efm = NULL; @@ -992,6 +998,7 @@ qf_init_ext( qf_new_list(qi, qf_title); } else if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { // Adding to existing list, use last entry. + adding = true; old_last = qi->qf_lists[qi->qf_curlist].qf_last; } @@ -1108,10 +1115,12 @@ qf_init_ext( } EMSG(_(e_readerrf)); error2: - qf_free(qi, qi->qf_curlist); - qi->qf_listcount--; - if (qi->qf_curlist > 0) { - qi->qf_curlist--; + if (!adding) { + qf_free(qi, qi->qf_curlist); + qi->qf_listcount--; + if (qi->qf_curlist > 0) { + qi->qf_curlist--; + } } qf_init_end: if (state.fd != NULL) { @@ -1408,7 +1417,7 @@ void copy_loclist(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ } -// Get buffer number for file "directory.fname". +// Get buffer number for file "directory/fname". // Also sets the b_has_qf_entry flag. static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname) { @@ -1869,7 +1878,7 @@ win_found: * If there is only one window and it is the quickfix window, create a * new one above the quickfix window. */ - if (((firstwin == lastwin) && bt_quickfix(curbuf)) || !usable_win) { + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { flags = WSP_ABOVE; if (ll_ref != NULL) flags |= WSP_NEWLOC; @@ -2362,7 +2371,9 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_index = 0; qf_clean_dir_stack(&qi->qf_dir_stack); + qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); + qi->qf_currfile = NULL; } /* @@ -4025,9 +4036,12 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { - return FAIL; + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + return FAIL; + } } flags |= QF_GETLIST_NR; } else { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a8353153fd..bcc996679b 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -991,7 +991,7 @@ static void win_update(win_T *wp) * first. */ if (mid_start == 0) { mid_end = wp->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { /* Clear the screen when it was not done by win_del_lines() or * win_ins_lines() above, "screen_cleared" is FALSE or MAYBE * then. */ @@ -7160,7 +7160,7 @@ static int fillchar_status(int *attr, win_T *wp) * window differs, or the fillchars differ, or this is not the * current window */ if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || firstwin == lastwin) + || !is_curwin || ONE_WINDOW) || (fill_stl != fill_stlnc))) { return fill; } diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 1103778107..d55170c27c 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -14,6 +14,7 @@ source test_float_func.vim source test_functions.vim source test_goto.vim source test_jumps.vim +source test_fileformat.vim source test_lambda.vim source test_menu.vim source test_mapping.vim diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 5c916e2dd7..1c9350c416 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -87,9 +87,17 @@ function Test_get_buf_options() endfunc function Test_get_win_options() + if has('folding') + set foldlevel=999 + endif + set list let opts = getwinvar(1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) + if has('folding') + call assert_equal(999, opts.foldlevel) + endif if has('signs') call assert_equal('auto', opts.signcolumn) endif @@ -97,7 +105,12 @@ function Test_get_win_options() let opts = gettabwinvar(1, 1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) if has('signs') call assert_equal('auto', opts.signcolumn) endif + set list& + if has('folding') + set foldlevel=0 + endif endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 82c5d21bd0..710eae9b8b 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -418,6 +418,9 @@ func Test_function_with_funcref() let s:fref = function(s:f) call assert_equal(v:t_string, s:fref('x')) call assert_fails("call function('s:f')", 'E700:') + + call assert_fails("call function('foo()')", 'E475:') + call assert_fails("call function('foo()')", 'foo()') endfunc func Test_funcref() diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim new file mode 100644 index 0000000000..584f20cdfc --- /dev/null +++ b/src/nvim/testdir/test_fileformat.vim @@ -0,0 +1,17 @@ +" Test behavior of fileformat after bwipeout of last buffer + +func Test_fileformat_after_bw() + bwipeout + set fileformat& + if &fileformat == 'dos' + let test_fileformats = 'unix' + elseif &fileformat == 'unix' + let test_fileformats = 'mac' + else " must be mac + let test_fileformats = 'dos' + endif + exec 'set fileformats='.test_fileformats + bwipeout! + call assert_equal(test_fileformats, &fileformat) + set fileformats& +endfunc diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index c788689e33..c11f1a84a9 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -277,6 +277,7 @@ function! Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) call matchadd('CheckedByCoq', '\%<2l\%>9c\%<16c') + redraw! call assert_equal(expect, s:screenline(1)) call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) diff --git a/src/nvim/testdir/test_nested_function.vim b/src/nvim/testdir/test_nested_function.vim index f881730529..7e301ed33e 100644 --- a/src/nvim/testdir/test_nested_function.vim +++ b/src/nvim/testdir/test_nested_function.vim @@ -1,32 +1,42 @@ "Tests for nested functions " -function! NestedFunc() - fu! Func1() +func NestedFunc() + func! Func1() let g:text .= 'Func1 ' - endfunction + endfunc call Func1() - fu! s:func2() + func! s:func2() let g:text .= 's:func2 ' - endfunction + endfunc call s:func2() - fu! s:_func3() + func! s:_func3() let g:text .= 's:_func3 ' - endfunction + endfunc call s:_func3() let fn = 'Func4' - fu! {fn}() + func! {fn}() let g:text .= 'Func4 ' - endfunction + endfunc call {fn}() let fn = 'func5' - fu! s:{fn}() + func! s:{fn}() let g:text .= 's:func5' - endfunction + endfunc call s:{fn}() -endfunction +endfunc -function! Test_nested_functions() +func Test_nested_functions() let g:text = '' call NestedFunc() call assert_equal('Func1 s:func2 s:_func3 Func4 s:func5', g:text) endfunction + +func Test_nested_argument() + func g:X() + let g:Y = function('sort') + endfunc + let g:Y = function('sort') + echo g:Y([], g:X()) + delfunc g:X + unlet g:Y +endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 75ab01f013..aff5fc2eed 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -6,7 +6,7 @@ endif set encoding=utf-8 -function! s:setup_commands(cchar) +func s:setup_commands(cchar) if a:cchar == 'c' command! -nargs=* -bang Xlist <mods>clist<bang> <args> command! -nargs=* Xgetexpr <mods>cgetexpr <args> @@ -68,10 +68,10 @@ function! s:setup_commands(cchar) let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) endif -endfunction +endfunc " Tests for the :clist and :llist commands -function XlistTests(cchar) +func XlistTests(cchar) call s:setup_commands(a:cchar) " With an empty list, command should return error @@ -128,17 +128,17 @@ function XlistTests(cchar) let l = split(result, "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) -endfunction +endfunc -function Test_clist() +func Test_clist() call XlistTests('c') call XlistTests('l') -endfunction +endfunc " Tests for the :colder, :cnewer, :lolder and :lnewer commands " Note that this test assumes that a quickfix/location list is " already set by the caller. -function XageTests(cchar) +func XageTests(cchar) call s:setup_commands(a:cchar) " Jumping to a non existent list should return error @@ -171,20 +171,20 @@ function XageTests(cchar) Xnewer 2 let l = g:Xgetlist() call assert_equal('Line3', l[0].text) -endfunction +endfunc -function Test_cage() +func Test_cage() let list = [{'bufnr': 1, 'lnum': 1}] call setqflist(list) call XageTests('c') call setloclist(0, list) call XageTests('l') -endfunction +endfunc " Tests for the :cwindow, :lwindow :cclose, :lclose, :copen and :lopen " commands -function XwindowTests(cchar) +func XwindowTests(cchar) call s:setup_commands(a:cchar) " Create a list with no valid entries @@ -227,16 +227,16 @@ function XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) -endfunction +endfunc -function Test_cwindow() +func Test_cwindow() call XwindowTests('c') call XwindowTests('l') -endfunction +endfunc " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. -function XfileTests(cchar) +func XfileTests(cchar) call s:setup_commands(a:cchar) call writefile(['Xtestfile1:700:10:Line 700', @@ -275,16 +275,16 @@ function XfileTests(cchar) \ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333') call delete('Xqftestfile1') -endfunction +endfunc -function Test_cfile() +func Test_cfile() call XfileTests('c') call XfileTests('l') -endfunction +endfunc " Tests for the :cbuffer, :lbuffer, :caddbuffer, :laddbuffer, :cgetbuffer and " :lgetbuffer commands. -function XbufferTests(cchar) +func XbufferTests(cchar) call s:setup_commands(a:cchar) enew! @@ -316,26 +316,26 @@ function XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! -endfunction +endfunc -function Test_cbuffer() +func Test_cbuffer() call XbufferTests('c') call XbufferTests('l') -endfunction +endfunc -function XexprTests(cchar) +func XexprTests(cchar) call s:setup_commands(a:cchar) call assert_fails('Xexpr 10', 'E777:') -endfunction +endfunc -function Test_cexpr() +func Test_cexpr() call XexprTests('c') call XexprTests('l') -endfunction +endfunc " Tests for :cnext, :cprev, :cfirst, :clast commands -function Xtest_browse(cchar) +func Xtest_browse(cchar) call s:setup_commands(a:cchar) call s:create_test_file('Xqftestfile1') @@ -366,14 +366,14 @@ function Xtest_browse(cchar) call delete('Xqftestfile1') call delete('Xqftestfile2') -endfunction +endfunc -function Test_browse() +func Test_browse() call Xtest_browse('c') call Xtest_browse('l') -endfunction +endfunc -function! s:test_xhelpgrep(cchar) +func s:test_xhelpgrep(cchar) call s:setup_commands(a:cchar) Xhelpgrep quickfix Xopen @@ -385,9 +385,9 @@ function! s:test_xhelpgrep(cchar) call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose -endfunction +endfunc -function Test_helpgrep() +func Test_helpgrep() call s:test_xhelpgrep('c') helpclose call s:test_xhelpgrep('l') @@ -425,7 +425,7 @@ func Test_vimgreptitle() augroup! QfBufWinEnter endfunc -function XqfTitleTests(cchar) +func XqfTitleTests(cchar) call s:setup_commands(a:cchar) Xgetexpr ['file:1:1:message'] @@ -444,16 +444,16 @@ function XqfTitleTests(cchar) endif call assert_equal(title, w:quickfix_title) Xclose -endfunction +endfunc " Tests for quickfix window's title -function Test_qf_title() +func Test_qf_title() call XqfTitleTests('c') call XqfTitleTests('l') -endfunction +endfunc " Tests for 'errorformat' -function Test_efm() +func Test_efm() let save_efm = &efm set efm=%EEEE%m,%WWWW%m,%+CCCC%.%#,%-GGGG%.%# cgetexpr ['WWWW', 'EEEE', 'CCCC'] @@ -466,7 +466,7 @@ function Test_efm() let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]'))) call assert_equal("[['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]", l) let &efm = save_efm -endfunction +endfunc " This will test for problems in quickfix: " A. incorrectly copying location lists which caused the location list to show @@ -477,7 +477,7 @@ endfunction " window it belongs to. " " Set up the test environment: -function! ReadTestProtocol(name) +func ReadTestProtocol(name) let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '') let word = substitute(base, '\v(.*)\..*', '\1', '') @@ -496,9 +496,9 @@ function! ReadTestProtocol(name) setl nomodifiable setl readonly exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '') -endfunction +endfunc -function Test_locationlist() +func Test_locationlist() enew augroup testgroup @@ -578,15 +578,15 @@ function Test_locationlist() wincmd n | only augroup! testgroup -endfunction + endfunc -function Test_locationlist_curwin_was_closed() +func Test_locationlist_curwin_was_closed() augroup testgroup au! autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) augroup END - function! R(n) + func! R(n) quit endfunc @@ -597,9 +597,9 @@ function Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup -endfunction + endfunc -function Test_locationlist_cross_tab_jump() +func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') call writefile(['loclistbar'], 'loclistbar') set switchbuf=usetab @@ -613,10 +613,10 @@ function Test_locationlist_cross_tab_jump() set switchbuf&vim call delete('loclistfoo') call delete('loclistbar') -endfunction +endfunc " More tests for 'errorformat' -function! Test_efm1() +func Test_efm1() if !has('unix') " The 'errorformat' setting is different on non-Unix systems. " This test works only on Unix-like systems. @@ -734,10 +734,10 @@ function! Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') -endfunction + endfunc " Test for quickfix directory stack support -function! s:dir_stack_tests(cchar) +func s:dir_stack_tests(cchar) call s:setup_commands(a:cchar) let save_efm=&efm @@ -779,10 +779,10 @@ function! s:dir_stack_tests(cchar) call assert_equal(5, qf[11].lnum) let &efm=save_efm -endfunction +endfunc " Tests for %D and %X errorformat options -function! Test_efm_dirstack() +func Test_efm_dirstack() " Create the directory stack and files call mkdir('dir1') call mkdir('dir1/a') @@ -814,10 +814,33 @@ function! Test_efm_dirstack() call delete('dir1', 'rf') call delete('dir2', 'rf') call delete('habits1.txt') -endfunction +endfunc + +" Test for resync after continuing an ignored message +func Xefm_ignore_continuations(cchar) + call s:setup_commands(a:cchar) + + let save_efm = &efm + + let &efm = + \ '%Eerror %m %l,' . + \ '%-Wignored %m %l,' . + \ '%+Cmore ignored %m %l,' . + \ '%Zignored end' + Xgetexpr ['ignored warning 1', 'more ignored continuation 2', 'ignored end', 'error resync 4'] + let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]') + call assert_equal([['resync', 1, 4, 'E']], l) + + let &efm = save_efm +endfunc + +func Test_efm_ignore_continuations() + call Xefm_ignore_continuations('c') + call Xefm_ignore_continuations('l') +endfunc " Tests for invalid error format specifies -function Xinvalid_efm_Tests(cchar) +func Xinvalid_efm_Tests(cchar) call s:setup_commands(a:cchar) let save_efm = &efm @@ -850,17 +873,17 @@ function Xinvalid_efm_Tests(cchar) call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:') let &efm = save_efm -endfunction +endfunc -function Test_invalid_efm() +func Test_invalid_efm() call Xinvalid_efm_Tests('c') call Xinvalid_efm_Tests('l') -endfunction +endfunc " TODO: " Add tests for the following formats in 'errorformat' " %r %O -function! Test_efm2() +func Test_efm2() let save_efm = &efm " Test for %s format in efm @@ -946,19 +969,19 @@ function! Test_efm2() call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) let &efm = save_efm -endfunction +endfunc -function XquickfixChangedByAutocmd(cchar) +func XquickfixChangedByAutocmd(cchar) call s:setup_commands(a:cchar) if a:cchar == 'c' let ErrorNr = 'E925' - function! ReadFunc() + func! ReadFunc() colder cgetexpr [] endfunc else let ErrorNr = 'E926' - function! ReadFunc() + func! ReadFunc() lolder lgetexpr [] endfunc @@ -981,10 +1004,10 @@ function XquickfixChangedByAutocmd(cchar) augroup! testgroup endfunc -function Test_quickfix_was_changed_by_autocmd() +func Test_quickfix_was_changed_by_autocmd() call XquickfixChangedByAutocmd('c') call XquickfixChangedByAutocmd('l') -endfunction +endfunc func Test_caddbuffer_to_empty() helpgr quickfix @@ -1006,7 +1029,7 @@ func Test_cgetexpr_works() endfunc " Tests for the setqflist() and setloclist() functions -function SetXlistTests(cchar, bnum) +func SetXlistTests(cchar, bnum) call s:setup_commands(a:cchar) call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1}, @@ -1041,9 +1064,9 @@ function SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) -endfunction +endfunc -function Test_setqflist() +func Test_setqflist() new Xtestfile | only let bnum = bufnr('%') call setline(1, range(1,5)) @@ -1053,9 +1076,9 @@ function Test_setqflist() enew! call delete('Xtestfile') -endfunction +endfunc -function Xlist_empty_middle(cchar) +func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1078,12 +1101,12 @@ function Xlist_empty_middle(cchar) call assert_equal(matchlen, len(g:Xgetlist())) endfunc -function Test_setqflist_empty_middle() +func Test_setqflist_empty_middle() call Xlist_empty_middle('c') call Xlist_empty_middle('l') -endfunction +endfunc -function Xlist_empty_older(cchar) +func Xlist_empty_older(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1104,14 +1127,14 @@ function Xlist_empty_older(cchar) call assert_equal(twolen, len(g:Xgetlist())) Xnewer call assert_equal(threelen, len(g:Xgetlist())) -endfunction +endfunc -function Test_setqflist_empty_older() +func Test_setqflist_empty_older() call Xlist_empty_older('c') call Xlist_empty_older('l') -endfunction +endfunc -function! XquickfixSetListWithAct(cchar) +func XquickfixSetListWithAct(cchar) call s:setup_commands(a:cchar) let list1 = [{'filename': 'fnameA', 'text': 'A'}, @@ -1185,12 +1208,12 @@ function! XquickfixSetListWithAct(cchar) call assert_fails("call g:Xsetlist(list1, 0)", 'E928:') endfunc -function Test_quickfix_set_list_with_act() +func Test_quickfix_set_list_with_act() call XquickfixSetListWithAct('c') call XquickfixSetListWithAct('l') -endfunction +endfunc -function XLongLinesTests(cchar) +func XLongLinesTests(cchar) let l = g:Xgetlist() call assert_equal(4, len(l)) @@ -1208,9 +1231,9 @@ function XLongLinesTests(cchar) call assert_equal(10, len(l[3].text)) call g:Xsetlist([], 'r') -endfunction +endfunc -function s:long_lines_tests(cchar) +func s:long_lines_tests(cchar) call s:setup_commands(a:cchar) let testfile = 'samples/quickfix.txt' @@ -1231,22 +1254,22 @@ function s:long_lines_tests(cchar) exe 'edit' testfile exe 'Xbuffer' bufnr('%') call XLongLinesTests(a:cchar) -endfunction +endfunc -function Test_long_lines() +func Test_long_lines() call s:long_lines_tests('c') call s:long_lines_tests('l') -endfunction +endfunc -function! s:create_test_file(filename) +func s:create_test_file(filename) let l = [] for i in range(1, 20) call add(l, 'Line' . i) endfor call writefile(l, a:filename) -endfunction +endfunc -function! Test_switchbuf() +func Test_switchbuf() call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') call s:create_test_file('Xqftestfile3') @@ -1333,9 +1356,9 @@ function! Test_switchbuf() call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') -endfunction +endfunc -function! Xadjust_qflnum(cchar) +func Xadjust_qflnum(cchar) call s:setup_commands(a:cchar) enew | only @@ -1360,17 +1383,17 @@ function! Xadjust_qflnum(cchar) enew! call delete(fname) -endfunction +endfunc -function! Test_adjust_lnum() +func Test_adjust_lnum() call setloclist(0, []) call Xadjust_qflnum('c') call setqflist([]) call Xadjust_qflnum('l') -endfunction +endfunc " Tests for the :grep/:lgrep and :grepadd/:lgrepadd commands -function! s:test_xgrep(cchar) +func s:test_xgrep(cchar) call s:setup_commands(a:cchar) " The following lines are used for the grep test. Don't remove. @@ -1389,9 +1412,9 @@ function! s:test_xgrep(cchar) set makeef=Temp_File_## silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim call assert_true(len(g:Xgetlist()) == 6) -endfunction +endfunc -function! Test_grep() +func Test_grep() if !has('unix') " The grepprg may not be set on non-Unix systems return @@ -1399,9 +1422,9 @@ function! Test_grep() call s:test_xgrep('c') call s:test_xgrep('l') -endfunction +endfunc -function! Test_two_windows() +func Test_two_windows() " Use one 'errorformat' for two windows. Add an expression to each of them, " make sure they each keep their own state. set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' @@ -1427,12 +1450,10 @@ function! Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) -echo string(loc_one) call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) -echo string(loc_two) call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) @@ -1444,7 +1465,7 @@ echo string(loc_two) call delete('Xtwo', 'rf') endfunc -function XbottomTests(cchar) +func XbottomTests(cchar) call s:setup_commands(a:cchar) call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) @@ -1460,12 +1481,12 @@ function XbottomTests(cchar) endfunc " Tests for the :cbottom and :lbottom commands -function Test_cbottom() +func Test_cbottom() call XbottomTests('c') call XbottomTests('l') -endfunction +endfunc -function HistoryTest(cchar) +func HistoryTest(cchar) call s:setup_commands(a:cchar) call assert_fails(a:cchar . 'older 99', 'E380:') @@ -1505,7 +1526,7 @@ func Test_duplicate_buf() endfunc " Quickfix/Location list set/get properties tests -function Xproperty_tests(cchar) +func Xproperty_tests(cchar) call s:setup_commands(a:cchar) " Error cases @@ -1532,6 +1553,11 @@ function Xproperty_tests(cchar) call assert_equal('N1', g:Xgetlist({'all':1}).title) call g:Xsetlist([], ' ', {'title' : 'N2'}) call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr) + + let res = g:Xgetlist({'nr': 0}) + call assert_equal(qfnr + 1, res.nr) + call assert_equal(['nr'], keys(res)) + call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) @@ -1544,21 +1570,21 @@ function Xproperty_tests(cchar) call assert_equal({}, g:Xgetlist({'abc':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, ['title'])) + call assert_equal({}, getloclist(99, {'title': 1})) endif -endfunction + endfunc -function Test_qf_property() +func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') -endfunction + endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands -function QfAutoCmdHandler(loc, cmd) +func QfAutoCmdHandler(loc, cmd) call add(g:acmds, a:loc . a:cmd) -endfunction +endfunc -function Test_Autocmd() +func Test_Autocmd() autocmd QuickFixCmdPre * call QfAutoCmdHandler('pre', expand('<amatch>')) autocmd QuickFixCmdPost * call QfAutoCmdHandler('post', expand('<amatch>')) @@ -1586,9 +1612,9 @@ function Test_Autocmd() \ 'precaddbuffer', \ 'postcaddbuffer'] call assert_equal(l, g:acmds) -endfunction +endfunc -function! Test_Autocmd_Exception() +func Test_Autocmd_Exception() set efm=%m lgetexpr '?' @@ -1603,4 +1629,47 @@ function! Test_Autocmd_Exception() call assert_equal('1', getloclist(0)[0].text) set efm&vim -endfunction +endfunc + +func Test_caddbuffer_wrong() + " This used to cause a memory access in freed memory. + let save_efm = &efm + set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.# + cgetexpr ['WWWW', 'EEEE', 'CCCC'] + let &efm = save_efm + caddbuffer + bwipe! +endfunc + +func Test_caddexpr_wrong() + " This used to cause a memory access in freed memory. + cbuffer + cbuffer + copen + let save_efm = &efm + set efm=% + call assert_fails('caddexpr ""', 'E376:') + let &efm = save_efm +endfunc + +func Test_dirstack_cleanup() + " This used to cause a memory access in freed memory. + let save_efm = &efm + lexpr '0' + lopen + fun X(c) + let save_efm=&efm + set efm=%D%f + if a:c == 'c' + caddexpr '::' + else + laddexpr ':0:0' + endif + let &efm=save_efm + endfun + call X('c') + call X('l') + call setqflist([], 'r') + caddbuffer + let &efm = save_efm +endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index ab78a72909..0975b87ea3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1007,7 +1007,7 @@ static void tui_flush(UI *ui) size_t nrevents = loop_size(data->loop); if (nrevents > TOO_MANY_EVENTS) { - ILOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); + WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); // Back-pressure: UI events may accumulate much faster than the terminal // device can serve them. Even if SIGINT/CTRL-C is received, user must still // wait for the TUI event-queue to drain, and if there are ~millions of @@ -1690,7 +1690,7 @@ static const char *tui_get_stty_erase(void) if (tcgetattr(input_global_fd(), &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[1] = '\0'; - ILOG("stty/termios:erase=%s", stty_erase); + DLOG("stty/termios:erase=%s", stty_erase); } #endif return stty_erase; @@ -1707,12 +1707,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, } if (strequal(name, "key_backspace")) { - ILOG("libtermkey:kbs=%s", value); + DLOG("libtermkey:kbs=%s", value); if (stty_erase[0] != 0) { return stty_erase; } } else if (strequal(name, "key_dc")) { - ILOG("libtermkey:kdch1=%s", value); + DLOG("libtermkey:kdch1=%s", value); // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 0a2154438f..a60c061949 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,6 +8,7 @@ #include <limits.h> #include "nvim/vim.h" +#include "nvim/log.h" #include "nvim/ui.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -59,6 +60,27 @@ static int busy = 0; static int height, width; static int old_mode_idx = -1; +#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL +# define UI_LOG(funname, ...) +#else +static size_t uilog_seen = 0; +static char uilog_last_event[1024] = { 0 }; +# define UI_LOG(funname, ...) \ + do { \ + if (strequal(uilog_last_event, STR(funname))) { \ + uilog_seen++; \ + } else { \ + if (uilog_seen > 0) { \ + do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \ + "%s (+%zu times...)", uilog_last_event, uilog_seen); \ + } \ + DLOG("ui: " STR(funname)); \ + uilog_seen = 0; \ + xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \ + } \ + } while (0) +#endif + // UI_CALL invokes a function on all registered UI instances. The functions can // have 0-5 arguments (configurable by SELECT_NTH). // @@ -67,6 +89,7 @@ static int old_mode_idx = -1; # define UI_CALL(funname, ...) \ do { \ flush_cursor_update(); \ + UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_MORE(funname, __VA_ARGS__); \ @@ -76,6 +99,7 @@ static int old_mode_idx = -1; # define UI_CALL(...) \ do { \ flush_cursor_update(); \ + UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ @@ -85,6 +109,7 @@ static int old_mode_idx = -1; #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) #define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) +// Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) #define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) #define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 0165db7c0c..5585886612 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -24,30 +24,10 @@ #define UI(b) (((UIBridgeData *)b)->ui) -#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL -static size_t uilog_seen = 0; -static argv_callback uilog_event = NULL; -#define UI_CALL(ui, name, argc, ...) \ - do { \ - if (uilog_event == ui_bridge_##name##_event) { \ - uilog_seen++; \ - } else { \ - if (uilog_seen > 0) { \ - DLOG("UI bridge: ...%zu times", uilog_seen); \ - } \ - DLOG("UI bridge: " STR(name)); \ - uilog_seen = 0; \ - uilog_event = ui_bridge_##name##_event; \ - } \ - ((UIBridgeData *)ui)->scheduler( \ - event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)); \ - } while (0) -#else // Schedule a function call on the UI bridge thread. -#define UI_CALL(ui, name, argc, ...) \ +#define UI_BRIDGE_CALL(ui, name, argc, ...) \ ((UIBridgeData *)ui)->scheduler( \ event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) -#endif #define INT2PTR(i) ((void *)(intptr_t)i) #define PTR2INT(p) ((Integer)(intptr_t)p) @@ -128,7 +108,7 @@ static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; bool stopped = bridge->stopped = false; - UI_CALL(b, stop, 1, b); + UI_BRIDGE_CALL(b, stop, 1, b); for (;;) { uv_mutex_lock(&bridge->mutex); stopped = bridge->stopped; @@ -154,7 +134,7 @@ static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); *a = attrs; - UI_CALL(b, highlight_set, 2, b, a); + UI_BRIDGE_CALL(b, highlight_set, 2, b, a); } static void ui_bridge_highlight_set_event(void **argv) { @@ -167,7 +147,7 @@ static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; uv_mutex_lock(&data->mutex); - UI_CALL(b, suspend, 1, b); + UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; // suspend the main thread until CONTINUE is called by the UI thread while (!data->ready) { diff --git a/src/nvim/version.c b/src/nvim/version.c index b6a0ca3e1b..62e8919913 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -644,23 +644,23 @@ static const int included_patches[] = { // 88, // 87 NA // 86, - // 85, - // 84, + 85, + 84, 83, // 82 NA // 81, // 80 NA - // 79, - // 78, + 79, + 78, // 77 NA // 76 NA // 75, // 74, - // 73, + 73, // 72 NA // 71 NA // 70 NA - // 69, + 69, 68, // 67 NA 66, @@ -677,7 +677,7 @@ static const int included_patches[] = { // 55 NA // 54 NA 53, - // 52, + 52, // 51 NA // 50 NA 49, @@ -691,19 +691,19 @@ static const int included_patches[] = { 41, 40, // 39 NA - // 38, + 38, 37, // 36 NA 35, // 34, 33, 32, - // 31, + 31, // 30 NA // 29 NA // 28 NA // 27 NA - // 26, + 26, 25, // 24 NA 23, @@ -712,7 +712,7 @@ static const int included_patches[] = { // 20, 19, // 18, - // 17, + 17, // 16 NA // 15 NA // 14 NA @@ -725,8 +725,8 @@ static const int included_patches[] = { // 7 NA 6, // 5 NA - // 4, - // 3, + 4, + 3, 2, 1, 0 diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 5d2c27a2f4..c71ca411ea 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -319,7 +319,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() // Lowest number used for window ID. Cannot have this many windows per tab. #define LOWEST_WIN_ID 1000 -#if defined(__FreeBSD__) && defined(S_ISCHR) +#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR) # define OPEN_CHR_FILES #endif diff --git a/src/nvim/window.c b/src/nvim/window.c index 29f5412ba0..faf5bceb56 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -193,7 +193,7 @@ newwindow: /* cursor to previous window with wrap around */ case 'W': CHECK_CMDWIN - if (firstwin == lastwin && Prenum != 1) /* just one window */ + if (ONE_WINDOW && Prenum != 1) /* just one window */ beep_flush(); else { if (Prenum) { /* go to specified window */ @@ -574,7 +574,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) oldwin = curwin; /* add a status line when p_ls == 1 and splitting the first window */ - if (lastwin == firstwin && p_ls == 1 && oldwin->w_status_height == 0) { + if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) { if (oldwin->w_height <= p_wmh && new_wp == NULL) { EMSG(_(e_noroom)); return FAIL; @@ -1182,7 +1182,7 @@ static void win_exchange(long Prenum) win_T *wp2; int temp; - if (lastwin == firstwin) { /* just one window */ + if (ONE_WINDOW) { /* just one window */ beep_flush(); return; } @@ -1271,7 +1271,7 @@ static void win_rotate(int upwards, int count) frame_T *frp; int n; - if (firstwin == lastwin) { /* nothing to do */ + if (ONE_WINDOW) { /* nothing to do */ beep_flush(); return; } @@ -1343,7 +1343,7 @@ static void win_totop(int size, int flags) int dir; int height = curwin->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { beep_flush(); return; } @@ -1728,7 +1728,7 @@ void close_windows(buf_T *buf, int keep_curwin) ++RedrawingDisabled; - for (win_T *wp = firstwin; wp != NULL && lastwin != firstwin; ) { + for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW; ) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { if (win_close(wp, false) == FAIL) { @@ -1810,7 +1810,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { - if (firstwin != lastwin) { + if (!ONE_WINDOW) { return false; } buf_T *old_curbuf = curbuf; @@ -2194,7 +2194,7 @@ winframe_remove ( /* * If there is only one window there is nothing to remove. */ - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) return NULL; /* @@ -2331,7 +2331,7 @@ win_altframe ( frame_T *frp; int b; - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) /* Last window in this tab page, will go to next tab page. */ return alt_tabpage()->tp_curwin->w_frame; @@ -2851,7 +2851,7 @@ close_others ( win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); } - if (message && lastwin != firstwin) + if (message && !ONE_WINDOW) EMSG(_("E445: Other window contains changes")); } @@ -5173,7 +5173,7 @@ last_status ( { /* Don't make a difference between horizontal or vertical split. */ last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || lastwin != firstwin)))); + || (p_ls == 1 && (morewin || !ONE_WINDOW)))); } static void last_status_rec(frame_T *fr, int statusline) |