Merge pull request #15952 from zeertzjq/vim-8.1.1291

vim-patch:8.0.{1459,1460,1461,1463},8.1.{0602,0604,1291},8.2.{0189,0876,0909,1411}: chdir and DirChanged related patches
This commit is contained in:
Jan Edmund Lazo 2021-10-17 10:26:11 -04:00 committed by GitHub
commit a1e8199fff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 615 additions and 132 deletions

View File

@ -519,11 +519,17 @@ DiffUpdated After diffs have been updated. Depending on
change or when doing |:diffupdate|. change or when doing |:diffupdate|.
*DirChanged* *DirChanged*
DirChanged After the |current-directory| was changed. DirChanged After the |current-directory| was changed.
The pattern can be:
"window" to trigger on `:lcd`
"tabpage" to trigger on `:tcd`
"global" to trigger on `:cd`
"auto" to trigger on 'autochdir'.
Sets these |v:event| keys: Sets these |v:event| keys:
cwd: current working directory cwd: current working directory
scope: "global", "tab", "window" scope: "global", "tab", "window"
changed_window: v:true if we fired the event changed_window: v:true if we fired the event
switching window (or tab) switching window (or tab)
<afile> is set to the new directory name.
Non-recursive (event cannot trigger itself). Non-recursive (event cannot trigger itself).
*FileAppendCmd* *FileAppendCmd*
FileAppendCmd Before appending to a file. Should do the FileAppendCmd Before appending to a file. Should do the

View File

@ -1275,10 +1275,12 @@ exist, the next-higher scope in the hierarchy applies.
*:chd* *:chdir* *:chd* *:chdir*
:chd[ir][!] [path] Same as |:cd|. :chd[ir][!] [path] Same as |:cd|.
*:tc* *:tcd* *E5000* *E5001* *E5002* *:tc* *:tcd*
:tc[d][!] {path} Like |:cd|, but set the current directory for the :tc[d][!] {path} Like |:cd|, but only set the directory for the current
current tab and window. The current directory for tab. The current window will also use this directory.
other tabs and windows is not changed. The current directory is not changed for windows in
other tabs and for windows in the current tab that
have their own window-local directory.
*:tcd-* *:tcd-*
:tc[d][!] - Change to the previous current directory (before the :tc[d][!] - Change to the previous current directory (before the
@ -1302,9 +1304,24 @@ exist, the next-higher scope in the hierarchy applies.
*:pw* *:pwd* *E187* *:pw* *:pwd* *E187*
:pw[d] Print the current directory name. :pw[d] Print the current directory name.
Also see |getcwd()|. Also see |getcwd()|.
*:pwd-verbose*
When 'verbose' is non-zero, |:pwd| will also display
what scope the current directory was set. Example: >
So long as no |:tcd| or |:lcd| command has been used, all windows share the " Set by :cd
same "current directory". Using a command to jump to another window doesn't :verbose pwd
[global] /path/to/current
" Set by :lcd
:verbose pwd
[window] /path/to/current
" Set by :tcd
:verbose pwd
[tabpage] /path/to/current
So long as no |:lcd| or |:tcd| command has been used, all windows share the
same current directory. Using a command to jump to another window doesn't
change anything for the current directory. change anything for the current directory.
When |:lcd| has been used for a window, the specified directory becomes the When |:lcd| has been used for a window, the specified directory becomes the

View File

@ -2316,6 +2316,7 @@ chansend({id}, {data}) Number Writes {data} to channel
char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
charidx({string}, {idx} [, {countcc}]) charidx({string}, {idx} [, {countcc}])
Number char index of byte {idx} in {string} Number char index of byte {idx} in {string}
chdir({dir}) String change current working directory
cindent({lnum}) Number C indent for line {lnum} cindent({lnum}) Number C indent for line {lnum}
clearmatches([{win}]) none clear all matches clearmatches([{win}]) none clear all matches
col({expr}) Number column nr of cursor or mark col({expr}) Number column nr of cursor or mark
@ -2461,6 +2462,7 @@ has({feature}) Number |TRUE| if feature {feature} supported
has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key} has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key}
haslocaldir([{winnr} [, {tabnr}]]) haslocaldir([{winnr} [, {tabnr}]])
Number |TRUE| if current window executed |:lcd| Number |TRUE| if current window executed |:lcd|
or |:tcd|
hasmapto({what} [, {mode} [, {abbr}]]) hasmapto({what} [, {mode} [, {abbr}]])
Number |TRUE| if mapping to {what} exists Number |TRUE| if mapping to {what} exists
histadd({history}, {item}) String add an item to a history histadd({history}, {item}) String add an item to a history
@ -3280,6 +3282,27 @@ charidx({string}, {idx} [, {countcc}])
echo charidx('áb́ć', 6, 1) returns 4 echo charidx('áb́ć', 6, 1) returns 4
echo charidx('áb́ć', 16) returns -1 echo charidx('áb́ć', 16) returns -1
chdir({dir}) *chdir()*
Change the current working directory to {dir}. The scope of
the directory change depends on the directory of the current
window:
- If the current window has a window-local directory
(|:lcd|), then changes the window local directory.
- Otherwise, if the current tabpage has a local
directory (|:tcd|) then changes the tabpage local
directory.
- Otherwise, changes the global directory.
If successful, returns the previous working directory. Pass
this to another chdir() to restore the directory.
On failure, returns an empty string.
Example: >
let save_dir = chdir(newdir)
if save_dir
" ... do some work
call chdir(save_dir)
endif
<
cindent({lnum}) *cindent()* cindent({lnum}) *cindent()*
Get the amount of indent for line {lnum} according the C Get the amount of indent for line {lnum} according the C
indenting rules, as with 'cindent'. indenting rules, as with 'cindent'.
@ -4987,6 +5010,8 @@ getcwd([{winnr}[, {tabnr}]]) *getcwd()*
getcwd(0, 0) getcwd(0, 0)
< If {winnr} is -1 it is ignored, only the tab is resolved. < If {winnr} is -1 it is ignored, only the tab is resolved.
{winnr} can be the window number or the |window-ID|. {winnr} can be the window number or the |window-ID|.
If both {winnr} and {tabnr} are -1 the global working
directory is returned.
Can also be used as a |method|: > Can also be used as a |method|: >
GetWinnr()->getcwd() GetWinnr()->getcwd()

View File

@ -202,14 +202,28 @@ the other window. This is called a local directory. >
:pwd :pwd
/home/Bram/VeryLongFileName /home/Bram/VeryLongFileName
So long as no ":lcd" command has been used, all windows share the same current So long as no `:lcd` command has been used, all windows share the same current
directory. Doing a ":cd" command in one window will also change the current directory. Doing a `:cd` command in one window will also change the current
directory of the other window. directory of the other window.
For a window where ":lcd" has been used a different current directory is For a window where `:lcd` has been used a different current directory is
remembered. Using ":cd" or ":lcd" in other windows will not change it. remembered. Using `:cd` or `:lcd` in other windows will not change it.
When using a ":cd" command in a window that uses a different current When using a `:cd` command in a window that uses a different current
directory, it will go back to using the shared directory. directory, it will go back to using the shared directory.
TAB LOCAL DIRECTORY
When you open a new tab page, it uses the directory of the window in the
previous tab page from which the new tab page was opened. You can change the
directory of the current tab page using the `:tcd` command. All the windows in
a tab page share this directory except for windows with a window-local
directory. Any new windows opened in this tab page will use this directory as
the current working directory. Using a `:cd` command in a tab page will not
change the working directory of tab pages which have a tab local directory.
When the global working directory is changed using the ":cd" command in a tab
page, it will also change the current tab page working directory.
============================================================================== ==============================================================================
*22.3* Finding a file *22.3* Finding a file

View File

@ -785,9 +785,10 @@ System functions and manipulation of files:
isdirectory() check if a directory exists isdirectory() check if a directory exists
getfsize() get the size of a file getfsize() get the size of a file
getcwd() get the current working directory getcwd() get the current working directory
haslocaldir() check if current window used |:lcd| haslocaldir() check if current window used |:lcd| or |:tcd|
tempname() get the name of a temporary file tempname() get the name of a temporary file
mkdir() create a new directory mkdir() create a new directory
chdir() change current working directory
delete() delete a file delete() delete a file
rename() rename a file rename() rename a file
system() get the result of a shell command as a string system() get the result of a shell command as a string

View File

@ -180,6 +180,7 @@ Commands:
|:match| can be invoked before highlight group is defined |:match| can be invoked before highlight group is defined
Events: Events:
|DirChanged| can be triggered when switching to another window
|Signal| |Signal|
|TabNewEntered| |TabNewEntered|
|TermClose| |TermClose|
@ -196,6 +197,10 @@ Functions:
|stdpath()| |stdpath()|
|system()|, |systemlist()| can run {cmd} directly (without 'shell') |system()|, |systemlist()| can run {cmd} directly (without 'shell')
|matchadd()| can be called before highlight group is defined |matchadd()| can be called before highlight group is defined
|getcwd()| and |haslocaldir()| may throw errors. *E5000* *E5001* *E5002*
|haslocaldir()|'s only possible return values are 0 and 1, it never returns 2.
`getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global
working directory. Use `getcwd(-1, -1)` to get the global working directory.
Highlight groups: Highlight groups:
|highlight-blend| controls blend level for a highlight group |highlight-blend| controls blend level for a highlight group

View File

@ -593,9 +593,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf->b_nwindows--; buf->b_nwindows--;
} }
// Change directories when the 'acd' option is set.
do_autochdir();
// Disable buffer-updates for the current buffer. // Disable buffer-updates for the current buffer.
// No need to check `unload_buf`: in that case the function returned above. // No need to check `unload_buf`: in that case the function returned above.
buf_updates_unload(buf, false); buf_updates_unload(buf, false);
@ -1628,7 +1625,7 @@ void do_autochdir(void)
if (p_acd) { if (p_acd) {
if (starting == 0 if (starting == 0
&& curbuf->b_ffname != NULL && curbuf->b_ffname != NULL
&& vim_chdirfile(curbuf->b_ffname) == OK) { && vim_chdirfile(curbuf->b_ffname, kCdCauseAuto) == OK) {
post_chdir(kCdScopeGlobal, false); post_chdir(kCdScopeGlobal, false);
shorten_fnames(true); shorten_fnames(true);
} }

View File

@ -953,6 +953,7 @@ struct tabpage_S {
ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dictionary. ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dictionary.
dict_T *tp_vars; ///< Internal variables, local to tab page. dict_T *tp_vars; ///< Internal variables, local to tab page.
char_u *tp_localdir; ///< Absolute path of local cwd or NULL. char_u *tp_localdir; ///< Absolute path of local cwd or NULL.
char_u *tp_prevdir; ///< Previous directory.
}; };
/* /*
@ -1381,8 +1382,8 @@ struct window_S {
// out of range!) // out of range!)
int w_arg_idx_invalid; // editing another file than w_arg_idx int w_arg_idx_invalid; // editing another file than w_arg_idx
char_u *w_localdir; /* absolute path of local directory or char_u *w_localdir; // absolute path of local directory or NULL
NULL */ char_u *w_prevdir; // previous directory
// Options local to a window. // Options local to a window.
// They are local because they influence the layout of the window or // They are local because they influence the layout of the window or
// depend on the window layout. // depend on the window layout.

View File

@ -72,6 +72,7 @@ return {
chansend={args=2}, chansend={args=2},
char2nr={args={1, 2}, base=1}, char2nr={args={1, 2}, base=1},
charidx={args={2, 3}}, charidx={args={2, 3}},
chdir={args=1, base=1},
cindent={args=1, base=1}, cindent={args=1, base=1},
clearmatches={args={0, 1}, base=1}, clearmatches={args={0, 1}, base=1},
col={args=1, base=1}, col={args=1, base=1},

View File

@ -28,6 +28,7 @@
#include "nvim/file_search.h" #include "nvim/file_search.h"
#include "nvim/fileio.h" #include "nvim/fileio.h"
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/globals.h"
#include "nvim/if_cscope.h" #include "nvim/if_cscope.h"
#include "nvim/indent.h" #include "nvim/indent.h"
#include "nvim/indent_c.h" #include "nvim/indent_c.h"
@ -1062,6 +1063,45 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = len > 0 ? len - 1 : 0; rettv->vval.v_number = len > 0 ? len - 1 : 0;
} }
// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
char_u *cwd;
CdScope scope = kCdScopeGlobal;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
if (argvars[0].v_type != VAR_STRING) {
// Returning an empty string means it failed.
// No error message, for historic reasons.
return;
}
// Return the current directory
cwd = xmalloc(MAXPATHL);
if (cwd != NULL) {
if (os_dirname(cwd, MAXPATHL) != FAIL) {
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(cwd);
#endif
rettv->vval.v_string = vim_strsave(cwd);
}
xfree(cwd);
}
if (curwin->w_localdir != NULL) {
scope = kCdScopeWindow;
} else if (curtab->tp_localdir != NULL) {
scope = kCdScopeTabpage;
}
if (!changedir_func(argvars[0].vval.v_string, scope)) {
// Directory change failed
XFREE_CLEAR(rettv->vval.v_string);
}
}
/* /*
* "cindent(lnum)" function * "cindent(lnum)" function
*/ */
@ -3405,8 +3445,8 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Numbers of the scope objects (window, tab) we want the working directory // Numbers of the scope objects (window, tab) we want the working directory
// of. A `-1` means to skip this scope, a `0` means the current object. // of. A `-1` means to skip this scope, a `0` means the current object.
int scope_number[] = { int scope_number[] = {
[kCdScopeWindow] = 0, // Number of window to look at. [kCdScopeWindow ] = 0, // Number of window to look at.
[kCdScopeTab ] = 0, // Number of tab to look at. [kCdScopeTabpage] = 0, // Number of tab to look at.
}; };
char_u *cwd = NULL; // Current working directory to print char_u *cwd = NULL; // Current working directory to print
@ -3449,8 +3489,8 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
// Find the tabpage by number // Find the tabpage by number
if (scope_number[kCdScopeTab] > 0) { if (scope_number[kCdScopeTabpage] > 0) {
tp = find_tabpage(scope_number[kCdScopeTab]); tp = find_tabpage(scope_number[kCdScopeTabpage]);
if (!tp) { if (!tp) {
EMSG(_("E5000: Cannot find tab number.")); EMSG(_("E5000: Cannot find tab number."));
return; return;
@ -3459,7 +3499,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Find the window in `tp` by number, `NULL` if none. // Find the window in `tp` by number, `NULL` if none.
if (scope_number[kCdScopeWindow] >= 0) { if (scope_number[kCdScopeWindow] >= 0) {
if (scope_number[kCdScopeTab] < 0) { if (scope_number[kCdScopeTabpage] < 0) {
EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
return; return;
} }
@ -3483,7 +3523,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break; break;
} }
FALLTHROUGH; FALLTHROUGH;
case kCdScopeTab: case kCdScopeTabpage:
assert(tp); assert(tp);
from = tp->tp_localdir; from = tp->tp_localdir;
if (from) { if (from) {
@ -4612,8 +4652,8 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Numbers of the scope objects (window, tab) we want the working directory // Numbers of the scope objects (window, tab) we want the working directory
// of. A `-1` means to skip this scope, a `0` means the current object. // of. A `-1` means to skip this scope, a `0` means the current object.
int scope_number[] = { int scope_number[] = {
[kCdScopeWindow] = 0, // Number of window to look at. [kCdScopeWindow ] = 0, // Number of window to look at.
[kCdScopeTab ] = 0, // Number of tab to look at. [kCdScopeTabpage] = 0, // Number of tab to look at.
}; };
tabpage_T *tp = curtab; // The tabpage to look at. tabpage_T *tp = curtab; // The tabpage to look at.
@ -4651,8 +4691,8 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
// Find the tabpage by number // Find the tabpage by number
if (scope_number[kCdScopeTab] > 0) { if (scope_number[kCdScopeTabpage] > 0) {
tp = find_tabpage(scope_number[kCdScopeTab]); tp = find_tabpage(scope_number[kCdScopeTabpage]);
if (!tp) { if (!tp) {
EMSG(_("E5000: Cannot find tab number.")); EMSG(_("E5000: Cannot find tab number."));
return; return;
@ -4661,7 +4701,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Find the window in `tp` by number, `NULL` if none. // Find the window in `tp` by number, `NULL` if none.
if (scope_number[kCdScopeWindow] >= 0) { if (scope_number[kCdScopeWindow] >= 0) {
if (scope_number[kCdScopeTab] < 0) { if (scope_number[kCdScopeTabpage] < 0) {
EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
return; return;
} }
@ -4680,7 +4720,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
assert(win); assert(win);
rettv->vval.v_number = win->w_localdir ? 1 : 0; rettv->vval.v_number = win->w_localdir ? 1 : 0;
break; break;
case kCdScopeTab: case kCdScopeTabpage:
assert(tp); assert(tp);
rettv->vval.v_number = tp->tp_localdir ? 1 : 0; rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
break; break;

View File

@ -7707,6 +7707,21 @@ void free_cd_dir(void)
#endif #endif
// Get the previous directory for the given chdir scope.
static char_u *get_prevdir(CdScope scope)
{
switch (scope) {
case kCdScopeTabpage:
return curtab->tp_prevdir;
break;
case kCdScopeWindow:
return curwin->w_prevdir;
break;
default:
return prev_dir;
}
}
/// Deal with the side effects of changing the current directory. /// Deal with the side effects of changing the current directory.
/// ///
/// @param scope Scope of the function call (global, tab or window). /// @param scope Scope of the function call (global, tab or window).
@ -7716,14 +7731,15 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
XFREE_CLEAR(curwin->w_localdir); XFREE_CLEAR(curwin->w_localdir);
// Overwrite the tab-local CWD for :cd, :tcd. // Overwrite the tab-local CWD for :cd, :tcd.
if (scope >= kCdScopeTab) { if (scope >= kCdScopeTabpage) {
XFREE_CLEAR(curtab->tp_localdir); XFREE_CLEAR(curtab->tp_localdir);
} }
if (scope < kCdScopeGlobal) { if (scope < kCdScopeGlobal) {
char_u *pdir = get_prevdir(scope);
// If still in global directory, set CWD as the global directory. // If still in global directory, set CWD as the global directory.
if (globaldir == NULL && prev_dir != NULL) { if (globaldir == NULL && pdir != NULL) {
globaldir = vim_strsave(prev_dir); globaldir = vim_strsave(pdir);
} }
} }
@ -7736,7 +7752,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
// We are now in the global directory, no need to remember its name. // We are now in the global directory, no need to remember its name.
XFREE_CLEAR(globaldir); XFREE_CLEAR(globaldir);
break; break;
case kCdScopeTab: case kCdScopeTabpage:
curtab->tp_localdir = (char_u *)xstrdup(cwd); curtab->tp_localdir = (char_u *)xstrdup(cwd);
break; break;
case kCdScopeWindow: case kCdScopeWindow:
@ -7749,59 +7765,92 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
shorten_fnames(true); shorten_fnames(true);
if (trigger_dirchanged) { if (trigger_dirchanged) {
do_autocmd_dirchanged(cwd, scope, false); do_autocmd_dirchanged(cwd, scope, kCdCauseManual);
} }
} }
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. /// Change directory function used by :cd/:tcd/:lcd Ex commands and the chdir() function.
/// @param new_dir The directory to change to.
/// @param scope Scope of the function call (global, tab or window).
/// @return true if the directory is successfully changed.
bool changedir_func(char_u *new_dir, CdScope scope)
{
char_u *tofree;
char_u *pdir = NULL;
bool retval = false;
if (new_dir == NULL || allbuf_locked()) {
return false;
}
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
pdir = get_prevdir(scope);
if (pdir == NULL) {
EMSG(_("E186: No previous directory"));
return false;
}
new_dir = pdir;
}
// Free the previous directory
tofree = get_prevdir(scope);
if (os_dirname(NameBuff, MAXPATHL) == OK) {
pdir = vim_strsave(NameBuff);
} else {
pdir = NULL;
}
switch (scope) {
case kCdScopeTabpage:
curtab->tp_prevdir = pdir;
break;
case kCdScopeWindow:
curwin->w_prevdir = pdir;
break;
default:
prev_dir = pdir;
}
#if defined(UNIX)
// On Unix ":cd" means: go to home directory.
if (*new_dir == NUL) {
// Use NameBuff for home directory name.
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
new_dir = NameBuff;
}
#endif
if (vim_chdir(new_dir) == 0) {
bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
post_chdir(scope, dir_differs);
retval = true;
} else {
EMSG(_(e_failed));
}
xfree(tofree);
return retval;
}
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
void ex_cd(exarg_T *eap) void ex_cd(exarg_T *eap)
{ {
char_u *new_dir; char_u *new_dir;
char_u *tofree;
new_dir = eap->arg; new_dir = eap->arg;
#if !defined(UNIX) #if !defined(UNIX) && !defined(VMS)
// for non-UNIX ":cd" means: print current directory // for non-UNIX ":cd" means: print current directory
if (*new_dir == NUL) { if (*new_dir == NUL) {
ex_pwd(NULL); ex_pwd(NULL);
} else } else
#endif #endif
{ {
if (allbuf_locked()) { CdScope scope = kCdScopeGlobal;
return;
}
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
if (prev_dir == NULL) {
EMSG(_("E186: No previous directory"));
return;
}
new_dir = prev_dir;
}
// Save current directory for next ":cd -"
tofree = prev_dir;
if (os_dirname(NameBuff, MAXPATHL) == OK) {
prev_dir = vim_strsave(NameBuff);
} else {
prev_dir = NULL;
}
#if defined(UNIX)
// On Unix ":cd" means: go to home directory.
if (*new_dir == NUL) {
// Use NameBuff for home directory name.
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
new_dir = NameBuff;
}
#endif
CdScope scope = kCdScopeGlobal; // Depends on command invoked
switch (eap->cmdidx) { switch (eap->cmdidx) {
case CMD_tcd: case CMD_tcd:
case CMD_tchdir: case CMD_tchdir:
scope = kCdScopeTab; scope = kCdScopeTabpage;
break; break;
case CMD_lcd: case CMD_lcd:
case CMD_lchdir: case CMD_lchdir:
@ -7810,18 +7859,12 @@ void ex_cd(exarg_T *eap)
default: default:
break; break;
} }
if (changedir_func(new_dir, scope)) {
if (vim_chdir(new_dir)) {
EMSG(_(e_failed));
} else {
post_chdir(scope, true);
// 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);
} }
} }
@ -7834,7 +7877,17 @@ static void ex_pwd(exarg_T *eap)
#ifdef BACKSLASH_IN_FILENAME #ifdef BACKSLASH_IN_FILENAME
slash_adjust(NameBuff); slash_adjust(NameBuff);
#endif #endif
msg(NameBuff); if (p_verbose > 0) {
char *context = "global";
if (curwin->w_localdir != NULL) {
context = "window";
} else if (curtab->tp_localdir != NULL) {
context = "tabpage";
}
smsg("[%s] %s", context, (char *)NameBuff);
} else {
msg(NameBuff);
}
} else { } else {
EMSG(_("E187: Unknown")); EMSG(_("E187: Unknown"));
} }

View File

@ -963,7 +963,7 @@ void ex_mkrc(exarg_T *eap)
*dirnow = NUL; *dirnow = NUL;
} }
if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) {
if (vim_chdirfile((char_u *)fname) == OK) { if (vim_chdirfile((char_u *)fname, kCdCauseOther) == OK) {
shorten_fnames(true); shorten_fnames(true);
} }
} else if (*dirnow != NUL } else if (*dirnow != NUL

View File

@ -54,6 +54,7 @@
#include "nvim/eval.h" #include "nvim/eval.h"
#include "nvim/file_search.h" #include "nvim/file_search.h"
#include "nvim/fileio.h" #include "nvim/fileio.h"
#include "nvim/globals.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/misc1.h" #include "nvim/misc1.h"
@ -1590,7 +1591,7 @@ theend:
return file_name; return file_name;
} }
void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
{ {
static bool recursive = false; static bool recursive = false;
@ -1609,8 +1610,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window)
case kCdScopeGlobal: case kCdScopeGlobal:
snprintf(buf, sizeof(buf), "global"); snprintf(buf, sizeof(buf), "global");
break; break;
case kCdScopeTab: case kCdScopeTabpage:
snprintf(buf, sizeof(buf), "tab"); snprintf(buf, sizeof(buf), "tabpage");
break; break;
case kCdScopeWindow: case kCdScopeWindow:
snprintf(buf, sizeof(buf), "window"); snprintf(buf, sizeof(buf), "window");
@ -1620,11 +1621,30 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window)
abort(); abort();
} }
#ifdef BACKSLASH_IN_FILENAME
char new_dir_buf[MAXPATHL];
STRCPY(new_dir_buf, new_dir);
slash_adjust(new_dir_buf);
new_dir = new_dir_buf;
#endif
tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614 tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
tv_dict_add_str(dict, S_LEN("cwd"), new_dir); tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
tv_dict_add_bool(dict, S_LEN("changed_window"), changed_window); tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict); tv_dict_set_keys_readonly(dict);
switch (cause) {
case kCdCauseManual:
case kCdCauseWindow:
break;
case kCdCauseAuto:
snprintf(buf, sizeof(buf), "auto");
break;
case kCdCauseOther:
// Should never happen.
abort();
}
apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
curbuf); curbuf);
@ -1636,7 +1656,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window)
/// 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
int vim_chdirfile(char_u *fname) int vim_chdirfile(char_u *fname, CdCause cause)
{ {
char dir[MAXPATHL]; char dir[MAXPATHL];
@ -1647,17 +1667,14 @@ int vim_chdirfile(char_u *fname)
NameBuff[0] = NUL; NameBuff[0] = NUL;
} }
if (os_chdir(dir) != 0) { if (os_chdir(dir) == 0) {
if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) {
do_autocmd_dirchanged(dir, kCdScopeWindow, cause);
}
} else {
return FAIL; return FAIL;
} }
#ifdef BACKSLASH_IN_FILENAME
slash_adjust((char_u *)dir);
#endif
if (!strequal(dir, (char *)NameBuff)) {
do_autocmd_dirchanged(dir, kCdScopeWindow, false);
}
return OK; return OK;
} }

View File

@ -1034,14 +1034,22 @@ typedef enum {
/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead. /// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
typedef enum { typedef enum {
kCdScopeInvalid = -1, kCdScopeInvalid = -1,
kCdScopeWindow, ///< Affects one window. kCdScopeWindow, ///< Affects one window.
kCdScopeTab, ///< Affects one tab page. kCdScopeTabpage, ///< Affects one tab page.
kCdScopeGlobal, ///< Affects the entire Nvim instance. kCdScopeGlobal, ///< Affects the entire Nvim instance.
} CdScope; } CdScope;
#define MIN_CD_SCOPE kCdScopeWindow #define MIN_CD_SCOPE kCdScopeWindow
#define MAX_CD_SCOPE kCdScopeGlobal #define MAX_CD_SCOPE kCdScopeGlobal
/// What caused the current directory to change.
typedef enum {
kCdCauseOther = -1,
kCdCauseManual, ///< Using `:cd`, `:tcd`, `:lcd` or `chdir()`.
kCdCauseWindow, ///< Switching to another window.
kCdCauseAuto, ///< On 'autochdir'.
} CdCause;
// Only filled for Win32. // Only filled for Win32.
EXTERN char windowsVersion[20] INIT(= { 0 }); EXTERN char windowsVersion[20] INIT(= { 0 });

View File

@ -8,13 +8,21 @@ func Test_set_filename()
let cwd = getcwd() let cwd = getcwd()
call test_autochdir() call test_autochdir()
set acd set acd
let s:li = []
autocmd DirChanged auto call add(s:li, "autocd")
autocmd DirChanged auto call add(s:li, expand("<afile>"))
new new
w samples/Xtest w samples/Xtest
call assert_equal("Xtest", expand('%')) call assert_equal("Xtest", expand('%'))
call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', '')) call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', ''))
call assert_equal(["autocd", getcwd()], s:li)
bwipe! bwipe!
au! DirChanged
set noacd set noacd
exe 'cd ' . cwd call chdir(cwd)
call delete('samples/Xtest') call delete('samples/Xtest')
endfunc endfunc

View File

@ -42,9 +42,7 @@ if has('timers')
endfunc endfunc
func Test_cursorhold_insert_with_timer_interrupt() func Test_cursorhold_insert_with_timer_interrupt()
if !has('job') CheckFeature job
return
endif
" Need to move the cursor. " Need to move the cursor.
call feedkeys("ggG", "xt") call feedkeys("ggG", "xt")
@ -551,9 +549,7 @@ endfunc
func Test_OptionSet() func Test_OptionSet()
CheckFunction test_override CheckFunction test_override
if !has("eval") || !exists("+autochdir") CheckOption autochdir
return
endif
call test_override('starting', 1) call test_override('starting', 1)
set nocp set nocp
@ -1328,6 +1324,71 @@ func Test_autocommand_all_events()
call assert_fails('au * x bwipe', 'E1155:') call assert_fails('au * x bwipe', 'E1155:')
endfunc endfunc
function s:Before_test_dirchanged()
augroup test_dirchanged
autocmd!
augroup END
let s:li = []
let s:dir_this = getcwd()
let s:dir_foo = s:dir_this . '/Xfoo'
call mkdir(s:dir_foo)
let s:dir_bar = s:dir_this . '/Xbar'
call mkdir(s:dir_bar)
endfunc
function s:After_test_dirchanged()
call chdir(s:dir_this)
call delete(s:dir_foo, 'd')
call delete(s:dir_bar, 'd')
augroup test_dirchanged
autocmd!
augroup END
endfunc
function Test_dirchanged_global()
call s:Before_test_dirchanged()
autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
call chdir(s:dir_foo)
call assert_equal(["cd:", s:dir_foo], s:li)
call chdir(s:dir_foo)
call assert_equal(["cd:", s:dir_foo], s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
call assert_equal(["cd:", s:dir_foo], s:li)
call s:After_test_dirchanged()
endfunc
function Test_dirchanged_local()
call s:Before_test_dirchanged()
autocmd test_dirchanged DirChanged window call add(s:li, "lcd:")
autocmd test_dirchanged DirChanged window call add(s:li, expand("<afile>"))
call chdir(s:dir_foo)
call assert_equal([], s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
call assert_equal(["lcd:", s:dir_bar], s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
call assert_equal(["lcd:", s:dir_bar], s:li)
call s:After_test_dirchanged()
endfunc
function Test_dirchanged_auto()
CheckFunction test_autochdir
CheckOption autochdir
call s:Before_test_dirchanged()
call test_autochdir()
autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
set acd
cd ..
call assert_equal([], s:li)
exe 'edit ' . s:dir_foo . '/Xfile'
call assert_equal(s:dir_foo, getcwd())
call assert_equal(["auto:", s:dir_foo], s:li)
set noacd
bwipe!
call s:After_test_dirchanged()
endfunc
" Test TextChangedI and TextChangedP " Test TextChangedI and TextChangedP
" See test/functional/viml/completion_spec.lua' " See test/functional/viml/completion_spec.lua'
func Test_ChangedP() func Test_ChangedP()

View File

@ -12,7 +12,7 @@ func Test_cd_up_and_down()
let path = getcwd() let path = getcwd()
cd .. cd ..
call assert_notequal(path, getcwd()) call assert_notequal(path, getcwd())
exe 'cd ' . path exe 'cd ' .. fnameescape(path)
call assert_equal(path, getcwd()) call assert_equal(path, getcwd())
endfunc endfunc
@ -23,7 +23,7 @@ func Test_cd_no_arg()
cd cd
call assert_equal($HOME, getcwd()) call assert_equal($HOME, getcwd())
call assert_notequal(path, getcwd()) call assert_notequal(path, getcwd())
exe 'cd ' . path exe 'cd ' .. fnameescape(path)
call assert_equal(path, getcwd()) call assert_equal(path, getcwd())
else else
" Test that cd without argument echoes cwd on non-Unix systems. " Test that cd without argument echoes cwd on non-Unix systems.
@ -43,6 +43,20 @@ func Test_cd_minus()
call assert_equal(path_dotdot, getcwd()) call assert_equal(path_dotdot, getcwd())
cd - cd -
call assert_equal(path, getcwd()) call assert_equal(path, getcwd())
" Test for :cd - without a previous directory
let lines =<< trim [SCRIPT]
call assert_fails('cd -', 'E186:')
call assert_fails('call chdir("-")', 'E186:')
call writefile(v:errors, 'Xresult')
qall!
[SCRIPT]
call writefile(lines, 'Xscript')
if RunVim([], [], '--clean -S Xscript')
call assert_equal([], readfile('Xresult'))
endif
call delete('Xscript')
call delete('Xresult')
endfunc endfunc
func Test_cd_with_cpo_chdir() func Test_cd_with_cpo_chdir()
@ -61,7 +75,7 @@ func Test_cd_with_cpo_chdir()
" :cd should succeed when buffer has been written. " :cd should succeed when buffer has been written.
w! w!
exe 'cd ' . path exe 'cd ' .. fnameescape(path)
call assert_equal(path, getcwd()) call assert_equal(path, getcwd())
call delete('Xfoo') call delete('Xfoo')
@ -69,6 +83,124 @@ func Test_cd_with_cpo_chdir()
bw! bw!
endfunc endfunc
" Test for chdir()
func Test_chdir_func()
let topdir = getcwd()
call mkdir('Xdir/y/z', 'p')
" Create a few tabpages and windows with different directories
new
cd Xdir
tabnew
tcd y
below new
below new
lcd z
tabfirst
call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd')))
call chdir('..')
call assert_equal('y', fnamemodify(getcwd(1, 2), ':t'))
call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
tabnext | wincmd t
call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd')))
call chdir('..')
call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
3wincmd w
call assert_match('^\[window\] .*/z$', trim(execute('verbose pwd')))
call chdir('..')
call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
call assert_equal('y', fnamemodify(getcwd(3, 2), ':t'))
call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
" Error case
call assert_fails("call chdir('dir-abcd')", 'E472:')
silent! let d = chdir("dir_abcd")
call assert_equal("", d)
" Should not crash
call chdir(d)
only | tabonly
call chdir(topdir)
call delete('Xdir', 'rf')
endfunc
" Test for changing to the previous directory '-'
func Test_prev_dir()
let topdir = getcwd()
call mkdir('Xdir/a/b/c', 'p')
" Create a few tabpages and windows with different directories
new | only
tabnew | new
tabnew
tabfirst
cd Xdir
tabnext | wincmd t
tcd a
wincmd w
lcd b
tabnext
tcd a/b/c
" Change to the previous directory twice in all the windows.
tabfirst
cd - | cd -
tabnext | wincmd t
tcd - | tcd -
wincmd w
lcd - | lcd -
tabnext
tcd - | tcd -
" Check the directory of all the windows
tabfirst
call assert_equal('Xdir', fnamemodify(getcwd(), ':t'))
tabnext | wincmd t
call assert_equal('a', fnamemodify(getcwd(), ':t'))
wincmd w
call assert_equal('b', fnamemodify(getcwd(), ':t'))
tabnext
call assert_equal('c', fnamemodify(getcwd(), ':t'))
" Change to the previous directory using chdir()
tabfirst
call chdir("-") | call chdir("-")
tabnext | wincmd t
call chdir("-") | call chdir("-")
wincmd w
call chdir("-") | call chdir("-")
tabnext
call chdir("-") | call chdir("-")
" Check the directory of all the windows
tabfirst
call assert_equal('Xdir', fnamemodify(getcwd(), ':t'))
tabnext | wincmd t
call assert_equal('a', fnamemodify(getcwd(), ':t'))
wincmd w
call assert_equal('b', fnamemodify(getcwd(), ':t'))
tabnext
call assert_equal('c', fnamemodify(getcwd(), ':t'))
only | tabonly
call chdir(topdir)
call delete('Xdir', 'rf')
endfunc
func Test_lcd_split()
let curdir = getcwd()
lcd ..
split
lcd -
call assert_equal(curdir, getcwd())
quit!
endfunc
func Test_cd_from_non_existing_dir() func Test_cd_from_non_existing_dir()
CheckNotMSWindows CheckNotMSWindows

View File

@ -36,7 +36,7 @@ func Test_find_complete()
" We shouldn't find any file till this point " We shouldn't find any file till this point
call mkdir('in/path', 'p') call mkdir('in/path', 'p')
exe 'cd ' . cwd call chdir(cwd)
call writefile(['Holy Grail'], 'Xfind/file.txt') call writefile(['Holy Grail'], 'Xfind/file.txt')
call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt') call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt')
call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt') call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt')
@ -133,12 +133,12 @@ func Test_find_complete()
call assert_equal('Voyager 2', getline(1)) call assert_equal('Voyager 2', getline(1))
" Check for correct handling of shorten_fname()'s behavior on windows " Check for correct handling of shorten_fname()'s behavior on windows
exec "cd " . cwd . "/Xfind/in" call chdir(cwd .. "/Xfind/in")
call feedkeys(":find file\t\n", "xt") call feedkeys(":find file\t\n", "xt")
call assert_equal('Jimmy Hoffa', getline(1)) call assert_equal('Jimmy Hoffa', getline(1))
" Test for relative to current buffer 'path' item " Test for relative to current buffer 'path' item
exec "cd " . cwd . "/Xfind/" call chdir(cwd . "/Xfind/")
set path=./path set path=./path
" Open the file where Jimmy Hoffa is found " Open the file where Jimmy Hoffa is found
e in/file.txt e in/file.txt
@ -157,7 +157,7 @@ func Test_find_complete()
call assert_equal('Another Holy Grail', getline(1)) call assert_equal('Another Holy Grail', getline(1))
enew | only enew | only
exe 'cd ' . cwd call chdir(cwd)
call delete('Xfind', 'rf') call delete('Xfind', 'rf')
set path& set path&
endfunc endfunc

View File

@ -113,7 +113,7 @@ func Test_findfile()
call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2)) call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2))
bwipe! bwipe!
exe 'cd ' . save_dir call chdir(save_dir)
call CleanFiles() call CleanFiles()
let &path = save_path let &path = save_path
let &shellslash = save_shellslash let &shellslash = save_shellslash
@ -171,7 +171,7 @@ func Test_finddir()
call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2)) call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2))
call assert_equal('Xdir3', finddir('Xdir3', '**;', 1)) call assert_equal('Xdir3', finddir('Xdir3', '**;', 1))
exe 'cd ' . save_dir call chdir(save_dir)
call CleanFiles() call CleanFiles()
let &path = save_path let &path = save_path
let &shellslash = save_shellslash let &shellslash = save_shellslash

View File

@ -46,7 +46,7 @@ endfunction
let g:cwd=getcwd() let g:cwd=getcwd()
function TearDown() function TearDown()
q q
exec "cd " . g:cwd call chdir(g:cwd)
call delete("Xtopdir", "rf") call delete("Xtopdir", "rf")
endfunction endfunction

View File

@ -23,6 +23,7 @@
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/garray.h" #include "nvim/garray.h"
#include "nvim/getchar.h" #include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h" #include "nvim/hashtab.h"
#include "nvim/main.h" #include "nvim/main.h"
#include "nvim/mark.h" #include "nvim/mark.h"
@ -1462,6 +1463,8 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
} }
newp->w_localdir = (oldp->w_localdir == NULL) newp->w_localdir = (oldp->w_localdir == NULL)
? NULL : vim_strsave(oldp->w_localdir); ? NULL : vim_strsave(oldp->w_localdir);
newp->w_prevdir = (oldp->w_prevdir == NULL)
? NULL : vim_strsave(oldp->w_prevdir);
// copy tagstack and folds // copy tagstack and folds
for (i = 0; i < oldp->w_tagstacklen; i++) { for (i = 0; i < oldp->w_tagstacklen; i++) {
@ -3732,6 +3735,7 @@ void free_tabpage(tabpage_T *tp)
} }
xfree(tp->tp_localdir); xfree(tp->tp_localdir);
xfree(tp->tp_prevdir);
xfree(tp); xfree(tp);
} }
@ -4540,9 +4544,9 @@ static void win_enter_ext(win_T *const wp, const int flags)
} }
} }
if (os_chdir(new_dir) == 0) { if (os_chdir(new_dir) == 0) {
if (!p_acd && !strequal(new_dir, cwd)) { if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) {
do_autocmd_dirchanged(new_dir, curwin->w_localdir do_autocmd_dirchanged(new_dir, curwin->w_localdir
? kCdScopeWindow : kCdScopeTab, true); ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow);
} }
shorten_fnames(true); shorten_fnames(true);
} }
@ -4550,8 +4554,8 @@ static void win_enter_ext(win_T *const wp, const int flags)
// Window doesn't have a local directory and we are not in the global // Window doesn't have a local directory and we are not in the global
// directory: Change to the global directory. // directory: Change to the global directory.
if (os_chdir((char *)globaldir) == 0) { if (os_chdir((char *)globaldir) == 0) {
if (!p_acd && !strequal((char *)globaldir, cwd)) { if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) {
do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, true); do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow);
} }
} }
XFREE_CLEAR(globaldir); XFREE_CLEAR(globaldir);
@ -4770,6 +4774,7 @@ static void win_free(win_T *wp, tabpage_T *tp)
} }
xfree(wp->w_localdir); xfree(wp->w_localdir);
xfree(wp->w_prevdir);
/* Remove the window from the b_wininfo lists, it may happen that the /* Remove the window from the b_wininfo lists, it may happen that the
* freed memory is re-used for another window. */ * freed memory is re-used for another window. */

View File

@ -6,6 +6,7 @@ local command = h.command
local eq = h.eq local eq = h.eq
local eval = h.eval local eval = h.eval
local request = h.request local request = h.request
local iswin = h.iswin
describe('autocmd DirChanged', function() describe('autocmd DirChanged', function()
local curdir = string.gsub(lfs.currentdir(), '\\', '/') local curdir = string.gsub(lfs.currentdir(), '\\', '/')
@ -14,6 +15,11 @@ describe('autocmd DirChanged', function()
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2', curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',
curdir .. '/Xtest-functional-autocmd-dirchanged.dir3', curdir .. '/Xtest-functional-autocmd-dirchanged.dir3',
} }
local win_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) 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) teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end)
@ -27,17 +33,20 @@ describe('autocmd DirChanged', function()
command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]]) command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
end) end)
it('sets v:event', function() it('sets v:event and <amatch>', function()
command('lcd '..dirs[1]) command('lcd '..dirs[1])
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
eq('window', eval('g:amatch'))
eq(1, eval('g:cdcount')) eq(1, eval('g:cdcount'))
command('tcd '..dirs[2]) command('tcd '..dirs[2])
eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
eq('tabpage', eval('g:amatch'))
eq(2, eval('g:cdcount')) eq(2, eval('g:cdcount'))
command('cd '..dirs[3]) command('cd '..dirs[3])
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
eq('global', eval('g:amatch'))
eq(3, eval('g:cdcount')) eq(3, eval('g:cdcount'))
end) end)
@ -63,17 +72,6 @@ describe('autocmd DirChanged', function()
eq(dirs[3], eval('getcwd()')) eq(dirs[3], eval('getcwd()'))
end) end)
it('sets <amatch> to CWD "scope"', function()
command('lcd '..dirs[1])
eq('window', eval('g:amatch'))
command('tcd '..dirs[2])
eq('tab', eval('g:amatch'))
command('cd '..dirs[3])
eq('global', eval('g:amatch'))
end)
it('does not trigger if :cd fails', function() it('does not trigger if :cd fails', function()
command('let g:ev = {}') command('let g:ev = {}')
@ -106,13 +104,79 @@ describe('autocmd DirChanged', function()
command('split '..dirs[1]..'/foo') command('split '..dirs[1]..'/foo')
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
eq('auto', eval('g:amatch'))
command('split '..dirs[2]..'/bar') command('split '..dirs[2]..'/bar')
eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev')) eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev'))
eq('auto', eval('g:amatch'))
eq(2, eval('g:cdcount')) eq(2, eval('g:cdcount'))
end) end)
it('does not trigger if directory has not changed', function()
command('lcd '..dirs[1])
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
eq('window', eval('g:amatch'))
eq(1, eval('g:cdcount'))
command('let g:ev = {}')
command('lcd '..dirs[1])
eq({}, eval('g:ev'))
eq(1, eval('g:cdcount'))
if iswin() then
command('lcd '..win_dirs[1])
eq({}, eval('g:ev'))
eq(1, eval('g:cdcount'))
end
command('tcd '..dirs[2])
eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
eq('tabpage', eval('g:amatch'))
eq(2, eval('g:cdcount'))
command('let g:ev = {}')
command('tcd '..dirs[2])
eq({}, eval('g:ev'))
eq(2, eval('g:cdcount'))
if iswin() then
command('tcd '..win_dirs[2])
eq({}, eval('g:ev'))
eq(2, eval('g:cdcount'))
end
command('cd '..dirs[3])
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
eq('global', eval('g:amatch'))
eq(3, eval('g:cdcount'))
command('let g:ev = {}')
command('cd '..dirs[3])
eq({}, eval('g:ev'))
eq(3, eval('g:cdcount'))
if iswin() then
command('cd '..win_dirs[3])
eq({}, eval('g:ev'))
eq(3, eval('g:cdcount'))
end
command('set autochdir')
command('split '..dirs[1]..'/foo')
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
eq('auto', eval('g:amatch'))
eq(4, eval('g:cdcount'))
command('let g:ev = {}')
command('split '..dirs[1]..'/bar')
eq({}, eval('g:ev'))
eq(4, eval('g:cdcount'))
if iswin() then
command('split '..win_dirs[1]..'/baz')
eq({}, eval('g:ev'))
eq(4, eval('g:cdcount'))
end
end)
it("is triggered by switching to win/tab with different CWD #6054", function() it("is triggered by switching to win/tab with different CWD #6054", function()
command('lcd '..dirs[3]) -- window 3 command('lcd '..dirs[3]) -- window 3
command('split '..dirs[2]..'/foo') -- window 2 command('split '..dirs[2]..'/foo') -- window 2
@ -122,6 +186,7 @@ describe('autocmd DirChanged', function()
command('2wincmd w') -- window 2 command('2wincmd w') -- window 2
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
eq('window', eval('g:amatch'))
eq(4, eval('g:cdcount')) eq(4, eval('g:cdcount'))
command('tabnew') -- tab 2 (tab-local CWD) command('tabnew') -- tab 2 (tab-local CWD)
@ -129,8 +194,10 @@ describe('autocmd DirChanged', function()
command('tcd '..dirs[3]) command('tcd '..dirs[3])
command('tabnext') -- tab 1 (no tab-local CWD) command('tabnext') -- tab 1 (no tab-local CWD)
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
eq('window', eval('g:amatch'))
command('tabnext') -- tab 2 command('tabnext') -- tab 2
eq({cwd=dirs[3], scope='tab', changed_window=true}, eval('g:ev')) eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev'))
eq('tabpage', eval('g:amatch'))
eq(7, eval('g:cdcount')) eq(7, eval('g:cdcount'))
command('tabnext') -- tab 1 command('tabnext') -- tab 1
@ -138,6 +205,31 @@ describe('autocmd DirChanged', function()
eq(9, eval('g:cdcount')) eq(9, eval('g:cdcount'))
command('tabnext') -- tab 2 (has the *same* CWD) command('tabnext') -- tab 2 (has the *same* CWD)
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
if iswin() then
command('tabnew') -- tab 3
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..win_dirs[3])
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 2
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 1
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('lcd '..win_dirs[3]) -- window 3
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 2
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 3
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
end
end) end)
it('is triggered by nvim_set_current_dir()', function() it('is triggered by nvim_set_current_dir()', function()