From 920473d2f228fb33c63c092c825b182a7434f25f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 01/12] vim-patch:8.0.1459: cannot handle change of directory Problem: Cannot handle change of directory. Solution: Add the DirChanged autocommand event. (Andy Massimino, closes vim/vim#888) Avoid changing directory for 'autochdir' too often. https://github.com/vim/vim/commit/b7407d3fc9496f9048fb65ab17b5ba3444965c0e Only add "auto" pattern. "window" and "global" are already implemented. Skip `Test_dirchanged_auto` using `CheckFunction test_autochdir`. Part of PR #15952. More information can be found there. N/A patches for version.c: vim-patch:8.0.1460: missing file in patch Problem: Missing file in patch. Solution: Add changes to missing file. https://github.com/vim/vim/commit/b5cb65ba2bcc6bbc6d2798a2dea18b95f0b38f5e vim-patch:8.0.1461: missing another file in patch Problem: Missing another file in patch. Solution: Add changes to missing file. https://github.com/vim/vim/commit/15833239a4131279935a4bd574b74fe3a2b0f49f --- runtime/doc/autocmd.txt | 6 ++++ runtime/doc/vim_diff.txt | 1 + src/nvim/buffer.c | 5 +-- src/nvim/ex_docmd.c | 2 +- src/nvim/ex_session.c | 2 +- src/nvim/file_search.c | 24 +++++++++---- src/nvim/globals.h | 8 +++++ src/nvim/testdir/test_autocmd.vim | 56 +++++++++++++++++++++++++++++++ src/nvim/window.c | 4 +-- 9 files changed, 94 insertions(+), 14 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 5923dada43..b7df0fba54 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -519,11 +519,17 @@ DiffUpdated After diffs have been updated. Depending on change or when doing |:diffupdate|. *DirChanged* DirChanged After the |current-directory| was changed. + The pattern can be: + "window" to trigger on `:lcd` + "tab" to trigger on `:tcd` + "global" to trigger on `:cd` + "auto" to trigger on 'autochdir'. Sets these |v:event| keys: cwd: current working directory scope: "global", "tab", "window" changed_window: v:true if we fired the event switching window (or tab) + is set to the new directory name. Non-recursive (event cannot trigger itself). *FileAppendCmd* FileAppendCmd Before appending to a file. Should do the diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 64824b2e3f..c85cae4004 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -180,6 +180,7 @@ Commands: |:match| can be invoked before highlight group is defined Events: + |DirChanged| can be triggered when switching to another window |Signal| |TabNewEntered| |TermClose| diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 37cc854959..386714b05a 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -593,9 +593,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_nwindows--; } - // Change directories when the 'acd' option is set. - do_autochdir(); - // Disable buffer-updates for the current buffer. // No need to check `unload_buf`: in that case the function returned above. buf_updates_unload(buf, false); @@ -1628,7 +1625,7 @@ void do_autochdir(void) if (p_acd) { if (starting == 0 && curbuf->b_ffname != NULL - && vim_chdirfile(curbuf->b_ffname) == OK) { + && vim_chdirfile(curbuf->b_ffname, kCdCauseAuto) == OK) { post_chdir(kCdScopeGlobal, false); shorten_fnames(true); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 16a9b78c6a..5d83e2218b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7749,7 +7749,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope, false); + do_autocmd_dirchanged(cwd, scope, kCdCauseManual); } } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 1e1da6a9a3..0dfd7e1edd 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -963,7 +963,7 @@ void ex_mkrc(exarg_T *eap) *dirnow = NUL; } if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *)fname) == OK) { + if (vim_chdirfile((char_u *)fname, kCdCauseOther) == OK) { shorten_fnames(true); } } else if (*dirnow != NUL diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 5458d8acf2..e7c4785beb 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1590,7 +1590,7 @@ theend: return file_name; } -void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) +void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) { static bool recursive = false; @@ -1621,10 +1621,22 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) } 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"), changed_window); + 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); + switch (cause) { + case kCdCauseManual: + case kCdCauseWindow: + break; + case kCdCauseAuto: + snprintf(buf, sizeof(buf), "auto"); + break; + case kCdCauseOther: + // Should never happen. + abort(); + } + apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, curbuf); @@ -1636,7 +1648,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) /// Change to a file's directory. /// Caller must call shorten_fnames()! /// @return OK or FAIL -int vim_chdirfile(char_u *fname) +int vim_chdirfile(char_u *fname, CdCause cause) { char dir[MAXPATHL]; @@ -1654,8 +1666,8 @@ int vim_chdirfile(char_u *fname) #ifdef BACKSLASH_IN_FILENAME slash_adjust((char_u *)dir); #endif - if (!strequal(dir, (char *)NameBuff)) { - do_autocmd_dirchanged(dir, kCdScopeWindow, false); + if (cause != kCdCauseOther && !strequal(dir, (char *)NameBuff)) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause); } return OK; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 8a36b3bedd..e04856e85f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1042,6 +1042,14 @@ typedef enum { #define MIN_CD_SCOPE kCdScopeWindow #define MAX_CD_SCOPE kCdScopeGlobal +/// What caused the current directory to change. +typedef enum { + kCdCauseOther = -1, + kCdCauseManual, ///< Using `:cd`, `:tcd`, `:lcd` or `chdir()`. + kCdCauseWindow, ///< Switching to another window. + kCdCauseAuto, ///< On 'autochdir'. +} CdCause; + // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= { 0 }); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 015979e1be..d174b11a9c 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1328,6 +1328,62 @@ func Test_autocommand_all_events() call assert_fails('au * x bwipe', 'E1155:') endfunc +function s:Before_test_dirchanged() + augroup test_dirchanged + autocmd! + augroup END + let s:li = [] + let s:dir_this = getcwd() + let s:dir_other = s:dir_this . '/foo' + call mkdir(s:dir_other) +endfunc + +function s:After_test_dirchanged() + exe 'cd' s:dir_this + call delete(s:dir_other, 'd') + augroup test_dirchanged + autocmd! + augroup END +endfunc + +function Test_dirchanged_global() + call s:Before_test_dirchanged() + autocmd test_dirchanged DirChanged global call add(s:li, "cd:") + autocmd test_dirchanged DirChanged global call add(s:li, expand("")) + exe 'cd' s:dir_other + call assert_equal(["cd:", s:dir_other], s:li) + exe 'lcd' s:dir_other + call assert_equal(["cd:", s:dir_other], s:li) + call s:After_test_dirchanged() +endfunc + +function Test_dirchanged_local() + call s:Before_test_dirchanged() + autocmd test_dirchanged DirChanged window call add(s:li, "lcd:") + autocmd test_dirchanged DirChanged window call add(s:li, expand("")) + exe 'cd' s:dir_other + call assert_equal([], s:li) + exe 'lcd' s:dir_other + call assert_equal(["lcd:", s:dir_other], s:li) + call s:After_test_dirchanged() +endfunc + +function Test_dirchanged_auto() + call s:Before_test_dirchanged() + call test_autochdir() + autocmd test_dirchanged DirChanged auto call add(s:li, "auto:") + autocmd test_dirchanged DirChanged auto call add(s:li, expand("")) + set acd + exe 'cd ..' + call assert_equal([], s:li) + exe 'edit ' . s:dir_other . '/Xfile' + call assert_equal(s:dir_other, getcwd()) + call assert_equal(["auto:", s:dir_other], s:li) + set noacd + bwipe! + call s:After_test_dirchanged() +endfunc + " Test TextChangedI and TextChangedP " See test/functional/viml/completion_spec.lua' func Test_ChangedP() diff --git a/src/nvim/window.c b/src/nvim/window.c index 1fe4aa1add..a7470889b3 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4542,7 +4542,7 @@ static void win_enter_ext(win_T *const wp, const int flags) if (os_chdir(new_dir) == 0) { if (!p_acd && !strequal(new_dir, cwd)) { do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTab, true); + ? kCdScopeWindow : kCdScopeTab, kCdCauseWindow); } shorten_fnames(true); } @@ -4551,7 +4551,7 @@ static void win_enter_ext(win_T *const wp, const int flags) // directory: Change to the global directory. if (os_chdir((char *)globaldir) == 0) { if (!p_acd && !strequal((char *)globaldir, cwd)) { - do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, true); + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow); } } XFREE_CLEAR(globaldir); From eed89d5e0c3bd6870e6e9959ccfdbf99549dce6b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 02/12] vim-patch:8.0.1463: test fails without 'autochdir' option Problem: Test fails without 'autochdir' option. Solution: Skip test if 'autochdir' is not supported. https://github.com/vim/vim/commit/ec48a9c58989babcad23d73483955f35b6e41492 --- src/nvim/testdir/test_autocmd.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index d174b11a9c..a2ed3ae677 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1369,6 +1369,9 @@ function Test_dirchanged_local() endfunc function Test_dirchanged_auto() + if !exists('+autochdir') + return + endif call s:Before_test_dirchanged() call test_autochdir() autocmd test_dirchanged DirChanged auto call add(s:li, "auto:") From e91dee5c21ffca22ac821336ad23bce6339b5f7c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 03/12] vim-patch:8.1.0602: DirChanged is also triggered when directory didn't change Problem: DirChanged is also triggered when the directory didn't change. (Daniel Hahler) Solution: Compare the current with the new directory. (closes vim/vim#3697) https://github.com/vim/vim/commit/2caad3fbbdbf1486a176c9f6bfbc3d9be90e09f7 --- src/nvim/ex_docmd.c | 5 +-- src/nvim/testdir/test_autochdir.vim | 8 +++++ src/nvim/testdir/test_autocmd.vim | 33 +++++++++++-------- test/functional/autocmd/dirchanged_spec.lua | 36 +++++++++++++++++++++ 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5d83e2218b..b81aab9ffe 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7811,10 +7811,11 @@ void ex_cd(exarg_T *eap) break; } - if (vim_chdir(new_dir)) { + bool dir_differs = prev_dir == NULL || STRCMP(prev_dir, new_dir) != 0; + if (dir_differs && vim_chdir(new_dir)) { EMSG(_(e_failed)); } else { - post_chdir(scope, true); + post_chdir(scope, dir_differs); // Echo the new current directory if the command was typed. if (KeyTyped || p_verbose >= 5) { ex_pwd(eap); diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index d071f4b325..3d1ba084c6 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -8,11 +8,19 @@ func Test_set_filename() let cwd = getcwd() call test_autochdir() set acd + + let s:li = [] + autocmd DirChanged auto call add(s:li, "autocd") + autocmd DirChanged auto call add(s:li, expand("")) + new w samples/Xtest call assert_equal("Xtest", expand('%')) call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', '')) + call assert_equal(["autocd", getcwd()], s:li) + bwipe! + au! DirChanged set noacd exe 'cd ' . cwd call delete('samples/Xtest') diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index a2ed3ae677..93083f55c7 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1334,13 +1334,16 @@ function s:Before_test_dirchanged() augroup END let s:li = [] let s:dir_this = getcwd() - let s:dir_other = s:dir_this . '/foo' - call mkdir(s:dir_other) + let s:dir_foo = s:dir_this . '/foo' + call mkdir(s:dir_foo) + let s:dir_bar = s:dir_this . '/bar' + call mkdir(s:dir_bar) endfunc function s:After_test_dirchanged() exe 'cd' s:dir_this - call delete(s:dir_other, 'd') + call delete(s:dir_foo, 'd') + call delete(s:dir_bar, 'd') augroup test_dirchanged autocmd! augroup END @@ -1350,10 +1353,12 @@ function Test_dirchanged_global() call s:Before_test_dirchanged() autocmd test_dirchanged DirChanged global call add(s:li, "cd:") autocmd test_dirchanged DirChanged global call add(s:li, expand("")) - exe 'cd' s:dir_other - call assert_equal(["cd:", s:dir_other], s:li) - exe 'lcd' s:dir_other - call assert_equal(["cd:", s:dir_other], s:li) + exe 'cd' s:dir_foo + call assert_equal(["cd:", s:dir_foo], s:li) + exe 'cd' s:dir_foo + call assert_equal(["cd:", s:dir_foo], s:li) + exe 'lcd' s:dir_bar + call assert_equal(["cd:", s:dir_foo], s:li) call s:After_test_dirchanged() endfunc @@ -1361,10 +1366,12 @@ function Test_dirchanged_local() call s:Before_test_dirchanged() autocmd test_dirchanged DirChanged window call add(s:li, "lcd:") autocmd test_dirchanged DirChanged window call add(s:li, expand("")) - exe 'cd' s:dir_other + exe 'cd' s:dir_foo call assert_equal([], s:li) - exe 'lcd' s:dir_other - call assert_equal(["lcd:", s:dir_other], s:li) + exe 'lcd' s:dir_bar + call assert_equal(["lcd:", s:dir_bar], s:li) + exe 'lcd' s:dir_bar + call assert_equal(["lcd:", s:dir_bar], s:li) call s:After_test_dirchanged() endfunc @@ -1379,9 +1386,9 @@ function Test_dirchanged_auto() set acd exe 'cd ..' call assert_equal([], s:li) - exe 'edit ' . s:dir_other . '/Xfile' - call assert_equal(s:dir_other, getcwd()) - call assert_equal(["auto:", s:dir_other], s:li) + exe 'edit ' . s:dir_foo . '/Xfile' + call assert_equal(s:dir_foo, getcwd()) + call assert_equal(["auto:", s:dir_foo], s:li) set noacd bwipe! call s:After_test_dirchanged() diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 6f2da24cf2..2e1aff3914 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -113,6 +113,42 @@ describe('autocmd DirChanged', function() eq(2, eval('g:cdcount')) end) + it('does not trigger if diretory has not changed', function() + command('lcd '..dirs[1]) + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq(1, eval('g:cdcount')) + command('let g:ev = {}') + command('lcd '..dirs[1]) + eq({}, eval('g:ev')) + eq(1, eval('g:cdcount')) + + command('tcd '..dirs[2]) + eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) + eq(2, eval('g:cdcount')) + command('let g:ev = {}') + command('tcd '..dirs[2]) + eq({}, eval('g:ev')) + eq(2, eval('g:cdcount')) + + command('cd '..dirs[3]) + eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq(3, eval('g:cdcount')) + command('let g:ev = {}') + command('cd '..dirs[3]) + eq({}, eval('g:ev')) + eq(3, eval('g:cdcount')) + + command('set autochdir') + + command('split '..dirs[1]..'/foo') + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq(4, eval('g:cdcount')) + command('let g:ev = {}') + command('split '..dirs[1]..'/bar') + eq({}, eval('g:ev')) + eq(4, eval('g:cdcount')) + end) + it("is triggered by switching to win/tab with different CWD #6054", function() command('lcd '..dirs[3]) -- window 3 command('split '..dirs[2]..'/foo') -- window 2 From 57651df9c11740a24a2f71801d9b7b81b894d601 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 04/12] vim-patch:8.1.0604: autocommand test fails on MS-Windows Problem: Autocommand test fails on MS-Windows. Solution: Use pathcmp() instead of strcmp() to check if a directory differs. https://github.com/vim/vim/commit/9eb76af451ddd8eaad0cd5dd629f18c4f4035171 --- src/nvim/ex_docmd.c | 2 +- src/nvim/file_search.c | 19 ++++--- src/nvim/window.c | 5 +- test/functional/autocmd/dirchanged_spec.lua | 55 +++++++++++++++++++++ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b81aab9ffe..cc30557ead 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7811,7 +7811,7 @@ void ex_cd(exarg_T *eap) break; } - bool dir_differs = prev_dir == NULL || STRCMP(prev_dir, new_dir) != 0; + bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0; if (dir_differs && vim_chdir(new_dir)) { EMSG(_(e_failed)); } else { diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index e7c4785beb..22c7b35fa9 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1620,6 +1620,13 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) abort(); } +#ifdef BACKSLASH_IN_FILENAME + char new_dir_buf[MAXPATHL]; + STRCPY(new_dir_buf, new_dir); + slash_adjust(new_dir_buf); + new_dir = new_dir_buf; +#endif + 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); @@ -1659,14 +1666,10 @@ int vim_chdirfile(char_u *fname, CdCause cause) NameBuff[0] = NUL; } - if (os_chdir(dir) != 0) { - return FAIL; - } - -#ifdef BACKSLASH_IN_FILENAME - slash_adjust((char_u *)dir); -#endif - if (cause != kCdCauseOther && !strequal(dir, (char *)NameBuff)) { + if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { + if (os_chdir(dir) != 0) { + return FAIL; + } do_autocmd_dirchanged(dir, kCdScopeWindow, cause); } diff --git a/src/nvim/window.c b/src/nvim/window.c index a7470889b3..49d7c9c9f5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -23,6 +23,7 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -4540,7 +4541,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } } if (os_chdir(new_dir) == 0) { - if (!p_acd && !strequal(new_dir, cwd)) { + if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) { do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTab, kCdCauseWindow); } @@ -4550,7 +4551,7 @@ static void win_enter_ext(win_T *const wp, const int flags) // Window doesn't have a local directory and we are not in the global // directory: Change to the global directory. if (os_chdir((char *)globaldir) == 0) { - if (!p_acd && !strequal((char *)globaldir, cwd)) { + if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) { do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow); } } diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 2e1aff3914..ce3b2e8d3e 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -6,6 +6,7 @@ local command = h.command local eq = h.eq local eval = h.eval local request = h.request +local iswin = h.iswin describe('autocmd DirChanged', function() local curdir = string.gsub(lfs.currentdir(), '\\', '/') @@ -14,6 +15,11 @@ describe('autocmd DirChanged', function() curdir .. '/Xtest-functional-autocmd-dirchanged.dir2', curdir .. '/Xtest-functional-autocmd-dirchanged.dir3', } + local win_dirs = { + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR1', + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR2', + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR3', + } setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end) teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end) @@ -122,6 +128,12 @@ describe('autocmd DirChanged', function() eq({}, eval('g:ev')) eq(1, eval('g:cdcount')) + if iswin() then + command('lcd '..win_dirs[1]) + eq({}, eval('g:ev')) + eq(1, eval('g:cdcount')) + end + command('tcd '..dirs[2]) eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) eq(2, eval('g:cdcount')) @@ -130,6 +142,12 @@ describe('autocmd DirChanged', function() eq({}, eval('g:ev')) eq(2, eval('g:cdcount')) + if iswin() then + command('tcd '..win_dirs[2]) + eq({}, eval('g:ev')) + eq(2, eval('g:cdcount')) + end + command('cd '..dirs[3]) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) eq(3, eval('g:cdcount')) @@ -138,6 +156,12 @@ describe('autocmd DirChanged', function() eq({}, eval('g:ev')) eq(3, eval('g:cdcount')) + if iswin() then + command('cd '..win_dirs[3]) + eq({}, eval('g:ev')) + eq(3, eval('g:cdcount')) + end + command('set autochdir') command('split '..dirs[1]..'/foo') @@ -147,6 +171,12 @@ describe('autocmd DirChanged', function() command('split '..dirs[1]..'/bar') eq({}, eval('g:ev')) eq(4, eval('g:cdcount')) + + if iswin() then + command('split '..win_dirs[1]..'/baz') + eq({}, eval('g:ev')) + eq(4, eval('g:cdcount')) + end end) it("is triggered by switching to win/tab with different CWD #6054", function() @@ -174,6 +204,31 @@ describe('autocmd DirChanged', function() eq(9, eval('g:cdcount')) command('tabnext') -- tab 2 (has the *same* CWD) eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + + if iswin() then + command('tabnew') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tcd '..win_dirs[3]) + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 2 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('lcd '..win_dirs[3]) -- window 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 2 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + end end) it('is triggered by nvim_set_current_dir()', function() From 8727d38012f3c4c54380d554e2a8c53e8c50ad0d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 05/12] vim-patch:8.1.1291: not easy to change directory and restore Problem: Not easy to change directory and restore. Solution: Add the chdir() function. (Yegappan Lakshmanan, closes vim/vim#4358) https://github.com/vim/vim/commit/1063f3d2008f22d02ccfa9dab83a23db52febbdc Also includes some documentation changes from patch 8.1.1218. --- runtime/doc/editing.txt | 14 +++--- runtime/doc/eval.txt | 25 ++++++++++ runtime/doc/usr_22.txt | 24 +++++++-- runtime/doc/usr_41.txt | 3 +- runtime/doc/vim_diff.txt | 4 ++ src/nvim/eval.lua | 1 + src/nvim/eval/funcs.c | 38 ++++++++++++++ src/nvim/ex_docmd.c | 96 ++++++++++++++++++++---------------- src/nvim/testdir/test_cd.vim | 41 +++++++++++++++ 9 files changed, 191 insertions(+), 55 deletions(-) diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index f91e4f0627..1eba702a06 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1275,10 +1275,12 @@ exist, the next-higher scope in the hierarchy applies. *:chd* *:chdir* :chd[ir][!] [path] Same as |:cd|. - *:tc* *:tcd* *E5000* *E5001* *E5002* -:tc[d][!] {path} Like |:cd|, but set the current directory for the - current tab and window. The current directory for - other tabs and windows is not changed. + *:tc* *:tcd* +:tc[d][!] {path} Like |:cd|, but only set the directory for the current + tab. The current window will also use this directory. + The current directory is not changed for windows in + other tabs and for windows in the current tab that + have their own window-local directory. *:tcd-* :tc[d][!] - Change to the previous current directory (before the @@ -1303,8 +1305,8 @@ exist, the next-higher scope in the hierarchy applies. :pw[d] Print the current directory name. Also see |getcwd()|. -So long as no |:tcd| or |:lcd| command has been used, all windows share the -same "current directory". Using a command to jump to another window doesn't +So long as no |:lcd| or |:tcd| command has been used, all windows share the +same current directory. Using a command to jump to another window doesn't change anything for the current directory. When |:lcd| has been used for a window, the specified directory becomes the diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index e956ccaa77..4467afaa16 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2316,6 +2316,7 @@ chansend({id}, {data}) Number Writes {data} to channel char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} +chdir({dir}) String change current working directory cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches col({expr}) Number column nr of cursor or mark @@ -2461,6 +2462,7 @@ has({feature}) Number |TRUE| if feature {feature} supported has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key} haslocaldir([{winnr} [, {tabnr}]]) Number |TRUE| if current window executed |:lcd| + or |:tcd| hasmapto({what} [, {mode} [, {abbr}]]) Number |TRUE| if mapping to {what} exists histadd({history}, {item}) String add an item to a history @@ -3280,6 +3282,27 @@ charidx({string}, {idx} [, {countcc}]) echo charidx('áb́ć', 6, 1) returns 4 echo charidx('áb́ć', 16) returns -1 +chdir({dir}) *chdir()* + Change the current working directory to {dir}. The scope of + the directory change depends on the directory of the current + window: + - If the current window has a window-local directory + (|:lcd|), then changes the window local directory. + - Otherwise, if the current tabpage has a local + directory (|:tcd|) then changes the tabpage local + directory. + - Otherwise, changes the global directory. + If successful, returns the previous working directory. Pass + this to another chdir() to restore the directory. + On failure, returns an empty string. + + Example: > + let save_dir = chdir(newdir) + if save_dir + " ... do some work + call chdir(save_dir) + endif +< cindent({lnum}) *cindent()* Get the amount of indent for line {lnum} according the C indenting rules, as with 'cindent'. @@ -4987,6 +5010,8 @@ getcwd([{winnr}[, {tabnr}]]) *getcwd()* getcwd(0, 0) < If {winnr} is -1 it is ignored, only the tab is resolved. {winnr} can be the window number or the |window-ID|. + If both {winnr} and {tabnr} are -1 the global working + directory is returned. Can also be used as a |method|: > GetWinnr()->getcwd() diff --git a/runtime/doc/usr_22.txt b/runtime/doc/usr_22.txt index 56fe5ada2b..f53d578456 100644 --- a/runtime/doc/usr_22.txt +++ b/runtime/doc/usr_22.txt @@ -202,14 +202,28 @@ the other window. This is called a local directory. > :pwd /home/Bram/VeryLongFileName -So long as no ":lcd" command has been used, all windows share the same current -directory. Doing a ":cd" command in one window will also change the current +So long as no `:lcd` command has been used, all windows share the same current +directory. Doing a `:cd` command in one window will also change the current directory of the other window. - For a window where ":lcd" has been used a different current directory is -remembered. Using ":cd" or ":lcd" in other windows will not change it. - When using a ":cd" command in a window that uses a different current + For a window where `:lcd` has been used a different current directory is +remembered. Using `:cd` or `:lcd` in other windows will not change it. + When using a `:cd` command in a window that uses a different current directory, it will go back to using the shared directory. + +TAB LOCAL DIRECTORY + +When you open a new tab page, it uses the directory of the window in the +previous tab page from which the new tab page was opened. You can change the +directory of the current tab page using the `:tcd` command. All the windows in +a tab page share this directory except for windows with a window-local +directory. Any new windows opened in this tab page will use this directory as +the current working directory. Using a `:cd` command in a tab page will not +change the working directory of tab pages which have a tab local directory. +When the global working directory is changed using the ":cd" command in a tab +page, it will also change the current tab page working directory. + + ============================================================================== *22.3* Finding a file diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index c575dd9fd8..6a9284dac9 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -785,9 +785,10 @@ System functions and manipulation of files: isdirectory() check if a directory exists getfsize() get the size of a file getcwd() get the current working directory - haslocaldir() check if current window used |:lcd| + haslocaldir() check if current window used |:lcd| or |:tcd| tempname() get the name of a temporary file mkdir() create a new directory + chdir() change current working directory delete() delete a file rename() rename a file system() get the result of a shell command as a string diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index c85cae4004..7e7bb7d62f 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -197,6 +197,10 @@ Functions: |stdpath()| |system()|, |systemlist()| can run {cmd} directly (without 'shell') |matchadd()| can be called before highlight group is defined + |getcwd()| and |haslocaldir()| may throw errors. *E5000* *E5001* *E5002* + |haslocaldir()|'s only possible return values are 0 and 1, it never returns 2. + `getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global + working directory. Use `getcwd(-1, -1)` to get the global working directory. Highlight groups: |highlight-blend| controls blend level for a highlight group diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 70aa2bb1f8..dfc51d80af 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -72,6 +72,7 @@ return { chansend={args=2}, char2nr={args={1, 2}, base=1}, charidx={args={2, 3}}, + chdir={args=1, base=1}, cindent={args=1, base=1}, clearmatches={args={0, 1}, base=1}, col={args=1, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index da129c1170..fd3353e18b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -28,6 +28,7 @@ #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/globals.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/indent_c.h" @@ -1062,6 +1063,43 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = len > 0 ? len - 1 : 0; } +// "chdir(dir)" function +static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *cwd; + CdScope scope = kCdScopeGlobal; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (argvars[0].v_type != VAR_STRING) { + return; + } + + // Return the current directory + cwd = xmalloc(MAXPATHL); + if (cwd != NULL) { + if (os_dirname(cwd, MAXPATHL) != FAIL) { +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(cwd); +#endif + rettv->vval.v_string = vim_strsave(cwd); + } + xfree(cwd); + } + + if (curwin->w_localdir != NULL) { + scope = kCdScopeWindow; + } else if (curtab->tp_localdir != NULL) { + scope = kCdScopeTab; + } + + if (!changedir_func(argvars[0].vval.v_string, scope)) { + // Directory change failed + XFREE_CLEAR(rettv->vval.v_string); + } +} + /* * "cindent(lnum)" function */ diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index cc30557ead..9191c2590c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7753,51 +7753,68 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) } } -/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. +/// Change directory function used by :cd/:tcd/:lcd Ex commands and the chdir() function. +/// @return true if the directory is successfully changed. +bool changedir_func(char_u *new_dir, CdScope scope) +{ + char_u *tofree; + bool retval = false; + + if (allbuf_locked()) { + return false; + } + + // ":cd -": Change to previous directory + if (STRCMP(new_dir, "-") == 0) { + if (prev_dir == NULL) { + EMSG(_("E186: No previous directory")); + return false; + } + new_dir = prev_dir; + } + + // Save current directory for next ":cd -" + tofree = prev_dir; + if (os_dirname(NameBuff, MAXPATHL) == OK) { + prev_dir = vim_strsave(NameBuff); + } else { + prev_dir = NULL; + } + +#if defined(UNIX) + // On Unix ":cd" means: go to home directory. + if (*new_dir == NUL) { + // Use NameBuff for home directory name. + expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + new_dir = NameBuff; + } +#endif + + bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0; + if (dir_differs && vim_chdir(new_dir)) { + EMSG(_(e_failed)); + } else { + post_chdir(scope, dir_differs); + retval = true; + } + xfree(tofree); + + return retval; +} + +/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir". void ex_cd(exarg_T *eap) { char_u *new_dir; - char_u *tofree; - new_dir = eap->arg; -#if !defined(UNIX) +#if !defined(UNIX) && !defined(VMS) // for non-UNIX ":cd" means: print current directory if (*new_dir == NUL) { ex_pwd(NULL); } else #endif { - if (allbuf_locked()) { - return; - } - - // ":cd -": Change to previous directory - if (STRCMP(new_dir, "-") == 0) { - if (prev_dir == NULL) { - EMSG(_("E186: No previous directory")); - return; - } - new_dir = prev_dir; - } - - // Save current directory for next ":cd -" - tofree = prev_dir; - if (os_dirname(NameBuff, MAXPATHL) == OK) { - prev_dir = vim_strsave(NameBuff); - } else { - prev_dir = NULL; - } - -#if defined(UNIX) - // On Unix ":cd" means: go to home directory. - if (*new_dir == NUL) { - // Use NameBuff for home directory name. - expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); - new_dir = NameBuff; - } -#endif - CdScope scope = kCdScopeGlobal; // Depends on command invoked - + CdScope scope = kCdScopeGlobal; switch (eap->cmdidx) { case CMD_tcd: case CMD_tchdir: @@ -7810,19 +7827,12 @@ void ex_cd(exarg_T *eap) default: break; } - - bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0; - if (dir_differs && vim_chdir(new_dir)) { - EMSG(_(e_failed)); - } else { - post_chdir(scope, dir_differs); + if (changedir_func(new_dir, scope)) { // Echo the new current directory if the command was typed. if (KeyTyped || p_verbose >= 5) { ex_pwd(eap); } } - - xfree(tofree); } } diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 02a23bf82f..7500c35563 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -69,6 +69,47 @@ func Test_cd_with_cpo_chdir() bw! endfunc +" Test for chdir() +func Test_chdir_func() + let topdir = getcwd() + call mkdir('Xdir/y/z', 'p') + + " Create a few tabpages and windows with different directories + new + cd Xdir + tabnew + tcd y + below new + below new + lcd z + + tabfirst + call chdir('..') + call assert_equal('y', fnamemodify(getcwd(1, 2), ':t')) + call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) + tabnext | wincmd t + call chdir('..') + call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) + call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) + call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) + call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) + 3wincmd w + call chdir('..') + call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) + call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) + call assert_equal('y', fnamemodify(getcwd(3, 2), ':t')) + call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) + + " Error case + call assert_fails("call chdir('dir-abcd')", 'E472:') + silent! let d = chdir("dir_abcd") + call assert_equal("", d) + + only | tabonly + exe 'cd ' . topdir + call delete('Xdir', 'rf') +endfunc + func Test_cd_from_non_existing_dir() CheckNotMSWindows From 8a2489d0a43f6fe0bfd21840293610c44c88dcff Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 06/12] test: partially port Vim patches 8.1.2278 and 8.2.1432 --- src/nvim/testdir/test_autochdir.vim | 2 +- src/nvim/testdir/test_autocmd.vim | 33 +++++++++++-------------- src/nvim/testdir/test_cd.vim | 8 +++--- src/nvim/testdir/test_find_complete.vim | 8 +++--- src/nvim/testdir/test_findfile.vim | 4 +-- src/nvim/testdir/test_getcwd.vim | 2 +- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 3d1ba084c6..0b76828dd7 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -22,7 +22,7 @@ func Test_set_filename() bwipe! au! DirChanged set noacd - exe 'cd ' . cwd + call chdir(cwd) call delete('samples/Xtest') endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 93083f55c7..c350a17236 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -42,9 +42,7 @@ if has('timers') endfunc func Test_cursorhold_insert_with_timer_interrupt() - if !has('job') - return - endif + CheckFeature job " Need to move the cursor. call feedkeys("ggG", "xt") @@ -551,9 +549,7 @@ endfunc func Test_OptionSet() CheckFunction test_override - if !has("eval") || !exists("+autochdir") - return - endif + CheckOption autochdir call test_override('starting', 1) set nocp @@ -1334,14 +1330,14 @@ function s:Before_test_dirchanged() augroup END let s:li = [] let s:dir_this = getcwd() - let s:dir_foo = s:dir_this . '/foo' + let s:dir_foo = s:dir_this . '/Xfoo' call mkdir(s:dir_foo) - let s:dir_bar = s:dir_this . '/bar' + let s:dir_bar = s:dir_this . '/Xbar' call mkdir(s:dir_bar) endfunc function s:After_test_dirchanged() - exe 'cd' s:dir_this + call chdir(s:dir_this) call delete(s:dir_foo, 'd') call delete(s:dir_bar, 'd') augroup test_dirchanged @@ -1353,11 +1349,11 @@ function Test_dirchanged_global() call s:Before_test_dirchanged() autocmd test_dirchanged DirChanged global call add(s:li, "cd:") autocmd test_dirchanged DirChanged global call add(s:li, expand("")) - exe 'cd' s:dir_foo + call chdir(s:dir_foo) call assert_equal(["cd:", s:dir_foo], s:li) - exe 'cd' s:dir_foo + call chdir(s:dir_foo) call assert_equal(["cd:", s:dir_foo], s:li) - exe 'lcd' s:dir_bar + exe 'lcd ' .. fnameescape(s:dir_bar) call assert_equal(["cd:", s:dir_foo], s:li) call s:After_test_dirchanged() endfunc @@ -1366,25 +1362,24 @@ function Test_dirchanged_local() call s:Before_test_dirchanged() autocmd test_dirchanged DirChanged window call add(s:li, "lcd:") autocmd test_dirchanged DirChanged window call add(s:li, expand("")) - exe 'cd' s:dir_foo + call chdir(s:dir_foo) call assert_equal([], s:li) - exe 'lcd' s:dir_bar + exe 'lcd ' .. fnameescape(s:dir_bar) call assert_equal(["lcd:", s:dir_bar], s:li) - exe 'lcd' s:dir_bar + exe 'lcd ' .. fnameescape(s:dir_bar) call assert_equal(["lcd:", s:dir_bar], s:li) call s:After_test_dirchanged() endfunc function Test_dirchanged_auto() - if !exists('+autochdir') - return - endif + CheckFunction test_autochdir + CheckOption autochdir call s:Before_test_dirchanged() call test_autochdir() autocmd test_dirchanged DirChanged auto call add(s:li, "auto:") autocmd test_dirchanged DirChanged auto call add(s:li, expand("")) set acd - exe 'cd ..' + cd .. call assert_equal([], s:li) exe 'edit ' . s:dir_foo . '/Xfile' call assert_equal(s:dir_foo, getcwd()) diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 7500c35563..96c67bd7fc 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -12,7 +12,7 @@ func Test_cd_up_and_down() let path = getcwd() cd .. call assert_notequal(path, getcwd()) - exe 'cd ' . path + exe 'cd ' .. fnameescape(path) call assert_equal(path, getcwd()) endfunc @@ -23,7 +23,7 @@ func Test_cd_no_arg() cd call assert_equal($HOME, getcwd()) call assert_notequal(path, getcwd()) - exe 'cd ' . path + exe 'cd ' .. fnameescape(path) call assert_equal(path, getcwd()) else " Test that cd without argument echoes cwd on non-Unix systems. @@ -61,7 +61,7 @@ func Test_cd_with_cpo_chdir() " :cd should succeed when buffer has been written. w! - exe 'cd ' . path + exe 'cd ' .. fnameescape(path) call assert_equal(path, getcwd()) call delete('Xfoo') @@ -106,7 +106,7 @@ func Test_chdir_func() call assert_equal("", d) only | tabonly - exe 'cd ' . topdir + call chdir(topdir) call delete('Xdir', 'rf') endfunc diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim index 0a00d9432f..32ca9672ef 100644 --- a/src/nvim/testdir/test_find_complete.vim +++ b/src/nvim/testdir/test_find_complete.vim @@ -36,7 +36,7 @@ func Test_find_complete() " We shouldn't find any file till this point call mkdir('in/path', 'p') - exe 'cd ' . cwd + call chdir(cwd) call writefile(['Holy Grail'], 'Xfind/file.txt') call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt') call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt') @@ -133,12 +133,12 @@ func Test_find_complete() call assert_equal('Voyager 2', getline(1)) " Check for correct handling of shorten_fname()'s behavior on windows - exec "cd " . cwd . "/Xfind/in" + call chdir(cwd .. "/Xfind/in") call feedkeys(":find file\t\n", "xt") call assert_equal('Jimmy Hoffa', getline(1)) " Test for relative to current buffer 'path' item - exec "cd " . cwd . "/Xfind/" + call chdir(cwd . "/Xfind/") set path=./path " Open the file where Jimmy Hoffa is found e in/file.txt @@ -157,7 +157,7 @@ func Test_find_complete() call assert_equal('Another Holy Grail', getline(1)) enew | only - exe 'cd ' . cwd + call chdir(cwd) call delete('Xfind', 'rf') set path& endfunc diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index d92706dbe5..5a20475d3d 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -113,7 +113,7 @@ func Test_findfile() call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2)) bwipe! - exe 'cd ' . save_dir + call chdir(save_dir) call CleanFiles() let &path = save_path let &shellslash = save_shellslash @@ -171,7 +171,7 @@ func Test_finddir() call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2)) call assert_equal('Xdir3', finddir('Xdir3', '**;', 1)) - exe 'cd ' . save_dir + call chdir(save_dir) call CleanFiles() let &path = save_path let &shellslash = save_shellslash diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim index 2ff396b641..a75583cd2c 100644 --- a/src/nvim/testdir/test_getcwd.vim +++ b/src/nvim/testdir/test_getcwd.vim @@ -46,7 +46,7 @@ endfunction let g:cwd=getcwd() function TearDown() q - exec "cd " . g:cwd + call chdir(g:cwd) call delete("Xtopdir", "rf") endfunction From b1dd90c760cd87f6c5c0c7c66bd0cc61c69319c3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 07/12] vim-patch:8.2.0189: cd() with NULL argument crashes Problem: cd() with NULL argument crashes. Solution: Check for NULL. (Ken Takata, closes vim/vim#5558) https://github.com/vim/vim/commit/7cc96923c44bbcc541cbd211b6308d87a965f0c3 --- src/nvim/ex_docmd.c | 2 +- src/nvim/testdir/test_cd.vim | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 9191c2590c..fef254355b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7760,7 +7760,7 @@ bool changedir_func(char_u *new_dir, CdScope scope) char_u *tofree; bool retval = false; - if (allbuf_locked()) { + if (new_dir == NULL || allbuf_locked()) { return false; } diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 96c67bd7fc..0421c1d6ff 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -104,6 +104,8 @@ func Test_chdir_func() call assert_fails("call chdir('dir-abcd')", 'E472:') silent! let d = chdir("dir_abcd") call assert_equal("", d) + " Should not crash + call chdir(d) only | tabonly call chdir(topdir) From 34cfe745681189bbd8ec2543971a49e013bfebf9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 08/12] vim-patch:8.2.0876: :pwd does not give a hint about the scope of the directory Problem: :pwd does not give a hint about the scope of the directory Solution: Make ":verbose pwd" show the scope. (Takuya Fujiwara, closes vim/vim#5469) https://github.com/vim/vim/commit/950587242cad52d067a15f0f0c83528a28f75731 --- runtime/doc/editing.txt | 15 +++++++++++++++ src/nvim/eval/funcs.c | 2 ++ src/nvim/ex_docmd.c | 12 +++++++++++- src/nvim/testdir/test_cd.vim | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 1eba702a06..efba9020f9 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1304,6 +1304,21 @@ exist, the next-higher scope in the hierarchy applies. *:pw* *:pwd* *E187* :pw[d] Print the current directory name. Also see |getcwd()|. + *:pwd-verbose* + When 'verbose' is non-zero, |:pwd| will also display + what scope the current directory was set. Example: > + + " Set by :cd + :verbose pwd + [global] /path/to/current + + " Set by :lcd + :verbose pwd + [window] /path/to/current + + " Set by :tcd + :verbose pwd + [tabpage] /path/to/current So long as no |:lcd| or |:tcd| command has been used, all windows share the same current directory. Using a command to jump to another window doesn't diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index fd3353e18b..2c962a4cf0 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1073,6 +1073,8 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = NULL; if (argvars[0].v_type != VAR_STRING) { + // Returning an empty string means it failed. + // No error message, for historic reasons. return; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index fef254355b..06e94fea99 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7845,7 +7845,17 @@ static void ex_pwd(exarg_T *eap) #ifdef BACKSLASH_IN_FILENAME slash_adjust(NameBuff); #endif - msg(NameBuff); + if (p_verbose > 0) { + char *context = "global"; + if (curwin->w_localdir != NULL) { + context = "window"; + } else if (curtab->tp_localdir != NULL) { + context = "tabpage"; + } + smsg("[%s] %s", context, (char *)NameBuff); + } else { + msg(NameBuff); + } } else { EMSG(_("E187: Unknown")); } diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 0421c1d6ff..c6eda58284 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -84,16 +84,19 @@ func Test_chdir_func() lcd z tabfirst + call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('y', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) tabnext | wincmd t + call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) 3wincmd w + call assert_match('^\[window\] .*/z$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) From 60584c0245a55d72422686aa702132814145b0c1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 09/12] vim-patch:8.2.0909: cannot go back to the previous local directory Problem: Cannot go back to the previous local directory. Solution: Add "tcd -" and "lcd -". (Yegappan Lakshmanan, closes vim/vim#4362) https://github.com/vim/vim/commit/002bc79991286934a9593b80635c27d4238cdfc4 --- src/nvim/buffer_defs.h | 5 ++- src/nvim/ex_docmd.c | 56 ++++++++++++++++++++------ src/nvim/file_search.c | 10 +++-- src/nvim/testdir/test_cd.vim | 77 ++++++++++++++++++++++++++++++++++++ src/nvim/window.c | 2 + 5 files changed, 132 insertions(+), 18 deletions(-) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 2c411553b8..19b0a3c5c6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -953,6 +953,7 @@ struct tabpage_S { ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dictionary. dict_T *tp_vars; ///< Internal variables, local to tab page. char_u *tp_localdir; ///< Absolute path of local cwd or NULL. + char_u *tp_prevdir; ///< Previous directory. }; /* @@ -1381,8 +1382,8 @@ struct window_S { // out of range!) int w_arg_idx_invalid; // editing another file than w_arg_idx - char_u *w_localdir; /* absolute path of local directory or - NULL */ + char_u *w_localdir; // absolute path of local directory or NULL + char_u *w_prevdir; // previous directory // Options local to a window. // They are local because they influence the layout of the window or // depend on the window layout. diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 06e94fea99..26c06b2675 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7707,6 +7707,21 @@ void free_cd_dir(void) #endif +// Get the previous directory for the given chdir scope. +static char_u *get_prevdir(CdScope scope) +{ + switch (scope) { + case kCdScopeTab: + return curtab->tp_prevdir; + break; + case kCdScopeWindow: + return curwin->w_prevdir; + break; + default: + return prev_dir; + } +} + /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). @@ -7721,9 +7736,10 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) } if (scope < kCdScopeGlobal) { + char_u *pdir = get_prevdir(scope); // If still in global directory, set CWD as the global directory. - if (globaldir == NULL && prev_dir != NULL) { - globaldir = vim_strsave(prev_dir); + if (globaldir == NULL && pdir != NULL) { + globaldir = vim_strsave(pdir); } } @@ -7754,10 +7770,13 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) } /// Change directory function used by :cd/:tcd/:lcd Ex commands and the chdir() function. +/// @param new_dir The directory to change to. +/// @param scope Scope of the function call (global, tab or window). /// @return true if the directory is successfully changed. bool changedir_func(char_u *new_dir, CdScope scope) { char_u *tofree; + char_u *pdir = NULL; bool retval = false; if (new_dir == NULL || allbuf_locked()) { @@ -7766,19 +7785,32 @@ bool changedir_func(char_u *new_dir, CdScope scope) // ":cd -": Change to previous directory if (STRCMP(new_dir, "-") == 0) { - if (prev_dir == NULL) { + pdir = get_prevdir(scope); + if (pdir == NULL) { EMSG(_("E186: No previous directory")); return false; } - new_dir = prev_dir; + new_dir = pdir; } - // Save current directory for next ":cd -" - tofree = prev_dir; + // Free the previous directory + tofree = get_prevdir(scope); + if (os_dirname(NameBuff, MAXPATHL) == OK) { - prev_dir = vim_strsave(NameBuff); + pdir = vim_strsave(NameBuff); } else { - prev_dir = NULL; + pdir = NULL; + } + + switch (scope) { + case kCdScopeTab: + curtab->tp_prevdir = pdir; + break; + case kCdScopeWindow: + curwin->w_prevdir = pdir; + break; + default: + prev_dir = pdir; } #if defined(UNIX) @@ -7790,12 +7822,12 @@ bool changedir_func(char_u *new_dir, CdScope scope) } #endif - bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0; - if (dir_differs && vim_chdir(new_dir)) { - EMSG(_(e_failed)); - } else { + if (vim_chdir(new_dir) == 0) { + bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; post_chdir(scope, dir_differs); retval = true; + } else { + EMSG(_(e_failed)); } xfree(tofree); diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 22c7b35fa9..0922017e2b 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -54,6 +54,7 @@ #include "nvim/eval.h" #include "nvim/file_search.h" #include "nvim/fileio.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" @@ -1666,11 +1667,12 @@ int vim_chdirfile(char_u *fname, CdCause cause) NameBuff[0] = NUL; } - if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { - if (os_chdir(dir) != 0) { - return FAIL; + if (os_chdir(dir) == 0) { + if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause); } - do_autocmd_dirchanged(dir, kCdScopeWindow, cause); + } else { + return FAIL; } return OK; diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index c6eda58284..36135b52ce 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -43,6 +43,20 @@ func Test_cd_minus() call assert_equal(path_dotdot, getcwd()) cd - call assert_equal(path, getcwd()) + + " Test for :cd - without a previous directory + let lines =<< trim [SCRIPT] + call assert_fails('cd -', 'E186:') + call assert_fails('call chdir("-")', 'E186:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') endfunc func Test_cd_with_cpo_chdir() @@ -115,6 +129,69 @@ func Test_chdir_func() call delete('Xdir', 'rf') endfunc +" Test for changing to the previous directory '-' +func Test_prev_dir() + let topdir = getcwd() + call mkdir('Xdir/a/b/c', 'p') + + " Create a few tabpages and windows with different directories + new | only + tabnew | new + tabnew + tabfirst + cd Xdir + tabnext | wincmd t + tcd a + wincmd w + lcd b + tabnext + tcd a/b/c + + " Change to the previous directory twice in all the windows. + tabfirst + cd - | cd - + tabnext | wincmd t + tcd - | tcd - + wincmd w + lcd - | lcd - + tabnext + tcd - | tcd - + + " Check the directory of all the windows + tabfirst + call assert_equal('Xdir', fnamemodify(getcwd(), ':t')) + tabnext | wincmd t + call assert_equal('a', fnamemodify(getcwd(), ':t')) + wincmd w + call assert_equal('b', fnamemodify(getcwd(), ':t')) + tabnext + call assert_equal('c', fnamemodify(getcwd(), ':t')) + + " Change to the previous directory using chdir() + tabfirst + call chdir("-") | call chdir("-") + tabnext | wincmd t + call chdir("-") | call chdir("-") + wincmd w + call chdir("-") | call chdir("-") + tabnext + call chdir("-") | call chdir("-") + + " Check the directory of all the windows + tabfirst + call assert_equal('Xdir', fnamemodify(getcwd(), ':t')) + tabnext | wincmd t + call assert_equal('a', fnamemodify(getcwd(), ':t')) + wincmd w + call assert_equal('b', fnamemodify(getcwd(), ':t')) + tabnext + call assert_equal('c', fnamemodify(getcwd(), ':t')) + + only | tabonly + call chdir(topdir) + call delete('Xdir', 'rf') +endfunc + func Test_cd_from_non_existing_dir() CheckNotMSWindows diff --git a/src/nvim/window.c b/src/nvim/window.c index 49d7c9c9f5..db71512a3b 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3733,6 +3733,7 @@ void free_tabpage(tabpage_T *tp) } xfree(tp->tp_localdir); + xfree(tp->tp_prevdir); xfree(tp); } @@ -4771,6 +4772,7 @@ static void win_free(win_T *wp, tabpage_T *tp) } xfree(wp->w_localdir); + xfree(wp->w_prevdir); /* Remove the window from the b_wininfo lists, it may happen that the * freed memory is re-used for another window. */ From 36290a2ebd3d49fa43f90fbdbf32e0cbe4f76d87 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 10/12] vim-patch:8.2.1411: when splitting a window localdir is copied but prevdir is not Problem: when splitting a window localdir is copied but prevdir is not. Solution: Also copy prevdir. (closes vim/vim#6667) https://github.com/vim/vim/commit/a9a47d157ab1946d1e286c9695bc68d71305af68 --- src/nvim/testdir/test_cd.vim | 9 +++++++++ src/nvim/window.c | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 36135b52ce..0bba321ee2 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -192,6 +192,15 @@ func Test_prev_dir() call delete('Xdir', 'rf') endfunc +func Test_lcd_split() + let curdir = getcwd() + lcd .. + split + lcd - + call assert_equal(curdir, getcwd()) + quit! +endfunc + func Test_cd_from_non_existing_dir() CheckNotMSWindows diff --git a/src/nvim/window.c b/src/nvim/window.c index db71512a3b..b841b219b2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1463,6 +1463,8 @@ static void win_init(win_T *newp, win_T *oldp, int flags) } newp->w_localdir = (oldp->w_localdir == NULL) ? NULL : vim_strsave(oldp->w_localdir); + newp->w_prevdir = (oldp->w_prevdir == NULL) + ? NULL : vim_strsave(oldp->w_prevdir); // copy tagstack and folds for (i = 0; i < oldp->w_tagstacklen; i++) { From 6004f9137a5625025cfa76700239d9abcac85f47 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 11/12] refactor(dirchanged): tab -> tabpage Match Vim's behavior. --- runtime/doc/autocmd.txt | 8 +++---- src/nvim/eval/funcs.c | 26 ++++++++++----------- src/nvim/ex_docmd.c | 10 ++++---- src/nvim/file_search.c | 4 ++-- src/nvim/globals.h | 6 ++--- src/nvim/window.c | 2 +- test/functional/autocmd/dirchanged_spec.lua | 25 ++++++++------------ 7 files changed, 38 insertions(+), 43 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index b7df0fba54..6c41dd3b10 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -520,10 +520,10 @@ DiffUpdated After diffs have been updated. Depending on *DirChanged* DirChanged After the |current-directory| was changed. The pattern can be: - "window" to trigger on `:lcd` - "tab" to trigger on `:tcd` - "global" to trigger on `:cd` - "auto" to trigger on 'autochdir'. + "window" to trigger on `:lcd` + "tabpage" to trigger on `:tcd` + "global" to trigger on `:cd` + "auto" to trigger on 'autochdir'. Sets these |v:event| keys: cwd: current working directory scope: "global", "tab", "window" diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 2c962a4cf0..5faf2b0b81 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1093,7 +1093,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; } else if (curtab->tp_localdir != NULL) { - scope = kCdScopeTab; + scope = kCdScopeTabpage; } if (!changedir_func(argvars[0].vval.v_string, scope)) { @@ -3445,8 +3445,8 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Numbers of the scope objects (window, tab) we want the working directory // of. A `-1` means to skip this scope, a `0` means the current object. int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. + [kCdScopeWindow ] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. }; char_u *cwd = NULL; // Current working directory to print @@ -3489,8 +3489,8 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); if (!tp) { EMSG(_("E5000: Cannot find tab number.")); return; @@ -3499,7 +3499,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Find the window in `tp` by number, `NULL` if none. if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { + if (scope_number[kCdScopeTabpage] < 0) { EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); return; } @@ -3523,7 +3523,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } FALLTHROUGH; - case kCdScopeTab: + case kCdScopeTabpage: assert(tp); from = tp->tp_localdir; if (from) { @@ -4652,8 +4652,8 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Numbers of the scope objects (window, tab) we want the working directory // of. A `-1` means to skip this scope, a `0` means the current object. int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. + [kCdScopeWindow ] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. }; tabpage_T *tp = curtab; // The tabpage to look at. @@ -4691,8 +4691,8 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); if (!tp) { EMSG(_("E5000: Cannot find tab number.")); return; @@ -4701,7 +4701,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Find the window in `tp` by number, `NULL` if none. if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { + if (scope_number[kCdScopeTabpage] < 0) { EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); return; } @@ -4720,7 +4720,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) assert(win); rettv->vval.v_number = win->w_localdir ? 1 : 0; break; - case kCdScopeTab: + case kCdScopeTabpage: assert(tp); rettv->vval.v_number = tp->tp_localdir ? 1 : 0; break; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 26c06b2675..c52d1576d6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7711,7 +7711,7 @@ void free_cd_dir(void) static char_u *get_prevdir(CdScope scope) { switch (scope) { - case kCdScopeTab: + case kCdScopeTabpage: return curtab->tp_prevdir; break; case kCdScopeWindow: @@ -7731,7 +7731,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) XFREE_CLEAR(curwin->w_localdir); // Overwrite the tab-local CWD for :cd, :tcd. - if (scope >= kCdScopeTab) { + if (scope >= kCdScopeTabpage) { XFREE_CLEAR(curtab->tp_localdir); } @@ -7752,7 +7752,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) // We are now in the global directory, no need to remember its name. XFREE_CLEAR(globaldir); break; - case kCdScopeTab: + case kCdScopeTabpage: curtab->tp_localdir = (char_u *)xstrdup(cwd); break; case kCdScopeWindow: @@ -7803,7 +7803,7 @@ bool changedir_func(char_u *new_dir, CdScope scope) } switch (scope) { - case kCdScopeTab: + case kCdScopeTabpage: curtab->tp_prevdir = pdir; break; case kCdScopeWindow: @@ -7850,7 +7850,7 @@ void ex_cd(exarg_T *eap) switch (eap->cmdidx) { case CMD_tcd: case CMD_tchdir: - scope = kCdScopeTab; + scope = kCdScopeTabpage; break; case CMD_lcd: case CMD_lchdir: diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 0922017e2b..9eab579243 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1610,8 +1610,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) case kCdScopeGlobal: snprintf(buf, sizeof(buf), "global"); break; - case kCdScopeTab: - snprintf(buf, sizeof(buf), "tab"); + case kCdScopeTabpage: + snprintf(buf, sizeof(buf), "tabpage"); break; case kCdScopeWindow: snprintf(buf, sizeof(buf), "window"); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e04856e85f..73cfffb0fb 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1034,9 +1034,9 @@ typedef enum { /// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead. typedef enum { kCdScopeInvalid = -1, - kCdScopeWindow, ///< Affects one window. - kCdScopeTab, ///< Affects one tab page. - kCdScopeGlobal, ///< Affects the entire Nvim instance. + kCdScopeWindow, ///< Affects one window. + kCdScopeTabpage, ///< Affects one tab page. + kCdScopeGlobal, ///< Affects the entire Nvim instance. } CdScope; #define MIN_CD_SCOPE kCdScopeWindow diff --git a/src/nvim/window.c b/src/nvim/window.c index b841b219b2..09c3f7cd71 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4546,7 +4546,7 @@ static void win_enter_ext(win_T *const wp, const int flags) if (os_chdir(new_dir) == 0) { if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) { do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTab, kCdCauseWindow); + ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow); } shorten_fnames(true); } diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index ce3b2e8d3e..bbbc98e5a2 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -33,17 +33,20 @@ describe('autocmd DirChanged', function() command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]]) end) - it('sets v:event', function() + it('sets v:event and ', function() command('lcd '..dirs[1]) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatch')) eq(1, eval('g:cdcount')) command('tcd '..dirs[2]) - eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) + eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) eq(2, eval('g:cdcount')) command('cd '..dirs[3]) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq('global', eval('g:amatch')) eq(3, eval('g:cdcount')) end) @@ -69,17 +72,6 @@ describe('autocmd DirChanged', function() eq(dirs[3], eval('getcwd()')) end) - it('sets to CWD "scope"', function() - command('lcd '..dirs[1]) - eq('window', eval('g:amatch')) - - command('tcd '..dirs[2]) - eq('tab', eval('g:amatch')) - - command('cd '..dirs[3]) - eq('global', eval('g:amatch')) - end) - it('does not trigger if :cd fails', function() command('let g:ev = {}') @@ -135,7 +127,8 @@ describe('autocmd DirChanged', function() end command('tcd '..dirs[2]) - eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) + eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) eq(2, eval('g:cdcount')) command('let g:ev = {}') command('tcd '..dirs[2]) @@ -195,8 +188,10 @@ describe('autocmd DirChanged', function() command('tcd '..dirs[3]) command('tabnext') -- tab 1 (no tab-local CWD) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatch')) command('tabnext') -- tab 2 - eq({cwd=dirs[3], scope='tab', changed_window=true}, eval('g:ev')) + eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) eq(7, eval('g:cdcount')) command('tabnext') -- tab 1 From 38821cc50e7d353b7e8a372da8413e550595b734 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Oct 2021 22:04:53 +0800 Subject: [PATCH 12/12] test(dirchanged): add tests for DirChanged pattern "auto" --- test/functional/autocmd/dirchanged_spec.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index bbbc98e5a2..f4a1642ebf 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -104,16 +104,19 @@ describe('autocmd DirChanged', function() command('split '..dirs[1]..'/foo') eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) command('split '..dirs[2]..'/bar') eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) eq(2, eval('g:cdcount')) end) - it('does not trigger if diretory has not changed', function() + it('does not trigger if directory has not changed', function() command('lcd '..dirs[1]) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatch')) eq(1, eval('g:cdcount')) command('let g:ev = {}') command('lcd '..dirs[1]) @@ -143,6 +146,7 @@ describe('autocmd DirChanged', function() command('cd '..dirs[3]) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq('global', eval('g:amatch')) eq(3, eval('g:cdcount')) command('let g:ev = {}') command('cd '..dirs[3]) @@ -159,6 +163,7 @@ describe('autocmd DirChanged', function() command('split '..dirs[1]..'/foo') eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) eq(4, eval('g:cdcount')) command('let g:ev = {}') command('split '..dirs[1]..'/bar') @@ -181,6 +186,7 @@ describe('autocmd DirChanged', function() command('2wincmd w') -- window 2 eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatch')) eq(4, eval('g:cdcount')) command('tabnew') -- tab 2 (tab-local CWD)