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
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
@ -563,6 +565,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 ']

View File

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

View File

@ -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"));
}

View File

@ -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
@ -102,6 +103,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,

View File

@ -6998,8 +6998,6 @@ void post_chdir(CdScope scope)
shorten_fnames(TRUE);
}
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
void ex_cd(exarg_T *eap)
{
@ -7041,30 +7039,31 @@ void ex_cd(exarg_T *eap)
new_dir = NameBuff;
}
#endif
if (vim_chdir(new_dir)) {
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;
}
if (vim_chdir(new_dir, scope)) {
EMSG(_(e_failed));
} else {
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;
}
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);
}
}

View File

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

View File

@ -48,6 +48,7 @@
#include <limits.h>
#include "nvim/vim.h"
#include "nvim/eval.h"
#include "nvim/ascii.h"
#include "nvim/file_search.h"
#include "nvim/charset.h"
@ -1522,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
@ -1531,18 +1573,28 @@ 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;
}
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;
}

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 */
} 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
@ -6775,10 +6776,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);

View File

@ -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 */

View File

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

View File

@ -14,4 +14,5 @@ typedef unsigned char char_u;
typedef uint32_t u8char_T;
typedef struct expand expand_T;
#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)