From bd8025727cc1ca4389af8fc690b467e2e25a3903 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Wed, 11 Jan 2017 00:51:31 +0100 Subject: New event: DirChanged --- src/nvim/auevents.lua | 2 ++ src/nvim/ex_docmd.c | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 8d891effae..6c62748aae 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -28,6 +28,7 @@ return { 'CursorHoldI', -- idem, in Insert mode 'CursorMoved', -- cursor was moved 'CursorMovedI', -- cursor was moved in Insert mode + 'DirChanged', -- directory changed 'EncodingChanged', -- after changing the 'encoding' option 'FileAppendCmd', -- append to a file using command 'FileAppendPost', -- after appending to a file @@ -101,6 +102,7 @@ return { -- List of neovim-specific events or aliases for the purpose of generating -- syntax file neovim_specific = { + DirChanged=true, TabClosed=true, TabNew=true, TabNewEntered=true, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5ff79bcfef..e5c05a2498 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6949,6 +6949,35 @@ void free_cd_dir(void) #endif +static void apply_autocmd_dirchanged(char_u *new_dir, CdScope scope) +{ + dict_T *dict = get_vim_var_dict(VV_EVENT); + char buf[8]; + + switch (scope) { + case kCdScopeGlobal: + snprintf(buf, sizeof(buf), "global"); + break; + case kCdScopeTab: + snprintf(buf, sizeof(buf), "tab"); + break; + case kCdScopeWindow: + snprintf(buf, sizeof(buf), "window"); + break; + case kCdScopeInvalid: + // Should never happen. + assert(false); + } + + dict_add_nr_str(dict, "scope", 0L, (char_u *)buf); + dict_add_nr_str(dict, "cwd", 0L, new_dir); + dict_set_keys_readonly(dict); + + apply_autocmds(EVENT_DIRCHANGED, NULL, new_dir, false, NULL); + + dict_clear(dict); +} + /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). @@ -6972,6 +7001,8 @@ void post_chdir(CdScope scope) } } + apply_autocmd_dirchanged(new_dir, scope); + switch (scope) { case kCdScopeGlobal: // We are now in the global directory, no need to remember its name. @@ -6998,8 +7029,6 @@ void post_chdir(CdScope scope) shorten_fnames(TRUE); } - - /// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. void ex_cd(exarg_T *eap) { @@ -7061,8 +7090,8 @@ void ex_cd(exarg_T *eap) post_chdir(scope); - /* Echo the new current directory if the command was typed. */ - if (KeyTyped || p_verbose >= 5) + // Echo the new current directory if the command was typed. + if (KeyTyped || p_verbose >= 5) { ex_pwd(eap); } xfree(tofree); -- cgit From 30f775f8a6061697c3a3619f26c21d237ad92027 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Wed, 11 Jan 2017 14:16:43 +0100 Subject: Don't expand filenames during autocmd --- src/nvim/fileio.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6f95ced147..3f5152aea3 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6716,8 +6716,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, fname = vim_strsave(fname); /* make a copy, so we can change it */ } else { sfname = vim_strsave(fname); - // don't try expanding the following events + // Don't try expanding the following events. if (event == EVENT_COLORSCHEME + || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET @@ -6726,10 +6727,11 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX - || event == EVENT_TABCLOSED) + || event == EVENT_TABCLOSED) { fname = vim_strsave(fname); - else - fname = (char_u *)FullName_save((char *)fname, FALSE); + } else { + fname = (char_u *)FullName_save((char *)fname, false); + } } if (fname == NULL) { /* out of memory */ xfree(sfname); -- cgit From 197f50bf9ab5c1c8ba4925e3e556830a9b7fd826 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Thu, 12 Jan 2017 14:25:11 +0100 Subject: Trigger DirChanged on 'autochdir' --- src/nvim/ex_docmd.c | 2 +- src/nvim/file_search.c | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e5c05a2498..86f387d9a5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6949,7 +6949,7 @@ void free_cd_dir(void) #endif -static void apply_autocmd_dirchanged(char_u *new_dir, CdScope scope) +void apply_autocmd_dirchanged(char_u *new_dir, CdScope scope) { dict_T *dict = get_vim_var_dict(VV_EVENT); char buf[8]; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 2ac8e27047..56c0cb73a6 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -51,6 +51,7 @@ #include "nvim/ascii.h" #include "nvim/file_search.h" #include "nvim/charset.h" +#include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -1531,7 +1532,12 @@ int vim_chdirfile(char_u *fname) STRLCPY(dir, fname, MAXPATHL); *path_tail_with_sep(dir) = NUL; - return os_chdir((char *)dir) == 0 ? OK : FAIL; + if (os_chdir((char *)dir) != 0) { + return FAIL; + } + apply_autocmd_dirchanged(dir, kCdScopeWindow); + + return OK; } /// Change directory to "new_dir". Search 'cdpath' for relative directory names. -- cgit From a2f8adad4c09b1af24c964695bf0b0eada3ddc42 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Wed, 11 Jan 2017 18:09:30 +0100 Subject: Add autocmd/dirchanged_spec.lua --- test/functional/autocmd/dirchanged_spec.lua | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/functional/autocmd/dirchanged_spec.lua diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua new file mode 100644 index 0000000000..30a460bc4c --- /dev/null +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -0,0 +1,88 @@ +local lfs = require('lfs') +local h = require('test.functional.helpers')(after_each) + +local clear = h.clear +local command = h.command +local eq = h.eq +local eval = h.eval +local request = h.request + +describe('DirChanged ->', function() + local curdir = lfs.currentdir() + local 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) + + before_each(function() + clear() + command('autocmd DirChanged * let g:event = copy(v:event)') + end) + + it('"autocmd DirChanged *" sets v:event for all :cd variants', function() + command('lcd '..dirs[1]) + eq({cwd=dirs[1], scope='window'}, eval('g:event')) + + command('tcd '..dirs[2]) + eq({cwd=dirs[2], scope='tab'}, eval('g:event')) + + command('cd '..dirs[3]) + eq({cwd=dirs[3], scope='global'}, eval('g:event')) + end) + + it('"autocmd DirChanged *" does not trigger for failing :cd variants', function() + command('let g:event = {}') + + local status1, err1 = pcall(function() + command('lcd '..dirs[1] .. '/doesnotexist') + end) + eq({}, eval('g:event')) + + local status2, err2 = pcall(function() + command('lcd '..dirs[2] .. '/doesnotexist') + end) + eq({}, eval('g:event')) + + local status3, err3 = pcall(function() + command('lcd '..dirs[3] .. '/doesnotexist') + end) + eq({}, eval('g:event')) + + eq(false, status1) + eq(false, status2) + eq(false, status3) + + eq('E344', string.match(err1, 'Vim.*:(.*):')) + eq('E344', string.match(err2, 'Vim.*:(.*):')) + eq('E344', string.match(err3, 'Vim.*:(.*):')) + end) + + it("'autochdir' triggers DirChanged", function() + command('set autochdir') + + command('split '..dirs[1]..'/foo') + eq({cwd=dirs[1], scope='window'}, eval('g:event')) + + command('split '..dirs[2]..'/bar') + eq({cwd=dirs[2], scope='window'}, eval('g:event')) + end) + + it('nvim_set_current_dir() triggers DirChanged', function() + request('nvim_set_current_dir', dirs[1]) + eq({cwd=dirs[1], scope='global'}, eval('g:event')) + + request('nvim_set_current_dir', dirs[2]) + eq({cwd=dirs[2], scope='global'}, eval('g:event')) + + 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'}, eval('g:event')) + end) +end) -- cgit From a05779aa1c13149c26678419890653f15fd00127 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Thu, 12 Jan 2017 19:26:53 +0100 Subject: Move apply_autocmd_dirchanged() to vim_chdir() --- src/nvim/api/vim.c | 2 +- src/nvim/ex_docmd.c | 66 ++++++++++++++------------------------------------ src/nvim/ex_docmd.h | 15 ------------ src/nvim/file_search.c | 52 ++++++++++++++++++++++++++++++++++++--- src/nvim/globals.h | 16 ++++++++++++ src/nvim/types.h | 1 + 6 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b17b59f7a6..f37e996f06 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -315,7 +315,7 @@ void nvim_set_current_dir(String dir, Error *err) try_start(); - if (vim_chdir((char_u *)string)) { + if (vim_chdir((char_u *)string, kCdScopeGlobal)) { if (!try_end(err)) { api_set_error(err, Exception, _("Failed to change directory")); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 86f387d9a5..e205901635 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6949,35 +6949,6 @@ void free_cd_dir(void) #endif -void apply_autocmd_dirchanged(char_u *new_dir, CdScope scope) -{ - dict_T *dict = get_vim_var_dict(VV_EVENT); - char buf[8]; - - switch (scope) { - case kCdScopeGlobal: - snprintf(buf, sizeof(buf), "global"); - break; - case kCdScopeTab: - snprintf(buf, sizeof(buf), "tab"); - break; - case kCdScopeWindow: - snprintf(buf, sizeof(buf), "window"); - break; - case kCdScopeInvalid: - // Should never happen. - assert(false); - } - - dict_add_nr_str(dict, "scope", 0L, (char_u *)buf); - dict_add_nr_str(dict, "cwd", 0L, new_dir); - dict_set_keys_readonly(dict); - - apply_autocmds(EVENT_DIRCHANGED, NULL, new_dir, false, NULL); - - dict_clear(dict); -} - /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). @@ -7001,8 +6972,6 @@ void post_chdir(CdScope scope) } } - apply_autocmd_dirchanged(new_dir, scope); - switch (scope) { case kCdScopeGlobal: // We are now in the global directory, no need to remember its name. @@ -7070,30 +7039,31 @@ void ex_cd(exarg_T *eap) new_dir = NameBuff; } #endif - if (vim_chdir(new_dir)) { - EMSG(_(e_failed)); - } else { - CdScope scope = kCdScopeGlobal; // Depends on command invoked + CdScope scope = kCdScopeGlobal; // Depends on command invoked - switch (eap->cmdidx) { - case CMD_tcd: - case CMD_tchdir: - scope = kCdScopeTab; - break; - case CMD_lcd: - case CMD_lchdir: - scope = kCdScopeWindow; - break; - default: - break; - } + switch (eap->cmdidx) { + case CMD_tcd: + case CMD_tchdir: + scope = kCdScopeTab; + break; + case CMD_lcd: + case CMD_lchdir: + scope = kCdScopeWindow; + break; + default: + break; + } + if (vim_chdir(new_dir, scope)) { + EMSG(_(e_failed)); + } else { post_chdir(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/ex_docmd.h b/src/nvim/ex_docmd.h index fb6aac223f..4def4cbbae 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -19,21 +19,6 @@ #define EXMODE_NORMAL 1 #define EXMODE_VIM 2 -/// The scope of a working-directory command like `:cd`. -/// -/// Scopes are enumerated from lowest to highest. When adding a scope make sure -/// to update all functions using scopes as well, such as the implementation of -/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes -/// 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 instance of Neovim. -} CdScope; -#define MIN_CD_SCOPE kCdScopeWindow -#define MAX_CD_SCOPE kCdScopeGlobal - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.h.generated.h" #endif diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 56c0cb73a6..eb93921bb0 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -48,10 +48,10 @@ #include #include "nvim/vim.h" +#include "nvim/eval.h" #include "nvim/ascii.h" #include "nvim/file_search.h" #include "nvim/charset.h" -#include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -1523,6 +1523,47 @@ theend: return file_name; } +static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope) +{ + static bool recursive = false; + + if (recursive || !has_event(EVENT_DIRCHANGED)) { + // No autocommand was defined or we changed + // the directory from this autocommand. + return; + } + + recursive = true; + + dict_T *dict = get_vim_var_dict(VV_EVENT); + char buf[8]; + + switch (scope) { + case kCdScopeGlobal: + snprintf(buf, sizeof(buf), "global"); + break; + case kCdScopeTab: + snprintf(buf, sizeof(buf), "tab"); + break; + case kCdScopeWindow: + snprintf(buf, sizeof(buf), "window"); + break; + case kCdScopeInvalid: + // Should never happen. + assert(false); + } + + dict_add_nr_str(dict, "scope", 0L, (char_u *)buf); + dict_add_nr_str(dict, "cwd", 0L, new_dir); + dict_set_keys_readonly(dict); + + apply_autocmds(EVENT_DIRCHANGED, NULL, new_dir, false, NULL); + + dict_clear(dict); + + recursive = false; +} + /// Change to a file's directory. /// Caller must call shorten_fnames()! /// @return OK or FAIL @@ -1535,20 +1576,25 @@ int vim_chdirfile(char_u *fname) if (os_chdir((char *)dir) != 0) { return FAIL; } - apply_autocmd_dirchanged(dir, kCdScopeWindow); + do_autocmd_dirchanged(dir, kCdScopeWindow); return OK; } /// Change directory to "new_dir". Search 'cdpath' for relative directory names. -int vim_chdir(char_u *new_dir) +int vim_chdir(char_u *new_dir, CdScope scope) { char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir), FNAME_MESS, curbuf->b_ffname); if (dir_name == NULL) { return -1; } + int r = os_chdir((char *)dir_name); + if (r == 0) { + do_autocmd_dirchanged(dir_name, scope); + } + xfree(dir_name); return r; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 463f4fcd8d..e3c84cb852 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1249,4 +1249,20 @@ typedef enum { kBroken } WorkingStatus; +/// The scope of a working-directory command like `:cd`. +/// +/// Scopes are enumerated from lowest to highest. When adding a scope make sure +/// to update all functions using scopes as well, such as the implementation of +/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes +/// 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 instance of Neovim. +} CdScope; + +#define MIN_CD_SCOPE kCdScopeWindow +#define MAX_CD_SCOPE kCdScopeGlobal + #endif /* NVIM_GLOBALS_H */ diff --git a/src/nvim/types.h b/src/nvim/types.h index 35a5d1e2bd..317bead3bb 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -14,4 +14,5 @@ typedef unsigned char char_u; typedef uint32_t u8char_T; typedef struct expand expand_T; + #endif // NVIM_TYPES_H -- cgit From 20867e63199f4660b3cd3941efecd085e5e32463 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Fri, 13 Jan 2017 11:03:43 +0100 Subject: Document DirChanged --- runtime/doc/autocmd.txt | 12 ++++++++++++ runtime/doc/vim_diff.txt | 1 + 2 files changed, 13 insertions(+) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 090c216fcb..9074141d77 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -273,6 +273,8 @@ Name triggered by ~ |VimLeave| before exiting Vim, after writing the shada file Various +|DirChanged| When the current working directory changed. + |FileChangedShell| Vim notices that a file changed since editing started |FileChangedShellPost| After handling a file changed since editing started |FileChangedRO| before making the first change to a read-only file @@ -562,6 +564,16 @@ CursorMoved After the cursor was moved in Normal or Visual CursorMovedI After the cursor was moved in Insert mode. Not triggered when the popup menu is visible. Otherwise the same as CursorMoved. + *DirChanged* +DirChanged When the current working directory was changed + using the |:cd| family of commands, + |nvim_set_current_dir()|, or on 'autochdir'. + The pattern must be * because its meaning may + change in the future. + It sets these |v:event| keys: + cwd: String (current working directory) + scope: String ("global", "tab", "window") + Recursion is ignored. *FileAppendCmd* FileAppendCmd Before appending to a file. Should do the appending to the file. Use the '[ and '] diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7c9d383a01..ae0bd5a867 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -129,6 +129,7 @@ Functions: |msgpackdump()|, |msgpackparse()| provide msgpack de/serialization Events: + |DirChanged| |TabNewEntered| |TermClose| |TermOpen| -- cgit From 1f7a119f5efc13fbce6446bf268c2e0f9d753147 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Fri, 13 Jan 2017 03:06:20 +0100 Subject: Rename yank_do_autocmd() to do_autocmd_textyankpost() --- src/nvim/ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 907353c271..10d6be85f8 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1404,7 +1404,7 @@ int op_delete(oparg_T *oap) if (oap->regname == 0) { set_clipboard(0, reg); - yank_do_autocmd(oap, reg); + do_autocmd_textyankpost(oap, reg); } } @@ -2315,7 +2315,7 @@ bool op_yank(oparg_T *oap, bool message) yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); set_clipboard(oap->regname, reg); - yank_do_autocmd(oap, reg); + do_autocmd_textyankpost(oap, reg); return true; } @@ -2538,7 +2538,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx) /// /// @param oap Operator arguments. /// @param reg The yank register used. -static void yank_do_autocmd(oparg_T *oap, yankreg_T *reg) +static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { static bool recursive = false; -- cgit