diff options
-rw-r--r-- | runtime/autoload/health/provider.vim | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 24 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 51 | ||||
-rwxr-xr-x | scripts/vim-patch.sh | 24 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 43 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 33 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 17 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 112 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 40 | ||||
-rw-r--r-- | src/nvim/menu.c | 20 | ||||
-rw-r--r-- | src/nvim/normal.c | 8 | ||||
-rw-r--r-- | src/nvim/path.c | 2 | ||||
-rw-r--r-- | src/nvim/tag.c | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_diffmode.vim | 52 | ||||
-rw-r--r-- | test/functional/core/job_spec.lua | 26 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 16 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 151 |
18 files changed, 370 insertions, 257 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index de540405e6..001379c85d 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -423,10 +423,6 @@ function! s:check_python(version) abort \ ' This could lead to confusing error messages.') endif - if a:version == 3 && str2float(pyversion) < 3.3 - call health#report_warn('Python 3.3+ is recommended.') - endif - call health#report_info('Python version: ' . pyversion) if s:is_bad_response(status) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 341a3e82fc..5dd7109bb0 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -297,26 +297,30 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end +local function call_hierarchy(method) + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(err, _, result) + if err then + vim.notify(err.message, vim.log.levels.WARN) + return + end + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, method, { item = call_hierarchy_item }) + end) +end + --- Lists all the call sites of the symbol under the cursor in the --- |quickfix| window. If the symbol can resolve to multiple --- items, the user can pick one in the |inputlist|. function M.incoming_calls() - local params = util.make_position_params() - request('textDocument/prepareCallHierarchy', params, function(_, _, result) - local call_hierarchy_item = pick_call_hierarchy_item(result) - vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item }) - end) + call_hierarchy('callHierarchy/incomingCalls') end --- Lists all the items that are called by the symbol under the --- cursor in the |quickfix| window. If the symbol can resolve to --- multiple items, the user can pick one in the |inputlist|. function M.outgoing_calls() - local params = util.make_position_params() - request('textDocument/prepareCallHierarchy', params, function(_, _, result) - local call_hierarchy_item = pick_call_hierarchy_item(result) - vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item }) - end) + call_hierarchy('callHierarchy/outgoingCalls') end --- List workspace folders. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 57919e907c..e403a8ee1b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -566,13 +566,15 @@ end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= "" then - if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then + local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat] + if insert_text_format == "PlainText" or insert_text_format == nil then return item.textEdit.newText else return M.parse_snippet(item.textEdit.newText) end elseif item.insertText ~= nil and item.insertText ~= "" then - if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then + local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat] + if insert_text_format == "PlainText" or insert_text_format == nil then return item.insertText else return M.parse_snippet(item.insertText) @@ -914,23 +916,6 @@ function M.make_floating_popup_options(width, height, opts) } end -local function _should_add_to_tagstack(new_item) - local stack = vim.fn.gettagstack() - - -- Check if we're at the bottom of the tagstack. - if stack.curidx <= 1 then return true end - - local top_item = stack.items[stack.curidx-1] - - -- Check if the item at the top of the tagstack is exactly the - -- same as the one we want to push. - if top_item.tagname ~= new_item.tagname then return true end - for i, v in ipairs(top_item.from) do - if v ~= new_item.from[i] then return true end - end - return false -end - --- Jumps to a location. --- --@param location (`Location`|`LocationLink`) @@ -939,36 +924,22 @@ function M.jump_to_location(location) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end - - local from_bufnr = vim.fn.bufnr('%') - local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0} - local item = {tagname=vim.fn.expand('<cword>'), from=from} - + local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist - vim.cmd("mark '") + vim.cmd "normal! m'" + + -- Push a new item into tagstack + local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0} + local items = {{tagname=vim.fn.expand('<cword>'), from=from}} + vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't') --- Jump to new location (adjusting for UTF-16 encoding of characters) - local bufnr = vim.uri_to_bufnr(uri) api.nvim_set_current_buf(bufnr) api.nvim_buf_set_option(0, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line local col = get_line_byte_from_position(0, range.start) - -- This prevents the tagstack to be filled with items that provide - -- no motion when CTRL-T is pressed because they're both the source - -- and the destination. - local motionless = - bufnr == from_bufnr and - row+1 == from[2] and col+1 == from[3] - if not motionless and _should_add_to_tagstack(item) then - local winid = vim.fn.win_getid() - local items = {item} - vim.fn.settagstack(winid, {items=items}, 't') - end - - -- Jump to new location api.nvim_win_set_cursor(0, {row + 1, col}) - return true end diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index f583b2fdea..86552c0c8d 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -578,7 +578,7 @@ list_missing_previous_vimpatches_for_patch() { local -a fnames while IFS= read -r line ; do fnames+=("$line") - done < <(git -C "${VIM_SOURCE_DIR}" diff-tree --no-commit-id --name-only -r "${vim_commit}") + done < <(git -C "${VIM_SOURCE_DIR}" diff-tree --no-commit-id --name-only -r "${vim_commit}" -- . ':!src/version.c') local i=0 local n=${#fnames[@]} printf '=== getting missing patches for %d files ===\n' "$n" @@ -593,18 +593,20 @@ list_missing_previous_vimpatches_for_patch() { _set_missing_vimpatches 1 -- "${fname}" set +u # Avoid "unbound variable" with bash < 4.4 below. - local missing_vim_commit_info="${missing_vim_patches[0]}" - if [[ -z "${missing_vim_commit_info}" ]]; then - printf -- "-\n" - else - local missing_vim_commit="${missing_vim_commit_info%%:*}" - if [[ -z "${vim_tag}" ]] || [[ "${missing_vim_commit}" < "${vim_tag}" ]]; then - printf -- "%s\n" "$missing_vim_commit_info" - missing_list+=("$missing_vim_commit_info") + for missing_vim_commit_info in "${missing_vim_patches[@]}"; do + if [[ -z "${missing_vim_commit_info}" ]]; then + printf -- "-\r" else - printf -- "-\n" + printf -- "-\r" + local missing_vim_commit="${missing_vim_commit_info%%:*}" + if [[ -z "${vim_tag}" ]] || [[ "${missing_vim_commit}" < "${vim_tag}" ]]; then + printf -- "%s\n" "$missing_vim_commit_info" + missing_list+=("$missing_vim_commit_info") + else + printf -- "-\r" + fi fi - fi + done set -u done diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 0f7008e150..5abdc33709 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -177,42 +177,47 @@ Object dict_get_value(dict_T *dict, String key, Error *err) return vim_to_object(&di->di_tv); } -/// Set a value in a scope dict. Objects are recursively expanded into their -/// vimscript equivalents. -/// -/// @param dict The vimscript dict -/// @param key The key -/// @param value The new value -/// @param del Delete key in place of setting it. Argument `value` is ignored in -/// this case. -/// @param retval If true the old value will be converted and returned. -/// @param[out] err Details of an error that may have occurred -/// @return The old value if `retval` is true and the key was present, else NIL -Object dict_set_var(dict_T *dict, String key, Object value, bool del, - bool retval, Error *err) +dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err) { - Object rv = OBJECT_INIT; dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); if (di != NULL) { if (di->di_flags & DI_FLAGS_RO) { api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); - return rv; } else if (di->di_flags & DI_FLAGS_LOCK) { api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); - return rv; } else if (del && (di->di_flags & DI_FLAGS_FIX)) { api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); - return rv; } } else if (dict->dv_lock) { api_set_error(err, kErrorTypeException, "Dictionary is locked"); - return rv; } else if (key.size == 0) { api_set_error(err, kErrorTypeValidation, "Key name is empty"); - return rv; } else if (key.size > INT_MAX) { api_set_error(err, kErrorTypeValidation, "Key name is too long"); + } + + return di; +} + +/// Set a value in a scope dict. Objects are recursively expanded into their +/// vimscript equivalents. +/// +/// @param dict The vimscript dict +/// @param key The key +/// @param value The new value +/// @param del Delete key in place of setting it. Argument `value` is ignored in +/// this case. +/// @param retval If true the old value will be converted and returned. +/// @param[out] err Details of an error that may have occurred +/// @return The old value if `retval` is true and the key was present, else NIL +Object dict_set_var(dict_T *dict, String key, Object value, bool del, + bool retval, Error *err) +{ + Object rv = OBJECT_INIT; + dictitem_T *di = dict_check_writable(dict, key, del, err); + + if (ERROR_SET(err)) { return rv; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1ae5cade67..a75cc78b7e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1619,7 +1619,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, char buf[IOSIZE]; // apply :filter /pat/ to variable name - xstrlcpy(buf, prefix, IOSIZE - 1); + xstrlcpy(buf, prefix, IOSIZE); xstrlcat(buf, (char *)di->di_key, IOSIZE); if (message_filtered((char_u *)buf)) { continue; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 072d206ecb..caaf675db2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1850,15 +1850,30 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) ptrdiff_t len = end - str; assert(len > 0); const char * value = str + len + 1; - if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) { + + char c = env[i][len]; + env[i][len] = NUL; + +#ifdef WIN32 + // Upper-case all the keys for Windows so we can detect duplicates + char *const key = strcase_save(str, true); +#else + char *const key = xstrdup(str); +#endif + + env[i][len] = c; + + if (tv_dict_find(rettv->vval.v_dict, key, len) != NULL) { // Since we're traversing from the end of the env block to the front, any // duplicate names encountered should be ignored. This preserves the // semantics of env vars defined later in the env block taking precedence. + xfree(key); continue; } tv_dict_add_str(rettv->vval.v_dict, - str, len, + key, len, value); + xfree(key); } os_free_fullenv(env); } @@ -5096,7 +5111,21 @@ static dict_T *create_environment(const dictitem_T *job_env, } if (job_env) { +#ifdef WIN32 + TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { + // Always use upper-case keys for Windows so we detect duplicate keys + char *const key = strcase_save((const char *)var->di_key, true); + size_t len = strlen(key); + dictitem_T *dv = tv_dict_find(env, key, len); + if (dv) { + tv_dict_item_remove(env, dv); + } + tv_dict_add_str(env, key, len, tv_get_string(&var->di_tv)); + xfree(key); + }); +#else tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force"); +#endif } if (pty) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c557bb2438..c93f9fe6f2 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1772,7 +1772,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, // count, it's a buffer name. /// if ((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg) - && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg)) == NUL + && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg + 1)) == NUL || ascii_iswhite(*p))) { n = getdigits_long(&ea.arg, false, -1); ea.arg = skipwhite(ea.arg); @@ -2790,15 +2790,18 @@ static struct cmdmod { */ int modifier_len(char_u *cmd) { - int i, j; char_u *p = cmd; - if (ascii_isdigit(*cmd)) - p = skipwhite(skipdigits(cmd)); - for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) { - for (j = 0; p[j] != NUL; ++j) - if (p[j] != cmdmods[i].name[j]) + if (ascii_isdigit(*cmd)) { + p = skipwhite(skipdigits(cmd + 1)); + } + for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { + int j; + for (j = 0; p[j] != NUL; j++) { + if (p[j] != cmdmods[i].name[j]) { break; + } + } if (j >= cmdmods[i].minlen && !ASCII_ISALPHA(p[j]) && (p == cmd || cmdmods[i].has_count)) { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f99a2dd0fe..0a52cc16cb 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -471,6 +471,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_wait); lua_setfield(lstate, -2, "wait"); + // _getvar + lua_pushcfunction(lstate, &nlua_getvar); + lua_setfield(lstate, -2, "_getvar"); + + // _setvar + lua_pushcfunction(lstate, &nlua_setvar); + lua_setfield(lstate, -2, "_setvar"); + + // vim.loop luv_set_loop(lstate, &main_loop.uv); luv_set_callback(lstate, nlua_luv_cfpcall); @@ -870,6 +879,109 @@ check_err: return request ? 1 : 0; } +static dict_T *nlua_get_var_scope(lua_State *lstate) { + const char *scope = luaL_checkstring(lstate, 1); + handle_T handle = (handle_T)luaL_checkinteger(lstate, 2); + dict_T *dict = NULL; + Error err = ERROR_INIT; + if (strequal(scope, "g")) { + dict = &globvardict; + } else if (strequal(scope, "v")) { + dict = &vimvardict; + } else if (strequal(scope, "b")) { + buf_T *buf = find_buffer_by_handle(handle, &err); + if (buf) { + dict = buf->b_vars; + } + } else if (strequal(scope, "w")) { + win_T *win = find_window_by_handle(handle, &err); + if (win) { + dict = win->w_vars; + } + } else if (strequal(scope, "t")) { + tabpage_T *tabpage = find_tab_by_handle(handle, &err); + if (tabpage) { + dict = tabpage->tp_vars; + } + } else { + luaL_error(lstate, "invalid scope", err.msg); + return NULL; + } + + if (ERROR_SET(&err)) { + luaL_error(lstate, "FAIL: %s", err.msg); + return NULL; + } + return dict; +} + + +static int nlua_getvar(lua_State *lstate) +{ + // non-local return if not found + dict_T *dict = nlua_get_var_scope(lstate); + size_t len; + const char *name = luaL_checklstring(lstate, 3, &len); + + dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len); + if (di == NULL) { + return 0; // nil + } + nlua_push_typval(lstate, &di->di_tv, false); + return 1; +} + +static int nlua_setvar(lua_State *lstate) +{ + // non-local return if not found + dict_T *dict = nlua_get_var_scope(lstate); + String key; + key.data = (char *)luaL_checklstring(lstate, 3, &key.size); + + bool del = (lua_gettop(lstate) < 4) || lua_isnil(lstate, 4); + + Error err = ERROR_INIT; + dictitem_T *di = dict_check_writable(dict, key, del, &err); + if (ERROR_SET(&err)) { + return 0; + } + + if (del) { + // Delete the key + if (di == NULL) { + // Doesn't exist, nothing to do + return 0; + } else { + // Delete the entry + tv_dict_item_remove(dict, di); + } + } else { + // Update the key + typval_T tv; + + // Convert the lua value to a vimscript type in the temporary variable + lua_pushvalue(lstate, 4); + if (!nlua_pop_typval(lstate, &tv)) { + return luaL_error(lstate, "Couldn't convert lua value"); + } + + if (di == NULL) { + // Need to create an entry + di = tv_dict_item_alloc_len(key.data, key.size); + tv_dict_add(dict, di); + } else { + // Clear the old value + tv_clear(&di->di_tv); + } + + // Update the value + tv_copy(&tv, &di->di_tv); + // Clear the temporary variable + tv_clear(&tv); + } + return 0; +} + static int nlua_nil_tostring(lua_State *lstate) { lua_pushstring(lstate, "vim.NIL"); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index a678432dda..bc0770da31 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -341,32 +341,24 @@ do end return setmetatable({}, mt) end - local function pcall_ret(status, ...) - if status then return ... end - end - local function nil_wrap(fn) - return function(...) - return pcall_ret(pcall(fn, ...)) + local function make_dict_accessor(scope) + validate { + scope = {scope, 's'}; + } + local mt = {} + function mt:__newindex(k, v) + return vim._setvar(scope, 0, k, v) end + function mt:__index(k) + return vim._getvar(scope, 0, k) + end + return setmetatable({}, mt) end - - vim.b = make_meta_accessor( - nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end), - function(v, k) return a.nvim_buf_set_var(0, v, k) end, - function(v) return a.nvim_buf_del_var(0, v) end - ) - vim.w = make_meta_accessor( - nil_wrap(function(v) return a.nvim_win_get_var(0, v) end), - function(v, k) return a.nvim_win_set_var(0, v, k) end, - function(v) return a.nvim_win_del_var(0, v) end - ) - vim.t = make_meta_accessor( - nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end), - function(v, k) return a.nvim_tabpage_set_var(0, v, k) end, - function(v) return a.nvim_tabpage_del_var(0, v) end - ) - vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) - vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.g = make_dict_accessor('g') + vim.v = make_dict_accessor('v') + vim.b = make_dict_accessor('b') + vim.w = make_dict_accessor('w') + vim.t = make_dict_accessor('t') vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) local function getenv(k) diff --git a/src/nvim/menu.c b/src/nvim/menu.c index ac3b7768e6..112f51fc64 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1069,7 +1069,7 @@ char_u *get_menu_names(expand_T *xp, int idx) #define TBUFFER_LEN 256 static char_u tbuffer[TBUFFER_LEN]; /*hack*/ char_u *str; - static int should_advance = FALSE; + static bool should_advance = false; if (idx == 0) { /* first call: start at first item */ menu = expand_menu; @@ -1089,12 +1089,13 @@ char_u *get_menu_names(expand_T *xp, int idx) if (menu->modes & expand_modes) { if (menu->children != NULL) { - if (should_advance) - STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN - 1); - else { - STRLCPY(tbuffer, menu->dname, TBUFFER_LEN - 1); - if (menu->en_dname == NULL) - should_advance = TRUE; + if (should_advance) { + STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN); + } else { + STRLCPY(tbuffer, menu->dname, TBUFFER_LEN); + if (menu->en_dname == NULL) { + should_advance = true; + } } /* hack on menu separators: use a 'magic' char for the separator * so that '.' in names gets escaped properly */ @@ -1105,8 +1106,9 @@ char_u *get_menu_names(expand_T *xp, int idx) str = menu->en_dname; else { str = menu->dname; - if (menu->en_dname == NULL) - should_advance = TRUE; + if (menu->en_dname == NULL) { + should_advance = true; + } } } } else diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2f4c441beb..13706fb14a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1942,10 +1942,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_FORMAT: if (*curbuf->b_p_fex != NUL) { op_formatexpr(oap); // use expression - } else if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { - op_colon(oap); // use external command } else { - op_format(oap, false); // use internal function + if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { + op_colon(oap); // use external command + } else { + op_format(oap, false); // use internal function + } } break; diff --git a/src/nvim/path.c b/src/nvim/path.c index 3e1713fbdd..fe50be5ea1 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -65,7 +65,7 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2, if (expandenv) { expand_env(s1, exp1, MAXPATHL); } else { - xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1); + xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL); } bool id_ok_1 = os_fileid((char *)exp1, &file_id_1); bool id_ok_2 = os_fileid((char *)s2, &file_id_2); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index f0e48013b2..ab35c936ca 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3010,7 +3010,7 @@ static int find_extra(char_u **pp) // Repeat for addresses separated with ';' for (;; ) { if (ascii_isdigit(*str)) { - str = skipdigits(str); + str = skipdigits(str + 1); } else if (*str == '/' || *str == '?') { str = skip_regexp(str + 1, *str, false, NULL); if (*str != first_char) { diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 21c1f98283..1ade11a8ed 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -781,17 +781,66 @@ func Test_diff_lastline() bwipe! endfunc +func WriteDiffFiles(buf, list1, list2) + call writefile(a:list1, 'Xfile1') + call writefile(a:list2, 'Xfile2') + if a:buf + call term_sendkeys(a:buf, ":checktime\<CR>") + endif +endfunc +" Verify a screendump with both the internal and external diff. +func VerifyBoth(buf, dumpfile, extra) + " trailing : for leaving the cursor on the command line + for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"] + call term_sendkeys(a:buf, cmd) + if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external') + break " don't let the next iteration overwrite the "failed" file. + " don't let the next iteration overwrite the "failed" file. + return + endif + endfor + + " also test unified diff + call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:") + call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified') + call term_sendkeys(a:buf, ":call StopUnified()\<CR>:") +endfunc + +" Verify a screendump with the internal diff only. +func VerifyInternal(buf, dumpfile, extra) + call term_sendkeys(a:buf, ":diffupdate!\<CR>") + " trailing : for leaving the cursor on the command line + call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:") + call TermWait(a:buf) + call VerifyScreenDump(a:buf, a:dumpfile, {}) +endfunc + func Test_diff_screen() CheckScreendump CheckFeature menu + let lines =<< trim END + func UnifiedDiffExpr() + " Prepend some text to check diff type detection + call writefile(['warning', ' message'], v:fname_out) + silent exe '!diff -u ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out + endfunc + func SetupUnified() + set diffexpr=UnifiedDiffExpr() + endfunc + func StopUnified() + set diffexpr= + endfunc + END + call writefile(lines, 'XdiffSetup') + " clean up already existing swap files, just in case call delete('.Xfile1.swp') call delete('.Xfile2.swp') " Test 1: Add a line in beginning of file 2 call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - let buf = RunVimInTerminal('-d Xfile1 Xfile2', {}) + let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {}) " Set autoread mode, so that Vim won't complain once we re-write the test " files call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") @@ -911,6 +960,7 @@ func Test_diff_screen() call StopVimInTerminal(buf) call delete('Xfile1') call delete('Xfile2') + call delete('XdiffSetup') endfunc func Test_diff_with_cursorline() diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 9de0d08e79..34ab90d760 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -118,6 +118,32 @@ describe('jobs', function() end end) + it('handles case-insensitively matching #env vars', function() + nvim('command', "let $TOTO = 'abc'") + -- Since $Toto is being set in the job, it should take precedence over the + -- global $TOTO on Windows + nvim('command', "let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}") + if iswin() then + nvim('command', [[let j = jobstart('set | find /I "toto="', g:job_opts)]]) + else + nvim('command', [[let j = jobstart('env | grep -i toto=', g:job_opts)]]) + end + nvim('command', "call jobwait([j])") + nvim('command', "let g:output = Normalize(g:job_opts.stdout)") + local actual = eval('g:output') + local expected + if iswin() then + -- Toto is normalized to TOTO so we can detect duplicates, and because + -- Windows doesn't care about case + expected = {'TOTO=def', ''} + else + expected = {'TOTO=abc', 'Toto=def', ''} + end + table.sort(actual) + table.sort(expected) + eq(expected, actual) + end) + it('uses &shell and &shellcmdflag if passed a string', function() nvim('command', "let $VAR = 'abc'") if iswin() then diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9bf00b594b..6566da4807 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -945,12 +945,20 @@ describe('lua stdlib', function() exec_lua [[ vim.api.nvim_set_var("testing", "hi") vim.api.nvim_set_var("other", 123) + vim.api.nvim_set_var("floaty", 5120.1) + vim.api.nvim_set_var("nullvar", vim.NIL) vim.api.nvim_set_var("to_delete", {hello="world"}) ]] eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") + eq(5120.1, funcs.luaeval "vim.g.floaty") eq(NIL, funcs.luaeval "vim.g.nonexistant") + eq(NIL, funcs.luaeval "vim.g.nullvar") + -- lost over RPC, so test locally: + eq({false, true}, exec_lua [[ + return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL} + ]]) eq({hello="world"}, funcs.luaeval "vim.g.to_delete") exec_lua [[ @@ -963,12 +971,20 @@ describe('lua stdlib', function() exec_lua [[ vim.api.nvim_buf_set_var(0, "testing", "hi") vim.api.nvim_buf_set_var(0, "other", 123) + vim.api.nvim_buf_set_var(0, "floaty", 5120.1) + vim.api.nvim_buf_set_var(0, "nullvar", vim.NIL) vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"}) ]] eq('hi', funcs.luaeval "vim.b.testing") eq(123, funcs.luaeval "vim.b.other") + eq(5120.1, funcs.luaeval "vim.b.floaty") eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b.nullvar") + -- lost over RPC, so test locally: + eq({false, true}, exec_lua [[ + return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} + ]]) eq({hello="world"}, funcs.luaeval "vim.b.to_delete") exec_lua [[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 2b7198bf63..663271deab 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -9,6 +9,7 @@ local eq = helpers.eq local pcall_err = helpers.pcall_err local pesc = helpers.pesc local insert = helpers.insert +local funcs = helpers.funcs local retry = helpers.retry local NIL = helpers.NIL local read_file = require('test.helpers').read_file @@ -1392,10 +1393,10 @@ describe('LSP', function() { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} }, { label='foocar', sortText="f", textEdit={newText='foobar'} }, -- real-world snippet text - { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, - { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} }, + { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, + { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} }, -- nested snippet tokens - { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} }, + { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} }, -- plain text { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, } @@ -1407,9 +1408,9 @@ describe('LSP', function() { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, } @@ -1820,36 +1821,20 @@ describe('LSP', function() end) describe('lsp.util.jump_to_location', function() - local default_target_bufnr - local default_target_uri = 'file://fake/uri' - - local create_buf = function(uri, lines) - for i, line in ipairs(lines) do - lines[i] = '"' .. line .. '"' - end - lines = table.concat(lines, ", ") - - -- Let's set "hidden" to true in order to avoid errors when switching - -- between buffers in test. - local code = string.format([[ - vim.api.nvim_set_option('hidden', true) + local target_bufnr - local bufnr = vim.uri_to_bufnr("%s") - local lines = {%s} + before_each(function() + target_bufnr = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]], uri, lines) - - return exec_lua(code) - end - - before_each(function() - default_target_bufnr = create_buf(default_target_uri, {'1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄'}) + ]] end) - local location = function(uri, start_line, start_char, end_line, end_char) + local location = function(start_line, start_char, end_line, end_char) return { - uri = uri, + uri = "file://fake/uri", range = { start = { line = start_line, character = start_char }, ["end"] = { line = end_line, character = end_char }, @@ -1857,9 +1842,9 @@ describe('LSP', function() } end - local jump = function(bufnr, msg) + local jump = function(msg) eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) - eq(bufnr, exec_lua[[return vim.fn.bufnr('%')]]) + eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], col = exec_lua[[return vim.fn.col('.')]], @@ -1867,13 +1852,13 @@ describe('LSP', function() end it('jumps to a Location', function() - local pos = jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9)) + local pos = jump(location(0, 9, 0, 9)) eq(1, pos.line) eq(10, pos.col) end) it('jumps to a LocationLink', function() - local pos = jump(default_target_bufnr, { + local pos = jump({ targetUri = "file://fake/uri", targetSelectionRange = { start = { line = 0, character = 4 }, @@ -1889,104 +1874,22 @@ describe('LSP', function() end) it('jumps to the correct multibyte column', function() - local pos = jump(default_target_bufnr, location(default_target_uri, 1, 2, 1, 2)) + local pos = jump(location(1, 2, 1, 2)) eq(2, pos.line) eq(4, pos.col) eq('å', exec_lua[[return vim.fn.expand('<cword>')]]) end) it('adds current position to jumplist before jumping', function() - exec_lua([[ - vim.api.nvim_win_set_buf(0, ...) - vim.api.nvim_win_set_cursor(0, {2, 0}) - ]], default_target_bufnr) - jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9)) - - local mark = exec_lua([[return vim.inspect(vim.api.nvim_buf_get_mark(..., "'"))]], default_target_bufnr) - eq('{ 2, 0 }', mark) - end) - - it('should not push item to tagstack if destination is the same as source', function() - -- Set cursor at the 2nd line, 1st character. This is the source position - -- for the test, and will also be the destination one, making the cursor - -- "motionless", thus not triggering a push to the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {2, 0}) - ]], default_target_bufnr)) - - -- Jump to 'f' in 'foobar', at the 2nd line. - jump(default_target_bufnr, location(default_target_uri, 1, 0, 1, 0)) + funcs.nvim_win_set_buf(0, target_bufnr) + local mark = funcs.nvim_buf_get_mark(target_bufnr, "'") + eq({ 1, 0 }, mark) - local stack = exec_lua[[return vim.fn.gettagstack()]] - eq(0, stack.length) - end) - - it('should not push the same item from same buffer twice to tagstack', function() - -- Set cursor at the 2nd line, 5th character. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {2, 4}) - ]], default_target_bufnr)) - - local stack - - -- Jump to 1st line, 1st column. - jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from) - - -- Go back to 5th character at 2nd line, which is currently at the top of - -- the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_cursor(0, {2, 4}) - ]], default_target_bufnr)) - - -- Jump again to 1st line, 1st column. Since we're jumping from the same - -- position we have just jumped from, this jump shouldn't be pushed to - -- the tagstack. - jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0)) + funcs.nvim_win_set_cursor(0, {2, 3}) + jump(location(0, 9, 0, 9)) - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from) - eq(1, stack.length) - end) - - it('should not push the same item from another buffer twice to tagstack', function() - local target_uri = 'file://foo/bar' - local target_bufnr = create_buf(target_uri, {'this is a line', 'foobar'}) - - -- Set cursor at the 1st line, 3rd character of the default test buffer. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {1, 2}) - ]], default_target_bufnr)) - - local stack - - -- Jump to 1st line, 1st column of a different buffer from the source - -- position. - jump(target_bufnr, location(target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from) - - -- Go back to 3rd character at 1st line of the default test buffer, which - -- is currently at the top of the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {1, 2}) - ]], default_target_bufnr)) - - -- Jump again to 1st line, 1st column of the different buffer. Since - -- we're jumping from the same position we have just jumped from, this - -- jump shouldn't be pushed to the tagstack. - jump(target_bufnr, location(target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from) - eq(1, stack.length) + mark = funcs.nvim_buf_get_mark(target_bufnr, "'") + eq({ 2, 3 }, mark) end) end) |