Merge #5928 'New event: DirChanged'

This commit is contained in:
Marco Hinz 2017-01-16 13:36:16 +01:00 committed by GitHub
commit 340f79b4b8
12 changed files with 204 additions and 46 deletions

View File

@ -273,6 +273,8 @@ Name triggered by ~
|VimLeave| before exiting Vim, after writing the shada file |VimLeave| before exiting Vim, after writing the shada file
Various Various
|DirChanged| When the current working directory changed.
|FileChangedShell| Vim notices that a file changed since editing started |FileChangedShell| Vim notices that a file changed since editing started
|FileChangedShellPost| After handling 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 |FileChangedRO| before making the first change to a read-only file
@ -563,6 +565,16 @@ CursorMoved After the cursor was moved in Normal or Visual
CursorMovedI After the cursor was moved in Insert mode. CursorMovedI After the cursor was moved in Insert mode.
Not triggered when the popup menu is visible. Not triggered when the popup menu is visible.
Otherwise the same as CursorMoved. 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*
FileAppendCmd Before appending to a file. Should do the FileAppendCmd Before appending to a file. Should do the
appending to the file. Use the '[ and '] appending to the file. Use the '[ and ']

View File

@ -129,6 +129,7 @@ Functions:
|msgpackdump()|, |msgpackparse()| provide msgpack de/serialization |msgpackdump()|, |msgpackparse()| provide msgpack de/serialization
Events: Events:
|DirChanged|
|TabNewEntered| |TabNewEntered|
|TermClose| |TermClose|
|TermOpen| |TermOpen|

View File

@ -315,7 +315,7 @@ void nvim_set_current_dir(String dir, Error *err)
try_start(); try_start();
if (vim_chdir((char_u *)string)) { if (vim_chdir((char_u *)string, kCdScopeGlobal)) {
if (!try_end(err)) { if (!try_end(err)) {
api_set_error(err, Exception, _("Failed to change directory")); api_set_error(err, Exception, _("Failed to change directory"));
} }

View File

@ -28,6 +28,7 @@ return {
'CursorHoldI', -- idem, in Insert mode 'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved 'CursorMoved', -- cursor was moved
'CursorMovedI', -- cursor was moved in Insert mode 'CursorMovedI', -- cursor was moved in Insert mode
'DirChanged', -- directory changed
'EncodingChanged', -- after changing the 'encoding' option 'EncodingChanged', -- after changing the 'encoding' option
'FileAppendCmd', -- append to a file using command 'FileAppendCmd', -- append to a file using command
'FileAppendPost', -- after appending to a file 'FileAppendPost', -- after appending to a file
@ -102,6 +103,7 @@ return {
-- List of neovim-specific events or aliases for the purpose of generating -- List of neovim-specific events or aliases for the purpose of generating
-- syntax file -- syntax file
neovim_specific = { neovim_specific = {
DirChanged=true,
TabClosed=true, TabClosed=true,
TabNew=true, TabNew=true,
TabNewEntered=true, TabNewEntered=true,

View File

@ -6998,8 +6998,6 @@ void post_chdir(CdScope scope)
shorten_fnames(TRUE); shorten_fnames(TRUE);
} }
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. /// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
void ex_cd(exarg_T *eap) void ex_cd(exarg_T *eap)
{ {
@ -7041,9 +7039,6 @@ void ex_cd(exarg_T *eap)
new_dir = NameBuff; new_dir = NameBuff;
} }
#endif #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) { switch (eap->cmdidx) {
@ -7059,12 +7054,16 @@ void ex_cd(exarg_T *eap)
break; break;
} }
if (vim_chdir(new_dir, scope)) {
EMSG(_(e_failed));
} else {
post_chdir(scope); post_chdir(scope);
// Echo the new current directory if the command was typed.
/* Echo the new current directory if the command was typed. */ if (KeyTyped || p_verbose >= 5) {
if (KeyTyped || p_verbose >= 5)
ex_pwd(eap); ex_pwd(eap);
} }
}
xfree(tofree); xfree(tofree);
} }
} }

View File

@ -19,21 +19,6 @@
#define EXMODE_NORMAL 1 #define EXMODE_NORMAL 1
#define EXMODE_VIM 2 #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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h" # include "ex_docmd.h.generated.h"
#endif #endif

View File

@ -48,6 +48,7 @@
#include <limits.h> #include <limits.h>
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/eval.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/file_search.h" #include "nvim/file_search.h"
#include "nvim/charset.h" #include "nvim/charset.h"
@ -1522,6 +1523,47 @@ theend:
return file_name; 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. /// Change to a file's directory.
/// Caller must call shorten_fnames()! /// Caller must call shorten_fnames()!
/// @return OK or FAIL /// @return OK or FAIL
@ -1531,18 +1573,28 @@ int vim_chdirfile(char_u *fname)
STRLCPY(dir, fname, MAXPATHL); STRLCPY(dir, fname, MAXPATHL);
*path_tail_with_sep(dir) = NUL; *path_tail_with_sep(dir) = NUL;
return os_chdir((char *)dir) == 0 ? OK : FAIL; if (os_chdir((char *)dir) != 0) {
return FAIL;
}
do_autocmd_dirchanged(dir, kCdScopeWindow);
return OK;
} }
/// Change directory to "new_dir". Search 'cdpath' for relative directory names. /// 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), char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir),
FNAME_MESS, curbuf->b_ffname); FNAME_MESS, curbuf->b_ffname);
if (dir_name == NULL) { if (dir_name == NULL) {
return -1; return -1;
} }
int r = os_chdir((char *)dir_name); int r = os_chdir((char *)dir_name);
if (r == 0) {
do_autocmd_dirchanged(dir_name, scope);
}
xfree(dir_name); xfree(dir_name);
return r; return r;
} }

View File

@ -6765,8 +6765,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 */ fname = vim_strsave(fname); /* make a copy, so we can change it */
} else { } else {
sfname = vim_strsave(fname); sfname = vim_strsave(fname);
// don't try expanding the following events // Don't try expanding the following events.
if (event == EVENT_COLORSCHEME if (event == EVENT_COLORSCHEME
|| event == EVENT_DIRCHANGED
|| event == EVENT_FILETYPE || event == EVENT_FILETYPE
|| event == EVENT_FUNCUNDEFINED || event == EVENT_FUNCUNDEFINED
|| event == EVENT_OPTIONSET || event == EVENT_OPTIONSET
@ -6775,10 +6776,11 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_REMOTEREPLY || event == EVENT_REMOTEREPLY
|| event == EVENT_SPELLFILEMISSING || event == EVENT_SPELLFILEMISSING
|| event == EVENT_SYNTAX || event == EVENT_SYNTAX
|| event == EVENT_TABCLOSED) || event == EVENT_TABCLOSED) {
fname = vim_strsave(fname); fname = vim_strsave(fname);
else } else {
fname = (char_u *)FullName_save((char *)fname, FALSE); fname = (char_u *)FullName_save((char *)fname, false);
}
} }
if (fname == NULL) { /* out of memory */ if (fname == NULL) { /* out of memory */
xfree(sfname); xfree(sfname);

View File

@ -1249,4 +1249,20 @@ typedef enum {
kBroken kBroken
} WorkingStatus; } 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 */ #endif /* NVIM_GLOBALS_H */

View File

@ -1404,7 +1404,7 @@ int op_delete(oparg_T *oap)
if (oap->regname == 0) { if (oap->regname == 0) {
set_clipboard(0, reg); 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); yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname)); op_yank_reg(oap, message, reg, is_append_register(oap->regname));
set_clipboard(oap->regname, reg); set_clipboard(oap->regname, reg);
yank_do_autocmd(oap, reg); do_autocmd_textyankpost(oap, reg);
return true; 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 oap Operator arguments.
/// @param reg The yank register used. /// @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 FUNC_ATTR_NONNULL_ALL
{ {
static bool recursive = false; static bool recursive = false;

View File

@ -14,4 +14,5 @@ typedef unsigned char char_u;
typedef uint32_t u8char_T; typedef uint32_t u8char_T;
typedef struct expand expand_T; typedef struct expand expand_T;
#endif // NVIM_TYPES_H #endif // NVIM_TYPES_H

View File

@ -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)