aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml (renamed from .github/workflows/linux.yml)26
-rwxr-xr-x.github/workflows/env.sh3
-rw-r--r--.travis.yml1
-rw-r--r--README.md2
-rwxr-xr-xci/before_script.sh2
-rw-r--r--ci/common/build.sh2
-rw-r--r--ci/common/test.sh4
-rw-r--r--runtime/doc/eval.txt19
-rw-r--r--runtime/doc/lsp.txt13
-rw-r--r--runtime/doc/treesitter.txt2
-rw-r--r--runtime/filetype.vim8
-rw-r--r--runtime/lua/vim/lsp.lua6
-rw-r--r--runtime/lua/vim/lsp/buf.lua55
-rw-r--r--runtime/lua/vim/lsp/protocol.lua24
-rw-r--r--runtime/lua/vim/lsp/util.lua3
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua3
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua5
-rw-r--r--src/nvim/api/buffer.c19
-rw-r--r--src/nvim/autocmd.c2092
-rw-r--r--src/nvim/autocmd.h81
-rw-r--r--src/nvim/decoration.c53
-rw-r--r--src/nvim/decoration.h5
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c25
-rw-r--r--src/nvim/ex_cmds2.c507
-rw-r--r--src/nvim/ex_cmds2.h6
-rw-r--r--src/nvim/fileio.c2147
-rw-r--r--src/nvim/fileio.h16
-rw-r--r--src/nvim/generators/gen_events.lua33
-rw-r--r--src/nvim/option.c305
-rw-r--r--src/nvim/runtime.c824
-rw-r--r--src/nvim/runtime.h18
-rw-r--r--src/nvim/screen.c4
-rw-r--r--src/nvim/testdir/test_autocmd.vim46
-rw-r--r--src/nvim/testdir/test_cmdline.vim23
-rw-r--r--src/nvim/testdir/test_filetype.vim4
-rw-r--r--src/nvim/testdir/test_popup.vim27
-rw-r--r--src/nvim/testdir/test_preview.vim44
-rw-r--r--src/nvim/testdir/test_visual.vim298
-rw-r--r--src/nvim/testdir/test_window_cmd.vim33
-rw-r--r--test/functional/ui/bufhl_spec.lua60
-rw-r--r--test/functional/ui/highlight_spec.lua53
42 files changed, 3835 insertions, 3067 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
diff --git a/README.md b/README.md
index bb83c26648..e2c82fa6e7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[Chat](https://gitter.im/neovim/neovim) |
[Twitter](https://twitter.com/Neovim)
-[![Linux CI](https://github.com/neovim/neovim/workflows/Linux%20CI/badge.svg)](https://github.com/neovim/neovim/actions?query=workflow%3A%22Linux+CI%22)
+[![GitHub CI](https://github.com/neovim/neovim/workflows/CI/badge.svg)](https://github.com/neovim/neovim/actions?query=workflow%3A%22CI%22)
[![AppVeyor build status](https://ci.appveyor.com/api/projects/status/urdqjrik5u521fac/branch/master?svg=true)](https://ci.appveyor.com/project/neovim/neovim/branch/master)
[![Codecov coverage](https://img.shields.io/codecov/c/github/neovim/neovim.svg)](https://codecov.io/gh/neovim/neovim)
[![Coverity Scan analysis](https://scan.coverity.com/projects/2227/badge.svg)](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/eval.txt b/runtime/doc/eval.txt
index 7c6013f1b2..343e35bf66 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2487,6 +2487,7 @@ wait({timeout}, {condition}[, {interval}])
wildmenumode() Number whether 'wildmenu' mode is active
win_findbuf({bufnr}) List find windows containing {bufnr}
win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab}
+win_gettype([{nr}]) String type of window {nr}
win_gotoid({expr}) Number go to |window-ID| {expr}
win_id2tabwin({expr}) List get tab and window nr from |window-ID|
win_id2win({expr}) Number get window nr from |window-ID|
@@ -9277,6 +9278,24 @@ win_getid([{win} [, {tab}]]) *win_getid()*
number {tab}. The first tab has number one.
Return zero if the window cannot be found.
+win_gettype([{nr}]) *win_gettype()*
+ Return the type of the window:
+ "autocmd" autocommand window. Temporary window
+ used to execute autocommands.
+ "popup" popup window |popup|
+ "preview" preview window |preview-window|
+ "command" command-line window |cmdwin|
+ (empty) normal window
+ "unknown" window {nr} not found
+
+ When {nr} is omitted return the type of the current window.
+ When {nr} is given return the type of this window by number or
+ |window-ID|.
+
+ Also see the 'buftype' option. When running a terminal in a
+ popup window then 'buftype' is "terminal" and win_gettype()
+ returns "popup".
+
win_gotoid({expr}) *win_gotoid()*
Go to window with ID {expr}. This may also change the current
tabpage.
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/filetype.vim b/runtime/filetype.vim
index 4e54bcaefd..b9d2a43d5d 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -693,15 +693,9 @@ au BufNewFile,BufRead *.haml setf haml
" Hamster Classic | Playground files
au BufNewFile,BufRead *.hsm setf hamster
-au BufNewFile,BufRead *.hsc
- \ if match(join(getline(1,10), "\n"), '\%(^\|\n\)\s*\%({-#\_s*LANGUAGE\>\|\<module\>\)') != -1 |
- \ setf haskell |
- \ else |
- \ setf hamster |
- \ endif
" Haskell
-au BufNewFile,BufRead *.hs,*.hs-boot setf haskell
+au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell
au BufNewFile,BufRead *.lhs setf lhaskell
au BufNewFile,BufRead *.chs setf chaskell
au BufNewFile,BufRead cabal.project setf cabalproject
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..218424fa14 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 = if_nil(workspace.workspaceFolders.supported, false);
+ changeNotifications = 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/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 70e2ac4c62..a8b62e21b9 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -104,12 +104,14 @@ function LanguageTree:parse()
parser:set_included_ranges(ranges)
local tree, tree_changes = parser:parse(old_tree, self._source)
+ self:_do_callback('changedtree', tree_changes, tree)
table.insert(self._trees, tree)
vim.list_extend(changes, tree_changes)
end
else
local tree, tree_changes = parser:parse(old_trees[1], self._source)
+ self:_do_callback('changedtree', tree_changes, tree)
table.insert(self._trees, tree)
vim.list_extend(changes, tree_changes)
@@ -146,7 +148,6 @@ function LanguageTree:parse()
self._valid = true
- self:_do_callback('changedtree', changes)
return self._trees, changes
end
@@ -432,7 +433,7 @@ local function region_contains(region, range)
end
function LanguageTree:contains(range)
- for _, region in pairs(self._region) do
+ for _, region in pairs(self._regions) do
if region_contains(region, range) then
return true
end
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/autocmd.c b/src/nvim/autocmd.c
new file mode 100644
index 0000000000..53b11c250e
--- /dev/null
+++ b/src/nvim/autocmd.c
@@ -0,0 +1,2092 @@
+// 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
+
+// autocmd.c: Autocommand related functions
+
+#include "nvim/autocmd.h"
+
+#include "nvim/api/private/handle.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/fileio.h"
+#include "nvim/getchar.h"
+#include "nvim/misc1.h"
+#include "nvim/option.h"
+#include "nvim/regexp.h"
+#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/ui_compositor.h"
+#include "nvim/vim.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+#include "auevents_name_map.generated.h"
+#include "autocmd.c.generated.h"
+#endif
+
+//
+// The autocommands are stored in a list for each event.
+// Autocommands for the same pattern, that are consecutive, are joined
+// together, to avoid having to match the pattern too often.
+// The result is an array of Autopat lists, which point to AutoCmd lists:
+//
+// last_autopat[0] -----------------------------+
+// V
+// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
+// Autopat.cmds Autopat.cmds
+// | |
+// V V
+// AutoCmd.next AutoCmd.next
+// | |
+// V V
+// AutoCmd.next NULL
+// |
+// V
+// NULL
+//
+// last_autopat[1] --------+
+// V
+// first_autopat[1] --> Autopat.next --> NULL
+// Autopat.cmds
+// |
+// V
+// AutoCmd.next
+// |
+// V
+// NULL
+// etc.
+//
+// The order of AutoCmds is important, this is the order in which they were
+// defined and will have to be executed.
+//
+
+// Code for automatic commands.
+static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
+
+/// List of autocmd group names
+static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL };
+#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i])
+#define BUFLOCAL_PAT_LEN 25
+
+// use get_deleted_augroup() to get this
+static const char *deleted_augroup = NULL;
+
+// The ID of the current group. Group 0 is the default one.
+static int current_augroup = AUGROUP_DEFAULT;
+
+static int au_need_clean = false; // need to delete marked patterns
+
+static event_T last_event;
+static int last_group;
+static int autocmd_blocked = 0; // block all autocmds
+
+static bool autocmd_nested = false;
+static bool autocmd_include_groups = false;
+
+static char_u *old_termresponse = NULL;
+
+static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
+{
+ if (deleted_augroup == NULL) {
+ deleted_augroup = _("--Deleted--");
+ }
+ return deleted_augroup;
+}
+
+// Show the autocommands for one AutoPat.
+static void show_autocmd(AutoPat *ap, event_T event)
+{
+ AutoCmd *ac;
+
+ // Check for "got_int" (here and at various places below), which is set
+ // when "q" has been hit for the "--more--" prompt
+ if (got_int) {
+ return;
+ }
+ // pattern has been removed
+ if (ap->pat == NULL) {
+ return;
+ }
+
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
+ if (event != last_event || ap->group != last_group) {
+ if (ap->group != AUGROUP_DEFAULT) {
+ if (AUGROUP_NAME(ap->group) == NULL) {
+ msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
+ } else {
+ msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T));
+ }
+ msg_puts(" ");
+ }
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
+ last_event = event;
+ last_group = ap->group;
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
+ }
+ msg_col = 4;
+ msg_outtrans(ap->pat);
+
+ for (ac = ap->cmds; ac != NULL; ac = ac->next) {
+ if (ac->cmd == NULL) { // skip removed commands
+ continue;
+ }
+ if (msg_col >= 14) {
+ msg_putchar('\n');
+ }
+ msg_col = 14;
+ if (got_int) {
+ return;
+ }
+ msg_outtrans(ac->cmd);
+ if (p_verbose > 0) {
+ last_set_msg(ac->script_ctx);
+ }
+ if (got_int) {
+ return;
+ }
+ if (ac->next != NULL) {
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
+ }
+ }
+}
+
+// Mark an autocommand handler for deletion.
+static void au_remove_pat(AutoPat *ap)
+{
+ XFREE_CLEAR(ap->pat);
+ ap->buflocal_nr = -1;
+ au_need_clean = true;
+}
+
+// Mark all commands for a pattern for deletion.
+static void au_remove_cmds(AutoPat *ap)
+{
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ XFREE_CLEAR(ac->cmd);
+ }
+ au_need_clean = true;
+}
+
+// Delete one command from an autocmd pattern.
+static void au_del_cmd(AutoCmd *ac)
+{
+ XFREE_CLEAR(ac->cmd);
+ au_need_clean = true;
+}
+
+/// Cleanup autocommands and patterns that have been deleted.
+/// This is only done when not executing autocommands.
+static void au_cleanup(void)
+{
+ AutoPat *ap, **prev_ap;
+ event_T event;
+
+ if (autocmd_busy || !au_need_clean) {
+ return;
+ }
+
+ // Loop over all events.
+ for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
+ event = (event_T)((int)event + 1)) {
+ // Loop over all autocommand patterns.
+ prev_ap = &(first_autopat[(int)event]);
+ for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
+ bool has_cmd = false;
+
+ // Loop over all commands for this pattern.
+ AutoCmd **prev_ac = &(ap->cmds);
+ for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
+ // Remove the command if the pattern is to be deleted or when
+ // the command has been marked for deletion.
+ if (ap->pat == NULL || ac->cmd == NULL) {
+ *prev_ac = ac->next;
+ xfree(ac->cmd);
+ xfree(ac);
+ } else {
+ has_cmd = true;
+ prev_ac = &(ac->next);
+ }
+ }
+
+ if (ap->pat != NULL && !has_cmd) {
+ // Pattern was not marked for deletion, but all of its commands were.
+ // So mark the pattern for deletion.
+ au_remove_pat(ap);
+ }
+
+ // Remove the pattern if it has been marked for deletion.
+ if (ap->pat == NULL) {
+ if (ap->next == NULL) {
+ if (prev_ap == &(first_autopat[(int)event])) {
+ last_autopat[(int)event] = NULL;
+ } else {
+ // this depends on the "next" field being the first in
+ // the struct
+ last_autopat[(int)event] = (AutoPat *)prev_ap;
+ }
+ }
+ *prev_ap = ap->next;
+ vim_regfree(ap->reg_prog);
+ xfree(ap);
+ } else {
+ prev_ap = &(ap->next);
+ }
+ }
+ }
+
+ au_need_clean = false;
+}
+
+// Called when buffer is freed, to remove/invalidate related buffer-local
+// autocmds.
+void aubuflocal_remove(buf_T *buf)
+{
+ AutoPat *ap;
+ event_T event;
+ AutoPatCmd *apc;
+
+ // invalidate currently executing autocommands
+ for (apc = active_apc_list; apc; apc = apc->next) {
+ if (buf->b_fnum == apc->arg_bufnr) {
+ apc->arg_bufnr = 0;
+ }
+ }
+
+ // invalidate buflocals looping through events
+ for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
+ event = (event_T)((int)event + 1)) {
+ // loop over all autocommand patterns
+ for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ if (ap->buflocal_nr == buf->b_fnum) {
+ au_remove_pat(ap);
+ if (p_verbose >= 6) {
+ verbose_enter();
+ smsg(_("auto-removing autocommand: %s <buffer=%d>"),
+ event_nr2name(event), buf->b_fnum);
+ verbose_leave();
+ }
+ }
+ }
+ }
+ au_cleanup();
+}
+
+// Add an autocmd group name.
+// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
+static int au_new_group(char_u *name)
+{
+ int i = au_find_group(name);
+ if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it.
+ // First try using a free entry.
+ for (i = 0; i < augroups.ga_len; i++) {
+ if (AUGROUP_NAME(i) == NULL) {
+ break;
+ }
+ }
+ if (i == augroups.ga_len) {
+ ga_grow(&augroups, 1);
+ }
+
+ AUGROUP_NAME(i) = xstrdup((char *)name);
+ if (i == augroups.ga_len) {
+ augroups.ga_len++;
+ }
+ }
+
+ return i;
+}
+
+static void au_del_group(char_u *name)
+{
+ int i = au_find_group(name);
+ if (i == AUGROUP_ERROR) { // the group doesn't exist
+ EMSG2(_("E367: No such group: \"%s\""), name);
+ } else if (i == current_augroup) {
+ EMSG(_("E936: Cannot delete the current group"));
+ } else {
+ event_T event;
+ AutoPat *ap;
+ int in_use = false;
+
+ for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
+ event = (event_T)((int)event + 1)) {
+ for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ if (ap->group == i && ap->pat != NULL) {
+ give_warning(
+ (char_u *)_("W19: Deleting augroup that is still in use"), true);
+ in_use = true;
+ event = NUM_EVENTS;
+ break;
+ }
+ }
+ }
+ xfree(AUGROUP_NAME(i));
+ if (in_use) {
+ AUGROUP_NAME(i) = (char *)get_deleted_augroup();
+ } else {
+ AUGROUP_NAME(i) = NULL;
+ }
+ }
+}
+
+/// Find the ID of an autocmd group name.
+///
+/// @param name augroup name
+///
+/// @return the ID or AUGROUP_ERROR (< 0) for error.
+static int au_find_group(const char_u *name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ for (int i = 0; i < augroups.ga_len; i++) {
+ if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup()
+ && STRCMP(AUGROUP_NAME(i), name) == 0) {
+ return i;
+ }
+ }
+ return AUGROUP_ERROR;
+}
+
+/// Return true if augroup "name" exists.
+///
+/// @param name augroup name
+bool au_has_group(const char_u *name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return au_find_group(name) != AUGROUP_ERROR;
+}
+
+/// ":augroup {name}".
+void do_augroup(char_u *arg, int del_group)
+{
+ if (del_group) {
+ if (*arg == NUL) {
+ EMSG(_(e_argreq));
+ } else {
+ au_del_group(arg);
+ }
+ } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
+ current_augroup = AUGROUP_DEFAULT;
+ } else if (*arg) { // ":aug xxx": switch to group xxx
+ int i = au_new_group(arg);
+ if (i != AUGROUP_ERROR) {
+ current_augroup = i;
+ }
+ } else { // ":aug": list the group names
+ msg_start();
+ for (int i = 0; i < augroups.ga_len; i++) {
+ if (AUGROUP_NAME(i) != NULL) {
+ msg_puts(AUGROUP_NAME(i));
+ msg_puts(" ");
+ }
+ }
+ msg_clr_eos();
+ msg_end();
+ }
+}
+
+#if defined(EXITFREE)
+void free_all_autocmds(void)
+{
+ for (current_augroup = -1; current_augroup < augroups.ga_len;
+ current_augroup++) {
+ do_autocmd((char_u *)"", true);
+ }
+
+ for (int i = 0; i < augroups.ga_len; i++) {
+ char *const s = ((char **)(augroups.ga_data))[i];
+ if ((const char *)s != get_deleted_augroup()) {
+ xfree(s);
+ }
+ }
+ ga_clear(&augroups);
+}
+#endif
+
+// Return the event number for event name "start".
+// Return NUM_EVENTS if the event name was not found.
+// Return a pointer to the next event name in "end".
+static event_T event_name2nr(const char_u *start, char_u **end)
+{
+ const char_u *p;
+ int i;
+ int len;
+
+ // the event name ends with end of line, '|', a blank or a comma
+ for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) {
+ }
+ for (i = 0; event_names[i].name != NULL; i++) {
+ len = (int)event_names[i].len;
+ if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) {
+ break;
+ }
+ }
+ if (*p == ',') {
+ p++;
+ }
+ *end = (char_u *)p;
+ if (event_names[i].name == NULL) {
+ return NUM_EVENTS;
+ }
+ return event_names[i].event;
+}
+
+/// Return the name for event
+///
+/// @param[in] event Event to return name for.
+///
+/// @return Event name, static string. Returns "Unknown" for unknown events.
+static const char *event_nr2name(event_T event)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST
+{
+ int i;
+
+ for (i = 0; event_names[i].name != NULL; i++) {
+ if (event_names[i].event == event) {
+ return event_names[i].name;
+ }
+ }
+ return "Unknown";
+}
+
+/// Scan over the events. "*" stands for all events.
+/// true when group name was found
+static char_u *find_end_event(char_u *arg, int have_group)
+{
+ char_u *pat;
+ char_u *p;
+
+ if (*arg == '*') {
+ if (arg[1] && !ascii_iswhite(arg[1])) {
+ EMSG2(_("E215: Illegal character after *: %s"), arg);
+ return NULL;
+ }
+ pat = arg + 1;
+ } else {
+ for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
+ if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) {
+ if (have_group) {
+ EMSG2(_("E216: No such event: %s"), pat);
+ } else {
+ EMSG2(_("E216: No such group or event: %s"), pat);
+ }
+ return NULL;
+ }
+ }
+ }
+ return pat;
+}
+
+/// Return true if "event" is included in 'eventignore'.
+///
+/// @param event event to check
+static bool event_ignored(event_T event)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *p = p_ei;
+
+ while (*p != NUL) {
+ if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
+ return true;
+ }
+ if (event_name2nr(p, &p) == event) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Return OK when the contents of p_ei is valid, FAIL otherwise.
+int check_ei(void)
+{
+ char_u *p = p_ei;
+
+ while (*p) {
+ if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
+ p += 3;
+ if (*p == ',') {
+ p++;
+ }
+ } else if (event_name2nr(p, &p) == NUM_EVENTS) {
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+// Add "what" to 'eventignore' to skip loading syntax highlighting for every
+// buffer loaded into the window. "what" must start with a comma.
+// Returns the old value of 'eventignore' in allocated memory.
+char_u *au_event_disable(char *what)
+{
+ char_u *new_ei;
+ char_u *save_ei;
+
+ save_ei = vim_strsave(p_ei);
+ new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what));
+ if (*what == ',' && *p_ei == NUL) {
+ STRCPY(new_ei, what + 1);
+ } else {
+ STRCAT(new_ei, what);
+ }
+ set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE);
+ xfree(new_ei);
+
+ return save_ei;
+}
+
+void au_event_restore(char_u *old_ei)
+{
+ if (old_ei != NULL) {
+ set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE);
+ xfree(old_ei);
+ }
+}
+
+// Implements :autocmd.
+// Defines an autocmd (does not execute; cf. apply_autocmds_group).
+//
+// Can be used in the following ways:
+//
+// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that
+// will be automatically executed for <event>
+// when editing a file matching <pat>, in
+// the current group.
+// :autocmd <event> <pat> Show the autocommands associated with
+// <event> and <pat>.
+// :autocmd <event> Show the autocommands associated with
+// <event>.
+// :autocmd Show all autocommands.
+// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with
+// <event> and <pat>, and add the command
+// <cmd>, for the current group.
+// :autocmd! <event> <pat> Remove all autocommands associated with
+// <event> and <pat> for the current group.
+// :autocmd! <event> Remove all autocommands associated with
+// <event> for the current group.
+// :autocmd! Remove ALL autocommands for the current
+// group.
+//
+// Multiple events and patterns may be given separated by commas. Here are
+// some examples:
+// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic
+// :autocmd bufleave * set tw=79 nosmartindent ic infercase
+//
+// :autocmd * *.c show all autocommands for *.c files.
+//
+// Mostly a {group} argument can optionally appear before <event>.
+void do_autocmd(char_u *arg_in, int forceit)
+{
+ char_u *arg = arg_in;
+ char_u *pat;
+ char_u *envpat = NULL;
+ char_u *cmd;
+ int need_free = false;
+ int nested = false;
+ bool once = false;
+ int group;
+
+ if (*arg == '|') {
+ arg = (char_u *)"";
+ group = AUGROUP_ALL; // no argument, use all groups
+ } else {
+ // Check for a legal group name. If not, use AUGROUP_ALL.
+ group = au_get_grouparg(&arg);
+ }
+
+ // Scan over the events.
+ // If we find an illegal name, return here, don't do anything.
+ pat = find_end_event(arg, group != AUGROUP_ALL);
+ if (pat == NULL) {
+ return;
+ }
+
+ pat = skipwhite(pat);
+ if (*pat == '|') {
+ pat = (char_u *)"";
+ cmd = (char_u *)"";
+ } else {
+ // Scan over the pattern. Put a NUL at the end.
+ cmd = pat;
+ while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) {
+ cmd++;
+ }
+ if (*cmd) {
+ *cmd++ = NUL;
+ }
+
+ // Expand environment variables in the pattern. Set 'shellslash', we want
+ // forward slashes here.
+ if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) {
+#ifdef BACKSLASH_IN_FILENAME
+ int p_ssl_save = p_ssl;
+
+ p_ssl = true;
+#endif
+ envpat = expand_env_save(pat);
+#ifdef BACKSLASH_IN_FILENAME
+ p_ssl = p_ssl_save;
+#endif
+ if (envpat != NULL) {
+ pat = envpat;
+ }
+ }
+
+ cmd = skipwhite(cmd);
+ for (size_t i = 0; i < 2; i++) {
+ if (*cmd != NUL) {
+ // Check for "++once" flag.
+ if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) {
+ if (once) {
+ EMSG2(_(e_duparg2), "++once");
+ }
+ once = true;
+ cmd = skipwhite(cmd + 6);
+ }
+
+ // Check for "++nested" flag.
+ if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) {
+ if (nested) {
+ EMSG2(_(e_duparg2), "++nested");
+ }
+ nested = true;
+ cmd = skipwhite(cmd + 8);
+ }
+
+ // Check for the old (deprecated) "nested" flag.
+ if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) {
+ if (nested) {
+ EMSG2(_(e_duparg2), "nested");
+ }
+ nested = true;
+ cmd = skipwhite(cmd + 6);
+ }
+ }
+ }
+
+ // Find the start of the commands.
+ // Expand <sfile> in it.
+ if (*cmd != NUL) {
+ cmd = expand_sfile(cmd);
+ if (cmd == NULL) { // some error
+ return;
+ }
+ need_free = true;
+ }
+ }
+
+ // Print header when showing autocommands.
+ if (!forceit && *cmd == NUL) {
+ // Highlight title
+ MSG_PUTS_TITLE(_("\n--- Autocommands ---"));
+ }
+
+ // Loop over the events.
+ last_event = (event_T)-1; // for listing the event name
+ last_group = AUGROUP_ERROR; // for listing the group name
+ if (*arg == '*' || *arg == NUL || *arg == '|') {
+ for (event_T event = (event_T)0; event < (int)NUM_EVENTS;
+ event = (event_T)(event + 1)) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
+ == FAIL) {
+ break;
+ }
+ }
+ } else {
+ while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
+ event_T event = event_name2nr(arg, &arg);
+ assert(event < NUM_EVENTS);
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
+ == FAIL) {
+ break;
+ }
+ }
+ }
+
+ if (need_free) {
+ xfree(cmd);
+ }
+ xfree(envpat);
+}
+
+// Find the group ID in a ":autocmd" or ":doautocmd" argument.
+// The "argp" argument is advanced to the following argument.
+//
+// Returns the group ID or AUGROUP_ALL.
+static int au_get_grouparg(char_u **argp)
+{
+ char_u *group_name;
+ char_u *p;
+ char_u *arg = *argp;
+ int group = AUGROUP_ALL;
+
+ for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
+ }
+ if (p > arg) {
+ group_name = vim_strnsave(arg, (size_t)(p - arg));
+ group = au_find_group(group_name);
+ if (group == AUGROUP_ERROR) {
+ group = AUGROUP_ALL; // no match, use all groups
+ } else {
+ *argp = skipwhite(p); // match, skip over group name
+ }
+ xfree(group_name);
+ }
+ return group;
+}
+
+// do_autocmd() for one event.
+// Defines an autocmd (does not execute; cf. apply_autocmds_group).
+//
+// If *pat == NUL: do for all patterns.
+// If *cmd == NUL: show entries.
+// If forceit == true: delete entries.
+// If group is not AUGROUP_ALL: only use this group.
+static int do_autocmd_event(event_T event,
+ char_u *pat,
+ bool once,
+ int nested,
+ char_u *cmd,
+ int forceit,
+ int group)
+{
+ AutoPat *ap;
+ AutoPat **prev_ap;
+ AutoCmd *ac;
+ AutoCmd **prev_ac;
+ int brace_level;
+ char_u *endpat;
+ int findgroup;
+ int allgroups;
+ int patlen;
+ int is_buflocal;
+ int buflocal_nr;
+ char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+
+ if (group == AUGROUP_ALL) {
+ findgroup = current_augroup;
+ } else {
+ findgroup = group;
+ }
+ allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL);
+
+ // Show or delete all patterns for an event.
+ if (*pat == NUL) {
+ for (ap = first_autopat[event]; ap != NULL; ap = ap->next) {
+ if (forceit) { // delete the AutoPat, if it's in the current group
+ if (ap->group == findgroup) {
+ au_remove_pat(ap);
+ }
+ } else if (group == AUGROUP_ALL || ap->group == group) {
+ show_autocmd(ap, event);
+ }
+ }
+ }
+
+ // Loop through all the specified patterns.
+ for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
+ // Find end of the pattern.
+ // Watch out for a comma in braces, like "*.\{obj,o\}".
+ endpat = pat;
+ // ignore single comma
+ if (*endpat == ',') {
+ continue;
+ }
+ brace_level = 0;
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
+ endpat++) {
+ if (*endpat == '{') {
+ brace_level++;
+ } else if (*endpat == '}') {
+ brace_level--;
+ }
+ }
+ patlen = (int)(endpat - pat);
+
+ // detect special <buflocal[=X]> buffer-local patterns
+ is_buflocal = false;
+ buflocal_nr = 0;
+
+ if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0
+ && pat[patlen - 1] == '>') {
+ // "<buffer...>": Error will be printed only for addition.
+ // printing and removing will proceed silently.
+ is_buflocal = true;
+ if (patlen == 8) {
+ // "<buffer>"
+ buflocal_nr = curbuf->b_fnum;
+ } else if (patlen > 9 && pat[7] == '=') {
+ if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) {
+ // "<buffer=abuf>"
+ buflocal_nr = autocmd_bufnr;
+ } else if (skipdigits(pat + 8) == pat + patlen - 1) {
+ // "<buffer=123>"
+ buflocal_nr = atoi((char *)pat + 8);
+ }
+ }
+ }
+
+ if (is_buflocal) {
+ // normalize pat into standard "<buffer>#N" form
+ snprintf(
+ (char *)buflocal_pat,
+ BUFLOCAL_PAT_LEN,
+ "<buffer=%d>",
+ buflocal_nr);
+
+ pat = buflocal_pat; // can modify pat and patlen
+ patlen = (int)STRLEN(buflocal_pat); // but not endpat
+ }
+
+ // Find AutoPat entries with this pattern. When adding a command it
+ // always goes at or after the last one, so start at the end.
+ if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) {
+ prev_ap = &last_autopat[(int)event];
+ } else {
+ prev_ap = &first_autopat[(int)event];
+ }
+ while ((ap = *prev_ap) != NULL) {
+ if (ap->pat != NULL) {
+ // Accept a pattern when:
+ // - a group was specified and it's that group, or a group was
+ // not specified and it's the current group, or a group was
+ // not specified and we are listing
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if ((allgroups || ap->group == findgroup) && ap->patlen == patlen
+ && STRNCMP(pat, ap->pat, patlen) == 0) {
+ // Remove existing autocommands.
+ // If adding any new autocmd's for this AutoPat, don't
+ // delete the pattern from the autopat list, append to
+ // this list.
+ if (forceit) {
+ if (*cmd != NUL && ap->next == NULL) {
+ au_remove_cmds(ap);
+ break;
+ }
+ au_remove_pat(ap);
+ } else if (*cmd == NUL) {
+ // Show autocmd's for this autopat, or buflocals <buffer=X>
+ show_autocmd(ap, event);
+
+ } else if (ap->next == NULL) {
+ // Add autocmd to this autopat, if it's the last one.
+ break;
+ }
+ }
+ }
+ prev_ap = &ap->next;
+ }
+
+ // Add a new command.
+ if (*cmd != NUL) {
+ // If the pattern we want to add a command to does appear at the
+ // end of the list (or not is not in the list at all), add the
+ // pattern at the end of the list.
+ if (ap == NULL) {
+ // refuse to add buffer-local ap if buffer number is invalid
+ if (is_buflocal
+ && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
+ emsgf(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
+ return FAIL;
+ }
+
+ ap = xmalloc(sizeof(AutoPat));
+ ap->pat = vim_strnsave(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
+ if (is_buflocal) {
+ ap->buflocal_nr = buflocal_nr;
+ ap->reg_prog = NULL;
+ } else {
+ char_u *reg_pat;
+
+ ap->buflocal_nr = 0;
+ reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_dirs, true);
+ if (reg_pat != NULL) {
+ ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
+ }
+ xfree(reg_pat);
+ if (reg_pat == NULL || ap->reg_prog == NULL) {
+ xfree(ap->pat);
+ xfree(ap);
+ return FAIL;
+ }
+ }
+ ap->cmds = NULL;
+ *prev_ap = ap;
+ last_autopat[(int)event] = ap;
+ ap->next = NULL;
+ if (group == AUGROUP_ALL) {
+ ap->group = current_augroup;
+ } else {
+ ap->group = group;
+ }
+ }
+
+ // Add the autocmd at the end of the AutoCmd list.
+ prev_ac = &(ap->cmds);
+ while ((ac = *prev_ac) != NULL) {
+ prev_ac = &ac->next;
+ }
+ ac = xmalloc(sizeof(AutoCmd));
+ ac->cmd = vim_strsave(cmd);
+ ac->script_ctx = current_sctx;
+ ac->script_ctx.sc_lnum += sourcing_lnum;
+ ac->next = NULL;
+ *prev_ac = ac;
+ ac->once = once;
+ ac->nested = nested;
+ }
+ }
+
+ au_cleanup(); // may really delete removed patterns/commands now
+ return OK;
+}
+
+// Implementation of ":doautocmd [group] event [fname]".
+// Return OK for success, FAIL for failure;
+int do_doautocmd(char_u *arg,
+ int do_msg, // give message for no matching autocmds?
+ bool *did_something)
+{
+ char_u *fname;
+ int nothing_done = true;
+ int group;
+
+ if (did_something != NULL) {
+ *did_something = false;
+ }
+
+ // Check for a legal group name. If not, use AUGROUP_ALL.
+ group = au_get_grouparg(&arg);
+
+ if (*arg == '*') {
+ EMSG(_("E217: Can't execute autocommands for ALL events"));
+ return FAIL;
+ }
+
+ // Scan over the events.
+ // If we find an illegal name, return here, don't do anything.
+ fname = find_end_event(arg, group != AUGROUP_ALL);
+ if (fname == NULL) {
+ return FAIL;
+ }
+
+ fname = skipwhite(fname);
+
+ // Loop over the events.
+ while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) {
+ if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, group,
+ curbuf, NULL)) {
+ nothing_done = false;
+ }
+ }
+
+ if (nothing_done && do_msg) {
+ MSG(_("No matching autocommands"));
+ }
+ if (did_something != NULL) {
+ *did_something = !nothing_done;
+ }
+
+ return aborting() ? FAIL : OK;
+}
+
+// ":doautoall": execute autocommands for each loaded buffer.
+void ex_doautoall(exarg_T *eap)
+{
+ int retval;
+ aco_save_T aco;
+ char_u *arg = eap->arg;
+ int call_do_modelines = check_nomodeline(&arg);
+ bufref_T bufref;
+
+ // This is a bit tricky: For some commands curwin->w_buffer needs to be
+ // equal to curbuf, but for some buffers there may not be a window.
+ // So we change the buffer for the current window for a moment. This
+ // gives problems when the autocommands make changes to the list of
+ // buffers or windows...
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ml.ml_mfp == NULL) {
+ continue;
+ }
+ // Find a window for this buffer and save some values.
+ aucmd_prepbuf(&aco, buf);
+ set_bufref(&bufref, buf);
+
+ bool did_aucmd;
+ // execute the autocommands for this buffer
+ retval = do_doautocmd(arg, false, &did_aucmd);
+
+ if (call_do_modelines && did_aucmd) {
+ // Execute the modeline settings, but don't set window-local
+ // options if we are using the current window for another
+ // buffer.
+ do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0);
+ }
+
+ // restore the current window
+ aucmd_restbuf(&aco);
+
+ // Stop if there is some error or buffer was deleted.
+ if (retval == FAIL || !bufref_valid(&bufref)) {
+ break;
+ }
+ }
+
+ check_cursor(); // just in case lines got deleted
+}
+
+/// Check *argp for <nomodeline>. When it is present return false, otherwise
+/// return true and advance *argp to after it. Thus do_modelines() should be
+/// called when true is returned.
+///
+/// @param[in,out] argp argument string
+bool check_nomodeline(char_u **argp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (STRNCMP(*argp, "<nomodeline>", 12) == 0) {
+ *argp = skipwhite(*argp + 12);
+ return false;
+ }
+ return true;
+}
+
+/// Prepare for executing autocommands for (hidden) buffer `buf`.
+/// If the current buffer is not in any visible window, put it in a temporary
+/// floating window `aucmd_win`.
+/// Set `curbuf` and `curwin` to match `buf`.
+///
+/// @param aco structure to save values in
+/// @param buf new curbuf
+void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
+{
+ win_T *win;
+ bool need_append = true; // Append `aucmd_win` to the window list.
+
+ // Find a window that is for the new buffer
+ if (buf == curbuf) { // be quick when buf is curbuf
+ win = curwin;
+ } else {
+ win = NULL;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ win = wp;
+ break;
+ }
+ }
+ }
+
+ // Allocate the `aucmd_win` dummy floating window.
+ if (win == NULL && aucmd_win == NULL) {
+ win_alloc_aucmd_win();
+ need_append = false;
+ }
+ if (win == NULL && aucmd_win_used) {
+ // Strange recursive autocommand, fall back to using the current
+ // window. Expect a few side effects...
+ win = curwin;
+ }
+
+ aco->save_curwin = curwin;
+ aco->save_prevwin = prevwin;
+ aco->save_curbuf = curbuf;
+ if (win != NULL) {
+ // There is a window for "buf" in the current tab page, make it the
+ // curwin. This is preferred, it has the least side effects (esp. if
+ // "buf" is curbuf).
+ aco->use_aucmd_win = false;
+ curwin = win;
+ } else {
+ // There is no window for "buf", use "aucmd_win". To minimize the side
+ // effects, insert it in the current tab page.
+ // Anything related to a window (e.g., setting folds) may have
+ // unexpected results.
+ aco->use_aucmd_win = true;
+ aucmd_win_used = true;
+ aucmd_win->w_buffer = buf;
+ aucmd_win->w_s = &buf->b_s;
+ buf->b_nwindows++;
+ win_init_empty(aucmd_win); // set cursor and topline to safe values
+
+ // Make sure w_localdir and globaldir are NULL to avoid a chdir() in
+ // win_enter_ext().
+ XFREE_CLEAR(aucmd_win->w_localdir);
+ aco->globaldir = globaldir;
+ globaldir = NULL;
+
+ block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
+ if (need_append) {
+ win_append(lastwin, aucmd_win);
+ handle_register_window(aucmd_win);
+ win_config_float(aucmd_win, aucmd_win->w_float_config);
+ }
+ // Prevent chdir() call in win_enter_ext(), through do_autochdir()
+ int save_acd = p_acd;
+ p_acd = false;
+ win_enter(aucmd_win, false);
+ p_acd = save_acd;
+ unblock_autocmds();
+ curwin = aucmd_win;
+ }
+ curbuf = buf;
+ aco->new_curwin = curwin;
+ set_bufref(&aco->new_curbuf, curbuf);
+}
+
+/// Cleanup after executing autocommands for a (hidden) buffer.
+/// Restore the window as it was (if possible).
+///
+/// @param aco structure holding saved values
+void aucmd_restbuf(aco_save_T *aco)
+{
+ if (aco->use_aucmd_win) {
+ curbuf->b_nwindows--;
+ // Find "aucmd_win", it can't be closed, but it may be in another tab page.
+ // Do not trigger autocommands here.
+ block_autocmds();
+ if (curwin != aucmd_win) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp == aucmd_win) {
+ if (tp != curtab) {
+ goto_tabpage_tp(tp, true, true);
+ }
+ win_goto(aucmd_win);
+ goto win_found;
+ }
+ }
+ }
+ win_found:
+
+ win_remove(curwin, NULL);
+ handle_unregister_window(curwin);
+ if (curwin->w_grid.chars != NULL) {
+ ui_comp_remove_grid(&curwin->w_grid);
+ ui_call_win_hide(curwin->w_grid.handle);
+ grid_free(&curwin->w_grid);
+ }
+
+ aucmd_win_used = false;
+ last_status(false); // may need to remove last status line
+
+ if (!valid_tabpage_win(curtab)) {
+ // no valid window in current tabpage
+ close_tabpage(curtab);
+ }
+
+ unblock_autocmds();
+
+ if (win_valid(aco->save_curwin)) {
+ curwin = aco->save_curwin;
+ } else {
+ // Hmm, original window disappeared. Just use the first one.
+ curwin = firstwin;
+ }
+ prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
+ : firstwin; // window disappeared?
+ vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables
+ hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab
+ curbuf = curwin->w_buffer;
+
+ xfree(globaldir);
+ globaldir = aco->globaldir;
+
+ // the buffer contents may have changed
+ check_cursor();
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
+ curwin->w_topfill = 0;
+ }
+ } else {
+ // restore curwin
+ if (win_valid(aco->save_curwin)) {
+ // Restore the buffer which was previously edited by curwin, if it was
+ // changed, we are still the same window and the buffer is valid.
+ if (curwin == aco->new_curwin && curbuf != aco->new_curbuf.br_buf
+ && bufref_valid(&aco->new_curbuf)
+ && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) {
+ if (curwin->w_s == &curbuf->b_s) {
+ curwin->w_s = &aco->new_curbuf.br_buf->b_s;
+ }
+ curbuf->b_nwindows--;
+ curbuf = aco->new_curbuf.br_buf;
+ curwin->w_buffer = curbuf;
+ curbuf->b_nwindows++;
+ }
+
+ curwin = aco->save_curwin;
+ prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
+ : firstwin; // window disappeared?
+ curbuf = curwin->w_buffer;
+ // In case the autocommand moves the cursor to a position that does not
+ // exist in curbuf
+ check_cursor();
+ }
+ }
+}
+
+/// Execute autocommands for "event" and file name "fname".
+///
+/// @param event event that occurred
+/// @param fname filename, NULL or empty means use actual file name
+/// @param fname_io filename to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+///
+/// @return true if some commands were executed.
+bool apply_autocmds(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf)
+{
+ return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf,
+ NULL);
+}
+
+/// Like apply_autocmds(), but with extra "eap" argument. This takes care of
+/// setting v:filearg.
+///
+/// @param event event that occurred
+/// @param fname NULL or empty means use actual file name
+/// @param fname_io fname to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+/// @param exarg Ex command arguments
+///
+/// @return true if some commands were executed.
+bool apply_autocmds_exarg(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf,
+ exarg_T *eap)
+{
+ return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf,
+ eap);
+}
+
+/// Like apply_autocmds(), but handles the caller's retval. If the script
+/// processing is being aborted or if retval is FAIL when inside a try
+/// conditional, no autocommands are executed. If otherwise the autocommands
+/// cause the script to be aborted, retval is set to FAIL.
+///
+/// @param event event that occurred
+/// @param fname NULL or empty means use actual file name
+/// @param fname_io fname to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+/// @param[in,out] retval caller's retval
+///
+/// @return true if some autocommands were executed
+bool apply_autocmds_retval(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf,
+ int *retval)
+{
+ if (should_abort(*retval)) {
+ return false;
+ }
+
+ bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
+ AUGROUP_ALL, buf, NULL);
+ if (did_cmd && aborting()) {
+ *retval = FAIL;
+ }
+ return did_cmd;
+}
+
+/// Return true if "event" autocommand is defined.
+///
+/// @param event the autocommand to check
+bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return first_autopat[event] != NULL;
+}
+
+/// Return true when there is a CursorHold/CursorHoldI autocommand defined for
+/// the current mode.
+bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return has_event(
+ (get_real_state() == NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI));
+ // return first_autopat[] != NULL;
+}
+
+/// Return true if the CursorHold/CursorHoldI event can be triggered.
+bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int state;
+
+ if (!did_cursorhold && has_cursorhold() && reg_recording == 0
+ && typebuf.tb_len == 0 && !ins_compl_active()) {
+ state = get_real_state();
+ if (state == NORMAL_BUSY || (state & INSERT) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Execute autocommands for "event" and file name "fname".
+///
+/// @param event event that occurred
+/// @param fname filename, NULL or empty means use actual file name
+/// @param fname_io filename to use for <afile> on cmdline,
+/// NULL means use `fname`.
+/// @param force When true, ignore autocmd_busy
+/// @param group autocmd group ID or AUGROUP_ALL
+/// @param buf Buffer for <abuf>
+/// @param eap Ex command arguments
+///
+/// @return true if some commands were executed.
+static bool apply_autocmds_group(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ int group,
+ buf_T *buf,
+ exarg_T *eap)
+{
+ char_u *sfname = NULL; // short file name
+ char_u *tail;
+ bool save_changed;
+ buf_T *old_curbuf;
+ bool retval = false;
+ char_u *save_sourcing_name;
+ linenr_T save_sourcing_lnum;
+ char_u *save_autocmd_fname;
+ int save_autocmd_bufnr;
+ char_u *save_autocmd_match;
+ int save_autocmd_busy;
+ int save_autocmd_nested;
+ static int nesting = 0;
+ AutoPatCmd patcmd;
+ AutoPat *ap;
+ char_u *save_cmdarg;
+ long save_cmdbang;
+ static int filechangeshell_busy = false;
+ proftime_T wait_time;
+ bool did_save_redobuff = false;
+ save_redo_T save_redo;
+ const bool save_KeyTyped = KeyTyped;
+
+ // Quickly return if there are no autocommands for this event or
+ // autocommands are blocked.
+ if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
+ || is_autocmd_blocked()) {
+ goto BYPASS_AU;
+ }
+
+ // When autocommands are busy, new autocommands are only executed when
+ // explicitly enabled with the "nested" flag.
+ if (autocmd_busy && !(force || autocmd_nested)) {
+ goto BYPASS_AU;
+ }
+
+ // Quickly return when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ if (aborting()) {
+ goto BYPASS_AU;
+ }
+
+ // FileChangedShell never nests, because it can create an endless loop.
+ if (filechangeshell_busy
+ && (event == EVENT_FILECHANGEDSHELL
+ || event == EVENT_FILECHANGEDSHELLPOST)) {
+ goto BYPASS_AU;
+ }
+
+ // Ignore events in 'eventignore'.
+ if (event_ignored(event)) {
+ goto BYPASS_AU;
+ }
+
+ // Allow nesting of autocommands, but restrict the depth, because it's
+ // possible to create an endless loop.
+ if (nesting == 10) {
+ EMSG(_("E218: autocommand nesting too deep"));
+ goto BYPASS_AU;
+ }
+
+ // Check if these autocommands are disabled. Used when doing ":all" or
+ // ":ball".
+ if ((autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER))
+ || (autocmd_no_leave
+ && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
+ goto BYPASS_AU;
+ }
+
+ // Save the autocmd_* variables and info about the current buffer.
+ save_autocmd_fname = autocmd_fname;
+ save_autocmd_bufnr = autocmd_bufnr;
+ save_autocmd_match = autocmd_match;
+ save_autocmd_busy = autocmd_busy;
+ save_autocmd_nested = autocmd_nested;
+ save_changed = curbuf->b_changed;
+ old_curbuf = curbuf;
+
+ // Set the file name to be used for <afile>.
+ // Make a copy to avoid that changing a buffer name or directory makes it
+ // invalid.
+ if (fname_io == NULL) {
+ if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
+ || event == EVENT_OPTIONSET) {
+ autocmd_fname = NULL;
+ } else if (fname != NULL && !ends_excmd(*fname)) {
+ autocmd_fname = fname;
+ } else if (buf != NULL) {
+ autocmd_fname = buf->b_ffname;
+ } else {
+ autocmd_fname = NULL;
+ }
+ } else {
+ autocmd_fname = fname_io;
+ }
+ if (autocmd_fname != NULL) {
+ // Allocate MAXPATHL for when eval_vars() resolves the fullpath.
+ autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL);
+ }
+
+ // Set the buffer number to be used for <abuf>.
+ if (buf == NULL) {
+ autocmd_bufnr = 0;
+ } else {
+ autocmd_bufnr = buf->b_fnum;
+ }
+
+ // When the file name is NULL or empty, use the file name of buffer "buf".
+ // Always use the full path of the file name to match with, in case
+ // "allow_dirs" is set.
+ if (fname == NULL || *fname == NUL) {
+ if (buf == NULL) {
+ fname = NULL;
+ } else {
+ if (event == EVENT_SYNTAX) {
+ fname = buf->b_p_syn;
+ } else if (event == EVENT_FILETYPE) {
+ fname = buf->b_p_ft;
+ } else {
+ if (buf->b_sfname != NULL) {
+ sfname = vim_strsave(buf->b_sfname);
+ }
+ fname = buf->b_ffname;
+ }
+ }
+ if (fname == NULL) {
+ fname = (char_u *)"";
+ }
+ fname = vim_strsave(fname); // make a copy, so we can change it
+ } else {
+ sfname = vim_strsave(fname);
+ // Don't try expanding the following events.
+ if (event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER
+ || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER
+ || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED
+ || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
+ || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE
+ || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET
+ || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE
+ || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING
+ || event == EVENT_SYNTAX || event == EVENT_SIGNAL
+ || event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) {
+ fname = vim_strsave(fname);
+ } else {
+ fname = (char_u *)FullName_save((char *)fname, false);
+ }
+ }
+ if (fname == NULL) { // out of memory
+ xfree(sfname);
+ retval = false;
+ goto BYPASS_AU;
+ }
+
+#ifdef BACKSLASH_IN_FILENAME
+ // Replace all backslashes with forward slashes. This makes the
+ // autocommand patterns portable between Unix and Windows.
+ if (sfname != NULL) {
+ forward_slash(sfname);
+ }
+ forward_slash(fname);
+#endif
+
+ // Set the name to be used for <amatch>.
+ autocmd_match = fname;
+
+ // Don't redraw while doing autocommands.
+ RedrawingDisabled++;
+ save_sourcing_name = sourcing_name;
+ sourcing_name = NULL; // don't free this one
+ save_sourcing_lnum = sourcing_lnum;
+ sourcing_lnum = 0; // no line number here
+
+ const sctx_T save_current_sctx = current_sctx;
+
+ if (do_profiling == PROF_YES) {
+ prof_child_enter(&wait_time); // doesn't count for the caller itself
+ }
+
+ // Don't use local function variables, if called from a function.
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
+
+ // When starting to execute autocommands, save the search patterns.
+ if (!autocmd_busy) {
+ save_search_patterns();
+ if (!ins_compl_active()) {
+ saveRedobuff(&save_redo);
+ did_save_redobuff = true;
+ }
+ did_filetype = keep_filetype;
+ }
+
+ // Note that we are applying autocmds. Some commands need to know.
+ autocmd_busy = true;
+ filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL);
+ nesting++; // see matching decrement below
+
+ // Remember that FileType was triggered. Used for did_filetype().
+ if (event == EVENT_FILETYPE) {
+ did_filetype = true;
+ }
+
+ tail = path_tail(fname);
+
+ // Find first autocommand that matches
+ patcmd.curpat = first_autopat[(int)event];
+ patcmd.nextcmd = NULL;
+ patcmd.group = group;
+ patcmd.fname = fname;
+ patcmd.sfname = sfname;
+ patcmd.tail = tail;
+ patcmd.event = event;
+ patcmd.arg_bufnr = autocmd_bufnr;
+ patcmd.next = NULL;
+ auto_next_pat(&patcmd, false);
+
+ // found one, start executing the autocommands
+ if (patcmd.curpat != NULL) {
+ // add to active_apc_list
+ patcmd.next = active_apc_list;
+ active_apc_list = &patcmd;
+
+ // set v:cmdarg (only when there is a matching pattern)
+ save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
+ if (eap != NULL) {
+ save_cmdarg = set_cmdarg(eap, NULL);
+ set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
+ } else {
+ save_cmdarg = NULL; // avoid gcc warning
+ }
+ retval = true;
+ // mark the last pattern, to avoid an endless loop when more patterns
+ // are added when executing autocommands
+ for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) {
+ ap->last = false;
+ }
+ ap->last = true;
+ check_lnums(true); // make sure cursor and topline are valid
+
+ // Execute the autocmd. The `getnextac` callback handles iteration.
+ do_cmdline(NULL, getnextac, (void *)&patcmd,
+ DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+
+ reset_lnums(); // restore cursor and topline, unless they were changed
+
+ if (eap != NULL) {
+ (void)set_cmdarg(NULL, save_cmdarg);
+ set_vim_var_nr(VV_CMDBANG, save_cmdbang);
+ }
+ // delete from active_apc_list
+ if (active_apc_list == &patcmd) { // just in case
+ active_apc_list = patcmd.next;
+ }
+ }
+
+ RedrawingDisabled--;
+ autocmd_busy = save_autocmd_busy;
+ filechangeshell_busy = false;
+ autocmd_nested = save_autocmd_nested;
+ xfree(sourcing_name);
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ xfree(autocmd_fname);
+ autocmd_fname = save_autocmd_fname;
+ autocmd_bufnr = save_autocmd_bufnr;
+ autocmd_match = save_autocmd_match;
+ current_sctx = save_current_sctx;
+ restore_funccal();
+ if (do_profiling == PROF_YES) {
+ prof_child_exit(&wait_time);
+ }
+ KeyTyped = save_KeyTyped;
+ xfree(fname);
+ xfree(sfname);
+ nesting--; // see matching increment above
+
+ // When stopping to execute autocommands, restore the search patterns and
+ // the redo buffer. Free any buffers in the au_pending_free_buf list and
+ // free any windows in the au_pending_free_win list.
+ if (!autocmd_busy) {
+ restore_search_patterns();
+ if (did_save_redobuff) {
+ restoreRedobuff(&save_redo);
+ }
+ did_filetype = false;
+ while (au_pending_free_buf != NULL) {
+ buf_T *b = au_pending_free_buf->b_next;
+ xfree(au_pending_free_buf);
+ au_pending_free_buf = b;
+ }
+ while (au_pending_free_win != NULL) {
+ win_T *w = au_pending_free_win->w_next;
+ xfree(au_pending_free_win);
+ au_pending_free_win = w;
+ }
+ }
+
+ // Some events don't set or reset the Changed flag.
+ // Check if still in the same buffer!
+ if (curbuf == old_curbuf
+ && (event == EVENT_BUFREADPOST || event == EVENT_BUFWRITEPOST
+ || event == EVENT_FILEAPPENDPOST || event == EVENT_VIMLEAVE
+ || event == EVENT_VIMLEAVEPRE)) {
+ if (curbuf->b_changed != save_changed) {
+ need_maketitle = true;
+ }
+ curbuf->b_changed = save_changed;
+ }
+
+ au_cleanup(); // may really delete removed patterns/commands now
+
+BYPASS_AU:
+ // When wiping out a buffer make sure all its buffer-local autocommands
+ // are deleted.
+ if (event == EVENT_BUFWIPEOUT && buf != NULL) {
+ aubuflocal_remove(buf);
+ }
+
+ if (retval == OK && event == EVENT_FILETYPE) {
+ au_did_filetype = true;
+ }
+
+ return retval;
+}
+
+// Block triggering autocommands until unblock_autocmd() is called.
+// Can be used recursively, so long as it's symmetric.
+void block_autocmds(void)
+{
+ // Remember the value of v:termresponse.
+ if (is_autocmd_blocked()) {
+ old_termresponse = get_vim_var_str(VV_TERMRESPONSE);
+ }
+ autocmd_blocked++;
+}
+
+void unblock_autocmds(void)
+{
+ autocmd_blocked--;
+
+ // When v:termresponse was set while autocommands were blocked, trigger
+ // the autocommands now. Esp. useful when executing a shell command
+ // during startup (nvim -d).
+ if (is_autocmd_blocked()
+ && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
+ apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
+ }
+}
+
+static inline bool is_autocmd_blocked(void)
+{
+ return autocmd_blocked != 0;
+}
+
+/// Find next autocommand pattern that matches.
+/// stop when 'last' flag is set
+void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
+{
+ AutoPat *ap;
+ AutoCmd *cp;
+ char *s;
+
+ XFREE_CLEAR(sourcing_name);
+
+ for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
+ apc->curpat = NULL;
+
+ // Only use a pattern when it has not been removed, has commands and
+ // the group matches. For buffer-local autocommands only check the
+ // buffer number.
+ if (ap->pat != NULL && ap->cmds != NULL
+ && (apc->group == AUGROUP_ALL || apc->group == ap->group)) {
+ // execution-condition
+ if (ap->buflocal_nr == 0
+ ? match_file_pat(
+ NULL,
+ &ap->reg_prog,
+ apc->fname,
+ apc->sfname,
+ apc->tail,
+ ap->allow_dirs)
+ : ap->buflocal_nr == apc->arg_bufnr) {
+ const char *const name = event_nr2name(apc->event);
+ s = _("%s Autocommands for \"%s\"");
+
+ const size_t sourcing_name_len
+ = (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1);
+
+ sourcing_name = xmalloc(sourcing_name_len);
+ snprintf((char *)sourcing_name, sourcing_name_len, s, name,
+ (char *)ap->pat);
+ if (p_verbose >= 8) {
+ verbose_enter();
+ smsg(_("Executing %s"), sourcing_name);
+ verbose_leave();
+ }
+
+ apc->curpat = ap;
+ apc->nextcmd = ap->cmds;
+ // mark last command
+ for (cp = ap->cmds; cp->next != NULL; cp = cp->next) {
+ cp->last = false;
+ }
+ cp->last = true;
+ }
+ line_breakcheck();
+ if (apc->curpat != NULL) { // found a match
+ break;
+ }
+ }
+ if (stop_at_last && ap->last) {
+ break;
+ }
+ }
+}
+
+/// Get next autocommand command.
+/// Called by do_cmdline() to get the next line for ":if".
+/// @return allocated string, or NULL for end of autocommands.
+char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
+{
+ AutoPatCmd *acp = (AutoPatCmd *)cookie;
+ char_u *retval;
+ AutoCmd *ac;
+
+ // Can be called again after returning the last line.
+ if (acp->curpat == NULL) {
+ return NULL;
+ }
+
+ // repeat until we find an autocommand to execute
+ for (;;) {
+ // skip removed commands
+ while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) {
+ if (acp->nextcmd->last) {
+ acp->nextcmd = NULL;
+ } else {
+ acp->nextcmd = acp->nextcmd->next;
+ }
+ }
+
+ if (acp->nextcmd != NULL) {
+ break;
+ }
+
+ // at end of commands, find next pattern that matches
+ if (acp->curpat->last) {
+ acp->curpat = NULL;
+ } else {
+ acp->curpat = acp->curpat->next;
+ }
+ if (acp->curpat != NULL) {
+ auto_next_pat(acp, true);
+ }
+ if (acp->curpat == NULL) {
+ return NULL;
+ }
+ }
+
+ ac = acp->nextcmd;
+
+ if (p_verbose >= 9) {
+ verbose_enter_scroll();
+ smsg(_("autocommand %s"), ac->cmd);
+ msg_puts("\n"); // don't overwrite this either
+ verbose_leave_scroll();
+ }
+ retval = vim_strsave(ac->cmd);
+ // Remove one-shot ("once") autocmd in anticipation of its execution.
+ if (ac->once) {
+ au_del_cmd(ac);
+ }
+ autocmd_nested = ac->nested;
+ current_sctx = ac->script_ctx;
+ if (ac->last) {
+ acp->nextcmd = NULL;
+ } else {
+ acp->nextcmd = ac->next;
+ }
+
+ return retval;
+}
+
+/// Return true if there is a matching autocommand for "fname".
+/// To account for buffer-local autocommands, function needs to know
+/// in which buffer the file will be opened.
+///
+/// @param event event that occurred.
+/// @param sfname filename the event occurred in.
+/// @param buf buffer the file is open in
+bool has_autocmd(event_T event,
+ char_u *sfname,
+ buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ AutoPat *ap;
+ char_u *fname;
+ char_u *tail = path_tail(sfname);
+ bool retval = false;
+
+ fname = (char_u *)FullName_save((char *)sfname, false);
+ if (fname == NULL) {
+ return false;
+ }
+
+#ifdef BACKSLASH_IN_FILENAME
+ // Replace all backslashes with forward slashes. This makes the
+ // autocommand patterns portable between Unix and Windows.
+ sfname = vim_strsave(sfname);
+ forward_slash(sfname);
+ forward_slash(fname);
+#endif
+
+ for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ if (ap->pat != NULL && ap->cmds != NULL
+ && (ap->buflocal_nr == 0
+ ? match_file_pat(
+ NULL,
+ &ap->reg_prog,
+ fname,
+ sfname,
+ tail,
+ ap->allow_dirs)
+ : buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
+ retval = true;
+ break;
+ }
+ }
+
+ xfree(fname);
+#ifdef BACKSLASH_IN_FILENAME
+ xfree(sfname);
+#endif
+
+ return retval;
+}
+
+// Function given to ExpandGeneric() to obtain the list of autocommand group
+// names.
+char_u *get_augroup_name(expand_T *xp, int idx)
+{
+ if (idx == augroups.ga_len) { // add "END" add the end
+ return (char_u *)"END";
+ }
+ if (idx >= augroups.ga_len) { // end of list
+ return NULL;
+ }
+ if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) {
+ // skip deleted entries
+ return (char_u *)"";
+ }
+ return (char_u *)AUGROUP_NAME(idx);
+}
+
+char_u *set_context_in_autocmd(
+ expand_T *xp,
+ char_u *arg,
+ int doautocmd // true for :doauto*, false for :autocmd
+)
+{
+ char_u *p;
+ int group;
+
+ // check for a group name, skip it if present
+ autocmd_include_groups = false;
+ p = arg;
+ group = au_get_grouparg(&arg);
+
+ // If there only is a group name that's what we expand.
+ if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) {
+ arg = p;
+ group = AUGROUP_ALL;
+ }
+
+ // skip over event name
+ for (p = arg; *p != NUL && !ascii_iswhite(*p); p++) {
+ if (*p == ',') {
+ arg = p + 1;
+ }
+ }
+ if (*p == NUL) {
+ if (group == AUGROUP_ALL) {
+ autocmd_include_groups = true;
+ }
+ xp->xp_context = EXPAND_EVENTS; // expand event name
+ xp->xp_pattern = arg;
+ return NULL;
+ }
+
+ // skip over pattern
+ arg = skipwhite(p);
+ while (*arg && (!ascii_iswhite(*arg) || arg[-1] == '\\')) {
+ arg++;
+ }
+ if (*arg) {
+ return arg; // expand (next) command
+ }
+
+ if (doautocmd) {
+ xp->xp_context = EXPAND_FILES; // expand file names
+ } else {
+ xp->xp_context = EXPAND_NOTHING; // pattern is not expanded
+ }
+ return NULL;
+}
+
+// Function given to ExpandGeneric() to obtain the list of event names.
+char_u *get_event_name(expand_T *xp, int idx)
+{
+ if (idx < augroups.ga_len) { // First list group names, if wanted
+ if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL
+ || AUGROUP_NAME(idx) == get_deleted_augroup()) {
+ return (char_u *)""; // skip deleted entries
+ }
+ return (char_u *)AUGROUP_NAME(idx);
+ }
+ return (char_u *)event_names[idx - augroups.ga_len].name;
+}
+
+/// Check whether given autocommand is supported
+///
+/// @param[in] event Event to check.
+///
+/// @return True if it is, false otherwise.
+bool autocmd_supported(const char *const event)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *p;
+ return event_name2nr((const char_u *)event, &p) != NUM_EVENTS;
+}
+
+/// Return true if an autocommand is defined for a group, event and
+/// pattern: The group can be omitted to accept any group.
+/// `event` and `pattern` can be omitted to accept any event and pattern.
+/// Buffer-local patterns <buffer> or <buffer=N> are accepted.
+/// Used for:
+/// exists("#Group") or
+/// exists("#Group#Event") or
+/// exists("#Group#Event#pat") or
+/// exists("#Event") or
+/// exists("#Event#pat")
+///
+/// @param arg autocommand string
+bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ event_T event;
+ AutoPat *ap;
+ buf_T *buflocal_buf = NULL;
+ int group;
+ bool retval = false;
+
+ // Make a copy so that we can change the '#' chars to a NUL.
+ char *const arg_save = xstrdup(arg);
+ char *p = strchr(arg_save, '#');
+ if (p != NULL) {
+ *p++ = NUL;
+ }
+
+ // First, look for an autocmd group name.
+ group = au_find_group((char_u *)arg_save);
+ char *event_name;
+ if (group == AUGROUP_ERROR) {
+ // Didn't match a group name, assume the first argument is an event.
+ group = AUGROUP_ALL;
+ event_name = arg_save;
+ } else {
+ if (p == NULL) {
+ // "Group": group name is present and it's recognized
+ retval = true;
+ goto theend;
+ }
+
+ // Must be "Group#Event" or "Group#Event#pat".
+ event_name = p;
+ p = strchr(event_name, '#');
+ if (p != NULL) {
+ *p++ = NUL; // "Group#Event#pat"
+ }
+ }
+
+ char *pattern = p; // "pattern" is NULL when there is no pattern.
+
+ // Find the index (enum) for the event name.
+ event = event_name2nr((char_u *)event_name, (char_u **)&p);
+
+ // return false if the event name is not recognized
+ if (event == NUM_EVENTS) {
+ goto theend;
+ }
+
+ // Find the first autocommand for this event.
+ // If there isn't any, return false;
+ // If there is one and no pattern given, return true;
+ ap = first_autopat[(int)event];
+ if (ap == NULL) {
+ goto theend;
+ }
+
+ // if pattern is "<buffer>", special handling is needed which uses curbuf
+ // for pattern "<buffer=N>, fnamecmp() will work fine
+ if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0) {
+ buflocal_buf = curbuf;
+ }
+
+ // Check if there is an autocommand with the given pattern.
+ for (; ap != NULL; ap = ap->next) {
+ // only use a pattern when it has not been removed and has commands.
+ // For buffer-local autocommands, fnamecmp() works fine.
+ if (ap->pat != NULL && ap->cmds != NULL
+ && (group == AUGROUP_ALL || ap->group == group)
+ && (pattern == NULL
+ || (buflocal_buf == NULL
+ ? fnamecmp(ap->pat, (char_u *)pattern) == 0
+ : ap->buflocal_nr == buflocal_buf->b_fnum))) {
+ retval = true;
+ break;
+ }
+ }
+
+theend:
+ xfree(arg_save);
+ return retval;
+}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
new file mode 100644
index 0000000000..af1eeb0fc4
--- /dev/null
+++ b/src/nvim/autocmd.h
@@ -0,0 +1,81 @@
+#ifndef NVIM_AUTOCMD_H
+#define NVIM_AUTOCMD_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/ex_cmds_defs.h"
+
+// Struct to save values in before executing autocommands for a buffer that is
+// not the current buffer.
+typedef struct {
+ buf_T *save_curbuf; ///< saved curbuf
+ int use_aucmd_win; ///< using aucmd_win
+ win_T *save_curwin; ///< saved curwin
+ win_T *save_prevwin; ///< saved prevwin
+ win_T *new_curwin; ///< new curwin
+ bufref_T new_curbuf; ///< new curbuf
+ char_u *globaldir; ///< saved value of globaldir
+} aco_save_T;
+
+typedef struct AutoCmd {
+ char_u *cmd; // Command to be executed (NULL when
+ // command has been removed)
+ bool once; // "One shot": removed after execution
+ bool nested; // If autocommands nest here
+ bool last; // last command in list
+ sctx_T script_ctx; // script context where defined
+ struct AutoCmd *next; // Next AutoCmd in list
+} AutoCmd;
+
+typedef struct AutoPat {
+ struct AutoPat *next; // next AutoPat in AutoPat list; MUST
+ // be the first entry
+ char_u *pat; // pattern as typed (NULL when pattern
+ // has been removed)
+ regprog_T *reg_prog; // compiled regprog for pattern
+ AutoCmd *cmds; // list of commands to do
+ int group; // group ID
+ int patlen; // strlen() of pat
+ int buflocal_nr; // !=0 for buffer-local AutoPat
+ char allow_dirs; // Pattern may match whole path
+ char last; // last pattern for apply_autocmds()
+} AutoPat;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "auevents_enum.generated.h"
+#endif
+
+///
+/// Struct used to keep status while executing autocommands for an event.
+///
+typedef struct AutoPatCmd {
+ AutoPat *curpat; // next AutoPat to examine
+ AutoCmd *nextcmd; // next AutoCmd to execute
+ int group; // group being used
+ char_u *fname; // fname to match with
+ char_u *sfname; // sfname to match with
+ char_u *tail; // tail of fname
+ event_T event; // current event
+ int arg_bufnr; // initially equal to <abuf>, set to zero when
+ // buf is deleted
+ struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation
+} AutoPatCmd;
+
+
+// Set by the apply_autocmds_group function if the given event is equal to
+// EVENT_FILETYPE. Used by the readfile function in order to determine if
+// EVENT_BUFREADPOST triggered the EVENT_FILETYPE.
+//
+// Relying on this value requires one to reset it prior calling
+// apply_autocmds_group.
+EXTERN bool au_did_filetype INIT(= false);
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "autocmd.h.generated.h"
+#endif
+
+#define AUGROUP_DEFAULT -1 // default autocmd group
+#define AUGROUP_ERROR -2 // erroneous autocmd group
+#define AUGROUP_ALL -3 // all autocmd groups
+
+#endif // NVIM_AUTOCMD_H
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/eval.lua b/src/nvim/eval.lua
index 6c316bb1fe..9f1994e299 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -385,6 +385,7 @@ return {
wildmenumode={},
win_findbuf={args=1},
win_getid={args={0,2}},
+ win_gettype={args={0,1}},
win_gotoid={args=1},
win_id2tabwin={args=1},
win_id2win={args=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 62a8022734..901f20bedf 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -10992,6 +10992,31 @@ static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = win_getid(argvars);
}
+/// "win_gettype(nr)" function
+static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = curwin;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_string = vim_strsave((char_u *)"unknown");
+ return;
+ }
+ }
+ if (wp == aucmd_win) {
+ rettv->vval.v_string = vim_strsave((char_u *)"autocmd");
+ } else if (wp->w_p_pvw) {
+ rettv->vval.v_string = vim_strsave((char_u *)"preview");
+ } else if (wp->w_floating) {
+ rettv->vval.v_string = vim_strsave((char_u *)"popup");
+ } else if (wp == curwin && cmdwin_type != 0) {
+ rettv->vval.v_string = vim_strsave((char_u *)"command");
+ }
+}
+
/// "win_gotoid()" function
static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
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/fileio.c b/src/nvim/fileio.c
index 6ee3a3f579..ab8072ae1d 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -67,85 +67,6 @@
#define UV_FS_COPYFILE_FICLONE 0
#endif
-//
-// The autocommands are stored in a list for each event.
-// Autocommands for the same pattern, that are consecutive, are joined
-// together, to avoid having to match the pattern too often.
-// The result is an array of Autopat lists, which point to AutoCmd lists:
-//
-// last_autopat[0] -----------------------------+
-// V
-// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
-// Autopat.cmds Autopat.cmds
-// | |
-// V V
-// AutoCmd.next AutoCmd.next
-// | |
-// V V
-// AutoCmd.next NULL
-// |
-// V
-// NULL
-//
-// last_autopat[1] --------+
-// V
-// first_autopat[1] --> Autopat.next --> NULL
-// Autopat.cmds
-// |
-// V
-// AutoCmd.next
-// |
-// V
-// NULL
-// etc.
-//
-// The order of AutoCmds is important, this is the order in which they were
-// defined and will have to be executed.
-//
-typedef struct AutoCmd {
- char_u *cmd; // Command to be executed (NULL when
- // command has been removed)
- bool once; // "One shot": removed after execution
- char nested; // If autocommands nest here
- char last; // last command in list
- sctx_T script_ctx; // script context where defined
- struct AutoCmd *next; // Next AutoCmd in list
-} AutoCmd;
-
-typedef struct AutoPat {
- struct AutoPat *next; // next AutoPat in AutoPat list; MUST
- // be the first entry
- char_u *pat; // pattern as typed (NULL when pattern
- // has been removed)
- regprog_T *reg_prog; // compiled regprog for pattern
- AutoCmd *cmds; // list of commands to do
- int group; // group ID
- int patlen; // strlen() of pat
- int buflocal_nr; // !=0 for buffer-local AutoPat
- char allow_dirs; // Pattern may match whole path
- char last; // last pattern for apply_autocmds()
-} AutoPat;
-
-///
-/// Struct used to keep status while executing autocommands for an event.
-///
-typedef struct AutoPatCmd {
- AutoPat *curpat; // next AutoPat to examine
- AutoCmd *nextcmd; // next AutoCmd to execute
- int group; // group being used
- char_u *fname; // fname to match with
- char_u *sfname; // sfname to match with
- char_u *tail; // tail of fname
- event_T event; // current event
- int arg_bufnr; // initially equal to <abuf>, set to zero when
- // buf is deleted
- struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation
-} AutoPatCmd;
-
-#define AUGROUP_DEFAULT -1 /* default autocmd group */
-#define AUGROUP_ERROR -2 /* erroneous autocmd group */
-#define AUGROUP_ALL -3 /* all autocmd groups */
-
#define HAS_BW_FLAGS
#define FIO_LATIN1 0x01 /* convert Latin1 */
#define FIO_UTF8 0x02 /* convert UTF-8 */
@@ -195,14 +116,6 @@ struct bw_info {
static char *e_auchangedbuf = N_(
"E812: Autocommands changed buffer or buffer name");
-// Set by the apply_autocmds_group function if the given event is equal to
-// EVENT_FILETYPE. Used by the readfile function in order to determine if
-// EVENT_BUFREADPOST triggered the EVENT_FILETYPE.
-//
-// Relying on this value requires one to reset it prior calling
-// apply_autocmds_group.
-static bool au_did_filetype INIT(= false);
-
void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
{
int msg_scroll_save;
@@ -231,14 +144,6 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
msg_scrolled_ign = FALSE;
}
-static AutoPat *last_autopat[NUM_EVENTS] = {
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
-};
/*
* Read lines from file "fname" into the buffer after line "from".
@@ -5404,2054 +5309,6 @@ char_u *vim_tempname(void)
}
-/*
- * Code for automatic commands.
- */
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "auevents_name_map.generated.h"
-#endif
-
-static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */
-
-/// List of autocmd group names
-static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL };
-#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i])
-
-/*
- * The ID of the current group. Group 0 is the default one.
- */
-static int current_augroup = AUGROUP_DEFAULT;
-
-static int au_need_clean = FALSE; /* need to delete marked patterns */
-
-
-
-static event_T last_event;
-static int last_group;
-static int autocmd_blocked = 0; /* block all autocmds */
-
-// use get_deleted_augroup() to get this
-static const char *deleted_augroup = NULL;
-
-static inline const char *get_deleted_augroup(void)
- FUNC_ATTR_ALWAYS_INLINE
-{
- if (deleted_augroup == NULL) {
- deleted_augroup = _("--Deleted--");
- }
- return deleted_augroup;
-}
-
-/*
- * Show the autocommands for one AutoPat.
- */
-static void show_autocmd(AutoPat *ap, event_T event)
-{
- AutoCmd *ac;
-
- /* Check for "got_int" (here and at various places below), which is set
- * when "q" has been hit for the "--more--" prompt */
- if (got_int)
- return;
- if (ap->pat == NULL) /* pattern has been removed */
- return;
-
- msg_putchar('\n');
- if (got_int)
- return;
- if (event != last_event || ap->group != last_group) {
- if (ap->group != AUGROUP_DEFAULT) {
- if (AUGROUP_NAME(ap->group) == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T));
- }
- msg_puts(" ");
- }
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- last_event = event;
- last_group = ap->group;
- msg_putchar('\n');
- if (got_int)
- return;
- }
- msg_col = 4;
- msg_outtrans(ap->pat);
-
- for (ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->cmd == NULL) { /* skip removed commands */
- continue;
- }
- if (msg_col >= 14) {
- msg_putchar('\n');
- }
- msg_col = 14;
- if (got_int) {
- return;
- }
- msg_outtrans(ac->cmd);
- if (p_verbose > 0) {
- last_set_msg(ac->script_ctx);
- }
- if (got_int) {
- return;
- }
- if (ac->next != NULL) {
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- }
- }
-}
-
-// Mark an autocommand handler for deletion.
-static void au_remove_pat(AutoPat *ap)
-{
- XFREE_CLEAR(ap->pat);
- ap->buflocal_nr = -1;
- au_need_clean = true;
-}
-
-// Mark all commands for a pattern for deletion.
-static void au_remove_cmds(AutoPat *ap)
-{
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- XFREE_CLEAR(ac->cmd);
- }
- au_need_clean = true;
-}
-
-// Delete one command from an autocmd pattern.
-static void au_del_cmd(AutoCmd *ac)
-{
- XFREE_CLEAR(ac->cmd);
- au_need_clean = true;
-}
-
-/// Cleanup autocommands and patterns that have been deleted.
-/// This is only done when not executing autocommands.
-static void au_cleanup(void)
-{
- AutoPat *ap, **prev_ap;
- event_T event;
-
- if (autocmd_busy || !au_need_clean) {
- return;
- }
-
- // Loop over all events.
- for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- // Loop over all autocommand patterns.
- prev_ap = &(first_autopat[(int)event]);
- for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- bool has_cmd = false;
-
- // Loop over all commands for this pattern.
- AutoCmd **prev_ac = &(ap->cmds);
- for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
- // Remove the command if the pattern is to be deleted or when
- // the command has been marked for deletion.
- if (ap->pat == NULL || ac->cmd == NULL) {
- *prev_ac = ac->next;
- xfree(ac->cmd);
- xfree(ac);
- } else {
- has_cmd = true;
- prev_ac = &(ac->next);
- }
- }
-
- if (ap->pat != NULL && !has_cmd) {
- // Pattern was not marked for deletion, but all of its commands were.
- // So mark the pattern for deletion.
- au_remove_pat(ap);
- }
-
- // Remove the pattern if it has been marked for deletion.
- if (ap->pat == NULL) {
- if (ap->next == NULL) {
- if (prev_ap == &(first_autopat[(int)event])) {
- last_autopat[(int)event] = NULL;
- } else {
- // this depends on the "next" field being the first in
- // the struct
- last_autopat[(int)event] = (AutoPat *)prev_ap;
- }
- }
- *prev_ap = ap->next;
- vim_regfree(ap->reg_prog);
- xfree(ap);
- } else {
- prev_ap = &(ap->next);
- }
- }
- }
-
- au_need_clean = false;
-}
-
-/*
- * Called when buffer is freed, to remove/invalidate related buffer-local
- * autocmds.
- */
-void aubuflocal_remove(buf_T *buf)
-{
- AutoPat *ap;
- event_T event;
- AutoPatCmd *apc;
-
- /* invalidate currently executing autocommands */
- for (apc = active_apc_list; apc; apc = apc->next)
- if (buf->b_fnum == apc->arg_bufnr)
- apc->arg_bufnr = 0;
-
- /* invalidate buflocals looping through events */
- for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
- event = (event_T)((int)event + 1))
- /* loop over all autocommand patterns */
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next)
- if (ap->buflocal_nr == buf->b_fnum) {
- au_remove_pat(ap);
- if (p_verbose >= 6) {
- verbose_enter();
- smsg(_("auto-removing autocommand: %s <buffer=%d>"),
- event_nr2name(event), buf->b_fnum);
- verbose_leave();
- }
- }
- au_cleanup();
-}
-
-// Add an autocmd group name.
-// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
-static int au_new_group(char_u *name)
-{
- int i = au_find_group(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it.
- // First try using a free entry.
- for (i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) == NULL) {
- break;
- }
- }
- if (i == augroups.ga_len) {
- ga_grow(&augroups, 1);
- }
-
- AUGROUP_NAME(i) = xstrdup((char *)name);
- if (i == augroups.ga_len) {
- augroups.ga_len++;
- }
- }
-
- return i;
-}
-
-static void au_del_group(char_u *name)
-{
- int i = au_find_group(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist
- EMSG2(_("E367: No such group: \"%s\""), name);
- } else if (i == current_augroup) {
- EMSG(_("E936: Cannot delete the current group"));
- } else {
- event_T event;
- AutoPat *ap;
- int in_use = false;
-
- for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->group == i && ap->pat != NULL) {
- give_warning((char_u *)
- _("W19: Deleting augroup that is still in use"), true);
- in_use = true;
- event = NUM_EVENTS;
- break;
- }
- }
- }
- xfree(AUGROUP_NAME(i));
- if (in_use) {
- AUGROUP_NAME(i) = (char *)get_deleted_augroup();
- } else {
- AUGROUP_NAME(i) = NULL;
- }
- }
-}
-
-/// Find the ID of an autocmd group name.
-///
-/// @param name augroup name
-///
-/// @return the ID or AUGROUP_ERROR (< 0) for error.
-static int au_find_group(const char_u *name)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- for (int i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup()
- && STRCMP(AUGROUP_NAME(i), name) == 0) {
- return i;
- }
- }
- return AUGROUP_ERROR;
-}
-
-/// Return true if augroup "name" exists.
-///
-/// @param name augroup name
-bool au_has_group(const char_u *name)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return au_find_group(name) != AUGROUP_ERROR;
-}
-
-/// ":augroup {name}".
-void do_augroup(char_u *arg, int del_group)
-{
- if (del_group) {
- if (*arg == NUL) {
- EMSG(_(e_argreq));
- } else {
- au_del_group(arg);
- }
- } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
- current_augroup = AUGROUP_DEFAULT;
- } else if (*arg) { // ":aug xxx": switch to group xxx
- int i = au_new_group(arg);
- if (i != AUGROUP_ERROR)
- current_augroup = i;
- } else { // ":aug": list the group names
- msg_start();
- for (int i = 0; i < augroups.ga_len; ++i) {
- if (AUGROUP_NAME(i) != NULL) {
- msg_puts(AUGROUP_NAME(i));
- msg_puts(" ");
- }
- }
- msg_clr_eos();
- msg_end();
- }
-}
-
-#if defined(EXITFREE)
-void free_all_autocmds(void)
-{
- for (current_augroup = -1; current_augroup < augroups.ga_len;
- current_augroup++) {
- do_autocmd((char_u *)"", true);
- }
-
- for (int i = 0; i < augroups.ga_len; i++) {
- char *const s = ((char **)(augroups.ga_data))[i];
- if ((const char *)s != get_deleted_augroup()) {
- xfree(s);
- }
- }
- ga_clear(&augroups);
-}
-#endif
-
-/*
- * Return the event number for event name "start".
- * Return NUM_EVENTS if the event name was not found.
- * Return a pointer to the next event name in "end".
- */
-static event_T event_name2nr(const char_u *start, char_u **end)
-{
- const char_u *p;
- int i;
- int len;
-
- // the event name ends with end of line, '|', a blank or a comma
- for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) {
- }
- for (i = 0; event_names[i].name != NULL; i++) {
- len = (int)event_names[i].len;
- if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) {
- break;
- }
- }
- if (*p == ',') {
- p++;
- }
- *end = (char_u *)p;
- if (event_names[i].name == NULL) {
- return NUM_EVENTS;
- }
- return event_names[i].event;
-}
-
-/// Return the name for event
-///
-/// @param[in] event Event to return name for.
-///
-/// @return Event name, static string. Returns "Unknown" for unknown events.
-static const char *event_nr2name(event_T event)
- FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST
-{
- int i;
-
- for (i = 0; event_names[i].name != NULL; i++) {
- if (event_names[i].event == event) {
- return event_names[i].name;
- }
- }
- return "Unknown";
-}
-
-/*
- * Scan over the events. "*" stands for all events.
- */
-static char_u *
-find_end_event (
- char_u *arg,
- int have_group /* TRUE when group name was found */
-)
-{
- char_u *pat;
- char_u *p;
-
- if (*arg == '*') {
- if (arg[1] && !ascii_iswhite(arg[1])) {
- EMSG2(_("E215: Illegal character after *: %s"), arg);
- return NULL;
- }
- pat = arg + 1;
- } else {
- for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
- if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) {
- if (have_group)
- EMSG2(_("E216: No such event: %s"), pat);
- else
- EMSG2(_("E216: No such group or event: %s"), pat);
- return NULL;
- }
- }
- }
- return pat;
-}
-
-/// Return true if "event" is included in 'eventignore'.
-///
-/// @param event event to check
-static bool event_ignored(event_T event)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- char_u *p = p_ei;
-
- while (*p != NUL) {
- if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
- return true;
- }
- if (event_name2nr(p, &p) == event) {
- return true;
- }
- }
-
- return false;
-}
-
-/*
- * Return OK when the contents of p_ei is valid, FAIL otherwise.
- */
-int check_ei(void)
-{
- char_u *p = p_ei;
-
- while (*p) {
- if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
- p += 3;
- if (*p == ',')
- ++p;
- } else if (event_name2nr(p, &p) == NUM_EVENTS)
- return FAIL;
- }
-
- return OK;
-}
-
-/*
- * Add "what" to 'eventignore' to skip loading syntax highlighting for every
- * buffer loaded into the window. "what" must start with a comma.
- * Returns the old value of 'eventignore' in allocated memory.
- */
-char_u *au_event_disable(char *what)
-{
- char_u *new_ei;
- char_u *save_ei;
-
- save_ei = vim_strsave(p_ei);
- new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what)));
- if (*what == ',' && *p_ei == NUL)
- STRCPY(new_ei, what + 1);
- else
- STRCAT(new_ei, what);
- set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE);
- xfree(new_ei);
-
- return save_ei;
-}
-
-void au_event_restore(char_u *old_ei)
-{
- if (old_ei != NULL) {
- set_string_option_direct((char_u *)"ei", -1, old_ei,
- OPT_FREE, SID_NONE);
- xfree(old_ei);
- }
-}
-
-// Implements :autocmd.
-// Defines an autocmd (does not execute; cf. apply_autocmds_group).
-//
-// Can be used in the following ways:
-//
-// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that
-// will be automatically executed for <event>
-// when editing a file matching <pat>, in
-// the current group.
-// :autocmd <event> <pat> Show the autocommands associated with
-// <event> and <pat>.
-// :autocmd <event> Show the autocommands associated with
-// <event>.
-// :autocmd Show all autocommands.
-// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with
-// <event> and <pat>, and add the command
-// <cmd>, for the current group.
-// :autocmd! <event> <pat> Remove all autocommands associated with
-// <event> and <pat> for the current group.
-// :autocmd! <event> Remove all autocommands associated with
-// <event> for the current group.
-// :autocmd! Remove ALL autocommands for the current
-// group.
-//
-// Multiple events and patterns may be given separated by commas. Here are
-// some examples:
-// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic
-// :autocmd bufleave * set tw=79 nosmartindent ic infercase
-//
-// :autocmd * *.c show all autocommands for *.c files.
-//
-// Mostly a {group} argument can optionally appear before <event>.
-void do_autocmd(char_u *arg_in, int forceit)
-{
- char_u *arg = arg_in;
- char_u *pat;
- char_u *envpat = NULL;
- char_u *cmd;
- int need_free = false;
- int nested = false;
- bool once = false;
- int group;
-
- if (*arg == '|') {
- arg = (char_u *)"";
- group = AUGROUP_ALL; // no argument, use all groups
- } else {
- // Check for a legal group name. If not, use AUGROUP_ALL.
- group = au_get_grouparg(&arg);
- }
-
- /*
- * Scan over the events.
- * If we find an illegal name, return here, don't do anything.
- */
- pat = find_end_event(arg, group != AUGROUP_ALL);
- if (pat == NULL)
- return;
-
- pat = skipwhite(pat);
- if (*pat == '|') {
- pat = (char_u *)"";
- cmd = (char_u *)"";
- } else {
- // Scan over the pattern. Put a NUL at the end.
- cmd = pat;
- while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) {
- cmd++;
- }
- if (*cmd) {
- *cmd++ = NUL;
- }
-
- // Expand environment variables in the pattern. Set 'shellslash', we want
- // forward slashes here.
- if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) {
-#ifdef BACKSLASH_IN_FILENAME
- int p_ssl_save = p_ssl;
-
- p_ssl = true;
-#endif
- envpat = expand_env_save(pat);
-#ifdef BACKSLASH_IN_FILENAME
- p_ssl = p_ssl_save;
-#endif
- if (envpat != NULL) {
- pat = envpat;
- }
- }
-
- cmd = skipwhite(cmd);
- for (size_t i = 0; i < 2; i++) {
- if (*cmd != NUL) {
- // Check for "++once" flag.
- if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (once) {
- EMSG2(_(e_duparg2), "++once");
- }
- once = true;
- cmd = skipwhite(cmd + 6);
- }
-
- // Check for "++nested" flag.
- if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) {
- if (nested) {
- EMSG2(_(e_duparg2), "++nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 8);
- }
-
- // Check for the old (deprecated) "nested" flag.
- if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (nested) {
- EMSG2(_(e_duparg2), "nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 6);
- }
- }
- }
-
- // Find the start of the commands.
- // Expand <sfile> in it.
- if (*cmd != NUL) {
- cmd = expand_sfile(cmd);
- if (cmd == NULL) { // some error
- return;
- }
- need_free = true;
- }
- }
-
- /*
- * Print header when showing autocommands.
- */
- if (!forceit && *cmd == NUL) {
- // Highlight title
- MSG_PUTS_TITLE(_("\n--- Autocommands ---"));
- }
-
- /*
- * Loop over the events.
- */
- last_event = (event_T)-1; // for listing the event name
- last_group = AUGROUP_ERROR; // for listing the group name
- if (*arg == '*' || *arg == NUL || *arg == '|') {
- for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
- break;
- }
- }
- } else {
- while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
- event_T event = event_name2nr(arg, &arg);
- assert(event < NUM_EVENTS);
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
- break;
- }
- }
- }
-
- if (need_free)
- xfree(cmd);
- xfree(envpat);
-}
-
-/*
- * Find the group ID in a ":autocmd" or ":doautocmd" argument.
- * The "argp" argument is advanced to the following argument.
- *
- * Returns the group ID or AUGROUP_ALL.
- */
-static int au_get_grouparg(char_u **argp)
-{
- char_u *group_name;
- char_u *p;
- char_u *arg = *argp;
- int group = AUGROUP_ALL;
-
- for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
- }
- if (p > arg) {
- group_name = vim_strnsave(arg, (int)(p - arg));
- group = au_find_group(group_name);
- if (group == AUGROUP_ERROR)
- group = AUGROUP_ALL; /* no match, use all groups */
- else
- *argp = skipwhite(p); /* match, skip over group name */
- xfree(group_name);
- }
- return group;
-}
-
-// do_autocmd() for one event.
-// Defines an autocmd (does not execute; cf. apply_autocmds_group).
-//
-// If *pat == NUL: do for all patterns.
-// If *cmd == NUL: show entries.
-// If forceit == TRUE: delete entries.
-// If group is not AUGROUP_ALL: only use this group.
-static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested,
- char_u *cmd, int forceit, int group)
-{
- AutoPat *ap;
- AutoPat **prev_ap;
- AutoCmd *ac;
- AutoCmd **prev_ac;
- int brace_level;
- char_u *endpat;
- int findgroup;
- int allgroups;
- int patlen;
- int is_buflocal;
- int buflocal_nr;
- char_u buflocal_pat[25]; /* for "<buffer=X>" */
-
- if (group == AUGROUP_ALL)
- findgroup = current_augroup;
- else
- findgroup = group;
- allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL);
-
- /*
- * Show or delete all patterns for an event.
- */
- if (*pat == NUL) {
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (forceit) { /* delete the AutoPat, if it's in the current group */
- if (ap->group == findgroup)
- au_remove_pat(ap);
- } else if (group == AUGROUP_ALL || ap->group == group)
- show_autocmd(ap, event);
- }
- }
-
- /*
- * Loop through all the specified patterns.
- */
- for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
- /*
- * Find end of the pattern.
- * Watch out for a comma in braces, like "*.\{obj,o\}".
- */
- endpat = pat;
- // ignore single comma
- if (*endpat == ',') {
- continue;
- }
- brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- ++endpat) {
- if (*endpat == '{')
- brace_level++;
- else if (*endpat == '}')
- brace_level--;
- }
- patlen = (int)(endpat - pat);
-
- /*
- * detect special <buflocal[=X]> buffer-local patterns
- */
- is_buflocal = FALSE;
- buflocal_nr = 0;
-
- if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0
- && pat[patlen - 1] == '>') {
- /* "<buffer...>": Error will be printed only for addition.
- * printing and removing will proceed silently. */
- is_buflocal = TRUE;
- if (patlen == 8)
- /* "<buffer>" */
- buflocal_nr = curbuf->b_fnum;
- else if (patlen > 9 && pat[7] == '=') {
- if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0)
- /* "<buffer=abuf>" */
- buflocal_nr = autocmd_bufnr;
- else if (skipdigits(pat + 8) == pat + patlen - 1)
- /* "<buffer=123>" */
- buflocal_nr = atoi((char *)pat + 8);
- }
- }
-
- if (is_buflocal) {
- /* normalize pat into standard "<buffer>#N" form */
- sprintf((char *)buflocal_pat, "<buffer=%d>", buflocal_nr);
- pat = buflocal_pat; /* can modify pat and patlen */
- patlen = (int)STRLEN(buflocal_pat); /* but not endpat */
- }
-
- // Find AutoPat entries with this pattern. When adding a command it
- // always goes at or after the last one, so start at the end.
- if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
- prev_ap = &first_autopat[(int)event];
- }
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- /* Accept a pattern when:
- * - a group was specified and it's that group, or a group was
- * not specified and it's the current group, or a group was
- * not specified and we are listing
- * - the length of the pattern matches
- * - the pattern matches.
- * For <buffer[=X]>, this condition works because we normalize
- * all buffer-local patterns.
- */
- if ((allgroups || ap->group == findgroup)
- && ap->patlen == patlen
- && STRNCMP(pat, ap->pat, patlen) == 0) {
- /*
- * Remove existing autocommands.
- * If adding any new autocmd's for this AutoPat, don't
- * delete the pattern from the autopat list, append to
- * this list.
- */
- if (forceit) {
- if (*cmd != NUL && ap->next == NULL) {
- au_remove_cmds(ap);
- break;
- }
- au_remove_pat(ap);
- }
- /*
- * Show autocmd's for this autopat, or buflocals <buffer=X>
- */
- else if (*cmd == NUL)
- show_autocmd(ap, event);
-
- /*
- * Add autocmd to this autopat, if it's the last one.
- */
- else if (ap->next == NULL)
- break;
- }
- }
- prev_ap = &ap->next;
- }
-
- /*
- * Add a new command.
- */
- if (*cmd != NUL) {
- /*
- * If the pattern we want to add a command to does appear at the
- * end of the list (or not is not in the list at all), add the
- * pattern at the end of the list.
- */
- if (ap == NULL) {
- /* refuse to add buffer-local ap if buffer number is invalid */
- if (is_buflocal && (buflocal_nr == 0
- || buflist_findnr(buflocal_nr) == NULL)) {
- emsgf(_("E680: <buffer=%d>: invalid buffer number "),
- buflocal_nr);
- return FAIL;
- }
-
- ap = xmalloc(sizeof(AutoPat));
- ap->pat = vim_strnsave(pat, patlen);
- ap->patlen = patlen;
-
- if (is_buflocal) {
- ap->buflocal_nr = buflocal_nr;
- ap->reg_prog = NULL;
- } else {
- char_u *reg_pat;
-
- ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, endpat,
- &ap->allow_dirs, TRUE);
- if (reg_pat != NULL)
- ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
- xfree(reg_pat);
- if (reg_pat == NULL || ap->reg_prog == NULL) {
- xfree(ap->pat);
- xfree(ap);
- return FAIL;
- }
- }
- ap->cmds = NULL;
- *prev_ap = ap;
- last_autopat[(int)event] = ap;
- ap->next = NULL;
- if (group == AUGROUP_ALL)
- ap->group = current_augroup;
- else
- ap->group = group;
- }
-
- /*
- * Add the autocmd at the end of the AutoCmd list.
- */
- prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL)
- prev_ac = &ac->next;
- ac = xmalloc(sizeof(AutoCmd));
- ac->cmd = vim_strsave(cmd);
- ac->script_ctx = current_sctx;
- ac->script_ctx.sc_lnum += sourcing_lnum;
- ac->next = NULL;
- *prev_ac = ac;
- ac->once = once;
- ac->nested = nested;
- }
- }
-
- au_cleanup(); /* may really delete removed patterns/commands now */
- return OK;
-}
-
-// Implementation of ":doautocmd [group] event [fname]".
-// Return OK for success, FAIL for failure;
-int
-do_doautocmd(
- char_u *arg,
- int do_msg, // give message for no matching autocmds?
- bool *did_something
-)
-{
- char_u *fname;
- int nothing_done = TRUE;
- int group;
-
- if (did_something != NULL) {
- *did_something = false;
- }
-
- /*
- * Check for a legal group name. If not, use AUGROUP_ALL.
- */
- group = au_get_grouparg(&arg);
-
- if (*arg == '*') {
- EMSG(_("E217: Can't execute autocommands for ALL events"));
- return FAIL;
- }
-
- /*
- * Scan over the events.
- * If we find an illegal name, return here, don't do anything.
- */
- fname = find_end_event(arg, group != AUGROUP_ALL);
- if (fname == NULL)
- return FAIL;
-
- fname = skipwhite(fname);
-
- // Loop over the events.
- while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) {
- if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true,
- group, curbuf, NULL)) {
- nothing_done = false;
- }
- }
-
- if (nothing_done && do_msg) {
- MSG(_("No matching autocommands"));
- }
- if (did_something != NULL) {
- *did_something = !nothing_done;
- }
-
- return aborting() ? FAIL : OK;
-}
-
-/*
- * ":doautoall": execute autocommands for each loaded buffer.
- */
-void ex_doautoall(exarg_T *eap)
-{
- int retval;
- aco_save_T aco;
- char_u *arg = eap->arg;
- int call_do_modelines = check_nomodeline(&arg);
- bufref_T bufref;
-
- /*
- * This is a bit tricky: For some commands curwin->w_buffer needs to be
- * equal to curbuf, but for some buffers there may not be a window.
- * So we change the buffer for the current window for a moment. This
- * gives problems when the autocommands make changes to the list of
- * buffers or windows...
- */
- FOR_ALL_BUFFERS(buf) {
- if (buf->b_ml.ml_mfp == NULL) {
- continue;
- }
- // Find a window for this buffer and save some values.
- aucmd_prepbuf(&aco, buf);
- set_bufref(&bufref, buf);
-
- bool did_aucmd;
- // execute the autocommands for this buffer
- retval = do_doautocmd(arg, false, &did_aucmd);
-
- if (call_do_modelines && did_aucmd) {
- // Execute the modeline settings, but don't set window-local
- // options if we are using the current window for another
- // buffer.
- do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0);
- }
-
- /* restore the current window */
- aucmd_restbuf(&aco);
-
- // Stop if there is some error or buffer was deleted.
- if (retval == FAIL || !bufref_valid(&bufref)) {
- break;
- }
- }
-
- check_cursor(); /* just in case lines got deleted */
-}
-
-/// Check *argp for <nomodeline>. When it is present return false, otherwise
-/// return true and advance *argp to after it. Thus do_modelines() should be
-/// called when true is returned.
-///
-/// @param[in,out] argp argument string
-bool check_nomodeline(char_u **argp)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (STRNCMP(*argp, "<nomodeline>", 12) == 0) {
- *argp = skipwhite(*argp + 12);
- return false;
- }
- return true;
-}
-
-/// Prepare for executing autocommands for (hidden) buffer `buf`.
-/// If the current buffer is not in any visible window, put it in a temporary
-/// floating window `aucmd_win`.
-/// Set `curbuf` and `curwin` to match `buf`.
-///
-/// @param aco structure to save values in
-/// @param buf new curbuf
-void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
-{
- win_T *win;
- bool need_append = true; // Append `aucmd_win` to the window list.
-
- /* Find a window that is for the new buffer */
- if (buf == curbuf) { /* be quick when buf is curbuf */
- win = curwin;
- } else {
- win = NULL;
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == buf) {
- win = wp;
- break;
- }
- }
- }
-
- // Allocate the `aucmd_win` dummy floating window.
- if (win == NULL && aucmd_win == NULL) {
- win_alloc_aucmd_win();
- need_append = false;
- }
- if (win == NULL && aucmd_win_used)
- /* Strange recursive autocommand, fall back to using the current
- * window. Expect a few side effects... */
- win = curwin;
-
- aco->save_curwin = curwin;
- aco->save_prevwin = prevwin;
- aco->save_curbuf = curbuf;
- if (win != NULL) {
- /* There is a window for "buf" in the current tab page, make it the
- * curwin. This is preferred, it has the least side effects (esp. if
- * "buf" is curbuf). */
- aco->use_aucmd_win = FALSE;
- curwin = win;
- } else {
- /* There is no window for "buf", use "aucmd_win". To minimize the side
- * effects, insert it in the current tab page.
- * Anything related to a window (e.g., setting folds) may have
- * unexpected results. */
- aco->use_aucmd_win = TRUE;
- aucmd_win_used = TRUE;
- aucmd_win->w_buffer = buf;
- aucmd_win->w_s = &buf->b_s;
- ++buf->b_nwindows;
- win_init_empty(aucmd_win); /* set cursor and topline to safe values */
-
- /* Make sure w_localdir and globaldir are NULL to avoid a chdir() in
- * win_enter_ext(). */
- XFREE_CLEAR(aucmd_win->w_localdir);
- aco->globaldir = globaldir;
- globaldir = NULL;
-
- block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
- if (need_append) {
- win_append(lastwin, aucmd_win);
- handle_register_window(aucmd_win);
- win_config_float(aucmd_win, aucmd_win->w_float_config);
- }
- // Prevent chdir() call in win_enter_ext(), through do_autochdir()
- int save_acd = p_acd;
- p_acd = false;
- win_enter(aucmd_win, false);
- p_acd = save_acd;
- unblock_autocmds();
- curwin = aucmd_win;
- }
- curbuf = buf;
- aco->new_curwin = curwin;
- set_bufref(&aco->new_curbuf, curbuf);
-}
-
-/// Cleanup after executing autocommands for a (hidden) buffer.
-/// Restore the window as it was (if possible).
-///
-/// @param aco structure holding saved values
-void aucmd_restbuf(aco_save_T *aco)
-{
- if (aco->use_aucmd_win) {
- curbuf->b_nwindows--;
- // Find "aucmd_win", it can't be closed, but it may be in another tab page.
- // Do not trigger autocommands here.
- block_autocmds();
- if (curwin != aucmd_win) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp == aucmd_win) {
- if (tp != curtab) {
- goto_tabpage_tp(tp, true, true);
- }
- win_goto(aucmd_win);
- goto win_found;
- }
- }
- }
-win_found:
-
- win_remove(curwin, NULL);
- handle_unregister_window(curwin);
- if (curwin->w_grid.chars != NULL) {
- ui_comp_remove_grid(&curwin->w_grid);
- ui_call_win_hide(curwin->w_grid.handle);
- grid_free(&curwin->w_grid);
- }
-
- aucmd_win_used = false;
- last_status(false); // may need to remove last status line
-
- if (!valid_tabpage_win(curtab)) {
- // no valid window in current tabpage
- close_tabpage(curtab);
- }
-
- unblock_autocmds();
-
- if (win_valid(aco->save_curwin)) {
- curwin = aco->save_curwin;
- } else {
- // Hmm, original window disappeared. Just use the first one.
- curwin = firstwin;
- }
- prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
- : firstwin; // window disappeared?
- vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables
- hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab
- curbuf = curwin->w_buffer;
-
- xfree(globaldir);
- globaldir = aco->globaldir;
-
- // the buffer contents may have changed
- check_cursor();
- if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- curwin->w_topfill = 0;
- }
- } else {
- // restore curwin
- if (win_valid(aco->save_curwin)) {
- // Restore the buffer which was previously edited by curwin, if it was
- // changed, we are still the same window and the buffer is valid.
- if (curwin == aco->new_curwin
- && curbuf != aco->new_curbuf.br_buf
- && bufref_valid(&aco->new_curbuf)
- && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) {
- if (curwin->w_s == &curbuf->b_s) {
- curwin->w_s = &aco->new_curbuf.br_buf->b_s;
- }
- curbuf->b_nwindows--;
- curbuf = aco->new_curbuf.br_buf;
- curwin->w_buffer = curbuf;
- curbuf->b_nwindows++;
- }
-
- curwin = aco->save_curwin;
- prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
- : firstwin; // window disappeared?
- curbuf = curwin->w_buffer;
- // In case the autocommand moves the cursor to a position that does not
- // exist in curbuf
- check_cursor();
- }
- }
-}
-
-static int autocmd_nested = FALSE;
-
-/// Execute autocommands for "event" and file name "fname".
-///
-/// @param event event that occurred
-/// @param fname filename, NULL or empty means use actual file name
-/// @param fname_io filename to use for <afile> on cmdline
-/// @param force When true, ignore autocmd_busy
-/// @param buf Buffer for <abuf>
-///
-/// @return true if some commands were executed.
-bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force,
- buf_T *buf)
-{
- return apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, NULL);
-}
-
-/// Like apply_autocmds(), but with extra "eap" argument. This takes care of
-/// setting v:filearg.
-///
-/// @param event event that occurred
-/// @param fname NULL or empty means use actual file name
-/// @param fname_io fname to use for <afile> on cmdline
-/// @param force When true, ignore autocmd_busy
-/// @param buf Buffer for <abuf>
-/// @param exarg Ex command arguments
-///
-/// @return true if some commands were executed.
-static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io,
- bool force, buf_T *buf, exarg_T *eap)
-{
- return apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, eap);
-}
-
-/// Like apply_autocmds(), but handles the caller's retval. If the script
-/// processing is being aborted or if retval is FAIL when inside a try
-/// conditional, no autocommands are executed. If otherwise the autocommands
-/// cause the script to be aborted, retval is set to FAIL.
-///
-/// @param event event that occurred
-/// @param fname NULL or empty means use actual file name
-/// @param fname_io fname to use for <afile> on cmdline
-/// @param force When true, ignore autocmd_busy
-/// @param buf Buffer for <abuf>
-/// @param[in,out] retval caller's retval
-///
-/// @return true if some autocommands were executed
-bool apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io,
- bool force, buf_T *buf, int *retval)
-{
- if (should_abort(*retval)) {
- return false;
- }
-
- bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, NULL);
- if (did_cmd && aborting()) {
- *retval = FAIL;
- }
- return did_cmd;
-}
-
-/// Return true when there is a CursorHold/CursorHoldI autocommand defined for
-/// the current mode.
-bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return first_autopat[(int)(get_real_state() == NORMAL_BUSY
- ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL;
-}
-
-/// Return true if the CursorHold/CursorHoldI event can be triggered.
-bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- int state;
-
- if (!did_cursorhold
- && has_cursorhold()
- && reg_recording == 0
- && typebuf.tb_len == 0
- && !ins_compl_active()
- ) {
- state = get_real_state();
- if (state == NORMAL_BUSY || (state & INSERT) != 0) {
- return true;
- }
- }
- return false;
-}
-
-/// Return true if "event" autocommand is defined.
-///
-/// @param event the autocommand to check
-bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return first_autopat[event] != NULL;
-}
-
-/// Execute autocommands for "event" and file name "fname".
-///
-/// @param event event that occurred
-/// @param fname filename, NULL or empty means use actual file name
-/// @param fname_io filename to use for <afile> on cmdline,
-/// NULL means use `fname`.
-/// @param force When true, ignore autocmd_busy
-/// @param group autocmd group ID or AUGROUP_ALL
-/// @param buf Buffer for <abuf>
-/// @param eap Ex command arguments
-///
-/// @return true if some commands were executed.
-static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
- bool force, int group, buf_T *buf,
- exarg_T *eap)
-{
- char_u *sfname = NULL; /* short file name */
- char_u *tail;
- bool save_changed;
- buf_T *old_curbuf;
- bool retval = false;
- char_u *save_sourcing_name;
- linenr_T save_sourcing_lnum;
- char_u *save_autocmd_fname;
- int save_autocmd_bufnr;
- char_u *save_autocmd_match;
- int save_autocmd_busy;
- int save_autocmd_nested;
- static int nesting = 0;
- AutoPatCmd patcmd;
- AutoPat *ap;
- char_u *save_cmdarg;
- long save_cmdbang;
- static int filechangeshell_busy = FALSE;
- proftime_T wait_time;
- bool did_save_redobuff = false;
- save_redo_T save_redo;
- const bool save_KeyTyped = KeyTyped;
-
- // Quickly return if there are no autocommands for this event or
- // autocommands are blocked.
- if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
- || autocmd_blocked > 0) {
- goto BYPASS_AU;
- }
-
- /*
- * When autocommands are busy, new autocommands are only executed when
- * explicitly enabled with the "nested" flag.
- */
- if (autocmd_busy && !(force || autocmd_nested))
- goto BYPASS_AU;
-
- /*
- * Quickly return when immediately aborting on error, or when an interrupt
- * occurred or an exception was thrown but not caught.
- */
- if (aborting())
- goto BYPASS_AU;
-
- /*
- * FileChangedShell never nests, because it can create an endless loop.
- */
- if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL
- || event == EVENT_FILECHANGEDSHELLPOST))
- goto BYPASS_AU;
-
- /*
- * Ignore events in 'eventignore'.
- */
- if (event_ignored(event))
- goto BYPASS_AU;
-
- /*
- * Allow nesting of autocommands, but restrict the depth, because it's
- * possible to create an endless loop.
- */
- if (nesting == 10) {
- EMSG(_("E218: autocommand nesting too deep"));
- goto BYPASS_AU;
- }
-
- /*
- * Check if these autocommands are disabled. Used when doing ":all" or
- * ":ball".
- */
- if ( (autocmd_no_enter
- && (event == EVENT_WINENTER || event == EVENT_BUFENTER))
- || (autocmd_no_leave
- && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE)))
- goto BYPASS_AU;
-
- /*
- * Save the autocmd_* variables and info about the current buffer.
- */
- save_autocmd_fname = autocmd_fname;
- save_autocmd_bufnr = autocmd_bufnr;
- save_autocmd_match = autocmd_match;
- save_autocmd_busy = autocmd_busy;
- save_autocmd_nested = autocmd_nested;
- save_changed = curbuf->b_changed;
- old_curbuf = curbuf;
-
- /*
- * Set the file name to be used for <afile>.
- * Make a copy to avoid that changing a buffer name or directory makes it
- * invalid.
- */
- if (fname_io == NULL) {
- if (event == EVENT_COLORSCHEME
- || event == EVENT_COLORSCHEMEPRE
- || event == EVENT_OPTIONSET) {
- autocmd_fname = NULL;
- } else if (fname != NULL && !ends_excmd(*fname)) {
- autocmd_fname = fname;
- } else if (buf != NULL) {
- autocmd_fname = buf->b_ffname;
- } else {
- autocmd_fname = NULL;
- }
- } else {
- autocmd_fname = fname_io;
- }
- if (autocmd_fname != NULL) {
- // Allocate MAXPATHL for when eval_vars() resolves the fullpath.
- autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL);
- }
-
- /*
- * Set the buffer number to be used for <abuf>.
- */
- if (buf == NULL)
- autocmd_bufnr = 0;
- else
- autocmd_bufnr = buf->b_fnum;
-
- /*
- * When the file name is NULL or empty, use the file name of buffer "buf".
- * Always use the full path of the file name to match with, in case
- * "allow_dirs" is set.
- */
- if (fname == NULL || *fname == NUL) {
- if (buf == NULL)
- fname = NULL;
- else {
- if (event == EVENT_SYNTAX)
- fname = buf->b_p_syn;
- else if (event == EVENT_FILETYPE)
- fname = buf->b_p_ft;
- else {
- if (buf->b_sfname != NULL)
- sfname = vim_strsave(buf->b_sfname);
- fname = buf->b_ffname;
- }
- }
- if (fname == NULL)
- fname = (char_u *)"";
- fname = vim_strsave(fname); /* make a copy, so we can change it */
- } else {
- sfname = vim_strsave(fname);
- // Don't try expanding the following events.
- if (event == EVENT_CMDLINECHANGED
- || event == EVENT_CMDLINEENTER
- || event == EVENT_CMDLINELEAVE
- || event == EVENT_CMDWINENTER
- || event == EVENT_CMDWINLEAVE
- || event == EVENT_CMDUNDEFINED
- || event == EVENT_COLORSCHEME
- || event == EVENT_COLORSCHEMEPRE
- || event == EVENT_DIRCHANGED
- || event == EVENT_FILETYPE
- || event == EVENT_FUNCUNDEFINED
- || event == EVENT_OPTIONSET
- || event == EVENT_QUICKFIXCMDPOST
- || event == EVENT_QUICKFIXCMDPRE
- || event == EVENT_REMOTEREPLY
- || event == EVENT_SPELLFILEMISSING
- || event == EVENT_SYNTAX
- || event == EVENT_SIGNAL
- || event == EVENT_TABCLOSED
- || event == EVENT_WINCLOSED) {
- fname = vim_strsave(fname);
- } else {
- fname = (char_u *)FullName_save((char *)fname, false);
- }
- }
- if (fname == NULL) { /* out of memory */
- xfree(sfname);
- retval = false;
- goto BYPASS_AU;
- }
-
-#ifdef BACKSLASH_IN_FILENAME
- // Replace all backslashes with forward slashes. This makes the
- // autocommand patterns portable between Unix and Windows.
- if (sfname != NULL) {
- forward_slash(sfname);
- }
- forward_slash(fname);
-#endif
-
-
- /*
- * Set the name to be used for <amatch>.
- */
- autocmd_match = fname;
-
-
- // Don't redraw while doing autocommands.
- RedrawingDisabled++;
- save_sourcing_name = sourcing_name;
- sourcing_name = NULL; /* don't free this one */
- save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 0; /* no line number here */
-
- const sctx_T save_current_sctx = current_sctx;
-
- if (do_profiling == PROF_YES)
- prof_child_enter(&wait_time); /* doesn't count for the caller itself */
-
- // Don't use local function variables, if called from a function.
- funccal_entry_T funccal_entry;
- save_funccal(&funccal_entry);
-
- /*
- * When starting to execute autocommands, save the search patterns.
- */
- if (!autocmd_busy) {
- save_search_patterns();
- if (!ins_compl_active()) {
- saveRedobuff(&save_redo);
- did_save_redobuff = true;
- }
- did_filetype = keep_filetype;
- }
-
- /*
- * Note that we are applying autocmds. Some commands need to know.
- */
- autocmd_busy = TRUE;
- filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL);
- ++nesting; /* see matching decrement below */
-
- /* Remember that FileType was triggered. Used for did_filetype(). */
- if (event == EVENT_FILETYPE)
- did_filetype = TRUE;
-
- tail = path_tail(fname);
-
- /* Find first autocommand that matches */
- patcmd.curpat = first_autopat[(int)event];
- patcmd.nextcmd = NULL;
- patcmd.group = group;
- patcmd.fname = fname;
- patcmd.sfname = sfname;
- patcmd.tail = tail;
- patcmd.event = event;
- patcmd.arg_bufnr = autocmd_bufnr;
- patcmd.next = NULL;
- auto_next_pat(&patcmd, false);
-
- /* found one, start executing the autocommands */
- if (patcmd.curpat != NULL) {
- /* add to active_apc_list */
- patcmd.next = active_apc_list;
- active_apc_list = &patcmd;
-
- // set v:cmdarg (only when there is a matching pattern)
- save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
- if (eap != NULL) {
- save_cmdarg = set_cmdarg(eap, NULL);
- set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
- } else {
- save_cmdarg = NULL; // avoid gcc warning
- }
- retval = true;
- // mark the last pattern, to avoid an endless loop when more patterns
- // are added when executing autocommands
- for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) {
- ap->last = false;
- }
- ap->last = true;
- check_lnums(true); // make sure cursor and topline are valid
-
- // Execute the autocmd. The `getnextac` callback handles iteration.
- do_cmdline(NULL, getnextac, (void *)&patcmd,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
-
- reset_lnums(); // restore cursor and topline, unless they were changed
-
- if (eap != NULL) {
- (void)set_cmdarg(NULL, save_cmdarg);
- set_vim_var_nr(VV_CMDBANG, save_cmdbang);
- }
- /* delete from active_apc_list */
- if (active_apc_list == &patcmd) /* just in case */
- active_apc_list = patcmd.next;
- }
-
- --RedrawingDisabled;
- autocmd_busy = save_autocmd_busy;
- filechangeshell_busy = FALSE;
- autocmd_nested = save_autocmd_nested;
- xfree(sourcing_name);
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- xfree(autocmd_fname);
- autocmd_fname = save_autocmd_fname;
- autocmd_bufnr = save_autocmd_bufnr;
- autocmd_match = save_autocmd_match;
- current_sctx = save_current_sctx;
- restore_funccal();
- if (do_profiling == PROF_YES) {
- prof_child_exit(&wait_time);
- }
- KeyTyped = save_KeyTyped;
- xfree(fname);
- xfree(sfname);
- --nesting; /* see matching increment above */
-
- // When stopping to execute autocommands, restore the search patterns and
- // the redo buffer. Free any buffers in the au_pending_free_buf list and
- // free any windows in the au_pending_free_win list.
- if (!autocmd_busy) {
- restore_search_patterns();
- if (did_save_redobuff) {
- restoreRedobuff(&save_redo);
- }
- did_filetype = FALSE;
- while (au_pending_free_buf != NULL) {
- buf_T *b = au_pending_free_buf->b_next;
- xfree(au_pending_free_buf);
- au_pending_free_buf = b;
- }
- while (au_pending_free_win != NULL) {
- win_T *w = au_pending_free_win->w_next;
- xfree(au_pending_free_win);
- au_pending_free_win = w;
- }
- }
-
- /*
- * Some events don't set or reset the Changed flag.
- * Check if still in the same buffer!
- */
- if (curbuf == old_curbuf
- && (event == EVENT_BUFREADPOST
- || event == EVENT_BUFWRITEPOST
- || event == EVENT_FILEAPPENDPOST
- || event == EVENT_VIMLEAVE
- || event == EVENT_VIMLEAVEPRE)) {
- if (curbuf->b_changed != save_changed)
- need_maketitle = TRUE;
- curbuf->b_changed = save_changed;
- }
-
- au_cleanup(); /* may really delete removed patterns/commands now */
-
-BYPASS_AU:
- /* When wiping out a buffer make sure all its buffer-local autocommands
- * are deleted. */
- if (event == EVENT_BUFWIPEOUT && buf != NULL)
- aubuflocal_remove(buf);
-
- if (retval == OK && event == EVENT_FILETYPE) {
- au_did_filetype = true;
- }
-
- return retval;
-}
-
-static char_u *old_termresponse = NULL;
-
-/*
- * Block triggering autocommands until unblock_autocmd() is called.
- * Can be used recursively, so long as it's symmetric.
- */
-void block_autocmds(void)
-{
- /* Remember the value of v:termresponse. */
- if (autocmd_blocked == 0)
- old_termresponse = get_vim_var_str(VV_TERMRESPONSE);
- ++autocmd_blocked;
-}
-
-void unblock_autocmds(void)
-{
- --autocmd_blocked;
-
- /* When v:termresponse was set while autocommands were blocked, trigger
- * the autocommands now. Esp. useful when executing a shell command
- * during startup (nvim -d). */
- if (autocmd_blocked == 0
- && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse)
- apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf);
-}
-
-// Find next autocommand pattern that matches.
-static void
-auto_next_pat(
- AutoPatCmd *apc,
- int stop_at_last /* stop when 'last' flag is set */
-)
-{
- AutoPat *ap;
- AutoCmd *cp;
- char *s;
-
- XFREE_CLEAR(sourcing_name);
-
- for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
- apc->curpat = NULL;
-
- /* Only use a pattern when it has not been removed, has commands and
- * the group matches. For buffer-local autocommands only check the
- * buffer number. */
- if (ap->pat != NULL && ap->cmds != NULL
- && (apc->group == AUGROUP_ALL || apc->group == ap->group)) {
- /* execution-condition */
- if (ap->buflocal_nr == 0
- ? match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname,
- apc->tail, ap->allow_dirs)
- : ap->buflocal_nr == apc->arg_bufnr) {
- const char *const name = event_nr2name(apc->event);
- s = _("%s Autocommands for \"%s\"");
- const size_t sourcing_name_len = (STRLEN(s) + strlen(name) + ap->patlen
- + 1);
- sourcing_name = xmalloc(sourcing_name_len);
- snprintf((char *)sourcing_name, sourcing_name_len, s, name,
- (char *)ap->pat);
- if (p_verbose >= 8) {
- verbose_enter();
- smsg(_("Executing %s"), sourcing_name);
- verbose_leave();
- }
-
- apc->curpat = ap;
- apc->nextcmd = ap->cmds;
- /* mark last command */
- for (cp = ap->cmds; cp->next != NULL; cp = cp->next)
- cp->last = FALSE;
- cp->last = TRUE;
- }
- line_breakcheck();
- if (apc->curpat != NULL) /* found a match */
- break;
- }
- if (stop_at_last && ap->last)
- break;
- }
-}
-
-/// Get next autocommand command.
-/// Called by do_cmdline() to get the next line for ":if".
-/// @return allocated string, or NULL for end of autocommands.
-char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
-{
- AutoPatCmd *acp = (AutoPatCmd *)cookie;
- char_u *retval;
- AutoCmd *ac;
-
- /* Can be called again after returning the last line. */
- if (acp->curpat == NULL)
- return NULL;
-
- /* repeat until we find an autocommand to execute */
- for (;; ) {
- /* skip removed commands */
- while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL)
- if (acp->nextcmd->last)
- acp->nextcmd = NULL;
- else
- acp->nextcmd = acp->nextcmd->next;
-
- if (acp->nextcmd != NULL)
- break;
-
- /* at end of commands, find next pattern that matches */
- if (acp->curpat->last)
- acp->curpat = NULL;
- else
- acp->curpat = acp->curpat->next;
- if (acp->curpat != NULL)
- auto_next_pat(acp, TRUE);
- if (acp->curpat == NULL)
- return NULL;
- }
-
- ac = acp->nextcmd;
-
- if (p_verbose >= 9) {
- verbose_enter_scroll();
- smsg(_("autocommand %s"), ac->cmd);
- msg_puts("\n"); // don't overwrite this either
- verbose_leave_scroll();
- }
- retval = vim_strsave(ac->cmd);
- // Remove one-shot ("once") autocmd in anticipation of its execution.
- if (ac->once) {
- au_del_cmd(ac);
- }
- autocmd_nested = ac->nested;
- current_sctx = ac->script_ctx;
- if (ac->last) {
- acp->nextcmd = NULL;
- } else {
- acp->nextcmd = ac->next;
- }
-
- return retval;
-}
-
-/// Return true if there is a matching autocommand for "fname".
-/// To account for buffer-local autocommands, function needs to know
-/// in which buffer the file will be opened.
-///
-/// @param event event that occurred.
-/// @param sfname filename the event occurred in.
-/// @param buf buffer the file is open in
-bool has_autocmd(event_T event, char_u *sfname, buf_T *buf)
- FUNC_ATTR_WARN_UNUSED_RESULT
-{
- AutoPat *ap;
- char_u *fname;
- char_u *tail = path_tail(sfname);
- bool retval = false;
-
- fname = (char_u *)FullName_save((char *)sfname, false);
- if (fname == NULL) {
- return false;
- }
-
-#ifdef BACKSLASH_IN_FILENAME
- // Replace all backslashes with forward slashes. This makes the
- // autocommand patterns portable between Unix and Windows.
- sfname = vim_strsave(sfname);
- forward_slash(sfname);
- forward_slash(fname);
-#endif
-
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->pat != NULL && ap->cmds != NULL
- && (ap->buflocal_nr == 0
- ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail,
- ap->allow_dirs)
- : buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
- retval = true;
- break;
- }
- }
-
- xfree(fname);
-#ifdef BACKSLASH_IN_FILENAME
- xfree(sfname);
-#endif
-
- return retval;
-}
-
-/*
- * Function given to ExpandGeneric() to obtain the list of autocommand group
- * names.
- */
-char_u *get_augroup_name(expand_T *xp, int idx)
-{
- if (idx == augroups.ga_len) { // add "END" add the end
- return (char_u *)"END";
- }
- if (idx >= augroups.ga_len) { // end of list
- return NULL;
- }
- if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- // skip deleted entries
- return (char_u *)"";
- }
- return (char_u *)AUGROUP_NAME(idx);
-}
-
-static int include_groups = FALSE;
-
-char_u *
-set_context_in_autocmd (
- expand_T *xp,
- char_u *arg,
- int doautocmd /* TRUE for :doauto*, FALSE for :autocmd */
-)
-{
- char_u *p;
- int group;
-
- /* check for a group name, skip it if present */
- include_groups = FALSE;
- p = arg;
- group = au_get_grouparg(&arg);
-
- /* If there only is a group name that's what we expand. */
- if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) {
- arg = p;
- group = AUGROUP_ALL;
- }
-
- /* skip over event name */
- for (p = arg; *p != NUL && !ascii_iswhite(*p); ++p)
- if (*p == ',')
- arg = p + 1;
- if (*p == NUL) {
- if (group == AUGROUP_ALL)
- include_groups = TRUE;
- xp->xp_context = EXPAND_EVENTS; /* expand event name */
- xp->xp_pattern = arg;
- return NULL;
- }
-
- /* skip over pattern */
- arg = skipwhite(p);
- while (*arg && (!ascii_iswhite(*arg) || arg[-1] == '\\'))
- arg++;
- if (*arg)
- return arg; /* expand (next) command */
-
- if (doautocmd)
- xp->xp_context = EXPAND_FILES; /* expand file names */
- else
- xp->xp_context = EXPAND_NOTHING; /* pattern is not expanded */
- return NULL;
-}
-
-/*
- * Function given to ExpandGeneric() to obtain the list of event names.
- */
-char_u *get_event_name(expand_T *xp, int idx)
-{
- if (idx < augroups.ga_len) { // First list group names, if wanted
- if (!include_groups || AUGROUP_NAME(idx) == NULL
- || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- return (char_u *)""; // skip deleted entries
- }
- return (char_u *)AUGROUP_NAME(idx);
- }
- return (char_u *)event_names[idx - augroups.ga_len].name;
-}
-
-
-/// Check whether given autocommand is supported
-///
-/// @param[in] event Event to check.
-///
-/// @return True if it is, false otherwise.
-bool autocmd_supported(const char *const event)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- char_u *p;
- return event_name2nr((const char_u *)event, &p) != NUM_EVENTS;
-}
-
-/// Return true if an autocommand is defined for a group, event and
-/// pattern: The group can be omitted to accept any group.
-/// `event` and `pattern` can be omitted to accept any event and pattern.
-/// Buffer-local patterns <buffer> or <buffer=N> are accepted.
-/// Used for:
-/// exists("#Group") or
-/// exists("#Group#Event") or
-/// exists("#Group#Event#pat") or
-/// exists("#Event") or
-/// exists("#Event#pat")
-///
-/// @param arg autocommand string
-bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- event_T event;
- AutoPat *ap;
- buf_T *buflocal_buf = NULL;
- int group;
- bool retval = false;
-
- // Make a copy so that we can change the '#' chars to a NUL.
- char *const arg_save = xstrdup(arg);
- char *p = strchr(arg_save, '#');
- if (p != NULL) {
- *p++ = NUL;
- }
-
- // First, look for an autocmd group name.
- group = au_find_group((char_u *)arg_save);
- char *event_name;
- if (group == AUGROUP_ERROR) {
- /* Didn't match a group name, assume the first argument is an event. */
- group = AUGROUP_ALL;
- event_name = arg_save;
- } else {
- if (p == NULL) {
- // "Group": group name is present and it's recognized
- retval = true;
- goto theend;
- }
-
- // Must be "Group#Event" or "Group#Event#pat".
- event_name = p;
- p = strchr(event_name, '#');
- if (p != NULL) {
- *p++ = NUL; // "Group#Event#pat"
- }
- }
-
- char *pattern = p; // "pattern" is NULL when there is no pattern.
-
- // Find the index (enum) for the event name.
- event = event_name2nr((char_u *)event_name, (char_u **)&p);
-
- /* return FALSE if the event name is not recognized */
- if (event == NUM_EVENTS)
- goto theend;
-
- /* Find the first autocommand for this event.
- * If there isn't any, return FALSE;
- * If there is one and no pattern given, return TRUE; */
- ap = first_autopat[(int)event];
- if (ap == NULL)
- goto theend;
-
- /* if pattern is "<buffer>", special handling is needed which uses curbuf */
- /* for pattern "<buffer=N>, fnamecmp() will work fine */
- if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0)
- buflocal_buf = curbuf;
-
- /* Check if there is an autocommand with the given pattern. */
- for (; ap != NULL; ap = ap->next)
- /* only use a pattern when it has not been removed and has commands. */
- /* For buffer-local autocommands, fnamecmp() works fine. */
- if (ap->pat != NULL && ap->cmds != NULL
- && (group == AUGROUP_ALL || ap->group == group)
- && (pattern == NULL
- || (buflocal_buf == NULL
- ? fnamecmp(ap->pat, (char_u *)pattern) == 0
- : ap->buflocal_nr == buflocal_buf->b_fnum))) {
- retval = true;
- break;
- }
-
-theend:
- xfree(arg_save);
- return retval;
-}
/// Tries matching a filename with a "pattern" ("prog" is NULL), or use the
/// precompiled regprog "prog" ("pattern" is NULL). That avoids calling
@@ -7467,8 +5324,8 @@ theend:
/// @param allow_dirs Allow matching with dir
///
/// @return true if there is a match, false otherwise
-static bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname,
- char_u *sfname, char_u *tail, int allow_dirs)
+bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname,
+ char_u *sfname, char_u *tail, int allow_dirs)
{
regmatch_T regmatch;
bool result = false;
diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h
index a6011ec414..71149bdcc1 100644
--- a/src/nvim/fileio.h
+++ b/src/nvim/fileio.h
@@ -3,6 +3,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/os/os.h"
+#include "nvim/autocmd.h"
// Values for readfile() flags
#define READ_NEW 0x01 // read a file into a new buffer
@@ -15,23 +16,8 @@
#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
-/*
- * Struct to save values in before executing autocommands for a buffer that is
- * not the current buffer.
- */
-typedef struct {
- buf_T *save_curbuf; ///< saved curbuf
- int use_aucmd_win; ///< using aucmd_win
- win_T *save_curwin; ///< saved curwin
- win_T *save_prevwin; ///< saved prevwin
- win_T *new_curwin; ///< new curwin
- bufref_T new_curbuf; ///< new curbuf
- char_u *globaldir; ///< saved value of globaldir
-} aco_save_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
// Events for autocommands
-# include "auevents_enum.generated.h"
# include "fileio.h.generated.h"
#endif
#endif // NVIM_FILEIO_H
diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua
index 98c3254e7a..6ee45a14af 100644
--- a/src/nvim/generators/gen_events.lua
+++ b/src/nvim/generators/gen_events.lua
@@ -41,22 +41,27 @@ names_tgt:write('\n {0, NULL, (event_T)0},')
enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
-names_tgt:write('\nstatic AutoPat *first_autopat[NUM_EVENTS] = {\n ')
-local line_len = 1
-for _ = 1,((#events) - 1) do
- line_len = line_len + #(' NULL,')
- if line_len > 80 then
- names_tgt:write('\n ')
- line_len = 1 + #(' NULL,')
+local gen_autopat_events = function(name)
+ names_tgt:write(string.format('\nstatic AutoPat *%s[NUM_EVENTS] = {\n ', name))
+ local line_len = 1
+ for _ = 1,((#events) - 1) do
+ line_len = line_len + #(' NULL,')
+ if line_len > 80 then
+ names_tgt:write('\n ')
+ line_len = 1 + #(' NULL,')
+ end
+ names_tgt:write(' NULL,')
end
- names_tgt:write(' NULL,')
-end
-if line_len + #(' NULL') > 80 then
- names_tgt:write('\n NULL')
-else
- names_tgt:write(' NULL')
+ if line_len + #(' NULL') > 80 then
+ names_tgt:write('\n NULL')
+ else
+ names_tgt:write(' NULL')
+ end
+ names_tgt:write('\n};\n')
end
-names_tgt:write('\n};\n')
+
+gen_autopat_events("first_autopat")
+gen_autopat_events("last_autopat")
enum_tgt:close()
names_tgt:close()
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/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 04a678eeb8..1fa7eeaea0 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -696,6 +696,7 @@ func Test_OptionSet_diffmode_close()
call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4'])
call assert_fails(':diffthis', 'E788')
call assert_equal(1, &diff)
+ set diffopt-=closeoff
bw!
call assert_fails(':diffoff!', 'E788')
bw!
@@ -1856,6 +1857,29 @@ func Test_FileChangedShell_reload()
call delete('Xchanged')
endfunc
+func LogACmd()
+ call add(g:logged, line('$'))
+endfunc
+
+func Test_TermChanged()
+ throw 'skipped: Nvim does not support TermChanged event'
+ CheckNotGui
+
+ enew!
+ tabnew
+ call setline(1, ['a', 'b', 'c', 'd'])
+ $
+ au TermChanged * call LogACmd()
+ let g:logged = []
+ let term_save = &term
+ set term=xterm
+ call assert_equal([1, 4], g:logged)
+
+ au! TermChanged
+ let &term = term_save
+ bwipe!
+endfunc
+
" Test for FileReadCmd autocmd
func Test_autocmd_FileReadCmd()
func ReadFileCmd()
@@ -1910,4 +1934,26 @@ func Test_autocmd_sigusr1()
unlet g:sigusr1_passed
endfunc
+" Test for the temporary internal window used to execute autocmds
+func Test_autocmd_window()
+ %bw!
+ edit one.txt
+ tabnew two.txt
+ let g:blist = []
+ augroup aucmd_win_test
+ au!
+ au BufEnter * call add(g:blist, [expand('<afile>'),
+ \ win_gettype(bufwinnr(expand('<afile>')))])
+ augroup END
+
+ doautoall BufEnter
+ call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist)
+
+ augroup aucmd_win_test
+ au!
+ augroup END
+ augroup! aucmd_win_test
+ %bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index e3c42a4fe3..81f653c393 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -796,6 +796,29 @@ func Test_cmdwin_feedkeys()
call feedkeys("q:\<CR>", 'x')
endfunc
+" Tests for the issues fixed in 7.4.441.
+" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim
+func Test_cmdwin_cedit()
+ exe "set cedit=\<C-c>"
+ normal! :
+ call assert_equal(1, winnr('$'))
+
+ let g:cmd_wintype = ''
+ func CmdWinType()
+ let g:cmd_wintype = getcmdwintype()
+ let g:wintype = win_gettype()
+ return ''
+ endfunc
+
+ call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>")
+ echo input('')
+ call assert_equal('@', g:cmd_wintype)
+ call assert_equal('command', g:wintype)
+
+ set cedit&vim
+ delfunc CmdWinType
+endfunc
+
func Test_buffers_lastused()
" check that buffers are sorted by time when wildmode has lastused
edit bufc " oldest
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index ed75bda7a5..52b5884c8b 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -200,8 +200,8 @@ let s:filename_checks = {
\ 'gsp': ['file.gsp'],
\ 'gtkrc': ['.gtkrc', 'gtkrc'],
\ 'haml': ['file.haml'],
- \ 'hamster': ['file.hsc', 'file.hsm'],
- \ 'haskell': ['file.hs', 'file.hs-boot'],
+ \ 'hamster': ['file.hsm'],
+ \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index fb464d95ea..4ee16558a0 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -735,20 +735,25 @@ func Test_popup_and_preview_autocommand()
endfunc
func Test_popup_and_previewwindow_dump()
- if !CanRunVimInTerminal()
- return
- endif
- call writefile([
- \ 'set previewheight=9',
- \ 'silent! pedit',
- \ 'call setline(1, map(repeat(["ab"], 10), "v:val. v:key"))',
- \ 'exec "norm! G\<C-E>\<C-E>"',
- \ ], 'Xscript')
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ set previewheight=9
+ silent! pedit
+ call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+ exec "norm! G\<C-E>\<C-E>"
+ END
+ call writefile(lines, 'Xscript')
let buf = RunVimInTerminal('-S Xscript', {})
+ " wait for the script to finish
+ call term_wait(buf)
+
" Test that popup and previewwindow do not overlap.
- call term_sendkeys(buf, "o\<C-X>\<C-N>")
- sleep 100m
+ call term_sendkeys(buf, "o")
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "\<C-X>\<C-N>")
call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
call term_sendkeys(buf, "\<Esc>u")
diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim
index 91923fb1e9..6c4ae414d3 100644
--- a/src/nvim/testdir/test_preview.vim
+++ b/src/nvim/testdir/test_preview.vim
@@ -11,3 +11,47 @@ func Test_Psearch()
call assert_equal(wincount, winnr('$'))
bwipe
endfunc
+
+func Test_window_preview()
+ " Open a preview window
+ pedit Xa
+ call assert_equal(2, winnr('$'))
+ call assert_equal(0, &previewwindow)
+
+ " Go to the preview window
+ wincmd P
+ call assert_equal(1, &previewwindow)
+ call assert_equal('preview', win_gettype())
+
+ " Close preview window
+ wincmd z
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, &previewwindow)
+
+ call assert_fails('wincmd P', 'E441:')
+endfunc
+
+func Test_window_preview_from_help()
+ filetype on
+ call writefile(['/* some C code */'], 'Xpreview.c')
+ help
+ pedit Xpreview.c
+ wincmd P
+ call assert_equal(1, &previewwindow)
+ call assert_equal('c', &filetype)
+ wincmd z
+
+ filetype off
+ close
+ call delete('Xpreview.c')
+endfunc
+
+func Test_multiple_preview_windows()
+ new
+ set previewwindow
+ new
+ call assert_fails('set previewwindow', 'E590:')
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 734f264672..7f50894f66 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -442,4 +442,302 @@ func Test_visual_put_in_block()
bwipe!
endfunc
+" Visual modes (v V CTRL-V) followed by an operator; count; repeating
+func Test_visual_mode_op()
+ new
+ call append(0, '')
+
+ call setline(1, 'apple banana cherry')
+ call cursor(1, 1)
+ normal lvld.l3vd.
+ call assert_equal('a y', getline(1))
+
+ call setline(1, ['line 1 line 1', 'line 2 line 2', 'line 3 line 3',
+ \ 'line 4 line 4', 'line 5 line 5', 'line 6 line 6'])
+ call cursor(1, 1)
+ exe "normal Vcnewline\<Esc>j.j2Vd."
+ call assert_equal(['newline', 'newline'], getline(1, '$'))
+
+ call deletebufline('', 1, '$')
+ call setline(1, ['xxxxxxxxxxxxx', 'xxxxxxxxxxxxx', 'xxxxxxxxxxxxx',
+ \ 'xxxxxxxxxxxxx'])
+ exe "normal \<C-V>jlc \<Esc>l.l2\<C-V>c----\<Esc>l."
+ call assert_equal([' --------x',
+ \ ' --------x',
+ \ 'xxxx--------x',
+ \ 'xxxx--------x'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Visual mode maps (movement and text object)
+" Visual mode maps; count; repeating
+" - Simple
+" - With an Ex command (custom text object)
+func Test_visual_mode_maps()
+ new
+ call append(0, '')
+
+ func SelectInCaps()
+ let [line1, col1] = searchpos('\u', 'bcnW')
+ let [line2, col2] = searchpos('.\u', 'nW')
+ call setpos("'<", [0, line1, col1, 0])
+ call setpos("'>", [0, line2, col2, 0])
+ normal! gv
+ endfunction
+
+ vnoremap W /\u/s-1<CR>
+ vnoremap iW :<C-U>call SelectInCaps()<CR>
+
+ call setline(1, 'KiwiRaspberryDateWatermelonPeach')
+ call cursor(1, 1)
+ exe "normal vWcNo\<Esc>l.fD2vd."
+ call assert_equal('NoNoberryach', getline(1))
+
+ call setline(1, 'JambuRambutanBananaTangerineMango')
+ call cursor(1, 1)
+ exe "normal llviWc-\<Esc>l.l2vdl."
+ call assert_equal('--ago', getline(1))
+
+ vunmap W
+ vunmap iW
+ bwipe!
+ delfunc SelectInCaps
+endfunc
+
+" Operator-pending mode maps (movement and text object)
+" - Simple
+" - With Ex command moving the cursor
+" - With Ex command and Visual selection (custom text object)
+func Test_visual_oper_pending_mode_maps()
+ new
+ call append(0, '')
+
+ func MoveToCap()
+ call search('\u', 'W')
+ endfunction
+
+ func SelectInCaps()
+ let [line1, col1] = searchpos('\u', 'bcnW')
+ let [line2, col2] = searchpos('.\u', 'nW')
+ call setpos("'<", [0, line1, col1, 0])
+ call setpos("'>", [0, line2, col2, 0])
+ normal! gv
+ endfunction
+
+ onoremap W /\u/<CR>
+ onoremap <Leader>W :<C-U>call MoveToCap()<CR>
+ onoremap iW :<C-U>call SelectInCaps()<CR>
+
+ call setline(1, 'PineappleQuinceLoganberryOrangeGrapefruitKiwiZ')
+ call cursor(1, 1)
+ exe "normal cW-\<Esc>l.l2.l."
+ call assert_equal('----Z', getline(1))
+
+ call setline(1, 'JuniperDurianZ')
+ call cursor(1, 1)
+ exe "normal g?\WfD."
+ call assert_equal('WhavcreQhevnaZ', getline(1))
+
+ call setline(1, 'LemonNectarineZ')
+ call cursor(1, 1)
+ exe "normal yiWPlciWNew\<Esc>fr."
+ call assert_equal('LemonNewNewZ', getline(1))
+
+ ounmap W
+ ounmap <Leader>W
+ ounmap iW
+ bwipe!
+ delfunc MoveToCap
+ delfunc SelectInCaps
+endfunc
+
+" Patch 7.3.879: Properly abort Operator-pending mode for "dv:<Esc>" etc.
+func Test_op_pend_mode_abort()
+ new
+ call append(0, '')
+
+ call setline(1, ['zzzz', 'zzzz'])
+ call cursor(1, 1)
+
+ exe "normal dV:\<CR>dv:\<CR>"
+ call assert_equal(['zzz'], getline(1, 2))
+ set nomodifiable
+ call assert_fails('exe "normal d:\<CR>"', 'E21:')
+ set modifiable
+ call feedkeys("dv:\<Esc>dV:\<Esc>", 'xt')
+ call assert_equal(['zzz'], getline(1, 2))
+ set nomodifiable
+ let v:errmsg = ''
+ call feedkeys("d:\<Esc>", 'xt')
+ call assert_true(v:errmsg !~# '^E21:')
+ set modifiable
+
+ bwipe!
+endfunc
+
+func Test_characterwise_visual_mode()
+ new
+
+ " characterwise visual mode: replace last line
+ $put ='a'
+ let @" = 'x'
+ normal v$p
+ call assert_equal('x', getline('$'))
+
+ " characterwise visual mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal G
+ normal kkv$d
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise visual mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gkkvj$d
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise visual mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gv$d
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise visual mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gkvj$d
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_characterwise_select_mode()
+ new
+
+ " Select mode maps
+ snoremap <lt>End> <End>
+ snoremap <lt>Down> <Down>
+ snoremap <lt>Del> <Del>
+
+ " characterwise select mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<End>\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Ggh\<End>\<Del>"
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ sunmap <lt>End>
+ sunmap <lt>Down>
+ sunmap <lt>Del>
+ bwipe!
+endfunc
+
+func Test_linewise_select_mode()
+ new
+
+ " linewise select mode: delete middle line
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+
+ " linewise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Down>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GgH\<Del>"
+ call assert_equal(['', 'a', 'b'], getline(1, '$'))
+
+ " linewise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkgH\<Down>\<Del>"
+ call assert_equal(['', 'a'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_visual_mode_put()
+ new
+
+ " v_p: replace last character with line register at middle line
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal k$vp
+ call assert_equal(['', 'aaa', 'bb', 'aaa', '', 'ccc'], getline(1, '$'))
+
+ " v_p: replace last character with line register at middle line selecting
+ " newline
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal k$v$p
+ call assert_equal(['', 'aaa', 'bb', 'aaa', 'ccc'], getline(1, '$'))
+
+ " v_p: replace last character with line register at last line
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal $vp
+ call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
+
+ " v_p: replace last character with line register at last line selecting
+ " newline
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal $v$p
+ call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_select_mode_gv()
+ new
+
+ " gv in exclusive select mode after operation
+ call append('$', ['zzz ', 'äà '])
+ set selection=exclusive
+ normal Gkv3lyjv3lpgvcxxx
+ call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$'))
+
+ " gv in exclusive select mode without operation
+ call deletebufline('', 1, '$')
+ call append('$', 'zzz ')
+ set selection=exclusive
+ exe "normal G0v3l\<Esc>gvcxxx"
+ call assert_equal(['', 'xxx '], getline(1, '$'))
+
+ set selection&vim
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index 500e3ff088..c630e678fd 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -172,39 +172,6 @@ func Test_window_split_edit_bufnr()
%bw!
endfunc
-func Test_window_preview()
- " Open a preview window
- pedit Xa
- call assert_equal(2, winnr('$'))
- call assert_equal(0, &previewwindow)
-
- " Go to the preview window
- wincmd P
- call assert_equal(1, &previewwindow)
-
- " Close preview window
- wincmd z
- call assert_equal(1, winnr('$'))
- call assert_equal(0, &previewwindow)
-
- call assert_fails('wincmd P', 'E441:')
-endfunc
-
-func Test_window_preview_from_help()
- filetype on
- call writefile(['/* some C code */'], 'Xpreview.c')
- help
- pedit Xpreview.c
- wincmd P
- call assert_equal(1, &previewwindow)
- call assert_equal('c', &filetype)
- wincmd z
-
- filetype off
- close
- call delete('Xpreview.c')
-endfunc
-
func Test_window_exchange()
e Xa
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({