aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml88
-rw-r--r--runtime/doc/api.txt2
-rw-r--r--runtime/doc/autocmd.txt15
-rw-r--r--runtime/doc/map.txt4
-rw-r--r--runtime/doc/vim_diff.txt3
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua18
-rw-r--r--src/nvim/api/vim.c10
-rw-r--r--src/nvim/auevents.lua5
-rw-r--r--src/nvim/autocmd.c13
-rw-r--r--src/nvim/ex_docmd.c15
-rw-r--r--src/nvim/file_search.c21
-rw-r--r--src/nvim/getchar.c29
-rw-r--r--src/nvim/testdir/test_autocmd.vim20
-rw-r--r--src/nvim/window.c23
-rw-r--r--test/functional/api/keymap_spec.lua18
-rw-r--r--test/functional/api/vim_spec.lua9
-rw-r--r--test/functional/autocmd/dirchanged_spec.lua128
-rw-r--r--test/functional/treesitter/highlight_spec.lua89
18 files changed, 363 insertions, 147 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b650c5dd85..c84c9f0bbd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -74,6 +74,11 @@ jobs:
- cc: clang
runner: macos-11.0
os: osx
+ - flavor: functionaltest-lua
+ cc: gcc
+ runner: ubuntu-20.04
+ os: linux
+ cmake: minimum_required
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
if: github.event.pull_request.draft == false
@@ -92,6 +97,24 @@ jobs:
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
+ - name: Install minimum required version of cmake
+ if: matrix.cmake == 'minimum_required'
+ env:
+ CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
+ CMAKE_VERSION: '3.10.0'
+ shell: bash
+ run: |
+ curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
+ mkdir -p "$HOME/.local/bin" /opt/cmake-custom
+ chmod a+x /tmp/cmake-installer.sh
+ /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
+ ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
+ cmake_version="$(cmake --version | head -1)"
+ echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
+ echo "Unexpected CMake version: $cmake_version"
+ exit 1
+ }
+
- name: Install new clang
if: matrix.flavor == 'asan' || matrix.flavor == 'tsan'
run: |
@@ -152,68 +175,3 @@ jobs:
run: powershell ci\build.ps1
env:
CONFIGURATION: ${{ matrix.config }}
-
- functionaltest:
- name: ${{ matrix.runner }} ${{ matrix.flavor }} (cc=${{ matrix.cc }})
- strategy:
- fail-fast: false
- matrix:
- include:
- - flavor: functionaltest-lua
- cc: gcc
- runner: ubuntu-20.04
- os: linux
- runs-on: ${{ matrix.runner }}
- timeout-minutes: 45
- env:
- CC: ${{ matrix.cc }}
- CI_OS_NAME: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v2
-
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh ${{ matrix.flavor }}
-
- - name: Install apt packages
- 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
-
- - name: Install minimum required version of cmake
- env:
- CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
- CMAKE_VERSION: '3.10.0'
- shell: bash
- run: |
- curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
- mkdir -p "$HOME/.local/bin" /opt/cmake-custom
- chmod a+x /tmp/cmake-installer.sh
- /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
- ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
- cmake_version="$(cmake --version | head -1)"
- echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
- echo "Unexpected CMake version: $cmake_version"
- exit 1
- }
-
- - name: Setup interpreter packages
- run: |
- ./ci/install.sh
-
- - name: Cache dependencies
- uses: actions/cache@v2
- with:
- path: |
- ${{ env.CACHE_NVIM_DEPS_DIR }}
- ~/.ccache
- key: ${{ matrix.runner }}-${{ matrix.flavor }}-${{ matrix.cc }}-${{ hashFiles('cmake/*', 'third-party/**', '**/CMakeLists.txt') }}-${{ github.base_ref }}
-
- - name: Build third-party
- run: ./ci/before_script.sh
-
- - name: Build and test
- run: ./ci/script.sh
-
- - name: Cache dependencies
- if: ${{ success() }}
- run: ./ci/before_cache.sh
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 7a5aeee603..1e1534c31f 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1584,7 +1584,7 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()*
{rhs} Right-hand-side |{rhs}| of the mapping.
{opts} Optional parameters map. Accepts all
|:map-arguments| as keys excluding |<buffer>| but
- including |noremap| and "desc". |desc| can be used
+ including |noremap| and "desc". "desc" can be used
to give a description to keymap. When called from
Lua, also accepts a "callback" key that takes a
Lua function to call when the mapping is executed.
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index ed75acf36e..dbe70b84cf 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -525,8 +525,19 @@ DirChanged After the |current-directory| was changed.
"global" to trigger on `:cd`
"auto" to trigger on 'autochdir'.
Sets these |v:event| keys:
- cwd: current working directory
- scope: "global", "tabpage", "window"
+ cwd: current working directory
+ scope: "global", "tabpage", "window"
+ changed_window: v:true if we fired the event
+ switching window (or tab)
+ <afile> is set to the new directory name.
+ Non-recursive (event cannot trigger itself).
+ *DirChangedPre*
+DirChangedPre When the |current-directory| is going to be
+ changed, as with |DirChanged|.
+ The pattern is like with |DirChanged|.
+ Sets these |v:event| keys:
+ directory: new working directory
+ scope: "global", "tabpage", "window"
changed_window: v:true if we fired the event
switching window (or tab)
<afile> is set to the new directory name.
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 2d2795b1ca..358e944261 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -502,7 +502,9 @@ Note: When using mappings for Visual mode, you can use the "'<" mark, which
is the start of the last selected Visual area in the current buffer |'<|.
The |:filter| command can be used to select what mappings to list. The
-pattern is matched against the {lhs} and {rhs} in the raw form.
+pattern is matched against the {lhs} and {rhs} in the raw form. If a
+description was added using |nvim_set_keymap()| or |nvim_buf_set_keymap()|
+then the pattern is also matched against it.
*:map-verbose*
When 'verbose' is non-zero, listing a key map will also display where it was
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 7892b82137..385ce34d70 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -433,7 +433,8 @@ Vimscript compatibility:
`this_session` does not alias to |v:this_session|
Working directory (Vim implemented some of these later than Nvim):
-- |DirChanged| can be triggered when switching to another window.
+- |DirChanged| and |DirChangedPre| can be triggered when switching to another
+ window or tab.
- |getcwd()| and |haslocaldir()| may throw errors if the tab page or window
cannot be found. *E5000* *E5001* *E5002*
- |haslocaldir()| checks for tab-local directory if and only if -1 is passed as
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 22b528838c..b6f61cfb2e 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -22,7 +22,21 @@ local _link_default_highlight_once = function(from, to)
return from
end
-TSHighlighter.hl_map = {
+-- If @definition.special does not exist use @definition instead
+local subcapture_fallback = {
+ __index = function(self, capture)
+ local rtn
+ local shortened = capture
+ while not rtn and shortened do
+ shortened = shortened:match('(.*)%.')
+ rtn = shortened and rawget(self, shortened)
+ end
+ rawset(self, capture, rtn or "__notfound")
+ return rtn
+ end
+}
+
+TSHighlighter.hl_map = setmetatable({
["error"] = "Error",
-- Miscs
@@ -66,7 +80,7 @@ TSHighlighter.hl_map = {
["type.builtin"] = "Type",
["structure"] = "Structure",
["include"] = "Include",
-}
+}, subcapture_fallback)
---@private
local function is_highlight_name(capture_name)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f4909b0801..11bb1750e4 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -497,8 +497,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- do_in_runtimepath((char_u *)(name.size ? name.data : ""),
- flags, find_runtime_cb, &rv);
+ TRY_WRAP({
+ try_start();
+ do_in_runtimepath((char_u *)(name.size ? name.data : ""),
+ flags, find_runtime_cb, &rv);
+ try_end(err);
+ });
return rv;
}
@@ -1584,7 +1588,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
/// @param rhs Right-hand-side |{rhs}| of the mapping.
/// @param opts Optional parameters map. Accepts all |:map-arguments|
/// as keys excluding |<buffer>| but including |noremap| and "desc".
-/// |desc| can be used to give a description to keymap.
+/// "desc" can be used to give a description to keymap.
/// When called from Lua, also accepts a "callback" key that takes
/// a Lua function to call when the mapping is executed.
/// Values are Booleans. Unknown key is an error.
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 8fe623fc96..518d0b52b2 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -40,6 +40,7 @@ return {
'DiagnosticChanged', -- diagnostics in a buffer were modified
'DiffUpdated', -- diffs have been updated
'DirChanged', -- directory changed
+ 'DirChangedPre', -- directory is going to change
'EncodingChanged', -- after changing the 'encoding' option
'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
@@ -132,18 +133,14 @@ return {
nvim_specific = {
BufModifiedSet=true,
DiagnosticChanged=true,
- DirChanged=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true,
- TabClosed=true,
- TabNew=true,
TabNewEntered=true,
TermClose=true,
TermOpen=true,
UIEnter=true,
UILeave=true,
- WinClosed=true,
WinScrolled=true,
},
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index dfba18b11d..9117dde089 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1517,12 +1517,13 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| 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_MODECHANGED
- || 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_DIRCHANGED || event == EVENT_DIRCHANGEDPRE
+ || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED
+ || event == EVENT_MODECHANGED || 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_USER
|| event == EVENT_WINCLOSED) {
fname = vim_strsave(fname);
} else {
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 87f8865133..9991584862 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -7797,7 +7797,7 @@ static char_u *get_prevdir(CdScope scope)
/// Deal with the side effects of changing the current directory.
///
/// @param scope Scope of the function call (global, tab or window).
-void post_chdir(CdScope scope, bool trigger_dirchanged)
+static void post_chdir(CdScope scope, bool trigger_dirchanged)
{
// Always overwrite the window-local CWD.
XFREE_CLEAR(curwin->w_localdir);
@@ -7838,7 +7838,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
shorten_fnames(true);
if (trigger_dirchanged) {
- do_autocmd_dirchanged(cwd, scope, kCdCauseManual);
+ do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false);
}
}
@@ -7882,10 +7882,13 @@ bool changedir_func(char_u *new_dir, CdScope scope)
}
bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
- if (dir_differs && vim_chdir(new_dir) != 0) {
- emsg(_(e_failed));
- xfree(pdir);
- return false;
+ if (dir_differs) {
+ do_autocmd_dirchanged((char *)new_dir, scope, kCdCauseManual, true);
+ if (vim_chdir(new_dir) != 0) {
+ emsg(_(e_failed));
+ xfree(pdir);
+ return false;
+ }
}
char_u **pp;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index 25dbf680de..d0f7a91d6c 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1600,11 +1600,13 @@ theend:
return file_name;
}
-void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
+void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre)
{
static bool recursive = false;
- if (recursive || !has_event(EVENT_DIRCHANGED)) {
+ event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED;
+
+ if (recursive || !has_event(event)) {
// No autocommand was defined or we changed
// the directory from this autocommand.
return;
@@ -1638,8 +1640,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
new_dir = new_dir_buf;
#endif
+ if (pre) {
+ tv_dict_add_str(dict, S_LEN("directory"), new_dir);
+ } else {
+ tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
+ }
tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
- tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict);
@@ -1655,8 +1661,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
abort();
}
- apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
- curbuf);
+ apply_autocmds(event, (char_u *)buf, (char_u *)new_dir, false, curbuf);
restore_v_event(dict, &save_v_event);
@@ -1682,12 +1687,16 @@ int vim_chdirfile(char_u *fname, CdCause cause)
return OK;
}
+ if (cause != kCdCauseOther) {
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true);
+ }
+
if (os_chdir(dir) != 0) {
return FAIL;
}
if (cause != kCdCauseOther) {
- do_autocmd_dirchanged(dir, kCdScopeWindow, cause);
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false);
}
return OK;
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 5d8a8ddbfe..741fc6d803 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1004,20 +1004,9 @@ int ins_char_typebuf(int c, int modifier)
buf[len + 2] = (char_u)K_THIRD(c);
buf[len + 3] = NUL;
} else {
- char_u *p = buf + len;
- int char_len = utf_char2bytes(c, p);
- len += char_len;
- // If the character contains K_SPECIAL bytes they need escaping.
- for (int i = char_len; --i >= 0; p++) {
- if ((uint8_t)(*p) == K_SPECIAL) {
- memmove(p + 3, p + 1, (size_t)i);
- *p++ = K_SPECIAL;
- *p++ = KS_SPECIAL;
- *p = KE_FILLER;
- len += 2;
- }
- }
- *p = NUL;
+ char_u *end = add_char2buf(c, buf + len);
+ *end = NUL;
+ len = (int)(end - buf);
}
(void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
return len;
@@ -2679,8 +2668,7 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
mapargs->orig_rhs_len = 0;
// stores <lua>ref_no<cr> in map_str
mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
- (char_u)KEY2TERMCAP0(K_LUA), KEY2TERMCAP1(K_LUA),
- rhs_lua);
+ (char_u)KS_EXTRA, KE_LUA, rhs_lua);
mapargs->rhs = vim_strsave((char_u *)tmp_buf);
}
@@ -3434,8 +3422,8 @@ static void showmap(mapblock_T *mp, bool local)
{
size_t len = 1;
- if (message_filtered(mp->m_keys)
- && mp->m_str != NULL && message_filtered(mp->m_str)) {
+ if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)
+ && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) {
return;
}
@@ -3484,7 +3472,7 @@ static void showmap(mapblock_T *mp, bool local)
char msg[100];
snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref);
msg_puts_attr(msg, HL_ATTR(HLF_8));
- } else if (mp->m_str == NULL) {
+ } else if (mp->m_str[0] == NUL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
// Remove escaping of K_SPECIAL, because "m_str" is in a format to be used
@@ -3584,8 +3572,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
mp = maphash[hash];
}
for (; mp; mp = mp->m_next) {
- if ((mp->m_mode & mode)
- && mp->m_str != NULL && strstr((char *)mp->m_str, rhs) != NULL) {
+ if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) {
return true;
}
}
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index a8d51ef598..0146c06109 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1827,6 +1827,14 @@ func Test_autocommand_all_events()
call assert_fails('au * x bwipe', 'E1155:')
endfunc
+func Test_autocmd_user()
+ au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")]
+ doautocmd User MyEvent
+ call assert_equal(['MyEvent', 'MyEvent'], s:res)
+ au! User
+ unlet s:res
+endfunc
+
function s:Before_test_dirchanged()
augroup test_dirchanged
autocmd!
@@ -1850,14 +1858,16 @@ endfunc
function Test_dirchanged_global()
call s:Before_test_dirchanged()
+ autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo]
+ call assert_equal(expected, s:li)
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
call s:After_test_dirchanged()
endfunc
@@ -1879,6 +1889,7 @@ function Test_dirchanged_auto()
CheckOption autochdir
call s:Before_test_dirchanged()
call test_autochdir()
+ autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
set acd
@@ -1886,7 +1897,8 @@ function Test_dirchanged_auto()
call assert_equal([], s:li)
exe 'edit ' . s:dir_foo . '/Xfile'
call assert_equal(s:dir_foo, getcwd())
- call assert_equal(["auto:", s:dir_foo], s:li)
+ let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
+ call assert_equal(expected, s:li)
set noacd
bwipe!
call s:After_test_dirchanged()
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1e737d2083..43667377c5 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -4688,20 +4688,29 @@ void fix_current_dir(void)
globaldir = (char_u *)xstrdup(cwd);
}
}
+ bool dir_differs = pathcmp(new_dir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, true);
+ }
if (os_chdir(new_dir) == 0) {
- if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) {
- do_autocmd_dirchanged(new_dir, curwin->w_localdir
- ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, false);
}
- last_chdir_reason = NULL;
- shorten_fnames(true);
}
+ last_chdir_reason = NULL;
+ shorten_fnames(true);
} else if (globaldir != NULL) {
// Window doesn't have a local directory and we are not in the global
// directory: Change to the global directory.
+ bool dir_differs = pathcmp((char *)globaldir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, true);
+ }
if (os_chdir((char *)globaldir) == 0) {
- if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) {
- do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, false);
}
}
XFREE_CLEAR(globaldir);
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 450a76ddac..e49e0b8cc4 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -877,6 +877,24 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq("\nn lhs rhs\n map description",
helpers.exec_capture("nmap lhs"))
end)
+
+ it ('can :filter maps based on description', function()
+ meths.set_keymap('n', 'asdf1', 'qwert', {desc='do the one thing'})
+ meths.set_keymap('n', 'asdf2', 'qwert', {desc='doesnot really do anything'})
+ meths.set_keymap('n', 'asdf3', 'qwert', {desc='do the other thing'})
+ eq([[
+
+n asdf3 qwert
+ do the other thing
+n asdf1 qwert
+ do the one thing]],
+ helpers.exec_capture('filter the nmap'))
+ end)
+
+ it ('shows <nop> as map rhs', function()
+ meths.set_keymap('n', 'asdf', '<nop>', {})
+ eq('\nn asdf <Nop>', helpers.exec_capture('nmap asdf'))
+ end)
end)
describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 5a387f3deb..71cd055e08 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -23,6 +23,7 @@ local next_msg = helpers.next_msg
local tmpname = helpers.tmpname
local write_file = helpers.write_file
local exec_lua = helpers.exec_lua
+local exc_exec = helpers.exc_exec
local pcall_err = helpers.pcall_err
local format_string = helpers.format_string
@@ -2240,6 +2241,14 @@ describe('API', function()
eq({}, meths.get_runtime_file("foobarlang/", true))
end)
+ it('can handle bad patterns', function()
+ if helpers.pending_win32(pending) then return end
+
+ eq("Vim:E220: Missing }.", pcall_err(meths.get_runtime_file, "{", false))
+
+ eq('Vim(echo):E5555: API call: Vim:E220: Missing }.',
+ exc_exec("echo nvim_get_runtime_file('{', v:false)"))
+ end)
end)
describe('nvim_get_all_options_info', function()
diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua
index f4a1642ebf..45dc06b39b 100644
--- a/test/functional/autocmd/dirchanged_spec.lua
+++ b/test/functional/autocmd/dirchanged_spec.lua
@@ -8,7 +8,7 @@ local eval = h.eval
local request = h.request
local iswin = h.iswin
-describe('autocmd DirChanged', function()
+describe('autocmd DirChanged and DirChangedPre', function()
local curdir = string.gsub(lfs.currentdir(), '\\', '/')
local dirs = {
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
@@ -26,31 +26,43 @@ describe('autocmd DirChanged', function()
before_each(function()
clear()
+ command('autocmd DirChangedPre * let [g:evpre, g:amatchpre, g:cdprecount] '
+ ..'= [copy(v:event), expand("<amatch>"), 1 + get(g:, "cdprecount", 0)]')
command('autocmd DirChanged * let [g:getcwd, g:ev, g:amatch, g:cdcount] '
- ..' = [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
+ ..'= [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
-- Normalize path separators.
+ command([[autocmd DirChangedPre * let g:evpre['directory'] = substitute(g:evpre['directory'], '\\', '/', 'g')]])
command([[autocmd DirChanged * let g:ev['cwd'] = substitute(g:ev['cwd'], '\\', '/', 'g')]])
- command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
+ command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
end)
- it('sets v:event and <amatch>', function()
+ it('set v:event and <amatch>', function()
command('lcd '..dirs[1])
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
command('tcd '..dirs[2])
+ eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
command('cd '..dirs[3])
+ eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
+ eq('global', eval('g:amatchpre'))
eq('global', eval('g:amatch'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
end)
- it('sets getcwd() during event #6260', function()
+ it('DirChanged set getcwd() during event #6260', function()
command('lcd '..dirs[1])
eq(dirs[1], eval('g:getcwd'))
@@ -61,7 +73,7 @@ describe('autocmd DirChanged', function()
eq(dirs[3], eval('g:getcwd'))
end)
- it('disallows recursion', function()
+ it('disallow recursion', function()
command('set shellslash')
-- Set up a _nested_ handler.
command('autocmd DirChanged * nested lcd '..dirs[3])
@@ -72,23 +84,36 @@ describe('autocmd DirChanged', function()
eq(dirs[3], eval('getcwd()'))
end)
- it('does not trigger if :cd fails', function()
+ it('only DirChangedPre is triggered if :cd fails', function()
command('let g:ev = {}')
+ command('let g:cdcount = 0')
local status1, err1 = pcall(function()
- command('lcd '..dirs[1] .. '/doesnotexist')
+ command('lcd '..dirs[1]..'/doesnotexist')
end)
+ eq({directory=dirs[1]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(1, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
local status2, err2 = pcall(function()
- command('lcd '..dirs[2] .. '/doesnotexist')
+ command('lcd '..dirs[2]..'/doesnotexist')
end)
+ eq({directory=dirs[2]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(2, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
local status3, err3 = pcall(function()
- command('lcd '..dirs[3] .. '/doesnotexist')
+ command('lcd '..dirs[3]..'/doesnotexist')
end)
+ eq({directory=dirs[3]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(3, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
eq(false, status1)
eq(false, status2)
@@ -99,85 +124,121 @@ describe('autocmd DirChanged', function()
eq('E344:', string.match(err3, "E%d*:"))
end)
- it("is triggered by 'autochdir'", function()
+ it("are triggered by 'autochdir'", function()
command('set autochdir')
command('split '..dirs[1]..'/foo')
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('auto', eval('g:amatchpre'))
eq('auto', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
+ eq(1, eval('g:cdcount'))
command('split '..dirs[2]..'/bar')
+ eq({directory=dirs[2], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev'))
eq('auto', eval('g:amatch'))
-
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
end)
- it('does not trigger if directory has not changed', function()
+ it('do not trigger if directory has not changed', function()
command('lcd '..dirs[1])
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('lcd '..dirs[1])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
if iswin() then
command('lcd '..win_dirs[1])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
end
command('tcd '..dirs[2])
+ eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('tcd '..dirs[2])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
if iswin() then
command('tcd '..win_dirs[2])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
end
command('cd '..dirs[3])
+ eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
eq('global', eval('g:amatch'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('cd '..dirs[3])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
if iswin() then
command('cd '..win_dirs[3])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
end
command('set autochdir')
command('split '..dirs[1]..'/foo')
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('auto', eval('g:amatchpre'))
eq('auto', eval('g:amatch'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('split '..dirs[1]..'/bar')
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
if iswin() then
command('split '..win_dirs[1]..'/baz')
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
end
end)
- it("is triggered by switching to win/tab with different CWD #6054", function()
+ it("are triggered by switching to win/tab with different CWD #6054", function()
command('lcd '..dirs[3]) -- window 3
command('split '..dirs[2]..'/foo') -- window 2
command('lcd '..dirs[2])
@@ -185,72 +246,105 @@ describe('autocmd DirChanged', function()
command('lcd '..dirs[1])
command('2wincmd w') -- window 2
+ eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
command('tabnew') -- tab 2 (tab-local CWD)
+ eq(4, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(4, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..dirs[3])
command('tabnext') -- tab 1 (no tab-local CWD)
+ eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
command('tabnext') -- tab 2
+ eq({directory=dirs[3], scope='tabpage', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(7, eval('g:cdprecount'))
eq(7, eval('g:cdcount'))
command('tabnext') -- tab 1
command('3wincmd w') -- window 3
+ eq(9, eval('g:cdprecount'))
eq(9, eval('g:cdcount'))
command('tabnext') -- tab 2 (has the *same* CWD)
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
if iswin() then
command('tabnew') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..win_dirs[3])
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 2
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('lcd '..win_dirs[3]) -- window 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 2
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
end
end)
- it('is triggered by nvim_set_current_dir()', function()
+ it('are triggered by nvim_set_current_dir()', function()
request('nvim_set_current_dir', dirs[1])
+ eq({directory=dirs[1], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='global', changed_window=false}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
+ eq(1, eval('g:cdcount'))
request('nvim_set_current_dir', dirs[2])
+ eq({directory=dirs[2], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
+ eq(2, eval('g:cdcount'))
local status, err = pcall(function()
request('nvim_set_current_dir', '/doesnotexist')
end)
eq(false, status)
eq('Failed to change directory', string.match(err, ': (.*)'))
- eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev'))
+ eq({directory='/doesnotexist', scope='global', changed_window=false}, eval('g:evpre'))
+ eq(3, eval('g:cdprecount'))
+ eq(2, eval('g:cdcount'))
end)
- it('works when local to buffer', function()
+ it('work when local to buffer', function()
+ command('let g:triggeredpre = 0')
command('let g:triggered = 0')
+ command('autocmd DirChangedPre <buffer> let g:triggeredpre = 1')
command('autocmd DirChanged <buffer> let g:triggered = 1')
command('cd '..dirs[1])
+ eq(1, eval('g:triggeredpre'))
eq(1, eval('g:triggered'))
end)
end)
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 175525b3f2..ed28d8a37d 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -23,7 +23,7 @@ local hl_query = [[
"enum" @type
"extern" @type
- (string_literal) @string
+ (string_literal) @string.nonexistent-specializer-for-string.should-fallback-to-string
(number_literal) @number
(char_literal) @string
@@ -613,4 +613,91 @@ describe('treesitter highlighting', function()
[12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100};
}}
end)
+
+ it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function()
+ if pending_c_parser(pending) then return end
+
+ insert([[
+ char* x = "Will somebody ever read this?";
+ ]])
+
+ screen:expect{grid=[[
+ char* x = "Will somebody ever read this?"; |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua [[
+ local parser = vim.treesitter.get_parser(0, "c", {})
+ local highlighter = vim.treesitter.highlighter
+ highlighter.hl_map['foo.bar'] = 'Type'
+ highlighter.hl_map['foo'] = 'String'
+ test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}})
+ ]]
+
+ screen:expect{grid=[[
+ {3:char}* x = {5:"Will somebody ever read this?"}; |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it("hl_map has the correct fallback behavior", function()
+ exec_lua [[
+ local hl_map = vim.treesitter.highlighter.hl_map
+ hl_map["foo"] = 1
+ hl_map["foo.bar"] = 2
+ hl_map["foo.bar.baz"] = 3
+
+ assert(hl_map["foo"] == 1)
+ assert(hl_map["foo.a.b.c.d"] == 1)
+ assert(hl_map["foo.bar"] == 2)
+ assert(hl_map["foo.bar.a.b.c.d"] == 2)
+ assert(hl_map["foo.bar.baz"] == 3)
+ assert(hl_map["foo.bar.baz.d"] == 3)
+
+ hl_map["FOO"] = 1
+ hl_map["FOO.BAR"] = 2
+ assert(hl_map["FOO.BAR.BAZ"] == 2)
+
+ hl_map["foo.missing.exists"] = 3
+ assert(hl_map["foo.missing"] == 1)
+ assert(hl_map["foo.missing.exists"] == 3)
+ assert(hl_map["foo.missing.exists.bar"] == 3)
+ assert(hl_map["total.nonsense.but.a.lot.of.dots"] == nil)
+ -- It will not perform a second look up of this variable but return a sentinel value
+ assert(hl_map["total.nonsense.but.a.lot.of.dots"] == "__notfound")
+ ]]
+
+ end)
end)