diff options
27 files changed, 461 insertions, 225 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index eb3dbd9b70..da39a56164 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5001,11 +5001,11 @@ getcurpos() Get the position of the cursor. This is like getpos('.'), but getcwd([{winnr}[, {tabnr}]]) *getcwd()* With no arguments, returns the name of the effective |current-directory|. With {winnr} or {tabnr} the working - directory of that scope is returned. + directory of that scope is returned, and 'autochdir' is + ignored. Tabs and windows are identified by their respective numbers, - 0 means current tab or window. Missing argument implies 0. + 0 means current tab or window. Missing tab number implies 0. Thus the following are equivalent: > - getcwd() getcwd(0) getcwd(0, 0) < If {winnr} is -1 it is ignored, only the tab is resolved. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 2fab111498..1e84402a9f 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -745,15 +745,12 @@ set_log_level({level}) *vim.lsp.set_log_level()* start_client({config}) *vim.lsp.start_client()* Starts and initializes a client with the given configuration. - Parameters `cmd` and `root_dir` are required. + Parameter `cmd` is required. The following parameters describe fields in the {config} table. Parameters: ~ - {root_dir} (string) Directory where the LSP - server will base its rootUri on - initialization. {cmd} (required, string or list treated like |jobstart()|) Base command that initiates the LSP client. @@ -768,6 +765,13 @@ start_client({config}) *vim.lsp.start_client()* { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } < + {workspace_folders} (table) List of workspace folders + passed to the language server. For + backwards compatibility rootUri and + rootPath will be derived from the + first workspace folder in this list. + See `workspaceFolders` in the LSP + spec. {capabilities} Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|, @@ -801,10 +805,6 @@ start_client({config}) *vim.lsp.start_client()* See `initialize` in the LSP spec. {name} (string, default=client-id) Name in log messages. - {workspace_folders} (table) List of workspace folders - passed to the language server. - Defaults to root_dir if not set. See - `workspaceFolders` in the LSP spec {get_language_id} function(bufnr, filetype) -> language ID as string. Defaults to the filetype. @@ -873,6 +873,10 @@ start_client({config}) *vim.lsp.start_client()* debounce occurs if nil • exit_timeout (number, default 500): Milliseconds to wait for server to + {root_dir} string Directory where the LSP server + will base its workspaceFolders, + rootUri, and rootPath on + initialization. Return: ~ Client id. |vim.lsp.get_client_by_id()| Note: client may diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index cc70c7b2fd..5e9189158a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1617,14 +1617,12 @@ validate({opt}) *vim.validate()* vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} => NOP (success) -< -> - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') -< -> - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') + + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') + + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') < Parameters: ~ diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 441ae13df4..ac10aeec88 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -531,11 +531,9 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) for id, node in pairs(match) do local name = query.captures[id] -- `node` was captured by the `name` capture in the match -< -> - local node_data = metadata[id] -- Node level metadata -< -> + + local node_data = metadata[id] -- Node level metadata + ... use the info here ... end end diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 0bc7a305f0..9b57467a52 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -36,7 +36,51 @@ M.handlers = setmetatable({}, { end, }) --- Local functions {{{ +-- Metatable that automatically creates an empty table when assigning to a missing key +local bufnr_and_namespace_cacher_mt = { + __index = function(t, bufnr) + if not bufnr or bufnr == 0 then + bufnr = vim.api.nvim_get_current_buf() + end + + rawset(t, bufnr, {}) + + return rawget(t, bufnr) + end, + + __newindex = function(t, bufnr, v) + if not bufnr or bufnr == 0 then + bufnr = vim.api.nvim_get_current_buf() + end + + rawset(t, bufnr, v) + end, +} + +local diagnostic_cache = setmetatable({}, { + __index = function(t, bufnr) + if not bufnr or bufnr == 0 then + bufnr = vim.api.nvim_get_current_buf() + end + + vim.api.nvim_buf_attach(bufnr, false, { + on_detach = function() + rawset(t, bufnr, nil) -- clear cache + end + }) + + rawset(t, bufnr, {}) + + return rawget(t, bufnr) + end, +}) + +local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) +local diagnostic_attached_buffers = {} +local diagnostic_disabled = {} +local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) + +local all_namespaces = {} ---@private local function to_severity(severity) @@ -106,8 +150,6 @@ local function reformat_diagnostics(format, diagnostics) return formatted end -local all_namespaces = {} - ---@private local function enabled_value(option, namespace) local ns = namespace and M.get_namespace(namespace) or {} @@ -213,36 +255,6 @@ local function get_bufnr(bufnr) return bufnr end --- Metatable that automatically creates an empty table when assigning to a missing key -local bufnr_and_namespace_cacher_mt = { - __index = function(t, bufnr) - if not bufnr or bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - - if rawget(t, bufnr) == nil then - rawset(t, bufnr, {}) - end - - return rawget(t, bufnr) - end, - - __newindex = function(t, bufnr, v) - if not bufnr or bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - - rawset(t, bufnr, v) - end, -} - -local diagnostic_cleanup = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_attached_buffers = {} -local diagnostic_disabled = {} -local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) - ---@private local function is_disabled(namespace, bufnr) local ns = M.get_namespace(namespace) @@ -287,11 +299,6 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics) end ---@private -local function clear_diagnostic_cache(namespace, bufnr) - diagnostic_cache[bufnr][namespace] = nil -end - ----@private local function restore_extmarks(bufnr, last) for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true}) @@ -377,6 +384,59 @@ local function clear_scheduled_display(namespace, bufnr) end ---@private +local function get_diagnostics(bufnr, opts, clamp) + opts = opts or {} + + local namespace = opts.namespace + local diagnostics = {} + local buf_line_count = clamp and vim.api.nvim_buf_line_count(bufnr) or math.huge + + ---@private + local function add(d) + if not opts.lnum or d.lnum == opts.lnum then + if clamp and (d.lnum >= buf_line_count or d.end_lnum >= buf_line_count) then + d = vim.deepcopy(d) + d.lnum = math.max(math.min(d.lnum, buf_line_count - 1), 0) + d.end_lnum = math.max(math.min(d.end_lnum, buf_line_count - 1), 0) + end + table.insert(diagnostics, d) + end + end + + if namespace == nil and bufnr == nil then + for _, t in pairs(diagnostic_cache) do + for _, v in pairs(t) do + for _, diagnostic in pairs(v) do + add(diagnostic) + end + end + end + elseif namespace == nil then + for iter_namespace in pairs(diagnostic_cache[bufnr]) do + for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do + add(diagnostic) + end + end + elseif bufnr == nil then + for _, t in pairs(diagnostic_cache) do + for _, diagnostic in pairs(t[namespace] or {}) do + add(diagnostic) + end + end + else + for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do + add(diagnostic) + end + end + + if opts.severity then + diagnostics = filter_by_severity(opts.severity, diagnostics) + end + + return diagnostics +end + +---@private local function set_list(loclist, opts) opts = opts or {} local open = vim.F.if_nil(opts.open, true) @@ -386,7 +446,7 @@ local function set_list(loclist, opts) if loclist then bufnr = vim.api.nvim_win_get_buf(winnr) end - local diagnostics = M.get(bufnr, opts) + local diagnostics = get_diagnostics(bufnr, opts, true) local items = M.toqflist(diagnostics) if loclist then vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items }) @@ -399,27 +459,12 @@ local function set_list(loclist, opts) end ---@private ---- To (slightly) improve performance, modifies diagnostics in place. -local function clamp_line_numbers(bufnr, diagnostics) - local buf_line_count = vim.api.nvim_buf_line_count(bufnr) - if buf_line_count == 0 then - return - end - - for _, diagnostic in ipairs(diagnostics) do - diagnostic.lnum = math.max(math.min(diagnostic.lnum, buf_line_count - 1), 0) - diagnostic.end_lnum = math.max(math.min(diagnostic.end_lnum, buf_line_count - 1), 0) - end -end - ----@private local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 bufnr = get_bufnr(bufnr) local wrap = vim.F.if_nil(opts.wrap, true) local line_count = vim.api.nvim_buf_line_count(bufnr) - local diagnostics = M.get(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace})) - clamp_line_numbers(bufnr, diagnostics) + local diagnostics = get_diagnostics(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}), true) local line_diagnostics = diagnostic_lines(diagnostics) for i = 0, line_count do local offset = i * (search_forward and 1 or -1) @@ -431,13 +476,14 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) lnum = (lnum + line_count) % line_count end if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then + local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] local sort_diagnostics, is_next if search_forward then sort_diagnostics = function(a, b) return a.col < b.col end - is_next = function(diagnostic) return diagnostic.col > position[2] end + is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end else sort_diagnostics = function(a, b) return a.col > b.col end - is_next = function(diagnostic) return diagnostic.col < position[2] end + is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end end table.sort(line_diagnostics[lnum], sort_diagnostics) if i == 0 then @@ -481,10 +527,6 @@ local function diagnostic_move_pos(opts, pos) end end --- }}} - --- Public API {{{ - --- Configure diagnostic options globally or for a specific diagnostic --- namespace. --- @@ -618,19 +660,8 @@ function M.set(namespace, bufnr, diagnostics, opts) } if vim.tbl_isempty(diagnostics) then - clear_diagnostic_cache(namespace, bufnr) + diagnostic_cache[bufnr][namespace] = nil else - if not diagnostic_cleanup[bufnr][namespace] then - diagnostic_cleanup[bufnr][namespace] = true - - -- Clean up our data when the buffer unloads. - vim.api.nvim_buf_attach(bufnr, false, { - on_detach = function(_, b) - clear_diagnostic_cache(namespace, b) - diagnostic_cleanup[b][namespace] = nil - end - }) - end set_diagnostic_cache(namespace, bufnr, diagnostics) end @@ -689,49 +720,7 @@ function M.get(bufnr, opts) opts = { opts, 't', true }, } - opts = opts or {} - - local namespace = opts.namespace - local diagnostics = {} - - ---@private - local function add(d) - if not opts.lnum or d.lnum == opts.lnum then - table.insert(diagnostics, d) - end - end - - if namespace == nil and bufnr == nil then - for _, t in pairs(diagnostic_cache) do - for _, v in pairs(t) do - for _, diagnostic in pairs(v) do - add(diagnostic) - end - end - end - elseif namespace == nil then - for iter_namespace in pairs(diagnostic_cache[bufnr]) do - for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do - add(diagnostic) - end - end - elseif bufnr == nil then - for _, t in pairs(diagnostic_cache) do - for _, diagnostic in pairs(t[namespace] or {}) do - add(diagnostic) - end - end - else - for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do - add(diagnostic) - end - end - - if opts.severity then - diagnostics = filter_by_severity(opts.severity, diagnostics) - end - - return diagnostics + return get_diagnostics(bufnr, opts, false) end --- Get the previous diagnostic closest to the cursor position. @@ -1115,7 +1104,7 @@ function M.show(namespace, bufnr, diagnostics, opts) M.hide(namespace, bufnr) - diagnostics = diagnostics or M.get(bufnr, {namespace=namespace}) + diagnostics = diagnostics or get_diagnostics(bufnr, {namespace=namespace}, true) if not diagnostics or vim.tbl_isempty(diagnostics) then return @@ -1141,8 +1130,6 @@ function M.show(namespace, bufnr, diagnostics, opts) end end - clamp_line_numbers(bufnr, diagnostics) - for handler_name, handler in pairs(M.handlers) do if handler.show and opts[handler_name] then handler.show(namespace, bufnr, diagnostics, opts) @@ -1213,8 +1200,7 @@ function M.open_float(bufnr, opts) opts = get_resolved_options({ float = float_opts }, nil, bufnr).float end - local diagnostics = M.get(bufnr, opts) - clamp_line_numbers(bufnr, diagnostics) + local diagnostics = get_diagnostics(bufnr, opts, true) if scope == "line" then diagnostics = vim.tbl_filter(function(d) @@ -1338,7 +1324,7 @@ function M.reset(namespace, bufnr) for _, iter_bufnr in ipairs(buffers) do local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr]) for _, iter_namespace in ipairs(namespaces) do - clear_diagnostic_cache(iter_namespace, iter_bufnr) + diagnostic_cache[iter_bufnr][iter_namespace] = nil M.hide(iter_namespace, iter_bufnr) end end @@ -1563,6 +1549,4 @@ function M.fromqflist(list) return diagnostics end --- }}} - return M diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 88dcb38dd1..7433e7c04d 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -115,6 +115,13 @@ local format_line_ending = { ["mac"] = '\r', } +---@private +---@param bufnr (number) +---@returns (string) +local function buf_get_line_ending(bufnr) + return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n' +end + local client_index = 0 ---@private --- Returns a new, unused client id. @@ -236,7 +243,6 @@ local function validate_client_config(config) config = { config, 't' }; } validate { - root_dir = { config.root_dir, optional_validator(is_dir), "directory" }; handlers = { config.handlers, "t", true }; capabilities = { config.capabilities, "t", true }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; @@ -278,9 +284,10 @@ end ---@param bufnr (number) Buffer handle, or 0 for current. ---@returns Buffer text as string. local function buf_get_full_text(bufnr) - local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') + local line_ending = buf_get_line_ending(bufnr) + local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending) if nvim_buf_get_option(bufnr, 'eol') then - text = text .. '\n' + text = text .. line_ending end return text end @@ -362,9 +369,9 @@ do local incremental_changes = function(client) local cached_buffers = state_by_client[client.id].buffers local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true) - local line_ending = format_line_ending[vim.api.nvim_buf_get_option(0, 'fileformat')] + local line_ending = buf_get_line_ending(bufnr) local incremental_change = sync.compute_diff( - cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending or '\n') + cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) cached_buffers[bufnr] = curr_lines return incremental_change end @@ -566,12 +573,10 @@ end -- --- Starts and initializes a client with the given configuration. --- ---- Parameters `cmd` and `root_dir` are required. +--- Parameter `cmd` is required. --- --- The following parameters describe fields in the {config} table. --- ----@param root_dir: (string) Directory where the LSP server will base ---- its rootUri on initialization. --- ---@param cmd: (required, string or list treated like |jobstart()|) Base command --- that initiates the LSP client. @@ -587,6 +592,11 @@ end --- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } --- </pre> --- +---@param workspace_folders (table) List of workspace folders passed to the +--- language server. For backwards compatibility rootUri and rootPath will be +--- derived from the first workspace folder in this list. See `workspaceFolders` in +--- the LSP spec. +--- ---@param capabilities Map overriding the default capabilities defined by --- |vim.lsp.protocol.make_client_capabilities()|, passed to the language --- server on initialization. Hint: use make_client_capabilities() and modify @@ -610,10 +620,6 @@ end --- as `initializationOptions`. See `initialize` in the LSP spec. --- ---@param name (string, default=client-id) Name in log messages. --- ----@param workspace_folders (table) List of workspace folders passed to the ---- language server. Defaults to root_dir if not set. See `workspaceFolders` in ---- the LSP spec --- ---@param get_language_id function(bufnr, filetype) -> language ID as string. --- Defaults to the filetype. @@ -663,6 +669,11 @@ end --- - exit_timeout (number, default 500): Milliseconds to wait for server to -- exit cleanly after sending the 'shutdown' request before sending kill -15. -- If set to false, nvim exits immediately after sending the 'shutdown' request to the server. +--- +---@param root_dir string Directory where the LSP +--- server will base its workspaceFolders, rootUri, and rootPath +--- on initialization. +--- ---@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be --- fully initialized. Use `on_init` to do any actions once --- the client has been initialized. @@ -805,11 +816,24 @@ function lsp.start_client(config) } local version = vim.version() - if config.root_dir and not config.workspace_folders then - config.workspace_folders = {{ - uri = vim.uri_from_fname(config.root_dir); - name = string.format("%s", config.root_dir); - }}; + local workspace_folders + local root_uri + local root_path + if config.workspace_folders or config.root_dir then + if config.root_dir and not config.workspace_folders then + workspace_folders = {{ + uri = vim.uri_from_fname(config.root_dir); + name = string.format("%s", config.root_dir); + }}; + else + workspace_folders = config.workspace_folders + end + root_uri = workspace_folders[1].uri + root_path = vim.uri_to_fname(root_uri) + else + workspace_folders = vim.NIL + root_uri = vim.NIL + root_path = vim.NIL end local initialize_params = { @@ -827,10 +851,15 @@ function lsp.start_client(config) -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. - rootPath = config.root_dir; + rootPath = root_path; -- The rootUri of the workspace. Is null if no folder is open. If both -- `rootPath` and `rootUri` are set `rootUri` wins. - rootUri = config.root_dir and vim.uri_from_fname(config.root_dir); + rootUri = root_uri; + -- The workspace folders configured in the client when the server starts. + -- This property is only available if the client supports workspace folders. + -- It can be `null` if the client supports workspace folders but none are + -- configured. + workspaceFolders = workspace_folders; -- User provided initialization options. initializationOptions = config.init_options; -- The capabilities provided by the client (editor or tool) @@ -838,21 +867,6 @@ function lsp.start_client(config) -- The initial trace setting. If omitted trace is disabled ("off"). -- trace = "off" | "messages" | "verbose"; trace = valid_traces[config.trace] or 'off'; - -- The workspace folders configured in the client when the server starts. - -- This property is only available if the client supports workspace folders. - -- It can be `null` if the client supports workspace folders but none are - -- configured. - -- - -- Since 3.6.0 - -- workspaceFolders?: WorkspaceFolder[] | null; - -- export interface WorkspaceFolder { - -- -- The associated URI for this workspace folder. - -- uri - -- -- The name of the workspace folder. Used to refer to this - -- -- workspace folder in the user interface. - -- name - -- } - workspaceFolders = config.workspace_folders, } if config.before_init then -- TODO(ashkan) handle errors here. diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 1e6f83c1ba..76a4dc30b7 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -717,5 +717,3 @@ end -- }}} return M - --- vim: fdm=marker diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1512d6bcd5..f2f4950e58 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1587,7 +1587,7 @@ void do_autochdir(void) if (starting == 0 && curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname, kCdCauseAuto) == OK) { - post_chdir(kCdScopeGlobal, false); + last_chdir_reason = "autochdir"; shorten_fnames(true); } } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 714c60c27e..dfadd28ebe 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3386,15 +3386,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } + const char *pattern = tv_get_string(&argvars[0]); if (strcmp(type, "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); + set_one_cmd_context(&xpc, pattern); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_col = STRLEN(pattern); goto theend; } ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); + xpc.xp_pattern = (char_u *)pattern; xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { @@ -3485,11 +3487,6 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - // Find the tabpage by number if (scope_number[kCdScopeTabpage] > 0) { tp = find_tabpage(scope_number[kCdScopeTabpage]); @@ -3535,12 +3532,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. + break; + } + FALLTHROUGH; // In global directory, just need to get OS CWD. + case kCdScopeInvalid: // If called without any arguments, get OS CWD. + if (os_dirname(cwd, MAXPATHL) == FAIL) { from = (char_u *)""; // Return empty string on failure. } - break; - case kCdScopeInvalid: // We should never get here - abort(); } if (from) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 34c3b3889e..62919c98f7 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7753,6 +7753,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) abort(); } + last_chdir_reason = NULL; shorten_fnames(true); if (trigger_dirchanged) { @@ -7870,7 +7871,9 @@ static void ex_pwd(exarg_T *eap) #endif if (p_verbose > 0) { char *context = "global"; - if (curwin->w_localdir != NULL) { + if (last_chdir_reason != NULL) { + context = last_chdir_reason; + } else if (curwin->w_localdir != NULL) { context = "window"; } else if (curtab->tp_localdir != NULL) { context = "tabpage"; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9c065c09f4..b5e1fda9f1 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -789,6 +789,8 @@ extern char_u *compiled_sys; // directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); +EXTERN char *last_chdir_reason INIT(= NULL); + // Whether 'keymodel' contains "stopsel" and "startsel". EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); diff --git a/src/nvim/path.c b/src/nvim/path.c index 19c820e4dd..7b9081eafa 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2245,11 +2245,17 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) } if (os_chdir(directory) != SUCCESS) { - // Do not return immediately since we may be in the wrong directory. - retval = FAIL; - } - - if (retval == FAIL || os_dirname((char_u *)buffer, len) == FAIL) { + // Path does not exist (yet). For a full path fail, + // will use the path as-is. For a relative path use + // the current directory and append the file name. + if (path_is_absolute((const char_u *)directory)) { + // Do not return immediately since we may be in the wrong directory. + retval = FAIL; + } else { + xstrlcpy(buffer, old_dir, len); + append_path(buffer, directory, len); + } + } else if (os_dirname((char_u *)buffer, len) == FAIL) { // Do not return immediately since we are in the wrong directory. retval = FAIL; } diff --git a/src/nvim/search.c b/src/nvim/search.c index f45ae102d2..2e45a8f509 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1808,6 +1808,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) initc = NUL; } else if (initc != '#' && initc != NUL) { find_mps_values(&initc, &findc, &backwards, true); + if (dir) { + backwards = (dir == FORWARD) ? false : true; + } if (findc == NUL) { return NULL; } @@ -3427,12 +3430,22 @@ int current_block(oparg_T *oap, long count, int include, int what, int other) // user wants. save_cpo = p_cpo; p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos } p_cpo = save_cpo; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 13833c256e..dd3f1b4dc9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7536,6 +7536,7 @@ static bool syn_list_header(const bool did_header, const int outlen, const int i { int endcol = 19; bool newline = true; + int name_col = 0; bool adjust = true; if (!did_header) { @@ -7544,6 +7545,7 @@ static bool syn_list_header(const bool did_header, const int outlen, const int i return true; } msg_outtrans(HL_TABLE()[id - 1].sg_name); + name_col = msg_col; endcol = 15; } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { msg_putchar(' '); @@ -7570,6 +7572,9 @@ static bool syn_list_header(const bool did_header, const int outlen, const int i // Show "xxx" with the attributes. if (!did_header) { + if (endcol == Columns - 1 && endcol <= name_col) { + msg_putchar(' '); + } msg_puts_attr("xxx", syn_id2attr(id)); msg_putchar(' '); } diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 0b76828dd7..9ad727241e 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -26,4 +26,42 @@ func Test_set_filename() call delete('samples/Xtest') endfunc +func Test_verbose_pwd() + CheckFunction test_autochdir + let cwd = getcwd() + call test_autochdir() + + edit global.txt + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + + call mkdir('Xautodir') + split Xautodir/local.txt + lcd Xautodir + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + set acd + wincmd w + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + execute 'lcd' cwd + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + execute 'tcd' cwd + call assert_match('\[tabpage\].*testdir$', execute('verbose pwd')) + execute 'cd' cwd + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + edit + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + wincmd w + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + set noacd + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + wincmd w + call assert_match('\[global\].*testdir', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + bwipe! + call chdir(cwd) + call delete('Xautodir', 'rf') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 0bba321ee2..57db0a2544 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -215,3 +215,42 @@ func Test_cd_from_non_existing_dir() cd - call assert_equal(saveddir, getcwd()) endfunc + +func Test_cd_unknown_dir() + call mkdir('Xa') + cd Xa + call writefile(['text'], 'Xb.txt') + edit Xa/Xb.txt + let first_buf = bufnr() + cd .. + edit + call assert_equal(first_buf, bufnr()) + edit Xa/Xb.txt + call assert_notequal(first_buf, bufnr()) + + bwipe! + exe "bwipe! " .. first_buf + call delete('Xa', 'rf') +endfunc + +func Test_getcwd_actual_dir() + CheckFunction test_autochdir + let startdir = getcwd() + call mkdir('Xactual') + call test_autochdir() + set autochdir + edit Xactual/file.txt + call assert_match('testdir.Xactual$', getcwd()) + lcd .. + call assert_match('testdir$', getcwd()) + edit + call assert_match('testdir.Xactual$', getcwd()) + call assert_match('testdir$', getcwd(win_getid())) + + set noautochdir + bwipe! + call chdir(startdir) + call delete('Xactual', 'rf') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 98340d0ac6..75e17b47b8 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -428,14 +428,17 @@ func Test_getcompletion() call assert_equal([], l) func T(a, c, p) + let g:cmdline_compl_params = [a:a, a:c, a:p] return "oneA\noneB\noneC" endfunc command -nargs=1 -complete=custom,T MyCmd let l = getcompletion('MyCmd ', 'cmdline') call assert_equal(['oneA', 'oneB', 'oneC'], l) + call assert_equal(['', 'MyCmd ', 6], g:cmdline_compl_params) delcommand MyCmd delfunc T + unlet g:cmdline_compl_params " For others test if the name is recognized. let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user'] diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 6fd9477ce9..c38bfa5677 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -651,6 +651,16 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc +function Test_no_space_before_xxx() + " Note: we need to create this highlight group in the test because it does not exist in Neovim + execute('hi StatusLineTermNC ctermfg=green') + let l:org_columns = &columns + set columns=17 + let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) + call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) + let &columns = l:org_columns +endfunction + " Test for using RGB color values in a highlight group func Test_xxlast_highlight_RGB_color() CheckCanRunGui diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index c259453b5e..2b6bb8b302 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -421,4 +421,36 @@ func Test_textobj_quote() close! endfunc +" Test for i(, i<, etc. when cursor is in front of a block +func Test_textobj_find_paren_forward() + new + + " i< and a> when cursor is in front of a block + call setline(1, '#include <foo.h>') + normal 0yi< + call assert_equal('foo.h', @") + normal 0ya> + call assert_equal('<foo.h>', @") + + " 2i(, 3i( in front of a block enters second/third nested '(' + call setline(1, 'foo (bar (baz (quux)))') + normal 0yi) + call assert_equal('bar (baz (quux))', @") + normal 02yi) + call assert_equal('baz (quux)', @") + normal 03yi) + call assert_equal('quux', @") + + " 3i( in front of a block doesn't enter third but un-nested '(' + call setline(1, 'foo (bar (baz) (quux))') + normal 03di) + call assert_equal('foo (bar (baz) (quux))', getline(1)) + normal 02di) + call assert_equal('foo (bar () (quux))', getline(1)) + normal 0di) + call assert_equal('foo ()', getline(1)) + + close! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index 9c13264684..4731c2cd41 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4592,6 +4592,7 @@ void fix_current_dir(void) do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow); } + last_chdir_reason = NULL; shorten_fnames(true); } } else if (globaldir != NULL) { @@ -4603,6 +4604,7 @@ void fix_current_dir(void) } } XFREE_CLEAR(globaldir); + last_chdir_reason = NULL; shorten_fnames(true); } } diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 283fcf9672..f9cce0deb6 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -294,7 +294,16 @@ describe("getcwd()", function () command('set autochdir') command('edit ' .. directories.global .. '/foo') eq(curdir .. pathsep .. directories.global, cwd()) + eq(curdir, wcwd()) + call('mkdir', 'bar') + command('edit ' .. 'bar/foo') + eq(curdir .. pathsep .. directories.global .. pathsep .. 'bar', cwd()) + eq(curdir, wcwd()) + command('lcd ..') + eq(curdir .. pathsep .. directories.global, cwd()) + eq(curdir .. pathsep .. directories.global, wcwd()) + command('edit') + eq(curdir .. pathsep .. directories.global .. pathsep .. 'bar', cwd()) + eq(curdir .. pathsep .. directories.global, wcwd()) end) end) - - diff --git a/test/functional/legacy/autochdir_spec.lua b/test/functional/legacy/autochdir_spec.lua index 466e3ed830..37a94476a0 100644 --- a/test/functional/legacy/autochdir_spec.lua +++ b/test/functional/legacy/autochdir_spec.lua @@ -1,10 +1,11 @@ local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) -local clear, eq = helpers.clear, helpers.eq -local eval, command = helpers.eval, helpers.command +local clear, eq, matches = helpers.clear, helpers.eq, helpers.matches +local eval, command, call = helpers.eval, helpers.command, helpers.call +local exec_capture = helpers.exec_capture describe('autochdir behavior', function() - local dir = 'Xtest-functional-legacy-autochdir' + local dir = 'Xtest_functional_legacy_autochdir' before_each(function() lfs.mkdir(dir) @@ -23,4 +24,35 @@ describe('autochdir behavior', function() eq('Xtest', eval("expand('%')")) eq(dir, eval([[substitute(getcwd(), '.*[/\\]\(\k*\)', '\1', '')]])) end) + + it(':verbose pwd shows whether autochdir is used', function() + local subdir = 'Xautodir' + command('cd '..dir) + local cwd = eval('getcwd()') + command('edit global.txt') + matches('%[global%].*'..dir, exec_capture('verbose pwd')) + call('mkdir', subdir) + command('split '..subdir..'/local.txt') + command('lcd '..subdir) + matches('%[window%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + command('set autochdir') + command('wincmd w') + matches('%[autochdir%].*'..dir, exec_capture('verbose pwd')) + command('lcd '..cwd) + matches('%[window%].*'..dir, exec_capture('verbose pwd')) + command('tcd '..cwd) + matches('%[tabpage%].*'..dir, exec_capture('verbose pwd')) + command('cd '..cwd) + matches('%[global%].*'..dir, exec_capture('verbose pwd')) + command('edit') + matches('%[autochdir%].*'..dir, exec_capture('verbose pwd')) + command('wincmd w') + matches('%[autochdir%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + command('set noautochdir') + matches('%[autochdir%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + command('wincmd w') + matches('%[global%].*'..dir, exec_capture('verbose pwd')) + command('wincmd w') + matches('%[window%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + end) end) diff --git a/test/functional/legacy/fnamemodify_spec.lua b/test/functional/legacy/fnamemodify_spec.lua index 6a5538c26f..6262db3a2f 100644 --- a/test/functional/legacy/fnamemodify_spec.lua +++ b/test/functional/legacy/fnamemodify_spec.lua @@ -29,7 +29,7 @@ describe('filename modifiers', function() call assert_equal('test.out', fnamemodify('test.out', ':.')) call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':.')) call assert_equal(fnamemodify(tmpdir, ':~').'/test.out', fnamemodify('test.out', ':~')) - call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':~')) + call assert_equal(fnamemodify(tmpdir, ':~').'/../testdir/a', fnamemodify('../testdir/a', ':~')) call assert_equal('a', fnamemodify('../testdir/a', ':t')) call assert_equal('', fnamemodify('.', ':p:t')) call assert_equal('test.out', fnamemodify('test.out', ':p:t')) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 6414483c0d..348b9de816 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -110,17 +110,21 @@ describe('vim.diagnostic', function() it('retrieves diagnostics from all buffers and namespaces', function() local result = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, 1, { + local other_bufnr = vim.api.nvim_create_buf(true, false) + local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} + vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { make_error('Diagnostic #1', 1, 1, 1, 1), make_error('Diagnostic #2', 2, 1, 2, 1), }) - vim.diagnostic.set(other_ns, 2, { + vim.diagnostic.set(other_ns, other_bufnr, { make_error('Diagnostic #3', 3, 1, 3, 1), }) return vim.diagnostic.get() ]] eq(3, #result) - eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == 1 end, ...)]], result)) + eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == diagnostic_bufnr end, ...)]], result)) eq('Diagnostic #1', result[1].message) end) @@ -339,6 +343,16 @@ describe('vim.diagnostic', function() eq(0, result[5]) eq(3, result[6]) end) + + it("doesn't error after bwipeout on buffer", function() + exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { lnum = 0, end_lnum = 0, col = 0, end_col = 0 }) + vim.cmd("bwipeout! " .. diagnostic_bufnr) + + vim.diagnostic.show(diagnostic_ns) + vim.diagnostic.hide(diagnostic_ns) + ]] + end) end) describe('enable() and disable()', function() @@ -625,6 +639,15 @@ describe('vim.diagnostic', function() ]]) end) + + it("doesn't error after bwipeout called on buffer", function() + exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { lnum = 0, end_lnum = 0, col = 0, end_col = 0 }) + vim.cmd("bwipeout! " .. diagnostic_bufnr) + + vim.diagnostic.reset(diagnostic_ns) + ]] + end) end) describe('get_next_pos()', function() @@ -682,6 +705,19 @@ describe('vim.diagnostic', function() return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } ]]) end) + + it('works with diagnostics past the end of the line #16349', function() + eq({4, 0}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 3, 9001, 3, 9001), + make_error('Diagnostic #2', 4, 0, 4, 0), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 1}) + vim.diagnostic.goto_next { float = false } + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } + ]]) + end) end) describe('get_prev_pos()', function() diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 81f1820986..dbfbe2dbfe 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -143,8 +143,8 @@ describe('URI methods', function() end) it('uri_to_fname returns non-file scheme URI without authority unchanged', function() - eq('zipfile:/path/to/archive.zip%3A%3Afilename.txt', exec_lua [[ - return vim.uri_to_fname('zipfile:/path/to/archive.zip%3A%3Afilename.txt') + eq('zipfile:///path/to/archive.zip%3A%3Afilename.txt', exec_lua [[ + return vim.uri_to_fname('zipfile:///path/to/archive.zip%3A%3Afilename.txt') ]]) end) end) @@ -186,7 +186,7 @@ describe('URI methods', function() end) it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris without authority', function() - local uri = 'zipfile:/path/to/archive.zip%3A%3Afilename.txt' + local uri = 'zipfile:///path/to/archive.zip%3A%3Afilename.txt' local test_case = string.format([[ local uri = '%s' return vim.uri_from_bufnr(vim.uri_to_bufnr(uri)) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 228fc06e9b..b12d4227d5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -66,7 +66,10 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) end end; }); - root_dir = vim.loop.cwd(); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; on_init = function(client, result) TEST_RPC_CLIENT = client vim.rpcrequest(1, "init", result) @@ -153,7 +156,10 @@ describe('LSP', function() "-c", string.format("lua TEST_NAME = %q", test_name), "-c", "luafile "..fixture_filename; }; - root_dir = vim.loop.cwd(); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; } end TEST_CLIENT1 = test__start_client() diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 41954de9be..15ce59747e 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -54,15 +54,21 @@ describe('path.c', function() eq(lfs.currentdir(), (ffi.string(buffer))) end) - itp('fails if the given directory does not exist', function() - eq(FAIL, path_full_dir_name('does_not_exist', buffer, length)) - end) - itp('works with a normal relative dir', function() local result = path_full_dir_name('unit-test-directory', buffer, length) eq(lfs.currentdir() .. '/unit-test-directory', (ffi.string(buffer))) eq(OK, result) end) + + itp('works with a non-existing relative dir', function() + local result = path_full_dir_name('does-not-exist', buffer, length) + eq(lfs.currentdir() .. '/does-not-exist', (ffi.string(buffer))) + eq(OK, result) + end) + + itp('fails with a non-existing absolute dir', function() + eq(FAIL, path_full_dir_name('/does_not_exist', buffer, length)) + end) end) describe('path_full_compare', function() |