mirror of
https://github.com/neovim/neovim.git
synced 2024-12-29 14:41:06 -07:00
Merge #3229 ':tcd'
This commit is contained in:
commit
5ebffaae4e
@ -1216,12 +1216,18 @@ use has("browsefilter"): >
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
7. The current directory *current-directory*
|
7. The current directory *current-directory*
|
||||||
|
|
||||||
You may use the |:cd| and |:lcd| commands to change to another directory, so
|
You can use |:cd|, |:tcd| and |:lcd| to change to another directory, so you
|
||||||
you will not have to type that directory name in front of the file names. It
|
will not have to type that directory name in front of the file names. It also
|
||||||
also makes a difference for executing external commands, e.g. ":!ls".
|
makes a difference for executing external commands, e.g. ":!ls" or ":te ls".
|
||||||
|
|
||||||
Changing directory fails when the current buffer is modified, the '.' flag is
|
There are three current-directory "scopes": global, tab and window. The
|
||||||
present in 'cpoptions' and "!" is not used in the command.
|
window-local working directory takes precedence over the tab-local
|
||||||
|
working directory, which in turn takes precedence over the global
|
||||||
|
working directory. If a local working directory (tab or window) does not
|
||||||
|
exist, the next-higher scope in the hierarchy applies.
|
||||||
|
|
||||||
|
Commands for changing the working directory can be suffixed with a bang "!"
|
||||||
|
(e.g. |:cd!|) which is ignored, for compatibility with Vim.
|
||||||
|
|
||||||
*:cd* *E747* *E472*
|
*:cd* *E747* *E472*
|
||||||
:cd[!] On non-Unix systems: Print the current directory
|
:cd[!] On non-Unix systems: Print the current directory
|
||||||
@ -1246,29 +1252,50 @@ present in 'cpoptions' and "!" is not used in the command.
|
|||||||
*:chd* *:chdir*
|
*:chd* *:chdir*
|
||||||
:chd[ir][!] [path] Same as |:cd|.
|
:chd[ir][!] [path] Same as |:cd|.
|
||||||
|
|
||||||
|
*:tc* *:tcd* *E5000* *E5001* *E5002*
|
||||||
|
:tc[d][!] {path} Like |:cd|, but set the current directory for the
|
||||||
|
current tab and window. The current directory for
|
||||||
|
other tabs and windows is not changed.
|
||||||
|
|
||||||
|
*:tcd-*
|
||||||
|
:tcd[!] - Change to the previous current directory (before the
|
||||||
|
previous ":tcd {path}" command).
|
||||||
|
|
||||||
|
*:tch* *:tchdir*
|
||||||
|
:tch[dir][!] Same as |:tcd|.
|
||||||
|
|
||||||
*:lc* *:lcd*
|
*:lc* *:lcd*
|
||||||
:lc[d][!] {path} Like |:cd|, but only set the current directory for the
|
:lc[d][!] {path} Like |:cd|, but only set the current directory for the
|
||||||
current window. The current directory for other
|
current window. The current directory for other
|
||||||
windows is not changed.
|
windows or any tabs is not changed.
|
||||||
|
|
||||||
*:lch* *:lchdir*
|
*:lch* *:lchdir*
|
||||||
:lch[dir][!] Same as |:lcd|.
|
:lch[dir][!] Same as |:lcd|.
|
||||||
|
|
||||||
|
*:lcd-*
|
||||||
|
:lcd[!] - Change to the previous current directory (before the
|
||||||
|
previous ":tcd {path}" command).
|
||||||
|
|
||||||
*: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()|.
|
||||||
|
|
||||||
So long as no |:lcd| command has been used, all windows share the same current
|
So long as no |:tcd| or |:lcd| command has been used, all windows share the
|
||||||
directory. Using a command to jump to another window doesn't change anything
|
same "current directory". Using a command to jump to another window doesn't
|
||||||
for the current directory.
|
change anything for the current directory.
|
||||||
When a |:lcd| command has been used for a window, the specified directory
|
|
||||||
becomes the current directory for that window. Windows where the |:lcd|
|
When |:lcd| has been used for a window, the specified directory becomes the
|
||||||
command has not been used stick to the global current directory. When jumping
|
current directory for that window. Windows where the |:lcd| command has not
|
||||||
to another window the current directory will become the last specified local
|
been used stick to the global or tab-local directory. When jumping to another
|
||||||
current directory. If none was specified, the global current directory is
|
window the current directory will become the last specified local current
|
||||||
used.
|
directory. If none was specified, the global or tab-local directory is used.
|
||||||
When a |:cd| command is used, the current window will lose his local current
|
|
||||||
directory and will use the global current directory from now on.
|
When changing tabs the same behaviour applies. If the current tab has no
|
||||||
|
local working directory the global working directory is used. When a |:cd|
|
||||||
|
command is used, the current window and tab will lose their local current
|
||||||
|
directories and will use the global current directory from now on. When
|
||||||
|
a |:tcd| command is used, only the current window will lose its local working
|
||||||
|
directory.
|
||||||
|
|
||||||
After using |:cd| the full path name will be used for reading and writing
|
After using |:cd| the full path name will be used for reading and writing
|
||||||
files. On some networked file systems this may cause problems. The result of
|
files. On some networked file systems this may cause problems. The result of
|
||||||
@ -1317,9 +1344,7 @@ There are a few things to remember when editing binary files:
|
|||||||
9. Encryption *encryption*
|
9. Encryption *encryption*
|
||||||
|
|
||||||
*:X* *E817* *E818* *E819* *E820*
|
*:X* *E817* *E818* *E819* *E820*
|
||||||
Support for editing encrypted files has been removed, but may be added back in
|
Support for editing encrypted files has been removed.
|
||||||
the future. See the following discussions for more information:
|
|
||||||
|
|
||||||
https://github.com/neovim/neovim/issues/694
|
https://github.com/neovim/neovim/issues/694
|
||||||
https://github.com/neovim/neovim/issues/701
|
https://github.com/neovim/neovim/issues/701
|
||||||
|
|
||||||
|
@ -1886,7 +1886,7 @@ getcmdpos() Number return cursor position in command-line
|
|||||||
getcmdtype() String return current command-line type
|
getcmdtype() String return current command-line type
|
||||||
getcmdwintype() String return current command-line window type
|
getcmdwintype() String return current command-line window type
|
||||||
getcurpos() List position of the cursor
|
getcurpos() List position of the cursor
|
||||||
getcwd() String the current working directory
|
getcwd( [{scope}]) String the current working directory
|
||||||
getfontname( [{name}]) String name of font being used
|
getfontname( [{name}]) String name of font being used
|
||||||
getfperm( {fname}) String file permissions of file {fname}
|
getfperm( {fname}) String file permissions of file {fname}
|
||||||
getfsize( {fname}) Number size in bytes of file {fname}
|
getfsize( {fname}) Number size in bytes of file {fname}
|
||||||
@ -3559,9 +3559,18 @@ getcurpos() Get the position of the cursor. This is like getpos('.'), but
|
|||||||
MoveTheCursorAround
|
MoveTheCursorAround
|
||||||
call setpos('.', save_cursor)
|
call setpos('.', save_cursor)
|
||||||
<
|
<
|
||||||
*getcwd()*
|
getcwd([{window}[, {tab}]]) *getcwd()*
|
||||||
getcwd() The result is a String, which is the name of the current
|
With no arguments the result is a String, which is the name of
|
||||||
working directory.
|
the current effective working directory. With {window} or
|
||||||
|
{tab} the working directory of that scope is returned.
|
||||||
|
Tabs and windows are identified by their respective numbers,
|
||||||
|
0 means current tab or window. Missing argument implies 0.
|
||||||
|
Thus the following are equivalent: >
|
||||||
|
getcwd()
|
||||||
|
getcwd(0)
|
||||||
|
getcwd(0, 0)
|
||||||
|
< If {window} is -1 it is ignored, only the tab is resolved.
|
||||||
|
|
||||||
|
|
||||||
getfsize({fname}) *getfsize()*
|
getfsize({fname}) *getfsize()*
|
||||||
The result is a Number, which is the size in bytes of the
|
The result is a Number, which is the size in bytes of the
|
||||||
@ -3896,9 +3905,18 @@ has_key({dict}, {key}) *has_key()*
|
|||||||
The result is a Number, which is 1 if |Dictionary| {dict} has
|
The result is a Number, which is 1 if |Dictionary| {dict} has
|
||||||
an entry with key {key}. Zero otherwise.
|
an entry with key {key}. Zero otherwise.
|
||||||
|
|
||||||
haslocaldir() *haslocaldir()*
|
haslocaldir([{window}[, {tab}]]) *haslocaldir()*
|
||||||
The result is a Number, which is 1 when the current
|
The result is a Number, which is 1 when the specified tabpage
|
||||||
window has set a local path via |:lcd|, and 0 otherwise.
|
or window has a local path set via |:lcd| or |:tcd|, and
|
||||||
|
0 otherwise.
|
||||||
|
|
||||||
|
Tabs and windows are identified by their respective numbers,
|
||||||
|
0 means current tab or window. Missing argument implies 0.
|
||||||
|
Thus the following are equivalent: >
|
||||||
|
haslocaldir()
|
||||||
|
haslocaldir(0)
|
||||||
|
haslocaldir(0, 0)
|
||||||
|
< If {window} is -1 it is ignored, only the tab is resolved.
|
||||||
|
|
||||||
hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()*
|
hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()*
|
||||||
The result is a Number, which is 1 if there is a mapping that
|
The result is a Number, which is 1 if there is a mapping that
|
||||||
|
@ -291,7 +291,7 @@ void vim_change_directory(String dir, Error *err)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
post_chdir(false);
|
post_chdir(kCdScopeGlobal);
|
||||||
try_end(err);
|
try_end(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,10 +816,12 @@ struct tabpage_S {
|
|||||||
was set */
|
was set */
|
||||||
diff_T *tp_first_diff;
|
diff_T *tp_first_diff;
|
||||||
buf_T *(tp_diffbuf[DB_COUNT]);
|
buf_T *(tp_diffbuf[DB_COUNT]);
|
||||||
int tp_diff_invalid; /* list of diffs is outdated */
|
int tp_diff_invalid; ///< list of diffs is outdated */
|
||||||
frame_T *(tp_snapshot[SNAP_COUNT]); /* window layout snapshots */
|
frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots
|
||||||
dictitem_T tp_winvar; /* variable for "t:" Dictionary */
|
dictitem_T 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 *localdir; ///< Absolute path of local directory or
|
||||||
|
///< NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
231
src/nvim/eval.c
231
src/nvim/eval.c
@ -6749,7 +6749,7 @@ static struct fst {
|
|||||||
{ "getcmdtype", 0, 0, f_getcmdtype },
|
{ "getcmdtype", 0, 0, f_getcmdtype },
|
||||||
{ "getcmdwintype", 0, 0, f_getcmdwintype },
|
{ "getcmdwintype", 0, 0, f_getcmdwintype },
|
||||||
{ "getcurpos", 0, 0, f_getcurpos },
|
{ "getcurpos", 0, 0, f_getcurpos },
|
||||||
{ "getcwd", 0, 0, f_getcwd },
|
{ "getcwd", 0, 2, f_getcwd },
|
||||||
{ "getfontname", 0, 1, f_getfontname },
|
{ "getfontname", 0, 1, f_getfontname },
|
||||||
{ "getfperm", 1, 1, f_getfperm },
|
{ "getfperm", 1, 1, f_getfperm },
|
||||||
{ "getfsize", 1, 1, f_getfsize },
|
{ "getfsize", 1, 1, f_getfsize },
|
||||||
@ -6773,7 +6773,7 @@ static struct fst {
|
|||||||
{ "globpath", 2, 5, f_globpath },
|
{ "globpath", 2, 5, f_globpath },
|
||||||
{ "has", 1, 1, f_has },
|
{ "has", 1, 1, f_has },
|
||||||
{ "has_key", 2, 2, f_has_key },
|
{ "has_key", 2, 2, f_has_key },
|
||||||
{ "haslocaldir", 0, 0, f_haslocaldir },
|
{ "haslocaldir", 0, 2, f_haslocaldir },
|
||||||
{ "hasmapto", 1, 3, f_hasmapto },
|
{ "hasmapto", 1, 3, f_hasmapto },
|
||||||
{ "highlightID", 1, 1, f_hlID }, // obsolete
|
{ "highlightID", 1, 1, f_hlID }, // obsolete
|
||||||
{ "highlight_exists", 1, 1, f_hlexists }, // obsolete
|
{ "highlight_exists", 1, 1, f_hlexists }, // obsolete
|
||||||
@ -9758,22 +9758,143 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv)
|
|||||||
rettv->vval.v_string[0] = cmdwin_type;
|
rettv->vval.v_string[0] = cmdwin_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// `getcwd([{win}[, {tab}]])` function
|
||||||
* "getcwd()" function
|
///
|
||||||
*/
|
/// Every scope not specified implies the currently selected scope object.
|
||||||
|
///
|
||||||
|
/// @pre The arguments must be of type number.
|
||||||
|
/// @pre There may not be more than two arguments.
|
||||||
|
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
||||||
|
///
|
||||||
|
/// @post The return value will be a string.
|
||||||
static void f_getcwd(typval_T *argvars, typval_T *rettv)
|
static void f_getcwd(typval_T *argvars, typval_T *rettv)
|
||||||
{
|
{
|
||||||
char_u *cwd;
|
// Possible scope of working directory to return.
|
||||||
|
CdScope scope = kCdScopeWindow;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
int scope_number[] = {
|
||||||
|
[kCdScopeWindow] = 0, // Number of window to look at.
|
||||||
|
[kCdScopeTab ] = 0, // Number of tab to look at.
|
||||||
|
};
|
||||||
|
|
||||||
|
char_u *cwd = NULL; // Current working directory to print
|
||||||
|
char_u *from = NULL; // The original string to copy
|
||||||
|
|
||||||
|
tabpage_T *tp = curtab; // The tabpage to look at.
|
||||||
|
win_T *win = curwin; // The window to look at.
|
||||||
|
|
||||||
rettv->v_type = VAR_STRING;
|
rettv->v_type = VAR_STRING;
|
||||||
rettv->vval.v_string = NULL;
|
rettv->vval.v_string = NULL;
|
||||||
|
|
||||||
|
// Pre-conditions and scope extraction together
|
||||||
|
for (int i = 0; i < kCdScopeGlobal; i++) {
|
||||||
|
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (argvars[i].v_type != VAR_NUMBER) {
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scope_number[i] = argvars[i].vval.v_number;
|
||||||
|
// The scope is the current iteration step.
|
||||||
|
scope = i;
|
||||||
|
|
||||||
|
if (scope_number[i] < -1) {
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and initialize the string to return.
|
||||||
cwd = xmalloc(MAXPATHL);
|
cwd = xmalloc(MAXPATHL);
|
||||||
if (os_dirname(cwd, MAXPATHL) != FAIL) {
|
|
||||||
|
// Get the scope and numbers from the arguments
|
||||||
|
for (int i = 0; i < MAX_CD_SCOPE; i++) {
|
||||||
|
// If there is no argument there are no more scopes after it, break out.
|
||||||
|
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scope_number[i] = argvars[i].vval.v_number;
|
||||||
|
// The scope is the current iteration step.
|
||||||
|
scope = i;
|
||||||
|
// It is an error for the scope number to be less than `-1`.
|
||||||
|
if (scope_number[i] < -1) {
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the deepest scope number is `-1` advance the scope.
|
||||||
|
if (scope_number[scope] < 0) {
|
||||||
|
scope++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the tabpage by number
|
||||||
|
if (scope_number[kCdScopeTab] == -1) {
|
||||||
|
tp = NULL;
|
||||||
|
} else if (scope_number[kCdScopeTab] > 0) {
|
||||||
|
tp = find_tabpage(scope_number[kCdScopeTab]);
|
||||||
|
if (!tp) {
|
||||||
|
EMSG(_("E5000: Cannot find tab number."));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the window in `tp` by number, `NULL` if none.
|
||||||
|
if (scope_number[kCdScopeWindow] == -1) {
|
||||||
|
win = NULL;
|
||||||
|
} else if (scope_number[kCdScopeWindow] >= 0) {
|
||||||
|
if (!tp) {
|
||||||
|
EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope_number[kCdScopeWindow] > 0) {
|
||||||
|
win = find_win_by_nr(&argvars[0], curtab);
|
||||||
|
if (!win) {
|
||||||
|
EMSG(_("E5002: Cannot find window number."));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (scope) {
|
||||||
|
case kCdScopeWindow:
|
||||||
|
from = win->w_localdir;
|
||||||
|
if (from) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kCdScopeTab: // FALLTHROUGH
|
||||||
|
from = tp->localdir;
|
||||||
|
if (from) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kCdScopeGlobal: // FALLTHROUGH
|
||||||
|
// The `globaldir` variable is not always set.
|
||||||
|
if (globaldir) {
|
||||||
|
from = globaldir;
|
||||||
|
} else {
|
||||||
|
// Copy the OS path directly into output string and jump to the end.
|
||||||
|
if (os_dirname(cwd, MAXPATHL) == FAIL) {
|
||||||
|
EMSG(_("E41: Could not display path."));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from) {
|
||||||
|
xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
|
||||||
|
}
|
||||||
|
|
||||||
rettv->vval.v_string = vim_strsave(cwd);
|
rettv->vval.v_string = vim_strsave(cwd);
|
||||||
#ifdef BACKSLASH_IN_FILENAME
|
#ifdef BACKSLASH_IN_FILENAME
|
||||||
slash_adjust(rettv->vval.v_string);
|
slash_adjust(rettv->vval.v_string);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
end:
|
||||||
xfree(cwd);
|
xfree(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10593,12 +10714,98 @@ static void f_has_key(typval_T *argvars, typval_T *rettv)
|
|||||||
get_tv_string(&argvars[1]), -1) != NULL;
|
get_tv_string(&argvars[1]), -1) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// `haslocaldir([{win}[, {tab}]])` function
|
||||||
* "haslocaldir()" function
|
///
|
||||||
*/
|
/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
|
||||||
|
/// scope object is not specified the current one is implied. This function
|
||||||
|
/// share a lot of code with `f_getcwd`.
|
||||||
|
///
|
||||||
|
/// @pre The arguments must be of type number.
|
||||||
|
/// @pre There may not be more than two arguments.
|
||||||
|
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
||||||
|
///
|
||||||
|
/// @post The return value will be either the number `1` or `0`.
|
||||||
static void f_haslocaldir(typval_T *argvars, typval_T *rettv)
|
static void f_haslocaldir(typval_T *argvars, typval_T *rettv)
|
||||||
{
|
{
|
||||||
rettv->vval.v_number = (curwin->w_localdir != NULL);
|
// Possible scope of working directory to return.
|
||||||
|
CdScope scope = kCdScopeWindow;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
int scope_number[] = {
|
||||||
|
[kCdScopeWindow] = 0, // Number of window to look at.
|
||||||
|
[kCdScopeTab ] = 0, // Number of tab to look at.
|
||||||
|
};
|
||||||
|
|
||||||
|
tabpage_T *tp = curtab; // The tabpage to look at.
|
||||||
|
win_T *win = curwin; // The window to look at.
|
||||||
|
|
||||||
|
rettv->v_type = VAR_NUMBER;
|
||||||
|
rettv->vval.v_number = 0;
|
||||||
|
|
||||||
|
// Pre-conditions and scope extraction together
|
||||||
|
for (int i = 0; i < kCdScopeGlobal; i++) {
|
||||||
|
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (argvars[i].v_type != VAR_NUMBER) {
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scope_number[i] = argvars[i].vval.v_number;
|
||||||
|
// The scope is the current iteration step.
|
||||||
|
scope = i;
|
||||||
|
if (scope_number[i] < -1) {
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It the deepest scope number is `-1` advance the scope by one.
|
||||||
|
if (scope_number[scope] < 0) {
|
||||||
|
++scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the tabpage by number
|
||||||
|
if (scope_number[kCdScopeTab] == -1) {
|
||||||
|
tp = NULL;
|
||||||
|
} else if (scope_number[kCdScopeTab] > 0) {
|
||||||
|
tp = find_tabpage(scope_number[kCdScopeTab]);
|
||||||
|
if (!tp) {
|
||||||
|
EMSG(_("5000: Cannot find tab number."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the window in `tp` by number, `NULL` if none.
|
||||||
|
if (scope_number[kCdScopeWindow] == -1) {
|
||||||
|
win = NULL;
|
||||||
|
} else if (scope_number[kCdScopeWindow] >= 0) {
|
||||||
|
if (!tp) {
|
||||||
|
EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope_number[kCdScopeWindow] > 0) {
|
||||||
|
win = find_win_by_nr(&argvars[0], curtab);
|
||||||
|
if (!win) {
|
||||||
|
EMSG(_("E5002: Cannot find window number."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (scope) {
|
||||||
|
case kCdScopeWindow:
|
||||||
|
rettv->vval.v_number = win->w_localdir ? 1 : 0;
|
||||||
|
break;
|
||||||
|
case kCdScopeTab:
|
||||||
|
rettv->vval.v_number = tp->localdir ? 1 : 0;
|
||||||
|
break;
|
||||||
|
case kCdScopeGlobal:
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2574,6 +2574,18 @@ return {
|
|||||||
addr_type=ADDR_LINES,
|
addr_type=ADDR_LINES,
|
||||||
func='ex_copymove',
|
func='ex_copymove',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='tcd',
|
||||||
|
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
|
||||||
|
addr_type=ADDR_LINES,
|
||||||
|
func='ex_cd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command='tchdir',
|
||||||
|
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
|
||||||
|
addr_type=ADDR_LINES,
|
||||||
|
func='ex_cd',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='tNext',
|
command='tNext',
|
||||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||||
|
@ -2958,8 +2958,11 @@ set_one_cmd_context (
|
|||||||
case CMD_chdir:
|
case CMD_chdir:
|
||||||
case CMD_lcd:
|
case CMD_lcd:
|
||||||
case CMD_lchdir:
|
case CMD_lchdir:
|
||||||
if (xp->xp_context == EXPAND_FILES)
|
case CMD_tcd:
|
||||||
|
case CMD_tchdir:
|
||||||
|
if (xp->xp_context == EXPAND_FILES) {
|
||||||
xp->xp_context = EXPAND_DIRECTORIES;
|
xp->xp_context = EXPAND_DIRECTORIES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_help:
|
case CMD_help:
|
||||||
xp->xp_context = EXPAND_HELP;
|
xp->xp_context = EXPAND_HELP;
|
||||||
@ -6814,36 +6817,55 @@ void free_cd_dir(void)
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/// Deal with the side effects of changing the current directory.
|
||||||
* Deal with the side effects of changing the current directory.
|
///
|
||||||
* When "local" is TRUE then this was after an ":lcd" command.
|
/// @param scope Scope of the function call (global, tab or window).
|
||||||
*/
|
void post_chdir(CdScope scope)
|
||||||
void post_chdir(int local)
|
|
||||||
{
|
{
|
||||||
|
// The local directory of the current window is always overwritten.
|
||||||
xfree(curwin->w_localdir);
|
xfree(curwin->w_localdir);
|
||||||
curwin->w_localdir = NULL;
|
curwin->w_localdir = NULL;
|
||||||
if (local) {
|
|
||||||
/* If still in global directory, need to remember current
|
// Overwrite the local directory of the current tab page for `cd` and `tcd`
|
||||||
* directory as global directory. */
|
if (scope >= kCdScopeTab) {
|
||||||
if (globaldir == NULL && prev_dir != NULL)
|
xfree(curtab->localdir);
|
||||||
|
curtab->localdir = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope < kCdScopeGlobal) {
|
||||||
|
// If still in global directory, need to remember current directory as
|
||||||
|
// global directory.
|
||||||
|
if (globaldir == NULL && prev_dir != NULL) {
|
||||||
globaldir = vim_strsave(prev_dir);
|
globaldir = vim_strsave(prev_dir);
|
||||||
/* Remember this local directory for the window. */
|
}
|
||||||
if (os_dirname(NameBuff, MAXPATHL) == OK)
|
}
|
||||||
curwin->w_localdir = vim_strsave(NameBuff);
|
|
||||||
} else {
|
switch (scope) {
|
||||||
/* We are now in the global directory, no need to remember its
|
case kCdScopeGlobal:
|
||||||
* name. */
|
// We are now in the global directory, no need to remember its name.
|
||||||
xfree(globaldir);
|
xfree(globaldir);
|
||||||
globaldir = NULL;
|
globaldir = NULL;
|
||||||
|
break;
|
||||||
|
case kCdScopeTab:
|
||||||
|
// Remember this local directory for the tab page.
|
||||||
|
if (os_dirname(NameBuff, MAXPATHL) == OK) {
|
||||||
|
curtab->localdir = vim_strsave(NameBuff);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kCdScopeWindow:
|
||||||
|
// Remember this local directory for the window.
|
||||||
|
if (os_dirname(NameBuff, MAXPATHL) == OK) {
|
||||||
|
curwin->w_localdir = vim_strsave(NameBuff);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
shorten_fnames(TRUE);
|
shorten_fnames(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ":cd", ":lcd", ":chdir" and ":lchdir".
|
/// `: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;
|
||||||
@ -6884,10 +6906,25 @@ void ex_cd(exarg_T *eap)
|
|||||||
new_dir = NameBuff;
|
new_dir = NameBuff;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (new_dir == NULL || vim_chdir(new_dir))
|
if (vim_chdir(new_dir)) {
|
||||||
EMSG(_(e_failed));
|
EMSG(_(e_failed));
|
||||||
else {
|
} else {
|
||||||
post_chdir(eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir);
|
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. */
|
/* Echo the new current directory if the command was typed. */
|
||||||
if (KeyTyped || p_verbose >= 5)
|
if (KeyTyped || p_verbose >= 5)
|
||||||
|
@ -19,6 +19,18 @@
|
|||||||
#define EXMODE_NORMAL 1
|
#define EXMODE_NORMAL 1
|
||||||
#define EXMODE_VIM 2
|
#define EXMODE_VIM 2
|
||||||
|
|
||||||
|
/// The scope of a command.
|
||||||
|
///
|
||||||
|
/// The lower a number, the deeper the scope.
|
||||||
|
typedef enum {
|
||||||
|
kCdScopeWindow, ///< Affects one window.
|
||||||
|
kCdScopeTab, ///< Affects one tab page.
|
||||||
|
kCdScopeGlobal, ///< Affects the entire instance of NeoVim.
|
||||||
|
} CdScope;
|
||||||
|
|
||||||
|
/// Last `:cd` scope defined.
|
||||||
|
#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
|
||||||
|
@ -2948,7 +2948,7 @@ void free_tabpage(tabpage_T *tp)
|
|||||||
unref_var_dict(tp->tp_vars);
|
unref_var_dict(tp->tp_vars);
|
||||||
|
|
||||||
|
|
||||||
|
xfree(tp->localdir); // Free tab-local working directory
|
||||||
xfree(tp);
|
xfree(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3560,18 +3560,24 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri
|
|||||||
curwin->w_cursor.coladd = 0;
|
curwin->w_cursor.coladd = 0;
|
||||||
changed_line_abv_curs(); /* assume cursor position needs updating */
|
changed_line_abv_curs(); /* assume cursor position needs updating */
|
||||||
|
|
||||||
if (curwin->w_localdir != NULL) {
|
// The new directory is either the local directory of the window, of the tab
|
||||||
/* Window has a local directory: Save current directory as global
|
// or NULL.
|
||||||
* directory (unless that was done already) and change to the local
|
char_u *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->localdir;
|
||||||
* directory. */
|
|
||||||
|
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.
|
||||||
if (globaldir == NULL) {
|
if (globaldir == NULL) {
|
||||||
char_u cwd[MAXPATHL];
|
char_u cwd[MAXPATHL];
|
||||||
|
|
||||||
if (os_dirname(cwd, MAXPATHL) == OK)
|
if (os_dirname(cwd, MAXPATHL) == OK) {
|
||||||
globaldir = vim_strsave(cwd);
|
globaldir = vim_strsave(cwd);
|
||||||
}
|
}
|
||||||
if (os_chdir((char *)curwin->w_localdir) == 0)
|
}
|
||||||
shorten_fnames(TRUE);
|
if (os_chdir((char *)new_dir) == 0) {
|
||||||
|
shorten_fnames(true);
|
||||||
|
}
|
||||||
} else if (globaldir != NULL) {
|
} else if (globaldir != NULL) {
|
||||||
/* 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. */
|
||||||
|
148
test/functional/ex_cmds/cd_spec.lua
Normal file
148
test/functional/ex_cmds/cd_spec.lua
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
-- Specs for :cd, :tcd, :lcd and getcwd()
|
||||||
|
|
||||||
|
local helpers = require('test.functional.helpers')
|
||||||
|
local execute, eq, clear, eval, exc_exec =
|
||||||
|
helpers.execute, helpers.eq, helpers.clear, helpers.eval, helpers.exc_exec
|
||||||
|
|
||||||
|
|
||||||
|
-- These directories will be created for testing
|
||||||
|
local directories = {
|
||||||
|
'Xtest-functional-ex_cmds-cd_spec.1', -- Tab
|
||||||
|
'Xtest-functional-ex_cmds-cd_spec.2', -- Window
|
||||||
|
'Xtest-functional-ex_cmds-cd_spec.3', -- New global
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Shorthand writing to get the current working directory
|
||||||
|
local cwd = function() return eval('getcwd( )') end -- effective working dir
|
||||||
|
local wcwd = function() return eval('getcwd( 0 )') end -- window dir
|
||||||
|
local tcwd = function() return eval('getcwd(-1, 0)') end -- tab dir
|
||||||
|
local gcwd = function() return eval('getcwd(-1, -1)') end -- global dir
|
||||||
|
|
||||||
|
-- Same, except these tell us if there is a working directory at all
|
||||||
|
local lwd = function() return eval('haslocaldir( )') end -- effective working dir
|
||||||
|
local wlwd = function() return eval('haslocaldir( 0 )') end -- window dir
|
||||||
|
local tlwd = function() return eval('haslocaldir(-1, 0)') end -- tab dir
|
||||||
|
local glwd = function() return eval('haslocaldir(-1, -1)') end -- global dir
|
||||||
|
|
||||||
|
-- Test both the `cd` and `chdir` variants
|
||||||
|
for _, cmd in ipairs {'cd', 'chdir'} do
|
||||||
|
describe(':*' .. cmd, function()
|
||||||
|
before_each(function()
|
||||||
|
clear()
|
||||||
|
for _, d in ipairs(directories) do
|
||||||
|
lfs.mkdir(d)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
after_each(function()
|
||||||
|
for _, d in ipairs(directories) do
|
||||||
|
lfs.rmdir(d)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works', function()
|
||||||
|
-- Store the initial working directory
|
||||||
|
local globalDir = cwd()
|
||||||
|
|
||||||
|
-- Create a new tab first and verify that is has the same working dir
|
||||||
|
execute('tabnew')
|
||||||
|
eq(globalDir, cwd())
|
||||||
|
eq(globalDir, tcwd()) -- has no tab-local directory
|
||||||
|
eq(0, tlwd())
|
||||||
|
eq(globalDir, wcwd()) -- has no window-local directory
|
||||||
|
eq(0, wlwd())
|
||||||
|
|
||||||
|
-- Change tab-local working directory and verify it is different
|
||||||
|
execute('t' .. cmd .. ' ' .. directories[1])
|
||||||
|
eq(globalDir .. '/' .. directories[1], cwd())
|
||||||
|
eq(cwd(), tcwd()) -- working directory maches tab directory
|
||||||
|
eq(1, tlwd())
|
||||||
|
eq(cwd(), wcwd()) -- still no window-directory
|
||||||
|
eq(0, wlwd())
|
||||||
|
|
||||||
|
-- Create a new window in this tab to test `:lcd`
|
||||||
|
execute('new')
|
||||||
|
eq(1, tlwd()) -- Still tab-local working directory
|
||||||
|
eq(0, wlwd()) -- Still no window-local working directory
|
||||||
|
eq(globalDir .. '/' .. directories[1], cwd())
|
||||||
|
execute('l' .. cmd .. ' ../' .. directories[2])
|
||||||
|
eq(globalDir .. '/' .. directories[2], cwd())
|
||||||
|
eq(globalDir .. '/' .. directories[1], tcwd())
|
||||||
|
eq(1, wlwd())
|
||||||
|
|
||||||
|
-- Verify the first window still has the tab local directory
|
||||||
|
execute('wincmd w')
|
||||||
|
eq(globalDir .. '/' .. directories[1], cwd())
|
||||||
|
eq(globalDir .. '/' .. directories[1], tcwd())
|
||||||
|
eq(0, wlwd()) -- No window-local directory
|
||||||
|
|
||||||
|
-- Change back to initial tab and verify working directory has stayed
|
||||||
|
execute('tabnext')
|
||||||
|
eq(globalDir, cwd() )
|
||||||
|
eq(0, tlwd())
|
||||||
|
eq(0, wlwd())
|
||||||
|
|
||||||
|
-- Verify global changes don't affect local ones
|
||||||
|
execute('' .. cmd .. ' ' .. directories[3])
|
||||||
|
eq(globalDir .. '/' .. directories[3], cwd())
|
||||||
|
execute('tabnext')
|
||||||
|
eq(globalDir .. '/' .. directories[1], cwd())
|
||||||
|
eq(globalDir .. '/' .. directories[1], tcwd())
|
||||||
|
eq(0, wlwd()) -- Still no window-local directory in this window
|
||||||
|
|
||||||
|
-- Unless the global change happened in a tab with local directory
|
||||||
|
execute('' .. cmd .. ' ..')
|
||||||
|
eq(globalDir, cwd() )
|
||||||
|
eq(0 , tlwd())
|
||||||
|
eq(0 , wlwd())
|
||||||
|
-- Which also affects the first tab
|
||||||
|
execute('tabnext')
|
||||||
|
eq(globalDir, cwd())
|
||||||
|
|
||||||
|
-- But not in a window with its own local directory
|
||||||
|
execute('tabnext | wincmd w')
|
||||||
|
eq(globalDir .. '/' .. directories[2], cwd() )
|
||||||
|
eq(0 , tlwd())
|
||||||
|
eq(globalDir .. '/' .. directories[2], wcwd())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test legal parameters for 'getcwd' and 'haslocaldir'
|
||||||
|
for _, cmd in ipairs {'getcwd', 'haslocaldir'} do
|
||||||
|
describe(cmd..'()', function()
|
||||||
|
-- Test invalid argument types
|
||||||
|
local expected = 'Vim(call):E474: Invalid argument'
|
||||||
|
it('fails on string', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '("some string")'))
|
||||||
|
end)
|
||||||
|
it('fails on float', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '(1.0)'))
|
||||||
|
end)
|
||||||
|
it('fails on list', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '([1, 2])'))
|
||||||
|
end)
|
||||||
|
it('fails on dictionary', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '({"key": "value"})'))
|
||||||
|
end)
|
||||||
|
it('fails on funcref', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '(function("tr"))'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Test invalid numbers
|
||||||
|
it('fails on number less than -1', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '(-2)'))
|
||||||
|
end)
|
||||||
|
local expected = 'Vim(call):E5001: Higher scope cannot be -1 if lower scope is >= 0.'
|
||||||
|
it('fails on -1 if previous arg is >=0', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '(0, -1)'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Test wrong number of arguments
|
||||||
|
local expected = 'Vim(call):E118: Too many arguments for function: ' .. cmd
|
||||||
|
it('fails to parse more than one argument', function()
|
||||||
|
eq(expected, exc_exec('call ' .. cmd .. '(0, 0, 0)'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user