Merge #3229 ':tcd'

This commit is contained in:
Justin M. Keyes 2016-04-21 03:15:52 -04:00
commit 5ebffaae4e
10 changed files with 550 additions and 83 deletions

View File

@ -1216,12 +1216,18 @@ use has("browsefilter"): >
==============================================================================
7. The current directory *current-directory*
You may use the |:cd| and |:lcd| commands to change to another directory, so
you will not have to type that directory name in front of the file names. It
also makes a difference for executing external commands, e.g. ":!ls".
You can use |:cd|, |:tcd| and |:lcd| to change to another directory, so you
will not have to type that directory name in front of the file names. It also
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
present in 'cpoptions' and "!" is not used in the command.
There are three current-directory "scopes": global, tab and window. The
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[!] 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[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[d][!] {path} Like |:cd|, but only set the current directory for the
current window. The current directory for other
windows is not changed.
windows or any tabs is not changed.
*:lch* *:lchdir*
:lch[dir][!] Same as |:lcd|.
*:lcd-*
:lcd[!] - Change to the previous current directory (before the
previous ":tcd {path}" command).
*:pw* *:pwd* *E187*
:pw[d] Print the current directory name.
Also see |getcwd()|.
So long as no |:lcd| 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.
When a |:lcd| command has been used for a window, the specified directory
becomes the current directory for that window. Windows where the |:lcd|
command has not been used stick to the global current directory. When jumping
to another window the current directory will become the last specified local
current directory. If none was specified, the global current 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.
So long as no |:tcd| or |:lcd| 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.
When |:lcd| has been used for a window, the specified directory becomes the
current directory for that window. Windows where the |:lcd| command has not
been used stick to the global or tab-local directory. When jumping to another
window the current directory will become the last specified local current
directory. If none was specified, the global or tab-local directory is used.
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
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*
*:X* *E817* *E818* *E819* *E820*
Support for editing encrypted files has been removed, but may be added back in
the future. See the following discussions for more information:
Support for editing encrypted files has been removed.
https://github.com/neovim/neovim/issues/694
https://github.com/neovim/neovim/issues/701

View File

@ -1886,7 +1886,7 @@ getcmdpos() Number return cursor position in command-line
getcmdtype() String return current command-line type
getcmdwintype() String return current command-line window type
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
getfperm( {fname}) String file permissions 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
call setpos('.', save_cursor)
<
*getcwd()*
getcwd() The result is a String, which is the name of the current
working directory.
getcwd([{window}[, {tab}]]) *getcwd()*
With no arguments the result is a String, which is the name of
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()*
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
an entry with key {key}. Zero otherwise.
haslocaldir() *haslocaldir()*
The result is a Number, which is 1 when the current
window has set a local path via |:lcd|, and 0 otherwise.
haslocaldir([{window}[, {tab}]]) *haslocaldir()*
The result is a Number, which is 1 when the specified tabpage
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()*
The result is a Number, which is 1 if there is a mapping that

View File

@ -291,7 +291,7 @@ void vim_change_directory(String dir, Error *err)
return;
}
post_chdir(false);
post_chdir(kCdScopeGlobal);
try_end(err);
}

View File

@ -816,10 +816,12 @@ struct tabpage_S {
was set */
diff_T *tp_first_diff;
buf_T *(tp_diffbuf[DB_COUNT]);
int tp_diff_invalid; /* list of diffs is outdated */
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 */
int tp_diff_invalid; ///< list of diffs is outdated */
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
};
/*

View File

@ -6656,7 +6656,7 @@ static struct fst {
} functions[] =
{
{ "abs", 1, 1, f_abs },
{ "acos", 1, 1, f_acos }, // WJMc
{ "acos", 1, 1, f_acos }, // WJMc
{ "add", 2, 2, f_add },
{ "and", 2, 2, f_and },
{ "append", 2, 2, f_append },
@ -6673,9 +6673,9 @@ static struct fst {
{ "browse", 4, 4, f_browse },
{ "browsedir", 2, 2, f_browsedir },
{ "bufexists", 1, 1, f_bufexists },
{ "buffer_exists", 1, 1, f_bufexists }, // obsolete
{ "buffer_name", 1, 1, f_bufname }, // obsolete
{ "buffer_number", 1, 1, f_bufnr }, // obsolete
{ "buffer_exists", 1, 1, f_bufexists }, // obsolete
{ "buffer_name", 1, 1, f_bufname }, // obsolete
{ "buffer_number", 1, 1, f_bufnr }, // obsolete
{ "buflisted", 1, 1, f_buflisted },
{ "bufloaded", 1, 1, f_bufloaded },
{ "bufname", 1, 1, f_bufname },
@ -6719,7 +6719,7 @@ static struct fst {
{ "expand", 1, 3, f_expand },
{ "extend", 2, 3, f_extend },
{ "feedkeys", 1, 2, f_feedkeys },
{ "file_readable", 1, 1, f_filereadable }, // obsolete
{ "file_readable", 1, 1, f_filereadable }, // obsolete
{ "filereadable", 1, 1, f_filereadable },
{ "filewritable", 1, 1, f_filewritable },
{ "filter", 2, 2, f_filter },
@ -6749,7 +6749,7 @@ static struct fst {
{ "getcmdtype", 0, 0, f_getcmdtype },
{ "getcmdwintype", 0, 0, f_getcmdwintype },
{ "getcurpos", 0, 0, f_getcurpos },
{ "getcwd", 0, 0, f_getcwd },
{ "getcwd", 0, 2, f_getcwd },
{ "getfontname", 0, 1, f_getfontname },
{ "getfperm", 1, 1, f_getfperm },
{ "getfsize", 1, 1, f_getfsize },
@ -6773,7 +6773,7 @@ static struct fst {
{ "globpath", 2, 5, f_globpath },
{ "has", 1, 1, f_has },
{ "has_key", 2, 2, f_has_key },
{ "haslocaldir", 0, 0, f_haslocaldir },
{ "haslocaldir", 0, 2, f_haslocaldir },
{ "hasmapto", 1, 3, f_hasmapto },
{ "highlightID", 1, 1, f_hlID }, // 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;
}
/*
* "getcwd()" function
*/
/// `getcwd([{win}[, {tab}]])` 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)
{
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->vval.v_string = NULL;
cwd = xmalloc(MAXPATHL);
if (os_dirname(cwd, MAXPATHL) != FAIL) {
rettv->vval.v_string = vim_strsave(cwd);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(rettv->vval.v_string);
#endif
// 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);
// 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);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(rettv->vval.v_string);
#endif
end:
xfree(cwd);
}
@ -10593,12 +10714,98 @@ static void f_has_key(typval_T *argvars, typval_T *rettv)
get_tv_string(&argvars[1]), -1) != NULL;
}
/*
* "haslocaldir()" function
*/
/// `haslocaldir([{win}[, {tab}]])` 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)
{
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;
}
}
/*

View File

@ -2574,6 +2574,18 @@ return {
addr_type=ADDR_LINES,
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',
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),

View File

@ -2958,8 +2958,11 @@ set_one_cmd_context (
case CMD_chdir:
case CMD_lcd:
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;
}
break;
case CMD_help:
xp->xp_context = EXPAND_HELP;
@ -6814,36 +6817,55 @@ void free_cd_dir(void)
#endif
/*
* Deal with the side effects of changing the current directory.
* When "local" is TRUE then this was after an ":lcd" command.
*/
void post_chdir(int local)
/// Deal with the side effects of changing the current directory.
///
/// @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.
xfree(curwin->w_localdir);
curwin->w_localdir = NULL;
if (local) {
/* If still in global directory, need to remember current
* directory as global directory. */
if (globaldir == NULL && prev_dir != NULL)
// Overwrite the local directory of the current tab page for `cd` and `tcd`
if (scope >= kCdScopeTab) {
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);
/* Remember this local directory for the window. */
if (os_dirname(NameBuff, MAXPATHL) == OK)
curwin->w_localdir = vim_strsave(NameBuff);
} else {
/* We are now in the global directory, no need to remember its
* name. */
}
}
switch (scope) {
case kCdScopeGlobal:
// We are now in the global directory, no need to remember its name.
xfree(globaldir);
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);
}
/*
* ":cd", ":lcd", ":chdir" and ":lchdir".
*/
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
void ex_cd(exarg_T *eap)
{
char_u *new_dir;
@ -6884,10 +6906,25 @@ void ex_cd(exarg_T *eap)
new_dir = NameBuff;
}
#endif
if (new_dir == NULL || vim_chdir(new_dir))
if (vim_chdir(new_dir)) {
EMSG(_(e_failed));
else {
post_chdir(eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir);
} else {
CdScope scope = kCdScopeGlobal; // Depends on command invoked
switch (eap->cmdidx) {
case CMD_tcd:
case CMD_tchdir:
scope = kCdScopeTab;
break;
case CMD_lcd:
case CMD_lchdir:
scope = kCdScopeWindow;
break;
default:
break;
}
post_chdir(scope);
/* Echo the new current directory if the command was typed. */
if (KeyTyped || p_verbose >= 5)

View File

@ -19,6 +19,18 @@
#define EXMODE_NORMAL 1
#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
# include "ex_docmd.h.generated.h"
#endif

View File

@ -2948,7 +2948,7 @@ void free_tabpage(tabpage_T *tp)
unref_var_dict(tp->tp_vars);
xfree(tp->localdir); // Free tab-local working directory
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;
changed_line_abv_curs(); /* assume cursor position needs updating */
if (curwin->w_localdir != NULL) {
/* Window has a local directory: Save current directory as global
* directory (unless that was done already) and change to the local
* directory. */
// 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;
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) {
char_u cwd[MAXPATHL];
if (os_dirname(cwd, MAXPATHL) == OK)
if (os_dirname(cwd, MAXPATHL) == OK) {
globaldir = vim_strsave(cwd);
}
}
if (os_chdir((char *)new_dir) == 0) {
shorten_fnames(true);
}
if (os_chdir((char *)curwin->w_localdir) == 0)
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. */

View 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