diff options
34 files changed, 344 insertions, 135 deletions
diff --git a/.gitignore b/.gitignore index 670340a519..1eb382424b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,7 @@ compile_commands.json /.nvimlog # Generated by scripts/vim-patch.sh -/.vim-src/ +/.vim-src # Generated by old (Vim) tests. /src/nvim/testdir/del diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index c3736d9a3e..cff87b2fed 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5361,9 +5361,8 @@ input({opts}) prompt "" Same as {prompt} in the first form. default "" Same as {text} in the first form. completion nothing Same as {completion} in the first form. - cancelreturn "" Same as {cancelreturn} from - |inputdialog()|. Also works with - input(). + cancelreturn "" The value returned when the dialog is + cancelled. highlight nothing Highlight handler: |Funcref|. The highlighting set with |:echohl| is used for the prompt. @@ -10099,6 +10098,8 @@ This function can then be called with: > The recursiveness of user functions is restricted with the |'maxfuncdepth'| option. +It is also possible to use `:eval`. It does not support a range. + AUTOMATICALLY LOADING FUNCTIONS ~ *autoload-functions* @@ -10532,6 +10533,20 @@ text... Unlock the internal variable {name}. Does the opposite of |:lockvar|. + *:eval* +:eval {expr} Evaluate {expr} and discard the result. Example: > + :eval append(Filter(Getlist()), '$') + +< The expression is supposed to have a side effect, + since the resulting value is not used. In the example + the `append()` call appends the List with text to the + buffer. This is similar to `:call` but works with any + expression. + + The command can be shortened to `:ev` or `:eva`, but + these are hard to recognize and therefore not to be + used. + :if {expr1} *:if* *:end* *:endif* *:en* *E171* *E579* *E580* :en[dif] Execute the commands until the next matching ":else" diff --git a/runtime/filetype.vim b/runtime/filetype.vim index b7157a14e7..7bfe048bbf 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -413,6 +413,10 @@ endif " Lynx config files au BufNewFile,BufRead lynx.cfg setf lynx +" Modula-3 configuration language (must be before *.cfg and *makefile) +au BufNewFile,BufRead *.quake,cm3.cfg setf m3quake +au BufNewFile,BufRead m3makefile,m3overrides setf m3build + " Quake au BufNewFile,BufRead *baseq[2-3]/*.cfg,*id1/*.cfg setf quake au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake @@ -1041,10 +1045,10 @@ au BufNewFile,BufRead *.mod \ setf modsim3 | \ endif -" Modula 2 (.md removed in favor of Markdown) +" Modula-2 (.md removed in favor of Markdown) au BufNewFile,BufRead *.m2,*.DEF,*.MOD,*.mi setf modula2 -" Modula 3 (.m3, .i3, .mg, .ig) +" Modula-3 (.m3, .i3, .mg, .ig) au BufNewFile,BufRead *.[mi][3g] setf modula3 " Monk diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 59b7180cf1..cb3d4bbe9f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -267,14 +267,20 @@ end --@private --- Memoizes a function. On first run, the function return value is saved and ---- immediately returned on subsequent runs. +--- immediately returned on subsequent runs. If the function returns a multival, +--- only the first returned value will be memoized and returned. The function will only be run once, +--- even if it has side-effects. --- --@param fn (function) Function to run --@returns (function) Memoized function local function once(fn) local value + local ran = false return function(...) - if not value then value = fn(...) end + if not ran then + value = fn(...) + ran = true + end return value end end @@ -1239,9 +1245,49 @@ function lsp.buf_request(bufnr, method, params, handler) return client_request_ids, _cancel_all_requests end ---- Sends a request to a server and waits for the response. +---Sends an async request for all active clients attached to the buffer. +---Executes the callback on the combined result. +---Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are +---different. +--- +--@param bufnr (number) Buffer handle, or 0 for current. +--@param method (string) LSP method name +--@param params (optional, table) Parameters to send to the server +--@param callback (function) The callback to call when all requests are finished. +-- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. +-- A map of client_id:request_result will be provided to the callback +-- +--@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`. +function lsp.buf_request_all(bufnr, method, params, callback) + local request_results = {} + local result_count = 0 + local expected_result_count = 0 + local cancel, client_request_ids + + local set_expected_result_count = once(function() + for _ in pairs(client_request_ids) do + expected_result_count = expected_result_count + 1 + end + end) + + local function _sync_handler(err, _, result, client_id) + request_results[client_id] = { error = err, result = result } + result_count = result_count + 1 + set_expected_result_count() + + if result_count >= expected_result_count then + callback(request_results) + end + end + + client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) + + return cancel +end + +--- Sends a request to all server and waits for the response of all of them. --- ---- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting the result. +--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result. --- Parameters are the same as |vim.lsp.buf_request()| but the return result is --- different. Wait maximum of {timeout_ms} (default 100) ms. --- @@ -1255,26 +1301,21 @@ end --- returns `(nil, err)` where `err` is a string describing the failure --- reason. function lsp.buf_request_sync(bufnr, method, params, timeout_ms) - local request_results = {} - local result_count = 0 - local function _sync_handler(err, _, result, client_id) - request_results[client_id] = { error = err, result = result } - result_count = result_count + 1 - end - local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) - local expected_result_count = 0 - for _ in pairs(client_request_ids) do - expected_result_count = expected_result_count + 1 - end + local request_results + + local cancel = lsp.buf_request_all(bufnr, method, params, function(it) + request_results = it + end) local wait_result, reason = vim.wait(timeout_ms or 100, function() - return result_count >= expected_result_count + return request_results ~= nil end, 10) if not wait_result then cancel() return nil, wait_result_reason[reason] end + return request_results end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 28a669778a..71ec85381b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -470,6 +470,7 @@ function M.apply_text_document_edit(text_document_edit, index) -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier if should_check_version and (text_document.version + and text_document.version > 0 and M.buf_versions[bufnr] and M.buf_versions[bufnr] > text_document.version) then print("Buffer ", text_document.uri, " newer than edits.") diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c55dc39605..e79a7a2de2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1437,6 +1437,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// default /// - "combine": combine with background text color /// - "blend": blend with background text color. +/// - hl_eol : when true, for a multiline highlight covering the +/// EOL of a line, continue the highlight for the rest +/// of the screen line (just like for diff and +/// cursorline highlight). /// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current @@ -1581,6 +1585,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("hl_eol", k.data)) { + decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err); + if (ERROR_SET(err)) { + goto error; + } } else if (strequal("hl_mode", k.data)) { if (v->type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, @@ -1669,7 +1678,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ephemeral) { d = &decor; } else if (kv_size(decor.virt_text) - || decor.priority != DECOR_PRIORITY_BASE) { + || decor.priority != DECOR_PRIORITY_BASE + || decor.hl_eol) { // TODO(bfredl): this is a bit sketchy. eventually we should // have predefined decorations for both marks/ephemerals d = xcalloc(1, sizeof(*d)); @@ -1680,7 +1690,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c98f2786c2..ce4163fccf 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1844,7 +1844,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, EMSG(_("W14: Warning: List of file names overflow")); if (emsg_silent == 0) { ui_flush(); - os_delay(3000L, true); // make sure it is noticed + os_delay(3001L, true); // make sure it is noticed } top_file_num = 1; } diff --git a/src/nvim/change.c b/src/nvim/change.c index 38bd591eca..74e27ca880 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -68,7 +68,7 @@ void change_warning(int col) (void)msg_end(); if (msg_silent == 0 && !silent_mode && ui_active()) { ui_flush(); - os_delay(1000L, true); // give the user time to think about it + os_delay(1002L, true); // give the user time to think about it } curbuf->b_did_warn = true; redraw_cmdline = false; // don't redraw and erase the message @@ -109,7 +109,7 @@ void changed(void) // and don't let the emsg() set msg_scroll. if (need_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(2000L, true); + os_delay(2002L, true); wait_return(true); msg_scroll = save_msg_scroll; } else { diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 52a48ae6fb..e39d2328f5 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -144,9 +144,9 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) state->row = -1; state->buf = buf; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = 0; @@ -190,14 +190,14 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) if (mark.id&MARKTREE_END_FLAG) { decor_add(state, altpos.row, altpos.col, mark.row, mark.col, - decor, false, 0); + decor, false); } else { if (altpos.row == -1) { altpos.row = mark.row; altpos.col = mark.col; } decor_add(state, mark.row, mark.col, altpos.row, altpos.col, - decor, false, 0); + decor, false); } next_mark: @@ -222,22 +222,19 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) } static void decor_add(DecorState *state, int start_row, int start_col, - int end_row, int end_col, Decoration *decor, bool owned, - DecorPriority priority) + int end_row, int end_col, Decoration *decor, bool owned) { int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - HlRange range = { start_row, start_col, end_row, end_col, - attr_id, MAX(priority, decor->priority), - decor->virt_text, - decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, + DecorRange range = { start_row, start_col, end_row, end_col, + *decor, attr_id, kv_size(decor->virt_text) && owned, -1 }; kv_pushp(state->active); size_t index; for (index = kv_size(state->active)-1; index > 0; index--) { - HlRange item = kv_A(state->active, index-1); - if (item.priority <= range.priority) { + DecorRange item = kv_A(state->active, index-1); + if (item.decor.priority <= range.decor.priority) { break; } kv_A(state->active, index) = kv_A(state->active, index-1); @@ -291,7 +288,7 @@ int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, } decor_add(state, mark.row, mark.col, endpos.row, endpos.col, - decor, false, 0); + decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -300,11 +297,11 @@ next_mark: int attr = 0; size_t j = 0; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && kv_size(item.virt_text))) { + if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { keep = false; } } else { @@ -324,13 +321,13 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if ((item.start_row == state->row && item.start_col <= col) - && kv_size(item.virt_text) && item.virt_col == -1) { - item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; + && kv_size(item.decor.virt_text) && item.virt_col == -1) { + item.virt_col = (item.decor.virt_text_hide && hidden) ? -2 : virt_col; } if (keep) { kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = j; @@ -343,28 +340,34 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr) { decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + VirtText text = VIRTTEXT_EMPTY; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && kv_size(item.virt_text) - && item.virt_text_pos == kVTEndOfLine) { - return item.virt_text; + DecorRange item = kv_A(state->active, i); + if (!kv_size(text) + && item.start_row == state->row && kv_size(item.decor.virt_text) + && item.decor.virt_text_pos == kVTEndOfLine) { + text = item.decor.virt_text; + } + + if (item.decor.hl_eol && item.start_row <= state->row) { + *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return VIRTTEXT_EMPTY; + + return text; } void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, - Decoration *decor, DecorPriority priority) + Decoration *decor) { if (end_row == -1) { end_row = start_row; end_col = start_col; } - decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, - priority); + decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index c5424a1642..08d69060f0 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -37,33 +37,28 @@ struct Decoration VirtTextPos virt_text_pos; bool virt_text_hide; HlMode hl_mode; + bool hl_eol; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free }; #define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ - kHlModeUnknown, DECOR_PRIORITY_BASE, false } + kHlModeUnknown, false, DECOR_PRIORITY_BASE, false } typedef struct { int start_row; int start_col; int end_row; int end_col; - int attr_id; - // TODO(bfredl): embed decoration instead, perhaps using an arena - // for ephemerals? - DecorPriority priority; - VirtText virt_text; - VirtTextPos virt_text_pos; - bool virt_text_hide; - HlMode hl_mode; + Decoration decor; + int attr_id; // cached lookup of decor.hl_id bool virt_text_owned; int virt_col; -} HlRange; +} DecorRange; typedef struct { MarkTreeIter itr[1]; - kvec_t(HlRange) active; + kvec_t(DecorRange) active; buf_T *buf; int top_row; int row; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ea13052f25..f2fddc89fe 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2058,7 +2058,7 @@ static bool check_compl_option(bool dict_opt) vim_beep(BO_COMPL); setcursor(); ui_flush(); - os_delay(2000L, false); + os_delay(2004L, false); } return false; } diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 689d05e079..00260bc3f7 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -833,6 +833,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, bool islambda = false; char_u numbuf[NUMBUFLEN]; char_u *name; + typval_T *tv_to_free[MAX_FUNC_ARGS]; + int tv_to_free_len = 0; proftime_T wait_start; proftime_T call_start; int started_profiling = false; @@ -985,6 +987,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv = isdefault ? def_rettv : argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (isdefault) { + // Need to free this later, no matter where it's stored. + tv_to_free[tv_to_free_len++] = &v->di_tv; + } + if (addlocal) { // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. @@ -1209,7 +1216,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, did_emsg |= save_did_emsg; depth--; - + for (int i = 0; i < tv_to_free_len; i++) { + tv_clear(tv_to_free[i]); + } cleanup_function_call(fc); if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index b191e8cf67..3e330b88a2 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -968,12 +968,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, kExtmarkNOOP); - // extmarks are handled separately - extmark_move_region(curbuf, line1-1, 0, start_byte, - line2-line1+1, 0, extent_byte, - dest+line_off, 0, dest_byte+byte_off, - kExtmarkUndo); - changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -995,6 +989,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) smsg(_("%" PRId64 " lines moved"), (int64_t)num_lines); } + extmark_move_region(curbuf, line1-1, 0, start_byte, + line2-line1+1, 0, extent_byte, + dest+line_off, 0, dest_byte+byte_off, + kExtmarkUndo); + /* * Leave the cursor on the last of the moved lines. */ diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 2965ea7496..d99383303b 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -928,6 +928,12 @@ module.cmds = { func='ex_edit', }, { + command='eval', + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + addr_type='ADDR_NONE', + func='ex_eval', + }, + { command='ex', flags=bit.bor(BANG, FILE1, CMDARG, ARGOPT, TRLBAR), addr_type='ADDR_NONE', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d1eddfc74f..ae5c334592 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1857,6 +1857,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_echoerr: case CMD_echomsg: case CMD_echon: + case CMD_eval: case CMD_execute: case CMD_filter: case CMD_help: diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 0917c6dd02..5ca88002f1 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -788,6 +788,15 @@ void report_discard_pending(int pending, void *value) } } +// ":eval". +void ex_eval(exarg_T *eap) +{ + typval_T tv; + + if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) { + tv_clear(&tv); + } +} /* * ":if". diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index cacbeddb32..2906a2196b 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -702,6 +702,7 @@ void extmark_move_region( int new_row, colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { + curbuf->deleted_bytes2 = 0; // TODO(bfredl): this is not synced to the buffer state inside the callback. // But unless we make the undo implementation smarter, this is not ensured // anyway. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 65bd809436..792ef81665 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4947,11 +4947,11 @@ int buf_check_timestamp(buf_T *buf) (void)msg_end(); if (emsg_silent == 0) { ui_flush(); - /* give the user some time to think about it */ - os_delay(1000L, true); + // give the user some time to think about it + os_delay(1004L, true); - /* don't redraw and erase the message */ - redraw_cmdline = FALSE; + // don't redraw and erase the message + redraw_cmdline = false; } } already_warned = TRUE; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f016ef6813..c948881eca 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -630,9 +630,9 @@ static void normal_redraw_mode_message(NormalState *s) ui_cursor_shape(); // show different cursor shape ui_flush(); if (msg_scroll || emsg_on_display) { - os_delay(1000L, true); // wait at least one second + os_delay(1003L, true); // wait at least one second } - os_delay(3000L, false); // wait up to three seconds + os_delay(3003L, false); // wait up to three seconds State = save_State; msg_scroll = false; diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index e7e0dc4013..9ea74716aa 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -62,6 +62,7 @@ uint64_t os_now(void) /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. void os_delay(uint64_t ms, bool ignoreinput) { + DLOG("%" PRIu64 " ms", ms); if (ignoreinput) { if (ms > INT_MAX) { ms = INT_MAX; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 9fb2eb2772..6be3b6fb60 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3951,7 +3951,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state); + virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr); if (kv_size(virt_text)) { do_virttext = true; } @@ -4381,11 +4381,12 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) { DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange *item = &kv_A(state->active, i); - if (item->start_row == state->row && kv_size(item->virt_text) - && item->virt_text_pos == kVTOverlay + DecorRange *item = &kv_A(state->active, i); + if (item->start_row == state->row && kv_size(item->decor.virt_text) + && item->decor.virt_text_pos == kVTOverlay && item->virt_col >= 0) { - VirtText vt = item->virt_text; + VirtText vt = item->decor.virt_text; + HlMode hl_mode = item->decor.hl_mode; LineState s = LINE_STATE(""); int virt_attr = 0; int col = item->virt_col; @@ -4405,9 +4406,9 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) } int attr; bool through = false; - if (item->hl_mode == kHlModeCombine) { + if (hl_mode == kHlModeCombine) { attr = hl_combine_attr(linebuf_attr[col], virt_attr); - } else if (item->hl_mode == kHlModeBlend) { + } else if (hl_mode == kHlModeBlend) { through = (*s.p == ' '); attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); } else { @@ -6226,7 +6227,7 @@ void check_for_delay(int check_msg_scroll) && !did_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1006L, true); emsg_on_display = false; if (check_msg_scroll) { msg_scroll = false; diff --git a/src/nvim/search.c b/src/nvim/search.c index c4479a077e..abe05bbd12 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2373,10 +2373,11 @@ showmatch( * brief pause, unless 'm' is present in 'cpo' and a character is * available. */ - if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) - os_delay(p_mat * 100L, true); - else if (!char_avail()) - os_delay(p_mat * 100L, false); + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { + os_delay(p_mat * 100L + 8, true); + } else if (!char_avail()) { + os_delay(p_mat * 100L + 9, false); + } curwin->w_cursor = save_cursor; // restore cursor position *so = save_so; *siso = save_siso; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 825aef1465..77a751e5ad 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5306,13 +5306,17 @@ get_id_list( xfree(name); break; } - if (name[1] == 'A') - id = SYNID_ALLBUT; - else if (name[1] == 'T') - id = SYNID_TOP; - else - id = SYNID_CONTAINED; - id += current_syn_inc_tag; + if (name[1] == 'A') { + id = SYNID_ALLBUT + current_syn_inc_tag; + } else if (name[1] == 'T') { + if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { + id = curwin->w_s->b_syn_topgrp; + } else { + id = SYNID_TOP + current_syn_inc_tag; + } + } else { + id = SYNID_CONTAINED + current_syn_inc_tag; + } } else if (name[1] == '@') { if (skip) { id = -1; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 588821f260..a6310344e9 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -625,7 +625,7 @@ do_tag( } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1007L, true); } } @@ -2853,7 +2853,7 @@ static int jumpto_tag( MSG(_("E435: Couldn't find tag, just guessing!")); if (!msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1010L, true); } } retval = OK; diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5e99edf233..5611560b1b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -276,28 +276,28 @@ func Test_augroup_warning() augroup TheWarning au VimEnter * echo 'entering' augroup END - call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0) + call assert_match("TheWarning.*VimEnter", execute('au VimEnter')) redir => res augroup! TheWarning redir END - call assert_true(match(res, "W19:") >= 0) - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("W19:", res) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) " check "Another" does not take the pace of the deleted entry augroup Another augroup END - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) augroup! Another " no warning for postpone aucmd delete augroup StartOK au VimEnter * call RemoveGroup() augroup END - call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0) + call assert_match("StartOK.*VimEnter", execute('au VimEnter')) redir => res doautocmd VimEnter redir END - call assert_true(match(res, "W19:") < 0) + call assert_notmatch("W19:", res) au! VimEnter endfunc @@ -325,7 +325,7 @@ func Test_augroup_deleted() au VimEnter * echo augroup end augroup! x - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) au! VimEnter endfunc diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim index d680b442db..11459991ea 100644 --- a/src/nvim/testdir/test_backspace_opt.vim +++ b/src/nvim/testdir/test_backspace_opt.vim @@ -1,15 +1,5 @@ " Tests for 'backspace' settings -func Exec(expr) - let str='' - try - exec a:expr - catch /.*/ - let str=v:exception - endtry - return str -endfunc - func Test_backspace_option() set backspace= call assert_equal('', &backspace) @@ -41,10 +31,10 @@ func Test_backspace_option() set backspace-=eol call assert_equal('', &backspace) " Check the error - call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474')) - call assert_equal(0, match(Exec('set backspace+=def'), '.*E474')) + call assert_fails('set backspace=ABC', 'E474:') + call assert_fails('set backspace+=def', 'E474:') " NOTE: Vim doesn't check following error... - "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) + "call assert_fails('set backspace-=ghi', 'E474:') " Check backwards compatibility with version 5.4 and earlier set backspace=0 @@ -55,8 +45,8 @@ func Test_backspace_option() call assert_equal('2', &backspace) set backspace=3 call assert_equal('3', &backspace) - call assert_false(match(Exec('set backspace=4'), '.*E474')) - call assert_false(match(Exec('set backspace=10'), '.*E474')) + call assert_fails('set backspace=4', 'E474:') + call assert_fails('set backspace=10', 'E474:') " Cleared when 'compatible' is set " set compatible diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 09d79979ce..0b41a1127a 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -501,3 +501,12 @@ func Test_empty_concatenate() call assert_equal('b', 'a'[4:0] . 'b') call assert_equal('b', 'b' . 'a'[4:0]) endfunc + +func Test_eval_after_if() + let s:val = '' + func SetVal(x) + let s:val ..= a:x + endfunc + if 0 | eval SetVal('a') | endif | call SetVal('b') + call assert_equal('b', s:val) +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 1a98dc6451..bd2e673570 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -275,6 +275,8 @@ let s:filename_checks = { \ 'lss': ['file.lss'], \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], \ 'lynx': ['lynx.cfg'], + \ 'm3build': ['m3makefile', 'm3overrides'], + \ 'm3quake': ['file.quake', 'cm3.cfg'], \ 'm4': ['file.at'], \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'], \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'], diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 555f549743..93f567b3a0 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1071,10 +1071,10 @@ func Test_inputlist() endfunc func Test_balloon_show() - if has('balloon_eval') - " This won't do anything but must not crash either. - call balloon_show('hi!') - endif + CheckFeature balloon_eval + + " This won't do anything but must not crash either. + call balloon_show('hi!') endfunc func Test_shellescape() @@ -1448,4 +1448,12 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +func HasDefault(msg = 'msg') + return a:msg +endfunc + +func Test_default_arg_value() + call assert_equal('msg', HasDefault()) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 66cb0bbe22..875e23894f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -24,6 +24,32 @@ func GetSyntaxItem(pat) return c endfunc +func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") + " Assert that the characters starting at a given (line, col) + " sequentially match the expected highlight groups. + " If groups are provided as a string, each character is assumed to be a + " group and spaces represent no group, useful for visually describing tests. + let l:expectedGroups = type(a:expected) == v:t_string + "\ ? a:expected->split('\zs')->map({_, v -> trim(v)}) + \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)}) + \ : a:expected + let l:errors = 0 + " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") + let l:msg = (empty(a:msg) ? "" : a:msg .. ": ") + \ .. "Wrong highlight group at " .. a:lnum .. "," + + " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) + " let l:errors += synID(a:lnum, l:i, a:trans) + " \ ->synIDattr("name") + " \ ->assert_equal(l:expectedGroups[l:i - 1], + for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1) + let l:errors += + \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"), + \ l:expectedGroups[l:i - 1], + \ l:msg .. l:i) + endfor +endfunc + func Test_syn_iskeyword() new call setline(1, [ @@ -707,3 +733,22 @@ func Test_syntax_foldlevel() quit! endfunc + +func Test_syn_include_contains_TOP() + let l:case = "TOP in included syntax means its group list name" + new + syntax include @INCLUDED syntax/c.vim + syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED + + call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ]) + let l:expected = ["cCppOutIf2"] + eval AssertHighlightGroups(3, 1, l:expected, 1) + " cCppOutElse has contains=TOP + let l:expected = ["cType"] + eval AssertHighlightGroups(5, 1, l:expected, 1, l:case) + syntax clear + bw! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua index 1d52e9948f..3f9a0ad09b 100644 --- a/test/functional/autoread/focus_spec.lua +++ b/test/functional/autoread/focus_spec.lua @@ -9,6 +9,7 @@ local feed_data = thelpers.feed_data if helpers.pending_win32(pending) then return end describe('autoread TUI FocusGained/FocusLost', function() + local f1 = 'xtest-foo' local screen before_each(function() @@ -17,8 +18,12 @@ describe('autoread TUI FocusGained/FocusLost', function() ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') end) + teardown(function() + os.remove(f1) + end) + it('external file change', function() - local path = 'xtest-foo' + local path = f1 local expected_addition = [[ line 1 line 2 diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index b94abd3a12..5da8452a51 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -919,6 +919,23 @@ describe('lua: nvim_buf_attach on_bytes', function() end) + it("flushes deleted bytes on move", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB", "CCC", "DDD"}) + + feed(":.move+1<cr>") + + check_events { + { "test1", "bytes", 1, 5, 0, 0, 0, 1, 0, 4, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 1, 0, 4, 0, 0, 0, 1, 0, 4 }; + } + + feed("jd2j") + + check_events { + { "test1", "bytes", 1, 6, 2, 0, 8, 2, 0, 8, 0, 0, 0 }; + } + end) + teardown(function() os.remove "Xtest-reload" os.remove "Xtest-undofile" diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 66b33cc9e1..557f8a206f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1124,14 +1124,14 @@ describe('LSP', function() '2nd line of 语text'; }, buf_lines(target_bufnr)) end) - it('correctly goes ahead with the edit if the version is vim.NIL', function() - -- we get vim.NIL when we decode json null value. - local json = exec_lua[[ - return vim.fn.json_decode("{ \"a\": 1, \"b\": null }") - ]] - eq(json.b, exec_lua("return vim.NIL")) - - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL"))) + it('always accepts edit with version = 0', function() + exec_lua([[ + local args = {...} + local bufnr = select(1, ...) + local text_edit = select(2, ...) + vim.lsp.util.buf_versions[bufnr] = 10 + vim.lsp.util.apply_text_document_edit(text_edit) + ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of 语text'; diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 295a54aec8..82d3075be2 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -29,6 +29,7 @@ describe('decorations providers', function() [10] = {italic = true, background = Screen.colors.Magenta}; [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; [12] = {foreground = tonumber('0x990000')}; + [13] = {background = Screen.colors.LightBlue}; } end) @@ -331,6 +332,37 @@ describe('decorations providers', function() | ]]} end) + + it('can highlight beyond EOL', function() + insert(mulholland) + setup_provider [[ + local test_ns = a.nvim_create_namespace "veberod" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + if string.find(a.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then + a.nvim_buf_set_extmark(buf, test_ns, line, 0, { + end_line = line+1; + hl_group = 'DiffAdd'; + hl_eol = true; + ephemeral = true; + }) + end + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); | + {13:bufref_T save_buf; }| + {13:switch_buffer(&save_buf, buf); }| + posp = getmark(mark, false); | + {13:restore_buffer(&save_buf);^ }| + | + ]]} + end) end) describe('extmark decorations', function() |