Merge #6262 from justinmk/dirchanged

DirChanged fixes
This commit is contained in:
Justin M. Keyes 2017-03-12 21:01:05 +01:00 committed by GitHub
commit bf5110266c
7 changed files with 128 additions and 64 deletions

View File

@ -825,8 +825,7 @@ struct tabpage_S {
frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots
dictitem_T tp_winvar; ///< variable for "t:" Dictionary
dict_T *tp_vars; ///< internal variables, local to tab page
char_u *localdir; ///< Absolute path of local directory or
///< NULL
char_u *tp_localdir; ///< Absolute path of local CWD or NULL
};
/*

View File

@ -10901,7 +10901,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
case kCdScopeTab: // FALLTHROUGH
assert(tp);
from = tp->localdir;
from = tp->tp_localdir;
if (from) {
break;
}
@ -12015,7 +12015,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break;
case kCdScopeTab:
assert(tp);
rettv->vval.v_number = tp->localdir ? 1 : 0;
rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
break;
case kCdScopeGlobal:
// The global scope never has a local directory

View File

@ -6943,24 +6943,27 @@ void free_cd_dir(void)
/// @param scope Scope of the function call (global, tab or window).
void post_chdir(CdScope scope)
{
// The local directory of the current window is always overwritten.
// Always overwrite the window-local CWD.
xfree(curwin->w_localdir);
curwin->w_localdir = NULL;
// Overwrite the local directory of the current tab page for `cd` and `tcd`
// Overwrite the tab-local CWD for :cd, :tcd.
if (scope >= kCdScopeTab) {
xfree(curtab->localdir);
curtab->localdir = NULL;
xfree(curtab->tp_localdir);
curtab->tp_localdir = NULL;
}
if (scope < kCdScopeGlobal) {
// If still in global directory, need to remember current directory as
// global directory.
// If still in global directory, set CWD as the global directory.
if (globaldir == NULL && prev_dir != NULL) {
globaldir = vim_strsave(prev_dir);
}
}
char cwd[MAXPATHL];
if (os_dirname((char_u *)cwd, MAXPATHL) != OK) {
return;
}
switch (scope) {
case kCdScopeGlobal:
// We are now in the global directory, no need to remember its name.
@ -6968,23 +6971,17 @@ void post_chdir(CdScope scope)
globaldir = NULL;
break;
case kCdScopeTab:
// Remember this local directory for the tab page.
if (os_dirname(NameBuff, MAXPATHL) == OK) {
curtab->localdir = vim_strsave(NameBuff);
}
curtab->tp_localdir = (char_u *)xstrdup(cwd);
break;
case kCdScopeWindow:
// Remember this local directory for the window.
if (os_dirname(NameBuff, MAXPATHL) == OK) {
curwin->w_localdir = vim_strsave(NameBuff);
}
curwin->w_localdir = (char_u *)xstrdup(cwd);
break;
case kCdScopeInvalid:
// We should never get here
assert(false);
}
shorten_fnames(TRUE);
shorten_fnames(true);
do_autocmd_dirchanged(cwd, scope);
}
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.

View File

@ -1519,7 +1519,7 @@ theend:
return file_name;
}
static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope)
void do_autocmd_dirchanged(char *new_dir, CdScope scope)
{
static bool recursive = false;
@ -1550,10 +1550,11 @@ static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope)
}
dict_add_nr_str(dict, "scope", 0L, (char_u *)buf);
dict_add_nr_str(dict, "cwd", 0L, new_dir);
dict_add_nr_str(dict, "cwd", 0L, (char_u *)new_dir);
dict_set_keys_readonly(dict);
apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, new_dir, false, NULL);
apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
NULL);
dict_clear(dict);
@ -1565,14 +1566,25 @@ static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope)
/// @return OK or FAIL
int vim_chdirfile(char_u *fname)
{
char_u dir[MAXPATHL];
char dir[MAXPATHL];
STRLCPY(dir, fname, MAXPATHL);
*path_tail_with_sep(dir) = NUL;
if (os_chdir((char *)dir) != 0) {
*path_tail_with_sep((char_u *)dir) = NUL;
if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) {
NameBuff[0] = NUL;
}
if (os_chdir(dir) != 0) {
return FAIL;
}
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(dir);
#endif
if (!strequal(dir, (char *)NameBuff)) {
do_autocmd_dirchanged(dir, kCdScopeWindow);
}
return OK;
}
@ -1587,10 +1599,6 @@ int vim_chdir(char_u *new_dir, CdScope scope)
}
int r = os_chdir((char *)dir_name);
if (r == 0) {
do_autocmd_dirchanged(dir_name, scope);
}
xfree(dir_name);
return r;
}

View File

@ -475,6 +475,13 @@ void *xmemdup(const void *data, size_t len)
return memcpy(xmalloc(len), data, len);
}
/// Returns true if strings `a` and `b` are equal. Arguments may be NULL.
bool strequal(const char *a, const char *b)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return (a == NULL && b == NULL) || (a && b && strcmp(a, b) == 0);
}
/*
* Avoid repeating the error message many times (they take 1 second each).
* Did_outofmem_msg is reset when a character is read.

View File

@ -2998,8 +2998,7 @@ void free_tabpage(tabpage_T *tp)
hash_init(&tp->tp_vars->dv_hashtab);
unref_var_dict(tp->tp_vars);
xfree(tp->localdir); // Free tab-local working directory
xfree(tp->tp_localdir);
xfree(tp);
}
@ -3025,7 +3024,7 @@ int win_new_tabpage(int after, char_u *filename)
return FAIL;
}
newtp->localdir = tp->localdir ? vim_strsave(tp->localdir) : NULL;
newtp->tp_localdir = tp->tp_localdir ? vim_strsave(tp->tp_localdir) : NULL;
curtab = newtp;
@ -3617,28 +3616,38 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
curwin->w_cursor.coladd = 0;
changed_line_abv_curs(); /* assume cursor position needs updating */
// The new directory is either the local directory of the window, of the tab
// or NULL.
char_u *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->localdir;
// New directory is either the local directory of the window, tab or NULL.
char *new_dir = (char *)(curwin->w_localdir
? curwin->w_localdir : curtab->tp_localdir);
char cwd[MAXPATHL];
if (os_dirname((char_u *)cwd, MAXPATHL) != OK) {
cwd[0] = NUL;
}
if (new_dir) {
// Window/tab has a local directory: Save current directory as global
// directory (unless that was done already) and change to the local
// directory.
// (unless that was done already) and change to the local directory.
if (globaldir == NULL) {
char_u cwd[MAXPATHL];
if (os_dirname(cwd, MAXPATHL) == OK) {
globaldir = vim_strsave(cwd);
if (cwd[0] != NUL) {
globaldir = (char_u *)xstrdup(cwd);
}
}
if (os_chdir((char *)new_dir) == 0) {
if (os_chdir(new_dir) == 0) {
if (!p_acd && !strequal(new_dir, cwd)) {
do_autocmd_dirchanged(new_dir, curwin->w_localdir
? kCdScopeWindow : kCdScopeTab);
}
shorten_fnames(true);
}
} else if (globaldir != NULL) {
/* Window doesn't have a local directory and we are not in the global
* directory: Change to the global directory. */
ignored = os_chdir((char *)globaldir);
// 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)) {
do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal);
}
}
xfree(globaldir);
globaldir = NULL;
shorten_fnames(TRUE);

View File

@ -20,29 +20,44 @@ describe('autocmd DirChanged', function()
before_each(function()
clear()
command('autocmd DirChanged * let [g:event, g:scope, g:cdcount] = [copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
command('autocmd DirChanged * let [g:getcwd, g:ev, g:amatch, g:cdcount] '
..' = [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
-- Normalize path separators.
command([[autocmd DirChanged * let g:ev['cwd'] = substitute(g:ev['cwd'], '\\', '/', 'g')]])
command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
end)
it('sets v:event', function()
command('lcd '..dirs[1])
eq({cwd=dirs[1], scope='window'}, eval('g:event'))
eq({cwd=dirs[1], scope='window'}, eval('g:ev'))
eq(1, eval('g:cdcount'))
command('tcd '..dirs[2])
eq({cwd=dirs[2], scope='tab'}, eval('g:event'))
eq({cwd=dirs[2], scope='tab'}, eval('g:ev'))
eq(2, eval('g:cdcount'))
command('cd '..dirs[3])
eq({cwd=dirs[3], scope='global'}, eval('g:event'))
eq({cwd=dirs[3], scope='global'}, eval('g:ev'))
eq(3, eval('g:cdcount'))
end)
it('sets getcwd() during event #6260', function()
command('lcd '..dirs[1])
eq(dirs[1], eval('g:getcwd'))
command('tcd '..dirs[2])
eq(dirs[2], eval('g:getcwd'))
command('cd '..dirs[3])
eq(dirs[3], eval('g:getcwd'))
end)
it('disallows recursion', function()
command('set shellslash')
-- Set up a _nested_ handler.
command('autocmd DirChanged * nested lcd '..dirs[3])
command('lcd '..dirs[1])
eq({cwd=dirs[1], scope='window'}, eval('g:event'))
eq({cwd=dirs[1], scope='window'}, eval('g:ev'))
eq(1, eval('g:cdcount'))
-- autocmd changed to dirs[3], but did NOT trigger another DirChanged.
eq(dirs[3], eval('getcwd()'))
@ -50,32 +65,32 @@ describe('autocmd DirChanged', function()
it('sets <amatch> to CWD "scope"', function()
command('lcd '..dirs[1])
eq('window', eval('g:scope'))
eq('window', eval('g:amatch'))
command('tcd '..dirs[2])
eq('tab', eval('g:scope'))
eq('tab', eval('g:amatch'))
command('cd '..dirs[3])
eq('global', eval('g:scope'))
eq('global', eval('g:amatch'))
end)
it('does not trigger if :cd fails', function()
command('let g:event = {}')
command('let g:ev = {}')
local status1, err1 = pcall(function()
command('lcd '..dirs[1] .. '/doesnotexist')
end)
eq({}, eval('g:event'))
eq({}, eval('g:ev'))
local status2, err2 = pcall(function()
command('lcd '..dirs[2] .. '/doesnotexist')
end)
eq({}, eval('g:event'))
eq({}, eval('g:ev'))
local status3, err3 = pcall(function()
command('lcd '..dirs[3] .. '/doesnotexist')
end)
eq({}, eval('g:event'))
eq({}, eval('g:ev'))
eq(false, status1)
eq(false, status2)
@ -90,24 +105,53 @@ describe('autocmd DirChanged', function()
command('set autochdir')
command('split '..dirs[1]..'/foo')
eq({cwd=dirs[1], scope='window'}, eval('g:event'))
eq({cwd=dirs[1], scope='window'}, eval('g:ev'))
command('split '..dirs[2]..'/bar')
eq({cwd=dirs[2], scope='window'}, eval('g:event'))
eq({cwd=dirs[2], scope='window'}, eval('g:ev'))
eq(2, 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
command('lcd '..dirs[2])
command('split '..dirs[1]..'/bar') -- window 1
command('lcd '..dirs[1])
command('2wincmd w') -- window 2
eq({cwd=dirs[2], scope='window'}, eval('g:ev'))
eq(4, eval('g:cdcount'))
command('tabnew') -- tab 2 (tab-local CWD)
eq(4, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..dirs[3])
command('tabnext') -- tab 1 (no tab-local CWD)
eq({cwd=dirs[2], scope='window'}, eval('g:ev'))
command('tabnext') -- tab 2
eq({cwd=dirs[3], scope='tab'}, eval('g:ev'))
eq(7, eval('g:cdcount'))
command('tabnext') -- tab 1
command('3wincmd w') -- window 3
eq(9, eval('g:cdcount'))
command('tabnext') -- tab 2 (has the *same* CWD)
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
end)
it('is triggered by nvim_set_current_dir()', function()
request('nvim_set_current_dir', dirs[1])
eq({cwd=dirs[1], scope='global'}, eval('g:event'))
eq({cwd=dirs[1], scope='global'}, eval('g:ev'))
request('nvim_set_current_dir', dirs[2])
eq({cwd=dirs[2], scope='global'}, eval('g:event'))
eq({cwd=dirs[2], scope='global'}, eval('g:ev'))
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'))
eq({cwd=dirs[2], scope='global'}, eval('g:ev'))
end)
end)