Skip to content

Commit

Permalink
Merge #5928 'New event: DirChanged'
Browse files Browse the repository at this point in the history
  • Loading branch information
mhinz committed Jan 16, 2017
2 parents fa94c4c + 1f7a119 commit 340f79b
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 46 deletions.
12 changes: 12 additions & 0 deletions runtime/doc/autocmd.txt
Expand Up @@ -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
Expand Down Expand Up @@ -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 ']
Expand Down
1 change: 1 addition & 0 deletions runtime/doc/vim_diff.txt
Expand Up @@ -129,6 +129,7 @@ Functions:
|msgpackdump()|, |msgpackparse()| provide msgpack de/serialization

Events:
|DirChanged|
|TabNewEntered|
|TermClose|
|TermOpen|
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/api/vim.c
Expand Up @@ -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"));
}
Expand Down
2 changes: 2 additions & 0 deletions src/nvim/auevents.lua
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
41 changes: 20 additions & 21 deletions src/nvim/ex_docmd.c
Expand Up @@ -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)
{
Expand Down Expand Up @@ -7041,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)
// Echo the new current directory if the command was typed.
if (KeyTyped || p_verbose >= 5) {
ex_pwd(eap);
}
}

xfree(tofree);
}
}
Expand Down
15 changes: 0 additions & 15 deletions src/nvim/ex_docmd.h
Expand Up @@ -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
Expand Down
56 changes: 54 additions & 2 deletions src/nvim/file_search.c
Expand Up @@ -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"
Expand Down Expand Up @@ -1522,6 +1523,47 @@ find_file_in_path_option (
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
Expand All @@ -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;
}
Expand Down
10 changes: 6 additions & 4 deletions src/nvim/fileio.c
Expand Up @@ -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
Expand All @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions src/nvim/globals.h
Expand Up @@ -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 */
6 changes: 3 additions & 3 deletions src/nvim/ops.c
Expand Up @@ -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);
}

}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/nvim/types.h
Expand Up @@ -14,4 +14,5 @@ typedef unsigned char char_u;
typedef uint32_t u8char_T;

typedef struct expand expand_T;

#endif // NVIM_TYPES_H
88 changes: 88 additions & 0 deletions 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)

0 comments on commit 340f79b

Please sign in to comment.