diff options
-rw-r--r-- | .github/workflows/ci.yml (renamed from .github/workflows/linux.yml) | 26 | ||||
-rwxr-xr-x | .github/workflows/env.sh | 3 | ||||
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rwxr-xr-x | ci/before_script.sh | 2 | ||||
-rw-r--r-- | ci/common/build.sh | 2 | ||||
-rw-r--r-- | ci/common/test.sh | 4 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 13 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 55 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 24 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 3 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 19 | ||||
-rw-r--r-- | src/nvim/decoration.c | 53 | ||||
-rw-r--r-- | src/nvim/decoration.h | 5 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 507 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.h | 6 | ||||
-rw-r--r-- | src/nvim/option.c | 305 | ||||
-rw-r--r-- | src/nvim/runtime.c | 824 | ||||
-rw-r--r-- | src/nvim/runtime.h | 18 | ||||
-rw-r--r-- | src/nvim/screen.c | 4 | ||||
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 60 | ||||
-rw-r--r-- | test/functional/ui/highlight_spec.lua | 53 |
25 files changed, 1162 insertions, 838 deletions
diff --git a/.github/workflows/linux.yml b/.github/workflows/ci.yml index b8a8c01137..70cdc3b4a2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/ci.yml @@ -1,21 +1,31 @@ -name: Linux CI +name: CI on: [push, pull_request] jobs: - linux: - name: ${{ matrix.flavor }} (cc=${{ matrix.cc }}) - runs-on: ubuntu-20.04 + build: + name: ${{ matrix.os }} ${{ matrix.flavor }} (cc=${{ matrix.cc }}) strategy: matrix: include: - flavor: asan cc: clang-11 + runner: ubuntu-20.04 + os: linux - flavor: lint cc: gcc + runner: ubuntu-20.04 + os: linux - flavor: tsan cc: clang-11 + runner: ubuntu-20.04 + os: linux + - cc: clang + runner: macos-10.15 + os: osx + runs-on: ${{ matrix.runner }} env: CC: ${{ matrix.cc }} + CI_OS_NAME: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -29,6 +39,7 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' - name: Install apt packages + if: matrix.os == 'linux' run: | sudo apt-get update sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip @@ -37,6 +48,13 @@ jobs: if: matrix.flavor == 'asan' || matrix.flavor == 'tsan' run: sudo apt-get install -y clang-11 + - name: Install brew packages + if: matrix.os == 'osx' + run: | + brew update >/dev/null + brew install automake ccache cpanminus ninja + brew upgrade + - name: Setup interpreter packages run: | ./ci/before_install.sh diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index 84f26f949e..cc1cef5cc4 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -u -FLAVOR=$1 +FLAVOR=${1:-} cat <<EOF >> "$GITHUB_PATH" $HOME/.local/bin @@ -24,6 +24,7 @@ DEPS_CMAKE_FLAGS=-DUSE_BUNDLED_GPERF=OFF FUNCTIONALTEST=functionaltest CCACHE_COMPRESS=1 CCACHE_SLOPPINESS=time_macros,file_macro +CCACHE_DIR=$HOME/.ccache EOF BUILD_FLAGS="CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3" diff --git a/.travis.yml b/.travis.yml index 2f4603fa5c..b68f4f1bc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,7 @@ env: - CCACHE_COMPRESS=1 - CCACHE_SLOPPINESS=time_macros,file_macro - CCACHE_BASEDIR="$TRAVIS_BUILD_DIR" + - CI_OS_NAME="$TRAVIS_OS_NAME" anchors: envs: &common-job-env @@ -4,7 +4,7 @@ [Chat](https://gitter.im/neovim/neovim) | [Twitter](https://twitter.com/Neovim) -[](https://github.com/neovim/neovim/actions?query=workflow%3A%22Linux+CI%22) +[](https://github.com/neovim/neovim/actions?query=workflow%3A%22CI%22) [](https://ci.appveyor.com/project/neovim/neovim/branch/master) [](https://codecov.io/gh/neovim/neovim) [](https://scan.coverity.com/projects/2227) diff --git a/ci/before_script.sh b/ci/before_script.sh index 8bab1c4e17..701fe1d9eb 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -7,7 +7,7 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" # Enable ipv6 on Travis. ref: a39c8b7ce30d -if ! test "${TRAVIS_OS_NAME}" = osx ; then +if test -n "${TRAVIS_OS_NAME}" && ! test "${TRAVIS_OS_NAME}" = osx ; then echo "before_script.sh: enable ipv6" sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 fi diff --git a/ci/common/build.sh b/ci/common/build.sh index f0bdec0a0e..0ee4b7493f 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -1,5 +1,5 @@ _stat() { - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then stat -f %Sm "${@}" else stat -c %y "${@}" diff --git a/ci/common/test.sh b/ci/common/test.sh index 4ef6260339..118e181dfa 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -15,7 +15,7 @@ print_core() { return 0 fi echo "======= Core file $core =======" - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then lldb -Q -o "bt all" -f "${app}" -c "${core}" else gdb -n -batch -ex 'thread apply all bt full' "${app}" -c "${core}" @@ -30,7 +30,7 @@ check_core_dumps() { fi local app="${1:-${BUILD_DIR}/bin/nvim}" local cores - if test "${TRAVIS_OS_NAME}" = osx ; then + if test "${CI_OS_NAME}" = osx ; then cores="$(find /cores/ -type f -print)" local _sudo='sudo' else diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 4cab716df0..5747ba6044 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -996,6 +996,19 @@ type_definition() *vim.lsp.buf.type_definition()* Jumps to the definition of the type of the symbol under the cursor. +add_workspace_folder({path}) *vim.lsp.buf.add_workspace_folder()* + Add the folder at path to the workspace folders. If {path} is + not provided, the user will be prompted for a path using + |input()|. + +remove_workspace_folder({path}) *vim.lsp.buf.remove_workspace_folder()* + Remove the folder at path from the workspace folders. If + {path} is not provided, the user will be prompted for + a path using |input()|. + +list_workspace_folders() *vim.lsp.buf.list_workspace_folders()* + List all folders in the workspace. + workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix window. diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index b6a238f158..ae77b0a35a 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -47,7 +47,7 @@ Whenever you need to access the current syntax tree, parse the buffer: > tstree = parser:parse() -<This will return an immutable tree that represents the current state of the +<This will return a table of immutable trees that represent the current state of the buffer. When the plugin wants to access the state after a (possible) edit it should call `parse()` again. If the buffer wasn't edited, the same tree will be returned again without extra work. If the buffer was parsed before, diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index dacdbcfa17..92f56b2ddf 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -597,7 +597,10 @@ function lsp.start_client(config) -- -- workspace folder in the user interface. -- name -- } - workspaceFolders = nil; + workspaceFolders = {{ + uri = vim.uri_from_fname(config.root_dir); + name = string.format("%s", config.root_dir); + }}; } if config.before_init then -- TODO(ashkan) handle errors here. @@ -610,6 +613,7 @@ function lsp.start_client(config) rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary}) client.initialized = true uninitialized_clients[client_id] = nil + client.workspaceFolders = initialize_params.workspaceFolders client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") -- These are the cleaned up capabilities we use for dynamically deciding -- when to send certain events to clients. diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fa62905c0a..a70581478b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -238,6 +238,61 @@ function M.outgoing_calls() end) end +--- List workspace folders. +function M.list_workspace_folders() + local workspace_folders = {} + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for _, folder in ipairs(client.workspaceFolders) do + table.insert(workspace_folders, folder.name) + end + end + return workspace_folders +end + +--- Add a workspace folder. +function M.add_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + if vim.fn.isdirectory(workspace_folder) == 0 then + print(workspace_folder, " is not a valid directory") + return + end + local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + local found = false + for _, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + found = true + print(workspace_folder, "is already part of this workspace") + break + end + end + if not found then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + table.insert(client.workspaceFolders, params.event.added[1]) + end + end +end + +--- Remove a workspace folder. +function M.remove_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for idx, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.workspaceFolders[idx] = nil + return + end + end + end + print(workspace_folder, "is not currently part of the workspace") +end + --- Lists all symbols in the current workspace in the quickfix window. --- --- The list is filtered against {query}; if the argument is omitted from the diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 07b4e8b926..e75198cb20 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -716,6 +716,7 @@ function protocol.make_client_capabilities() }; hierarchicalWorkspaceSymbolSupport = true; }; + workspaceFolders = true; applyEdit = true; }; callHierarchy = { @@ -974,6 +975,28 @@ function protocol.resolve_capabilities(server_capabilities) error("The server sent invalid implementationProvider") end + local workspace = server_capabilities.workspace + local workspace_properties = {} + if workspace == nil or workspace.workspaceFolders == nil then + -- Defaults if omitted. + workspace_properties = { + workspace_folder_properties = { + supported = false; + changeNotifications=false; + } + } + elseif type(workspace.workspaceFolders) == 'table' then + workspace_properties = { + workspace_folder_properties = { + supported = vim.F.if_nil(workspace.workspaceFolders.supported, false); + changeNotifications = vim.F.if_nil(workspace.workspaceFolders.changeNotifications, false); + + } + } + else + error("The server sent invalid workspace") + end + local signature_help_properties if server_capabilities.signatureHelpProvider == nil then signature_help_properties = { @@ -993,6 +1016,7 @@ function protocol.resolve_capabilities(server_capabilities) return vim.tbl_extend("error" , text_document_sync_properties , signature_help_properties + , workspace_properties , general_properties ) end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3deec6d74e..f78a36fda2 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1314,6 +1314,9 @@ function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end +function M.make_workspace_params(added, removed) + return { event = { added = added; removed = removed; } } +end --- Returns visual width of tabstop. --- --@see |softtabstop| diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 60db7f24cf..275e960e28 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -219,7 +219,8 @@ local function on_line_impl(self, buf, line) a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl, - ephemeral = true + ephemeral = true, + priority = 100 -- Low but leaves room below }) end if start_row > line then diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4fc0ee4fdf..1011f050fd 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1122,6 +1122,8 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) } PUT(dict, "virt_text", ARRAY_OBJ(chunks)); } + + PUT(dict, "priority", INTEGER_OBJ(decor->priority)); } if (dict.size) { @@ -1375,6 +1377,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, uint64_t id = 0; int line2 = -1, hl_id = 0; + DecorPriority priority = DECOR_PRIORITY_BASE; colnr_T col2 = 0; VirtText virt_text = KV_INITIAL_VALUE; for (size_t i = 0; i < opts.size; i++) { @@ -1446,6 +1449,19 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("priority", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + "priority is not a Number of the correct size"); + goto error; + } + + if (v->data.integer < 0 || v->data.integer > UINT16_MAX) { + api_set_error(err, kErrorTypeValidation, + "priority is not a valid value"); + goto error; + } + priority = (DecorPriority)v->data.integer; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; @@ -1479,7 +1495,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, *vt_allocated = virt_text; } decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, vt_allocated); + (int)line2, (colnr_T)col2, priority, vt_allocated); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); @@ -1492,6 +1508,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, decor->virt_text = virt_text; } else if (hl_id) { decor = decor_hl(hl_id); + decor->priority = priority; } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 03ce2a37b5..e6a616c927 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -43,6 +43,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, colnr_T hl_end = 0; Decoration *decor = decor_hl(hl_id); + decor->priority = DECOR_PRIORITY_BASE; // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { int end_off = 0; @@ -84,6 +85,7 @@ Decoration *decor_hl(int hl_id) Decoration *decor = xcalloc(1, sizeof(*decor)); decor->hl_id = hl_id; decor->shared = true; + decor->priority = DECOR_PRIORITY_BASE; *dp = decor; return decor; } @@ -191,12 +193,12 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) HlRange range; if (mark.id&MARKTREE_END_FLAG) { range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, vt, false }; + attr_id, decor->priority, vt, false }; } else { range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, vt, false }; + altpos.col, attr_id, decor->priority, vt, false }; } - kv_push(state->active, range); + hlrange_activate(range, state); next_mark: if (marktree_itr_node_done(state->itr)) { @@ -218,6 +220,34 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) return true; // TODO(bfredl): be more precise } +static void hlrange_activate(HlRange range, DecorState *state) +{ + // Get size before preparing the push, to have the number of elements + size_t s = kv_size(state->active); + + kv_pushp(state->active); + + size_t dest_index = 0; + + // Determine insertion dest_index + while (dest_index < s) { + HlRange item = kv_A(state->active, dest_index); + if (item.priority > range.priority) { + break; + } + + dest_index++; + } + + // Splice + for (size_t index = s; index > dest_index; index--) { + kv_A(state->active, index) = kv_A(state->active, index-1); + } + + // Insert + kv_A(state->active, dest_index) = range; +} + int decor_redraw_col(buf_T *buf, int col, DecorState *state) { if (col <= state->col_until) { @@ -257,9 +287,10 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - kv_push(state->active, ((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, vt, false })); + hlrange_activate((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, decor->priority, + vt, false }, state); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -321,10 +352,10 @@ VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) } void decor_add_ephemeral(int attr_id, int start_row, int start_col, - int end_row, int end_col, VirtText *virt_text) + int end_row, int end_col, DecorPriority priority, + VirtText *virt_text) { - kv_push(decor_state.active, - ((HlRange){ start_row, start_col, - end_row, end_col, - attr_id, virt_text, virt_text != NULL })); +hlrange_activate(((HlRange){ start_row, start_col, end_row, end_col, attr_id, + priority, virt_text, virt_text != NULL }), + &decor_state); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index c5941ae2fe..2533a641dd 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -15,11 +15,15 @@ typedef struct { typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) +typedef uint16_t DecorPriority; +#define DECOR_PRIORITY_BASE 0x1000 + struct Decoration { int hl_id; // highlight group VirtText virt_text; // TODO(bfredl): style, signs, etc + DecorPriority priority; bool shared; // shared decoration, don't free }; @@ -29,6 +33,7 @@ typedef struct { int end_row; int end_col; int attr_id; + DecorPriority priority; VirtText *virt_text; bool virt_text_owned; } HlRange; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 04624be9a2..6b03117ff3 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2389,513 +2389,6 @@ void ex_compiler(exarg_T *eap) } } -/// ":runtime [what] {name}" -void ex_runtime(exarg_T *eap) -{ - char_u *arg = eap->arg; - char_u *p = skiptowhite(arg); - ptrdiff_t len = p - arg; - int flags = eap->forceit ? DIP_ALL : 0; - - if (STRNCMP(arg, "START", len) == 0) { - flags += DIP_START + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "OPT", len) == 0) { - flags += DIP_OPT + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "PACK", len) == 0) { - flags += DIP_START + DIP_OPT + DIP_NORTP; - arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "ALL", len) == 0) { - flags += DIP_START + DIP_OPT; - arg = skipwhite(arg + len); - } - - source_runtime(arg, flags); -} - - -static void source_callback(char_u *fname, void *cookie) -{ - (void)do_source(fname, false, DOSO_NONE); -} - -/// Find the file "name" in all directories in "path" and invoke -/// "callback(fname, cookie)". -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// When "flags" has DIP_DIR: find directories instead of files. -/// When "flags" has DIP_ERR: give an error message if there is no match. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) -{ - char_u *tail; - int num_files; - char_u **files; - int i; - bool did_one = false; - - // Make a copy of 'runtimepath'. Invoking the callback may change the - // value. - char_u *rtp_copy = vim_strsave(path); - char_u *buf = xmallocz(MAXPATHL); - { - if (p_verbose > 10 && name != NULL) { - verbose_enter(); - smsg(_("Searching for \"%s\" in \"%s\""), - (char *)name, (char *)path); - verbose_leave(); - } - - // Loop over all entries in 'runtimepath'. - char_u *rtp = rtp_copy; - while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { - // Copy the path from 'runtimepath' to buf[]. - copy_option_part(&rtp, buf, MAXPATHL, ","); - size_t buflen = STRLEN(buf); - - // Skip after or non-after directories. - if (flags & (DIP_NOAFTER | DIP_AFTER)) { - bool is_after = buflen >= 5 - && STRCMP(buf + buflen - 5, "after") == 0; - - if ((is_after && (flags & DIP_NOAFTER)) - || (!is_after && (flags & DIP_AFTER))) { - continue; - } - } - - if (name == NULL) { - (*callback)(buf, (void *)&cookie); - if (!did_one) { - did_one = (cookie == NULL); - } - } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { - add_pathsep((char *)buf); - tail = buf + STRLEN(buf); - - // Loop over all patterns in "name" - char_u *np = name; - while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { - // Append the pattern from "name" to buf[]. - assert(MAXPATHL >= (tail - buf)); - copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), - "\t "); - - if (p_verbose > 10) { - verbose_enter(); - smsg(_("Searching for \"%s\""), buf); - verbose_leave(); - } - - // Expand wildcards, invoke the callback for each match. - if (gen_expand_wildcards(1, &buf, &num_files, &files, - (flags & DIP_DIR) ? EW_DIR - : EW_FILE) == OK) { - for (i = 0; i < num_files; i++) { - (*callback)(files[i], cookie); - did_one = true; - if (!(flags & DIP_ALL)) { - break; - } - } - FreeWild(num_files, files); - } - } - } - } - } - xfree(buf); - xfree(rtp_copy); - if (!did_one && name != NULL) { - char *basepath = path == p_rtp ? "runtimepath" : "packpath"; - - if (flags & DIP_ERR) { - EMSG3(_(e_dirnotf), basepath, name); - } else if (p_verbose > 0) { - verbose_enter(); - smsg(_("not found in '%s': \"%s\""), basepath, name); - verbose_leave(); - } - } - - - return did_one ? OK : FAIL; -} - -/// Find "name" in "path". When found, invoke the callback function for -/// it: callback(fname, "cookie") -/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first -/// one is used. -/// Returns OK when at least one match found, FAIL otherwise. -/// If "name" is NULL calls callback for each entry in "path". Cookie is -/// passed by reference in this case, setting it to NULL indicates that callback -/// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) -{ - int done = FAIL; - - if ((flags & DIP_NORTP) == 0) { - done = do_in_path(path, name, flags, callback, cookie); - } - - if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { - char *start_dir = "pack/*/start/*/%s"; // NOLINT - size_t len = STRLEN(start_dir) + STRLEN(name); - char_u *s = xmallocz(len); - - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - - if (done == FAIL|| (flags & DIP_ALL)) { - start_dir = "start/*/%s"; // NOLINT - len = STRLEN(start_dir) + STRLEN(name); - s = xmallocz(len); - - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - } - } - - if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) { - char *opt_dir = "pack/*/opt/*/%s"; // NOLINT - size_t len = STRLEN(opt_dir) + STRLEN(name); - char_u *s = xmallocz(len); - - vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - - if (done == FAIL || (flags & DIP_ALL)) { - opt_dir = "opt/*/%s"; // NOLINT - len = STRLEN(opt_dir) + STRLEN(name); - s = xmallocz(len); - - vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); - - xfree(s); - } - } - - return done; -} - -/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". -int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) -{ - return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); -} - -/// Source the file "name" from all directories in 'runtimepath'. -/// "name" can contain wildcards. -/// When "flags" has DIP_ALL: source all files, otherwise only the first one. -/// -/// return FAIL when no file could be sourced, OK otherwise. -int source_runtime(char_u *name, int flags) -{ - flags |= (flags & DIP_NORTP) ? 0 : DIP_START; - return source_in_path(p_rtp, name, flags); -} - -/// Just like source_runtime(), but use "path" instead of 'runtimepath'. -int source_in_path(char_u *path, char_u *name, int flags) -{ - return do_in_path_and_pp(path, name, flags, source_callback, NULL); -} - -// Expand wildcards in "pat" and invoke do_source() for each match. -static void source_all_matches(char_u *pat) -{ - int num_files; - char_u **files; - - if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { - for (int i = 0; i < num_files; i++) { - (void)do_source(files[i], false, DOSO_NONE); - } - FreeWild(num_files, files); - } -} - -/// Add the package directory to 'runtimepath' -static int add_pack_dir_to_rtp(char_u *fname) -{ - char_u *p4, *p3, *p2, *p1, *p; - char_u *buf = NULL; - char *afterdir = NULL; - int retval = FAIL; - - p4 = p3 = p2 = p1 = get_past_head(fname); - for (p = p1; *p; MB_PTR_ADV(p)) { - if (vim_ispathsep_nocolon(*p)) { - p4 = p3; p3 = p2; p2 = p1; p1 = p; - } - } - - // now we have: - // rtp/pack/name/start/name - // p4 p3 p2 p1 - // - // find the part up to "pack" in 'runtimepath' - p4++; // append pathsep in order to expand symlink - char_u c = *p4; - *p4 = NUL; - char *const ffname = fix_fname((char *)fname); - *p4 = c; - - if (ffname == NULL) { - return FAIL; - } - - // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences - // Also stop at the first "after" directory - size_t fname_len = strlen(ffname); - buf = try_malloc(MAXPATHL); - if (buf == NULL) { - goto theend; - } - const char *insp = NULL; - const char *after_insp = NULL; - for (const char *entry = (const char *)p_rtp; *entry != NUL; ) { - const char *cur_entry = entry; - - copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); - if (insp == NULL) { - add_pathsep((char *)buf); - char *const rtp_ffname = fix_fname((char *)buf); - if (rtp_ffname == NULL) { - goto theend; - } - bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; - xfree(rtp_ffname); - if (match) { - // Insert "ffname" after this entry (and comma). - insp = entry; - } - } - - if ((p = (char_u *)strstr((char *)buf, "after")) != NULL - && p > buf - && vim_ispathsep(p[-1]) - && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { - if (insp == NULL) { - // Did not find "ffname" before the first "after" directory, - // insert it before this entry. - insp = cur_entry; - } - after_insp = cur_entry; - break; - } - } - - if (insp == NULL) { - // Both "fname" and "after" not found, append at the end. - insp = (const char *)p_rtp + STRLEN(p_rtp); - } - - // check if rtp/pack/name/start/name/after exists - afterdir = concat_fnames((char *)fname, "after", true); - size_t afterlen = 0; - if (os_isdir((char_u *)afterdir)) { - afterlen = strlen(afterdir) + 1; // add one for comma - } - - const size_t oldlen = STRLEN(p_rtp); - const size_t addlen = STRLEN(fname) + 1; // add one for comma - const size_t new_rtp_capacity = oldlen + addlen + afterlen + 1; - // add one for NUL ------------------------------------------^ - char *const new_rtp = try_malloc(new_rtp_capacity); - if (new_rtp == NULL) { - goto theend; - } - - // We now have 'rtp' parts: {keep}{keep_after}{rest}. - // Create new_rtp, first: {keep},{fname} - size_t keep = (size_t)(insp - (const char *)p_rtp); - memmove(new_rtp, p_rtp, keep); - size_t new_rtp_len = keep; - if (*insp == NUL) { - new_rtp[new_rtp_len++] = ','; // add comma before - } - memmove(new_rtp + new_rtp_len, fname, addlen - 1); - new_rtp_len += addlen - 1; - if (*insp != NUL) { - new_rtp[new_rtp_len++] = ','; // add comma after - } - - if (afterlen > 0 && after_insp != NULL) { - size_t keep_after = (size_t)(after_insp - (const char *)p_rtp); - - // Add to new_rtp: {keep},{fname}{keep_after},{afterdir} - memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep); - new_rtp_len += keep_after - keep; - memmove(new_rtp + new_rtp_len, afterdir, afterlen - 1); - new_rtp_len += afterlen - 1; - new_rtp[new_rtp_len++] = ','; - keep = keep_after; - } - - if (p_rtp[keep] != NUL) { - // Append rest: {keep},{fname}{keep_after},{afterdir}{rest} - memmove(new_rtp + new_rtp_len, p_rtp + keep, oldlen - keep + 1); - } else { - new_rtp[new_rtp_len] = NUL; - } - - if (afterlen > 0 && after_insp == NULL) { - // Append afterdir when "after" was not found: - // {keep},{fname}{rest},{afterdir} - xstrlcat(new_rtp, ",", new_rtp_capacity); - xstrlcat(new_rtp, afterdir, new_rtp_capacity); - } - - set_option_value("rtp", 0L, new_rtp, 0); - xfree(new_rtp); - retval = OK; - -theend: - xfree(buf); - xfree(ffname); - xfree(afterdir); - return retval; -} - -/// Load scripts in "plugin" and "ftdetect" directories of the package. -static int load_pack_plugin(char_u *fname) -{ - static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT - static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - - int retval = FAIL; - char *const ffname = fix_fname((char *)fname); - size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = try_malloc(len + 1); - if (pat == NULL) { - goto theend; - } - vim_snprintf((char *)pat, len, plugpat, ffname); - source_all_matches(pat); - - char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); - - // If runtime/filetype.vim wasn't loaded yet, the scripts will be - // found when it loads. - if (eval_to_number(cmd) > 0) { - do_cmdline_cmd("augroup filetypedetect"); - vim_snprintf((char *)pat, len, ftpat, ffname); - source_all_matches(pat); - do_cmdline_cmd("augroup END"); - } - xfree(cmd); - xfree(pat); - retval = OK; - -theend: - xfree(ffname); - - return retval; -} - -// used for "cookie" of add_pack_plugin() -static int APP_ADD_DIR; -static int APP_LOAD; -static int APP_BOTH; - -static void add_pack_plugin(char_u *fname, void *cookie) -{ - if (cookie != &APP_LOAD) { - char *buf = xmalloc(MAXPATHL); - bool found = false; - - const char *p = (const char *)p_rtp; - while (*p != NUL) { - copy_option_part((char_u **)&p, (char_u *)buf, MAXPATHL, ","); - if (path_fnamecmp(buf, (char *)fname) == 0) { - found = true; - break; - } - } - xfree(buf); - if (!found) { - // directory is not yet in 'runtimepath', add it - if (add_pack_dir_to_rtp(fname) == FAIL) { - return; - } - } - } - - if (cookie != &APP_ADD_DIR) { - load_pack_plugin(fname); - } -} - -/// Add all packages in the "start" directory to 'runtimepath'. -void add_pack_start_dirs(void) -{ - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); - do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); -} - -/// Load plugins from all packages in the "start" directory. -void load_start_packages(void) -{ - did_source_packages = true; - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); - do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); -} - -// ":packloadall" -// Find plugins in the package directories and source them. -void ex_packloadall(exarg_T *eap) -{ - if (!did_source_packages || eap->forceit) { - // First do a round to add all directories to 'runtimepath', then load - // the plugins. This allows for plugins to use an autoload directory - // of another plugin. - add_pack_start_dirs(); - load_start_packages(); - } -} - -/// ":packadd[!] {name}" -void ex_packadd(exarg_T *eap) -{ - static const char *plugpat = "pack/*/%s/%s"; // NOLINT - int res = OK; - - // Round 1: use "start", round 2: use "opt". - for (int round = 1; round <= 2; round++) { - // Only look under "start" when loading packages wasn't done yet. - if (round == 1 && did_source_packages) { - continue; - } - - const size_t len = STRLEN(plugpat) + STRLEN(eap->arg) + 5; - char *pat = xmallocz(len); - vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); - // The first round don't give a "not found" error, in the second round - // only when nothing was found in the first round. - res = do_in_path(p_pp, (char_u *)pat, - DIP_ALL + DIP_DIR - + (round == 2 && res == FAIL ? DIP_ERR : 0), - add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); - xfree(pat); - } -} /// ":options" void ex_options(exarg_T *eap) diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index f85ea94ed6..de4e1429b7 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -4,8 +4,8 @@ #include <stdbool.h> #include "nvim/ex_docmd.h" +#include "nvim/runtime.h" -typedef void (*DoInRuntimepathCB)(char_u *, void *); // // flags for check_changed() @@ -16,10 +16,6 @@ typedef void (*DoInRuntimepathCB)(char_u *, void *); #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! -// last argument for do_source() -#define DOSO_NONE 0 -#define DOSO_VIMRC 1 // loading vimrc file - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.h.generated.h" #endif diff --git a/src/nvim/option.c b/src/nvim/option.c index c1b071b04e..77a13b16b1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -56,6 +56,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" +#include "nvim/runtime.h" #include "nvim/keymap.h" #include "nvim/garray.h" #include "nvim/cursor_shape.h" @@ -337,301 +338,6 @@ static char_u SHM_ALL[] = { # include "option.c.generated.h" #endif -/// Append string with escaped commas -static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - size_t shift = 0; - for (size_t i = 0; i < len; i++) { - if (src[i] == ',') { - dest[i + shift++] = '\\'; - } - dest[i + shift] = src[i]; - } - return &dest[len + shift]; -} - -/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some -/// suffixes -/// -/// @param[in] val ENV_SEPCHAR-separated array value. -/// @param[in] common_suf_len Length of the common suffix which is appended to -/// each item in the array, twice. -/// @param[in] single_suf_len Length of the suffix which is appended to each -/// item in the array once. -/// -/// @return Length of the ENV_SEPCHAR-separated string array that contains each -/// item in the original array twice with suffixes with given length -/// (common_suf is present after each new item, single_suf is present -/// after half of the new items) and with commas after each item, commas -/// inside the values are escaped. -static inline size_t compute_double_env_sep_len(const char *const val, - const size_t common_suf_len, - const size_t single_suf_len) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - if (val == NULL || *val == NUL) { - return 0; - } - size_t ret = 0; - const void *iter = NULL; - do { - size_t dir_len; - const char *dir; - iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len); - if (dir != NULL && dir_len > 0) { - ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len - + !after_pathsep(dir, dir + dir_len)) * 2 - + single_suf_len); - } - } while (iter != NULL); - return ret; -} - -#define NVIM_SIZE (sizeof("nvim") - 1) - -/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one -/// -/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in -/// addition to suf1 and suf2. -/// -/// @param[in,out] dest Destination comma-separated array. -/// @param[in] val Source ENV_SEPCHAR-separated array. -/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it -/// directory separator is appended. Suffix must not contain -/// commas. -/// @param[in] len1 Length of the suf1. -/// @param[in] suf2 If not NULL, another suffix appended to destination. Again -/// with directory separator behind. Suffix must not contain -/// commas. -/// @param[in] len2 Length of the suf2. -/// @param[in] forward If true, iterate over val in forward direction. -/// Otherwise in reverse. -/// -/// @return (dest + appended_characters_length) -static inline char *add_env_sep_dirs(char *dest, const char *const val, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2, - const bool forward) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) -{ - if (val == NULL || *val == NUL) { - return dest; - } - const void *iter = NULL; - do { - size_t dir_len; - const char *dir; - iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter, - &dir, &dir_len); - if (dir != NULL && dir_len > 0) { - dest = strcpy_comma_escaped(dest, dir, dir_len); - if (!after_pathsep(dest - 1, dest)) { - *dest++ = PATHSEP; - } - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; - if (suf1 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf1, len1); - dest += len1; - if (suf2 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf2, len2); - dest += len2; - } - } - *dest++ = ','; - } - } while (iter != NULL); - return dest; -} - -/// Adds directory `dest` to a comma-separated list of directories. -/// -/// Commas in the added directory are escaped. -/// -/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome. -/// -/// @see get_xdg_home -/// -/// @param[in,out] dest Destination comma-separated array. -/// @param[in] dir Directory to append. -/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data"). -/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it -/// directory separator is appended. Suffix must not contain -/// commas. -/// @param[in] len1 Length of the suf1. -/// @param[in] suf2 If not NULL, another suffix appended to destination. Again -/// with directory separator behind. Suffix must not contain -/// commas. -/// @param[in] len2 Length of the suf2. -/// @param[in] forward If true, iterate over val in forward direction. -/// Otherwise in reverse. -/// -/// @return (dest + appended_characters_length) -static inline char *add_dir(char *dest, const char *const dir, - const size_t dir_len, const XDGVarType type, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (dir == NULL || dir_len == 0) { - return dest; - } - dest = strcpy_comma_escaped(dest, dir, dir_len); - bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome); - if (append_nvim) { - if (!after_pathsep(dest - 1, dest)) { - *dest++ = PATHSEP; - } -#if defined(WIN32) - size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); - memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); - dest += size; -#else - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; -#endif - if (suf1 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf1, len1); - dest += len1; - if (suf2 != NULL) { - *dest++ = PATHSEP; - memmove(dest, suf2, len2); - dest += len2; - } - } - } - *dest++ = ','; - return dest; -} - -char *get_lib_dir(void) -{ - // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty - // in an appimage build - if (strlen(default_lib_dir) != 0 - && os_isdir((const char_u *)default_lib_dir)) { - return xstrdup(default_lib_dir); - } - - // Find library path relative to the nvim binary: ../lib/nvim/ - char exe_name[MAXPATHL]; - vim_get_prefix_from_exepath(exe_name); - if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { - return xstrdup(exe_name); - } - return NULL; -} - -/// Sets &runtimepath to default value. -/// -/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing -/// configuration and data files in the same path. #4403 -/// -/// If "clean_arg" is true, Nvim was started with --clean. -static void set_runtimepath_default(bool clean_arg) -{ - size_t rtp_size = 0; - char *const data_home = clean_arg - ? NULL - : stdpaths_get_xdg_var(kXDGDataHome); - char *const config_home = clean_arg - ? NULL - : stdpaths_get_xdg_var(kXDGConfigHome); - char *const vimruntime = vim_getenv("VIMRUNTIME"); - char *const libdir = get_lib_dir(); - char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); - char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); -#define SITE_SIZE (sizeof("site") - 1) -#define AFTER_SIZE (sizeof("after") - 1) - size_t data_len = 0; - size_t config_len = 0; - size_t vimruntime_len = 0; - size_t libdir_len = 0; - if (data_home != NULL) { - data_len = strlen(data_home); - if (data_len != 0) { -#if defined(WIN32) - size_t nvim_size = (sizeof("nvim-data") - 1); -#else - size_t nvim_size = NVIM_SIZE; -#endif - rtp_size += ((data_len + memcnt(data_home, ',', data_len) - + nvim_size + 1 + SITE_SIZE + 1 - + !after_pathsep(data_home, data_home + data_len)) * 2 - + AFTER_SIZE + 1); - } - } - if (config_home != NULL) { - config_len = strlen(config_home); - if (config_len != 0) { - rtp_size += ((config_len + memcnt(config_home, ',', config_len) - + NVIM_SIZE + 1 - + !after_pathsep(config_home, config_home + config_len)) * 2 - + AFTER_SIZE + 1); - } - } - if (vimruntime != NULL) { - vimruntime_len = strlen(vimruntime); - if (vimruntime_len != 0) { - rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; - } - } - if (libdir != NULL) { - libdir_len = strlen(libdir); - if (libdir_len != 0) { - rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; - } - } - rtp_size += compute_double_env_sep_len(data_dirs, - NVIM_SIZE + 1 + SITE_SIZE + 1, - AFTER_SIZE + 1); - rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, - AFTER_SIZE + 1); - if (rtp_size == 0) { - return; - } - char *const rtp = xmalloc(rtp_size); - char *rtp_cur = rtp; - rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, - NULL, 0, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); - rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, - "site", SITE_SIZE, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, - true); - rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, - NULL, 0, NULL, 0); - rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); - rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, - "after", AFTER_SIZE, false); - rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, - "site", SITE_SIZE, "after", AFTER_SIZE); - rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, - false); - rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, - "after", AFTER_SIZE, NULL, 0); - // Strip trailing comma. - rtp_cur[-1] = NUL; - assert((size_t) (rtp_cur - rtp) == rtp_size); -#undef SITE_SIZE -#undef AFTER_SIZE - set_string_default("runtimepath", rtp, true); - // Make a copy of 'rtp' for 'packpath' - set_string_default("packpath", rtp, false); - xfree(data_dirs); - xfree(config_dirs); - xfree(data_home); - xfree(config_home); - xfree(vimruntime); - xfree(libdir); -} - -#undef NVIM_SIZE - /// Initialize the options, first part. /// /// Called only once from main(), just after creating the first buffer. @@ -786,7 +492,14 @@ void set_init_1(bool clean_arg) true); // Set default for &runtimepath. All necessary expansions are performed in // this function. - set_runtimepath_default(clean_arg); + char *rtp = runtimepath_default(clean_arg); + if (rtp) { + ILOG("startup runtimepart/packpath value: %s", rtp); + set_string_default("runtimepath", rtp, true); + // Make a copy of 'rtp' for 'packpath' + set_string_default("packpath", rtp, false); + rtp = NULL; // ownership taken + } /* * Set all the options (except the terminal options) to their default diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c new file mode 100644 index 0000000000..1fb7e3b434 --- /dev/null +++ b/src/nvim/runtime.c @@ -0,0 +1,824 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// @file runtime.c +/// +/// Management of runtime files (including packages) + +#include "nvim/vim.h" +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/option.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/misc1.h" +#include "nvim/os/os.h" +#include "nvim/runtime.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "runtime.c.generated.h" +#endif + + +/// ":runtime [what] {name}" +void ex_runtime(exarg_T *eap) +{ + char_u *arg = eap->arg; + char_u *p = skiptowhite(arg); + ptrdiff_t len = p - arg; + int flags = eap->forceit ? DIP_ALL : 0; + + if (STRNCMP(arg, "START", len) == 0) { + flags += DIP_START + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "OPT", len) == 0) { + flags += DIP_OPT + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "PACK", len) == 0) { + flags += DIP_START + DIP_OPT + DIP_NORTP; + arg = skipwhite(arg + len); + } else if (STRNCMP(arg, "ALL", len) == 0) { + flags += DIP_START + DIP_OPT; + arg = skipwhite(arg + len); + } + + source_runtime(arg, flags); +} + + +static void source_callback(char_u *fname, void *cookie) +{ + (void)do_source(fname, false, DOSO_NONE); +} + +/// Find the file "name" in all directories in "path" and invoke +/// "callback(fname, cookie)". +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// When "flags" has DIP_DIR: find directories instead of files. +/// When "flags" has DIP_ERR: give an error message if there is no match. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int do_in_path(char_u *path, char_u *name, int flags, + DoInRuntimepathCB callback, void *cookie) +{ + char_u *tail; + int num_files; + char_u **files; + int i; + bool did_one = false; + + // Make a copy of 'runtimepath'. Invoking the callback may change the + // value. + char_u *rtp_copy = vim_strsave(path); + char_u *buf = xmallocz(MAXPATHL); + { + if (p_verbose > 10 && name != NULL) { + verbose_enter(); + smsg(_("Searching for \"%s\" in \"%s\""), + (char *)name, (char *)path); + verbose_leave(); + } + + // Loop over all entries in 'runtimepath'. + char_u *rtp = rtp_copy; + while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { + // Copy the path from 'runtimepath' to buf[]. + copy_option_part(&rtp, buf, MAXPATHL, ","); + size_t buflen = STRLEN(buf); + + // Skip after or non-after directories. + if (flags & (DIP_NOAFTER | DIP_AFTER)) { + bool is_after = buflen >= 5 + && STRCMP(buf + buflen - 5, "after") == 0; + + if ((is_after && (flags & DIP_NOAFTER)) + || (!is_after && (flags & DIP_AFTER))) { + continue; + } + } + + if (name == NULL) { + (*callback)(buf, (void *)&cookie); + if (!did_one) { + did_one = (cookie == NULL); + } + } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { + add_pathsep((char *)buf); + tail = buf + STRLEN(buf); + + // Loop over all patterns in "name" + char_u *np = name; + while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { + // Append the pattern from "name" to buf[]. + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), + "\t "); + + if (p_verbose > 10) { + verbose_enter(); + smsg(_("Searching for \"%s\""), buf); + verbose_leave(); + } + + // Expand wildcards, invoke the callback for each match. + if (gen_expand_wildcards(1, &buf, &num_files, &files, + (flags & DIP_DIR) ? EW_DIR : EW_FILE) + == OK) { + for (i = 0; i < num_files; i++) { + (*callback)(files[i], cookie); + did_one = true; + if (!(flags & DIP_ALL)) { + break; + } + } + FreeWild(num_files, files); + } + } + } + } + } + xfree(buf); + xfree(rtp_copy); + if (!did_one && name != NULL) { + char *basepath = path == p_rtp ? "runtimepath" : "packpath"; + + if (flags & DIP_ERR) { + EMSG3(_(e_dirnotf), basepath, name); + } else if (p_verbose > 0) { + verbose_enter(); + smsg(_("not found in '%s': \"%s\""), basepath, name); + verbose_leave(); + } + } + + + return did_one ? OK : FAIL; +} + +/// Find "name" in "path". When found, invoke the callback function for +/// it: callback(fname, "cookie") +/// When "flags" has DIP_ALL repeat for all matches, otherwise only the first +/// one is used. +/// Returns OK when at least one match found, FAIL otherwise. +/// If "name" is NULL calls callback for each entry in "path". Cookie is +/// passed by reference in this case, setting it to NULL indicates that callback +/// has done its job. +int do_in_path_and_pp(char_u *path, char_u *name, int flags, + DoInRuntimepathCB callback, void *cookie) +{ + int done = FAIL; + + if ((flags & DIP_NORTP) == 0) { + done = do_in_path(path, name, flags, callback, cookie); + } + + if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { + char *start_dir = "pack/*/start/*/%s"; // NOLINT + size_t len = STRLEN(start_dir) + STRLEN(name); + char_u *s = xmallocz(len); + + vim_snprintf((char *)s, len, start_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + + if (done == FAIL|| (flags & DIP_ALL)) { + start_dir = "start/*/%s"; // NOLINT + len = STRLEN(start_dir) + STRLEN(name); + s = xmallocz(len); + + vim_snprintf((char *)s, len, start_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + } + } + + if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) { + char *opt_dir = "pack/*/opt/*/%s"; // NOLINT + size_t len = STRLEN(opt_dir) + STRLEN(name); + char_u *s = xmallocz(len); + + vim_snprintf((char *)s, len, opt_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + + if (done == FAIL || (flags & DIP_ALL)) { + opt_dir = "opt/*/%s"; // NOLINT + len = STRLEN(opt_dir) + STRLEN(name); + s = xmallocz(len); + + vim_snprintf((char *)s, len, opt_dir, name); + done = do_in_path(p_pp, s, flags, callback, cookie); + + xfree(s); + } + } + + return done; +} + +/// Just like do_in_path_and_pp(), using 'runtimepath' for "path". +int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, + void *cookie) +{ + return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); +} + +/// Source the file "name" from all directories in 'runtimepath'. +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int source_runtime(char_u *name, int flags) +{ + flags |= (flags & DIP_NORTP) ? 0 : DIP_START; + return source_in_path(p_rtp, name, flags); +} + +/// Just like source_runtime(), but use "path" instead of 'runtimepath'. +int source_in_path(char_u *path, char_u *name, int flags) +{ + return do_in_path_and_pp(path, name, flags, source_callback, NULL); +} + +// Expand wildcards in "pat" and invoke do_source() for each match. +static void source_all_matches(char_u *pat) +{ + int num_files; + char_u **files; + + if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { + for (int i = 0; i < num_files; i++) { + (void)do_source(files[i], false, DOSO_NONE); + } + FreeWild(num_files, files); + } +} + +/// Add the package directory to 'runtimepath' +static int add_pack_dir_to_rtp(char_u *fname) +{ + char_u *p4, *p3, *p2, *p1, *p; + char_u *buf = NULL; + char *afterdir = NULL; + int retval = FAIL; + + p4 = p3 = p2 = p1 = get_past_head(fname); + for (p = p1; *p; MB_PTR_ADV(p)) { + if (vim_ispathsep_nocolon(*p)) { + p4 = p3; p3 = p2; p2 = p1; p1 = p; + } + } + + // now we have: + // rtp/pack/name/start/name + // p4 p3 p2 p1 + // + // find the part up to "pack" in 'runtimepath' + p4++; // append pathsep in order to expand symlink + char_u c = *p4; + *p4 = NUL; + char *const ffname = fix_fname((char *)fname); + *p4 = c; + + if (ffname == NULL) { + return FAIL; + } + + // Find "ffname" in "p_rtp", ignoring '/' vs '\' differences + // Also stop at the first "after" directory + size_t fname_len = strlen(ffname); + buf = try_malloc(MAXPATHL); + if (buf == NULL) { + goto theend; + } + const char *insp = NULL; + const char *after_insp = NULL; + for (const char *entry = (const char *)p_rtp; *entry != NUL; ) { + const char *cur_entry = entry; + + copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); + if (insp == NULL) { + add_pathsep((char *)buf); + char *const rtp_ffname = fix_fname((char *)buf); + if (rtp_ffname == NULL) { + goto theend; + } + bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0; + xfree(rtp_ffname); + if (match) { + // Insert "ffname" after this entry (and comma). + insp = entry; + } + } + + if ((p = (char_u *)strstr((char *)buf, "after")) != NULL + && p > buf + && vim_ispathsep(p[-1]) + && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { + if (insp == NULL) { + // Did not find "ffname" before the first "after" directory, + // insert it before this entry. + insp = cur_entry; + } + after_insp = cur_entry; + break; + } + } + + if (insp == NULL) { + // Both "fname" and "after" not found, append at the end. + insp = (const char *)p_rtp + STRLEN(p_rtp); + } + + // check if rtp/pack/name/start/name/after exists + afterdir = concat_fnames((char *)fname, "after", true); + size_t afterlen = 0; + if (os_isdir((char_u *)afterdir)) { + afterlen = strlen(afterdir) + 1; // add one for comma + } + + const size_t oldlen = STRLEN(p_rtp); + const size_t addlen = STRLEN(fname) + 1; // add one for comma + const size_t new_rtp_capacity = oldlen + addlen + afterlen + 1; + // add one for NUL ------------------------------------------^ + char *const new_rtp = try_malloc(new_rtp_capacity); + if (new_rtp == NULL) { + goto theend; + } + + // We now have 'rtp' parts: {keep}{keep_after}{rest}. + // Create new_rtp, first: {keep},{fname} + size_t keep = (size_t)(insp - (const char *)p_rtp); + memmove(new_rtp, p_rtp, keep); + size_t new_rtp_len = keep; + if (*insp == NUL) { + new_rtp[new_rtp_len++] = ','; // add comma before + } + memmove(new_rtp + new_rtp_len, fname, addlen - 1); + new_rtp_len += addlen - 1; + if (*insp != NUL) { + new_rtp[new_rtp_len++] = ','; // add comma after + } + + if (afterlen > 0 && after_insp != NULL) { + size_t keep_after = (size_t)(after_insp - (const char *)p_rtp); + + // Add to new_rtp: {keep},{fname}{keep_after},{afterdir} + memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep); + new_rtp_len += keep_after - keep; + memmove(new_rtp + new_rtp_len, afterdir, afterlen - 1); + new_rtp_len += afterlen - 1; + new_rtp[new_rtp_len++] = ','; + keep = keep_after; + } + + if (p_rtp[keep] != NUL) { + // Append rest: {keep},{fname}{keep_after},{afterdir}{rest} + memmove(new_rtp + new_rtp_len, p_rtp + keep, oldlen - keep + 1); + } else { + new_rtp[new_rtp_len] = NUL; + } + + if (afterlen > 0 && after_insp == NULL) { + // Append afterdir when "after" was not found: + // {keep},{fname}{rest},{afterdir} + xstrlcat(new_rtp, ",", new_rtp_capacity); + xstrlcat(new_rtp, afterdir, new_rtp_capacity); + } + + set_option_value("rtp", 0L, new_rtp, 0); + xfree(new_rtp); + retval = OK; + +theend: + xfree(buf); + xfree(ffname); + xfree(afterdir); + return retval; +} + +/// Load scripts in "plugin" and "ftdetect" directories of the package. +static int load_pack_plugin(char_u *fname) +{ + static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT + static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT + + int retval = FAIL; + char *const ffname = fix_fname((char *)fname); + size_t len = strlen(ffname) + STRLEN(ftpat); + char_u *pat = try_malloc(len + 1); + if (pat == NULL) { + goto theend; + } + vim_snprintf((char *)pat, len, plugpat, ffname); + source_all_matches(pat); + + char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); + + // If runtime/filetype.vim wasn't loaded yet, the scripts will be + // found when it loads. + if (eval_to_number(cmd) > 0) { + do_cmdline_cmd("augroup filetypedetect"); + vim_snprintf((char *)pat, len, ftpat, ffname); + source_all_matches(pat); + do_cmdline_cmd("augroup END"); + } + xfree(cmd); + xfree(pat); + retval = OK; + +theend: + xfree(ffname); + + return retval; +} + +// used for "cookie" of add_pack_plugin() +static int APP_ADD_DIR; +static int APP_LOAD; +static int APP_BOTH; + +static void add_pack_plugin(char_u *fname, void *cookie) +{ + if (cookie != &APP_LOAD) { + char *buf = xmalloc(MAXPATHL); + bool found = false; + + const char *p = (const char *)p_rtp; + while (*p != NUL) { + copy_option_part((char_u **)&p, (char_u *)buf, MAXPATHL, ","); + if (path_fnamecmp(buf, (char *)fname) == 0) { + found = true; + break; + } + } + xfree(buf); + if (!found) { + // directory is not yet in 'runtimepath', add it + if (add_pack_dir_to_rtp(fname) == FAIL) { + return; + } + } + } + + if (cookie != &APP_ADD_DIR) { + load_pack_plugin(fname); + } +} + +/// Add all packages in the "start" directory to 'runtimepath'. +void add_pack_start_dirs(void) +{ + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_ADD_DIR); + do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_ADD_DIR); +} + +/// Load plugins from all packages in the "start" directory. +void load_start_packages(void) +{ + did_source_packages = true; + do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_LOAD); + do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT + add_pack_plugin, &APP_LOAD); +} + +// ":packloadall" +// Find plugins in the package directories and source them. +void ex_packloadall(exarg_T *eap) +{ + if (!did_source_packages || eap->forceit) { + // First do a round to add all directories to 'runtimepath', then load + // the plugins. This allows for plugins to use an autoload directory + // of another plugin. + add_pack_start_dirs(); + load_start_packages(); + } +} + +/// ":packadd[!] {name}" +void ex_packadd(exarg_T *eap) +{ + static const char *plugpat = "pack/*/%s/%s"; // NOLINT + int res = OK; + + // Round 1: use "start", round 2: use "opt". + for (int round = 1; round <= 2; round++) { + // Only look under "start" when loading packages wasn't done yet. + if (round == 1 && did_source_packages) { + continue; + } + + const size_t len = STRLEN(plugpat) + STRLEN(eap->arg) + 5; + char *pat = xmallocz(len); + vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); + // The first round don't give a "not found" error, in the second round + // only when nothing was found in the first round. + res = do_in_path(p_pp, (char_u *)pat, + DIP_ALL + DIP_DIR + + (round == 2 && res == FAIL ? DIP_ERR : 0), + add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + xfree(pat); + } +} + +/// Append string with escaped commas +static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t shift = 0; + for (size_t i = 0; i < len; i++) { + if (src[i] == ',') { + dest[i + shift++] = '\\'; + } + dest[i + shift] = src[i]; + } + return &dest[len + shift]; +} + +/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some +/// suffixes +/// +/// @param[in] val ENV_SEPCHAR-separated array value. +/// @param[in] common_suf_len Length of the common suffix which is appended to +/// each item in the array, twice. +/// @param[in] single_suf_len Length of the suffix which is appended to each +/// item in the array once. +/// +/// @return Length of the ENV_SEPCHAR-separated string array that contains each +/// item in the original array twice with suffixes with given length +/// (common_suf is present after each new item, single_suf is present +/// after half of the new items) and with commas after each item, commas +/// inside the values are escaped. +static inline size_t compute_double_env_sep_len(const char *const val, + const size_t common_suf_len, + const size_t single_suf_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (val == NULL || *val == NUL) { + return 0; + } + size_t ret = 0; + const void *iter = NULL; + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len + + !after_pathsep(dir, dir + dir_len)) * 2 + + single_suf_len); + } + } while (iter != NULL); + return ret; +} + + +#define NVIM_SIZE (sizeof("nvim") - 1) +/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one +/// +/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in +/// addition to suf1 and suf2. +/// +/// @param[in,out] dest Destination comma-separated array. +/// @param[in] val Source ENV_SEPCHAR-separated array. +/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it +/// directory separator is appended. Suffix must not contain +/// commas. +/// @param[in] len1 Length of the suf1. +/// @param[in] suf2 If not NULL, another suffix appended to destination. Again +/// with directory separator behind. Suffix must not contain +/// commas. +/// @param[in] len2 Length of the suf2. +/// @param[in] forward If true, iterate over val in forward direction. +/// Otherwise in reverse. +/// +/// @return (dest + appended_characters_length) +static inline char *add_env_sep_dirs(char *dest, const char *const val, + const char *const suf1, const size_t len1, + const char *const suf2, const size_t len2, + const bool forward) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) +{ + if (val == NULL || *val == NUL) { + return dest; + } + const void *iter = NULL; + do { + size_t dir_len; + const char *dir; + iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter, + &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + dest = strcpy_comma_escaped(dest, dir, dir_len); + if (!after_pathsep(dest - 1, dest)) { + *dest++ = PATHSEP; + } + memmove(dest, "nvim", NVIM_SIZE); + dest += NVIM_SIZE; + if (suf1 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf1, len1); + dest += len1; + if (suf2 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf2, len2); + dest += len2; + } + } + *dest++ = ','; + } + } while (iter != NULL); + return dest; +} + +/// Adds directory `dest` to a comma-separated list of directories. +/// +/// Commas in the added directory are escaped. +/// +/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome. +/// +/// @see get_xdg_home +/// +/// @param[in,out] dest Destination comma-separated array. +/// @param[in] dir Directory to append. +/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data"). +/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it +/// directory separator is appended. Suffix must not contain +/// commas. +/// @param[in] len1 Length of the suf1. +/// @param[in] suf2 If not NULL, another suffix appended to destination. Again +/// with directory separator behind. Suffix must not contain +/// commas. +/// @param[in] len2 Length of the suf2. +/// @param[in] forward If true, iterate over val in forward direction. +/// Otherwise in reverse. +/// +/// @return (dest + appended_characters_length) +static inline char *add_dir(char *dest, const char *const dir, + const size_t dir_len, const XDGVarType type, + const char *const suf1, const size_t len1, + const char *const suf2, const size_t len2) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (dir == NULL || dir_len == 0) { + return dest; + } + dest = strcpy_comma_escaped(dest, dir, dir_len); + bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome); + if (append_nvim) { + if (!after_pathsep(dest - 1, dest)) { + *dest++ = PATHSEP; + } +#if defined(WIN32) + size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); + memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); + dest += size; +#else + memmove(dest, "nvim", NVIM_SIZE); + dest += NVIM_SIZE; +#endif + if (suf1 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf1, len1); + dest += len1; + if (suf2 != NULL) { + *dest++ = PATHSEP; + memmove(dest, suf2, len2); + dest += len2; + } + } + } + *dest++ = ','; + return dest; +} + +char *get_lib_dir(void) +{ + // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty + // in an appimage build + if (strlen(default_lib_dir) != 0 + && os_isdir((const char_u *)default_lib_dir)) { + return xstrdup(default_lib_dir); + } + + // Find library path relative to the nvim binary: ../lib/nvim/ + char exe_name[MAXPATHL]; + vim_get_prefix_from_exepath(exe_name); + if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { + return xstrdup(exe_name); + } + return NULL; +} + +/// Determine the startup value for &runtimepath +/// +/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing +/// configuration and data files in the same path. #4403 +/// +/// @param clean_arg Nvim was started with --clean. +/// @return allocated string with the value +char *runtimepath_default(bool clean_arg) +{ + size_t rtp_size = 0; + char *const data_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGDataHome); + char *const config_home = clean_arg + ? NULL + : stdpaths_get_xdg_var(kXDGConfigHome); + char *const vimruntime = vim_getenv("VIMRUNTIME"); + char *const libdir = get_lib_dir(); + char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); + char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); +#define SITE_SIZE (sizeof("site") - 1) +#define AFTER_SIZE (sizeof("after") - 1) + size_t data_len = 0; + size_t config_len = 0; + size_t vimruntime_len = 0; + size_t libdir_len = 0; + if (data_home != NULL) { + data_len = strlen(data_home); + if (data_len != 0) { +#if defined(WIN32) + size_t nvim_size = (sizeof("nvim-data") - 1); +#else + size_t nvim_size = NVIM_SIZE; +#endif + rtp_size += ((data_len + memcnt(data_home, ',', data_len) + + nvim_size + 1 + SITE_SIZE + 1 + + !after_pathsep(data_home, data_home + data_len)) * 2 + + AFTER_SIZE + 1); + } + } + if (config_home != NULL) { + config_len = strlen(config_home); + if (config_len != 0) { + rtp_size += ((config_len + memcnt(config_home, ',', config_len) + + NVIM_SIZE + 1 + + !after_pathsep(config_home, config_home + config_len)) * 2 + + AFTER_SIZE + 1); + } + } + if (vimruntime != NULL) { + vimruntime_len = strlen(vimruntime); + if (vimruntime_len != 0) { + rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; + } + } + if (libdir != NULL) { + libdir_len = strlen(libdir); + if (libdir_len != 0) { + rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; + } + } + rtp_size += compute_double_env_sep_len(data_dirs, + NVIM_SIZE + 1 + SITE_SIZE + 1, + AFTER_SIZE + 1); + rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, + AFTER_SIZE + 1); + if (rtp_size == 0) { + return NULL; + } + char *const rtp = xmalloc(rtp_size); + char *rtp_cur = rtp; + rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, + NULL, 0, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true); + rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, + "site", SITE_SIZE, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0, + true); + rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, + NULL, 0, NULL, 0); + rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); + rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, + "after", AFTER_SIZE, false); + rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, + "site", SITE_SIZE, "after", AFTER_SIZE); + rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0, + false); + rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome, + "after", AFTER_SIZE, NULL, 0); + // Strip trailing comma. + rtp_cur[-1] = NUL; + assert((size_t)(rtp_cur - rtp) == rtp_size); +#undef SITE_SIZE +#undef AFTER_SIZE + xfree(data_dirs); + xfree(config_dirs); + xfree(data_home); + xfree(config_home); + xfree(vimruntime); + xfree(libdir); + + return rtp; +} +#undef NVIM_SIZE diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h new file mode 100644 index 0000000000..b40c2b670e --- /dev/null +++ b/src/nvim/runtime.h @@ -0,0 +1,18 @@ +#ifndef NVIM_RUNTIME_H +#define NVIM_RUNTIME_H + +#include <stdbool.h> + +#include "nvim/ex_docmd.h" + +typedef void (*DoInRuntimepathCB)(char_u *, void *); + +// last argument for do_source() +#define DOSO_NONE 0 +#define DOSO_VIMRC 1 // loading vimrc file + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "runtime.h.generated.h" +#endif +#endif // NVIM_RUNTIME_H diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 425458f210..4373d6d5a8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2836,9 +2836,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, * required when 'linebreak' is also set. */ if (tocol == vcol) tocol += n_extra; - /* combine 'showbreak' with 'cursorline' */ + // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { - char_attr = hl_combine_attr(char_attr, win_hl_attr(wp, HLF_CUL)); + char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUL), char_attr); } } } diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index d7269d2c29..af709cd521 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -534,6 +534,62 @@ describe('Buffer highlighting', function() ]]} end) + it('respects priority', function() + local set_extmark = curbufmeths.set_extmark + local id = meths.create_namespace('') + insert [[foobar]] + + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 5, + hl_group = "Statement", + priority = 100 + }) + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 6, + hl_group = "String", + priority = 1 + }) + + screen:expect [[ + {3:fooba}{2:^r} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] + + clear_namespace(id, 0, -1) + + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 6, + hl_group = "String", + priority = 1 + }) + set_extmark(id, 0, 0, { + end_line = 0, + end_col = 5, + hl_group = "Statement", + priority = 100 + }) + + screen:expect [[ + {3:fooba}{2:^r} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] + end) + it('works with multibyte text', function() insert([[ Ta båten över sjön!]]) @@ -699,12 +755,12 @@ describe('Buffer highlighting', function() -- TODO: only a virtual text from the same ns curretly overrides -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) - eq({{1, 0, 0, {virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) + eq({{1, 0, 0, { priority = 0, virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) -- TODO: is this really valid? shouldn't the max be line_count()-1? local lastline = line_count() set_virtual_text(id1, line_count(), s2, {}) - eq({{3, lastline, 0, {virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) + eq({{3, lastline, 0, { priority = 0, virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {})) end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 28e4e88326..ef3acd7d2e 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -604,7 +604,7 @@ describe("'listchars' highlight", function() ]]) end) - it("'cursorline' and with 'listchar' option: space, eol, tab, and trail", function() + it("'cursorline' and with 'listchars' option", function() screen:set_default_attr_ids({ [1] = {background=Screen.colors.Grey90}, [2] = { @@ -861,6 +861,57 @@ describe('CursorLine highlight', function() ]]) end) + it("overridden by NonText in 'showbreak' characters", function() + local screen = Screen.new(20,5) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Yellow, background = Screen.colors.Blue}; + [2] = {foreground = Screen.colors.Black, background = Screen.colors.White}; + [3] = {foreground = Screen.colors.Yellow, background = Screen.colors.White}; + [4] = {foreground = Screen.colors.Yellow}; + }) + screen:attach() + + feed_command('set wrap cursorline') + feed_command('set showbreak=>>>') + feed_command('highlight clear NonText') + feed_command('highlight clear CursorLine') + feed_command('highlight NonText guifg=Yellow guibg=Blue gui=NONE') + feed_command('highlight CursorLine guifg=Black guibg=White gui=NONE') + + feed('30iø<esc>o<esc>30ia<esc>') + screen:expect([[ + øøøøøøøøøøøøøøøøøøøø| + {1:>>>}øøøøøøøøøø | + {2:aaaaaaaaaaaaaaaaaaaa}| + {1:>>>}{2:aaaaaaaaa^a }| + | + ]]) + feed('k') + screen:expect([[ + {2:øøøøøøøøøøøøøøøøøøøø}| + {1:>>>}{2:øøøøøøøøø^ø }| + aaaaaaaaaaaaaaaaaaaa| + {1:>>>}aaaaaaaaaa | + | + ]]) + feed_command('highlight NonText guibg=NONE') + screen:expect([[ + {2:øøøøøøøøøøøøøøøøøøøø}| + {3:>>>}{2:øøøøøøøøø^ø }| + aaaaaaaaaaaaaaaaaaaa| + {4:>>>}aaaaaaaaaa | + | + ]]) + feed_command('set nocursorline') + screen:expect([[ + øøøøøøøøøøøøøøøøøøøø| + {4:>>>}øøøøøøøøø^ø | + aaaaaaaaaaaaaaaaaaaa| + {4:>>>}aaaaaaaaaa | + :set nocursorline | + ]]) + end) + it('always updated. vim-patch:8.1.0849', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ |