diff options
-rw-r--r-- | runtime/doc/editing.txt | 65 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 32 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 2 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 10 | ||||
-rw-r--r-- | src/nvim/eval.c | 250 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 12 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 81 | ||||
-rw-r--r-- | src/nvim/ex_docmd.h | 12 | ||||
-rw-r--r-- | src/nvim/version.c | 501 | ||||
-rw-r--r-- | src/nvim/window.c | 22 | ||||
-rw-r--r-- | test/functional/ex_cmds/cd_spec.lua | 148 | ||||
-rw-r--r-- | test/functional/legacy/eval_spec.lua | 22 | ||||
-rw-r--r-- | test/functional/shada/compatibility_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/shada/registers_spec.lua | 18 |
14 files changed, 990 insertions, 189 deletions
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index d2b9374143..0ad917006f 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -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 diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index fea9423df2..148ac7732a 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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} @@ -3565,9 +3565,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 @@ -3902,9 +3911,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 diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 10110b0f62..243806b3f1 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -291,7 +291,7 @@ void vim_change_directory(String dir, Error *err) return; } - post_chdir(false); + post_chdir(kCdScopeGlobal); try_end(err); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 86e63eb52c..ecd13d5ee0 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -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 }; /* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f02eabfe9f..31b264395b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6655,7 +6655,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 }, @@ -6672,9 +6672,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 }, @@ -6718,7 +6718,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 }, @@ -6748,7 +6748,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 }, @@ -6772,7 +6772,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 @@ -9784,22 +9784,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; + + // 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); - if (os_dirname(cwd, MAXPATHL) != FAIL) { - rettv->vval.v_string = vim_strsave(cwd); + + // 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); + slash_adjust(rettv->vval.v_string); #endif - } + +end: xfree(cwd); } @@ -10122,9 +10243,10 @@ static void f_getreg(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_LIST; rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); - if (rettv->vval.v_list != NULL) { - rettv->vval.v_list->lv_refcount++; + if (rettv->vval.v_list == NULL) { + rettv->vval.v_list = list_alloc(); } + rettv->vval.v_list->lv_refcount++; } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -10618,12 +10740,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; + } } /* diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 6c8835b5c5..04fd88cc8d 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2575,6 +2575,18 @@ return { 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), addr_type=ADDR_LINES, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6391e023c3..2d316cb106 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -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) diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index a5a4edbbbf..7af3ee233c 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -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 diff --git a/src/nvim/version.c b/src/nvim/version.c index ca7438e708..ca6b66f220 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -69,82 +69,395 @@ static char *features[] = { // clang-format off static int included_patches[] = { + 1757, + 1755, 1654, 1652, 1643, 1641, + + // 1600 NA + // 1599 NA + // 1598 NA + // 1597 NA + // 1596, + // 1595 NA + // 1594 NA + // 1593 NA + // 1592, + // 1591, + // 1590, + // 1589, + // 1588, + // 1587 NA + // 1586, + // 1585, + // 1584 NA + // 1583 NA + // 1582, + + // 1581, + // 1580, + // 1579, + // 1578, + // 1577, + 1576, + // 1575 NA 1574, + // 1573, + // 1572 NA + // 1571, 1570, + 1569, + // 1568, + // 1567, + // 1566 NA + // 1565, + // 1564, + // 1563, + // 1562, + // 1561 NA + // 1560, + // 1559, + // 1558, + // 1557, + // 1556, + // 1555, + // 1554, + // 1553, + // 1552, + // 1551, + // 1550, + // 1549, + // 1548, + // 1547, + // 1546, + // 1545 NA + // 1544 NA + // 1543 NA + // 1542 NA + // 1541 NA + // 1540 NA + // 1539 NA + // 1538 NA + // 1537 NA + // 1536 NA + // 1535, + // 1534 NA + // 1533, + // 1532 NA + // 1531 NA + // 1530 NA + // 1529 NA + // 1528, + // 1527 NA + // 1526 NA + // 1525 NA + // 1524 NA + // 1523 NA + // 1522 NA + // 1521, + // 1520 NA + // 1519 NA + // 1518 NA + // 1517 NA + // 1516, + // 1515 NA + // 1514 NA + // 1513, + // 1512 NA 1511, + // 1510 NA + // 1509 NA + // 1508 NA + // 1507 NA + // 1506 NA + // 1505 NA + // 1504 NA + // 1503 NA + // 1502 NA + // 1501 NA + 1500, + // 1499, + // 1498 NA + // 1497 NA + // 1496 NA + // 1495 NA + // 1494, + // 1493 NA + // 1492, + // 1491, + // 1490 NA + // 1489 NA + // 1488 NA + // 1487 NA + // 1486, + // 1485 NA + // 1484 NA + // 1483 NA + // 1482 NA + // 1481 NA + // 1480, + // 1479, + // 1478, + // 1477, + // 1476 NA + // 1475 NA + // 1474 NA + // 1473 NA + // 1472 NA + // 1471 NA + // 1470 NA + // 1469 NA + // 1468, + // 1467 NA + // 1466 NA + // 1465 NA + // 1464, + // 1463 NA + // 1462 NA + // 1461 NA + // 1460 NA + // 1459 NA + // 1458 NA + // 1457 NA + // 1456, + // 1455 NA + // 1454 NA + // 1453 NA + // 1452 NA + // 1451 NA + // 1450 NA + // 1449 NA + // 1448 NA + // 1447 NA + // 1446 NA + // 1445 NA + // 1444 NA + // 1443 NA + // 1442 NA + // 1441 NA + // 1440 NA + // 1439 NA + // 1438 NA + // 1437 NA + // 1436 NA + // 1435 NA + // 1434 NA + // 1433 NA + // 1432 NA + // 1431 NA + // 1430 NA + // 1429 NA + // 1428 NA + // 1427 NA + // 1426 NA 1425, + // 1424 NA + // 1423 NA + // 1422 NA + // 1421 NA + // 1420 NA + // 1419 NA + // 1418 NA + // 1417 NA + // 1416 NA + // 1415 NA + // 1414 NA + // 1413 NA + // 1412 NA + // 1411 NA + 1410, + // 1409 NA + // 1408 NA + // 1407 NA + 1406, + 1405, + // 1404 NA + // 1403 NA + // 1402 NA + // 1401, + // 1400 NA + // 1399 NA + // 1398 NA + // 1397, + // 1396, + // 1395 NA + // 1394, + // 1393 NA + // 1392 NA + // 1391 NA + // 1390 NA + // 1389 NA + // 1388, + // 1387 NA + // 1386 NA + // 1385 NA + // 1384, + // 1383 NA + // 1382 NA + // 1381 NA + // 1380 NA + // 1379 NA + // 1378 NA + // 1377 NA + // 1376 NA + // 1375 NA + // 1374 NA + // 1373 NA + // 1372 NA + // 1371 NA + // 1370 NA + // 1369 NA + // 1368 NA + // 1367 NA 1366, + // 1365, + // 1364 NA + // 1363 NA + // 1362 NA + // 1361 NA + // 1360 NA + // 1359 NA + // 1358 NA + // 1357 NA + // 1356 NA + // 1355 NA + // 1354 NA + // 1353 NA + // 1352, + // 1351 NA + // 1350 NA + // 1349 NA + // 1348 NA + // 1347, + 1346, + // 1345 NA + // 1344 NA + // 1343 NA + // 1342 NA + // 1341 NA + // 1340 NA + // 1339 NA + // 1338 NA + // 1337 NA + // 1336 NA + // 1335 NA + // 1334 NA + // 1333 NA + // 1332 NA + // 1331 NA + // 1330 NA + // 1329 NA + // 1328 NA + // 1327 NA + // 1326 NA + // 1325 NA + // 1324 NA + // 1323 NA + // 1322 NA + // 1321 NA + // 1320 NA + // 1319 NA + // 1318 NA + // 1317 NA + // 1316 NA + // 1315 NA + // 1314 NA + // 1313 NA + // 1312 NA + // 1311 NA + // 1310 NA + 1309, + // 1308 NA + // 1307 NA + // 1306 NA + // 1305, 1304, + // 1303 NA + // 1302 NA + // 1301 NA + // 1300, + // 1299 NA + // 1298 NA + // 1297 NA + // 1296, + // 1295 NA + // 1294 NA + // 1293 NA 1292, + // 1291 NA + // 1290 NA + // 1289 NA + // 1288 NA + // 1287 NA + // 1286 NA + // 1285, 1284, - // 1283 + // 1283 NA 1282, - // 1281 - // 1280 + // 1281, + // 1280 NA // 1279 NA // 1278 NA - // 1277 - // 1276 - // 1275 - // 1274 - // 1273 - // 1272 + // 1277 NA + // 1276, + // 1275 NA + // 1274 NA + // 1273, + // 1272 NA 1271, - // 1270 + // 1270 NA 1269, - // 1268 + // 1268 NA 1267, // 1266 - // 1265 - // 1264 - // 1263 - // 1262 - // 1261 - // 1260 - // 1259 - // 1258 - // 1257 - // 1256 - // 1255 - // 1254 - // 1253 - // 1252 - // 1251 - // 1250 - // 1249 - // 1248 - // 1247 - // 1246 - // 1245 - // 1244 + // 1265 NA + // 1264 NA + // 1263 NA + // 1262 NA + // 1261 NA + // 1260 NA + // 1259, + // 1258 NA + // 1257 NA + // 1256 NA + // 1255 NA + // 1254 NA + // 1253 NA + // 1252 NA + // 1251 NA + // 1250 NA + // 1249 NA + // 1248 NA + // 1247 NA + // 1246 NA + // 1245 NA + // 1244 NA // 1243 NA - // 1242 - // 1241 - // 1240 - // 1239 - // 1238 - // 1237 - // 1236 - // 1235 - // 1234 - // 1233 - // 1232 + // 1242 NA + // 1241 NA + // 1240 NA + // 1239 NA + // 1238 NA + // 1237, + // 1236, + // 1235 NA + // 1234 NA + // 1233 NA + // 1232 NA // 1231 NA - // 1230 - // 1229 + // 1230 NA + // 1229 NA 1228, - // 1227 - // 1226 - // 1225 - // 1224 - // 1223 - // 1223 - // 1221 - // 1220 + // 1227 NA + // 1226 NA + // 1225 NA + // 1224 NA + // 1223, + // 1222 NA + // 1221 NA + // 1220 NA // 1219 NA // 1218 NA // 1217 NA @@ -176,7 +489,7 @@ static int included_patches[] = { // 1191 NA // 1190 NA // 1189 NA - // 1188, + // 1188 NA // 1187 NA // 1186, // 1185 NA @@ -210,7 +523,7 @@ static int included_patches[] = { 1157, // 1156 NA // 1155 NA - // 1154, + // 1154 NA // 1153, // 1152 NA // 1151, @@ -229,8 +542,8 @@ static int included_patches[] = { // 1138 NA 1137, // 1136, - // 1135 NA, - // 1134 NA, + // 1135 NA + // 1134 NA // 1133 NA // 1132, // 1131 NA @@ -281,30 +594,30 @@ static int included_patches[] = { // 1086, 1085, 1084, - // 1083 NA, - // 1082 NA, + // 1083 NA + // 1082 NA 1081, - // 1080 NA, + // 1080 NA // 1079, - // 1078 NA, - // 1077 NA, + // 1078 NA + // 1077 NA 1076, // 1075, - // 1074 NA, + // 1074 NA // 1073, 1072, // 1071, - // 1070 NA, - // 1069 NA, + // 1070 NA + // 1069 NA // 1068, - // 1067 NA, - // 1066 NA, + // 1067 NA + // 1066 NA 1065, // 1064, - // 1063 NA, - // 1062 NA, + // 1063 NA + // 1062 NA // 1061, - // 1060 NA, + // 1060 NA // 1059, // 1058, // 1057, @@ -319,44 +632,44 @@ static int included_patches[] = { // 1048, // 1047, // 1046, - // 1045 NA, - // 1044 NA, - // 1043 NA, + // 1045 NA + // 1044 NA + // 1043 NA // 1042, 1041, - // 1040 NA, + // 1040 NA // 1039, - // 1038 NA, + // 1038 NA // 1037, // 1036, // 1035, // 1034, - // 1033 NA, + // 1033 NA 1032, // 1031 NA, 1030, 1029, - // 1028 NA, + // 1028 NA 1027, - // 1026 NA, - // 1025 NA, - // 1024 NA, - // 1023 NA, - // 1022 NA, - // 1021 NA, - // 1020 NA, - // 1019 NA, + // 1026 NA + // 1025 NA + // 1024 NA + // 1023 NA + // 1022 NA + // 1021 NA + // 1020 NA + // 1019 NA // 1018, // 1017, - // 1016 NA, + // 1016 NA // 1015, - // 1014 NA, + // 1014 NA 1013, - // 1012 NA, - // 1011 NA, + // 1012 NA + // 1011 NA // 1010, - // 1009 NA, - // 1008 NA, + // 1009 NA + // 1008 NA // 1007, // 1006, // 1005, @@ -1120,13 +1433,13 @@ static int included_patches[] = { 247, // 246 NA 245, - // 244, + // 244 NA 243, 242, 241, 240, 239, - // 238, + // 238 NA 237, 236, 235, diff --git a/src/nvim/window.c b/src/nvim/window.c index 1b8c953596..93a7a58678 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -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. */ diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua new file mode 100644 index 0000000000..f5cce65104 --- /dev/null +++ b/test/functional/ex_cmds/cd_spec.lua @@ -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 + diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index d1ed96cc2e..3ff1092a4b 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -495,6 +495,28 @@ describe('eval', function() ' abcE4b10-4\000abcE4b10-4-2') end) + it('getreg("a",1,1) returns a valid list when "a is unset', function() + -- Precondition: "a is actually unset and "0 is nonempty + eq('', eval("getregtype('a')")) + eq('', eval("getreg('a')")) + execute("call setreg('0','text')") + + -- This used to return a NULL list + -- which setreg didn't handle + execute("let x = getreg('a',1,1)") + execute("call setreg('0',x)") + + -- nvim didn't crash and "0 was emptied + eq(2, eval("1+1")) + eq({}, eval("getreg('0',1,1)")) + + -- x is a mutable list + execute("let y = x") + eq({}, eval("y")) + execute("call add(x, 'item')") + eq({'item'}, eval("y")) + end) + it('search and expressions', function() execute('so test_eval_setup.vim') execute([=[call SetReg('/', ['abc/'])]=]) diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 774a1f1346..1fa88c58e5 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -270,9 +270,7 @@ describe('ShaDa forward compatibility support code', function() it('works with register item with type 10', function() wshada('\005\001\019\132\161na\162rX\194\162rc\145\196\001-\162rt\010') eq(0, exc_exec(sdrcmd(true))) - -- getreg may return empty list as list with NULL pointer which API - -- translates into nil for some reason. - eq(NIL, funcs.getreg('a', 1, 1) or {}) + eq({}, funcs.getreg('a', 1, 1)) eq('', funcs.getregtype('a')) nvim_command('wshada ' .. shada_fname) local found = 0 diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index d3af35cf7f..4043d94a69 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -43,9 +43,9 @@ describe('ShaDa support code', function() setreg('b', {'bca', 'abc', 'cba'}, 'b3') nvim_command('qall') reset() - eq({NIL, ''}, getreg('c')) - eq({NIL, ''}, getreg('l')) - eq({NIL, ''}, getreg('b')) + eq({{}, ''}, getreg('c')) + eq({{}, ''}, getreg('l')) + eq({{}, ''}, getreg('b')) end) it('does restore registers with zero <', function() @@ -67,9 +67,9 @@ describe('ShaDa support code', function() setreg('b', {'bca', 'abc', 'cba'}, 'b3') nvim_command('qall') reset() - eq({NIL, ''}, getreg('c')) - eq({NIL, ''}, getreg('l')) - eq({NIL, ''}, getreg('b')) + eq({{}, ''}, getreg('c')) + eq({{}, ''}, getreg('l')) + eq({{}, ''}, getreg('b')) end) it('does restore registers with zero "', function() @@ -103,7 +103,7 @@ describe('ShaDa support code', function() nvim_command('qall') reset() eq({{'d'}, 'v'}, getreg('o')) - eq({NIL, ''}, getreg('t')) + eq({{}, ''}, getreg('t')) end) it('does limit number of lines according to "', function() @@ -113,7 +113,7 @@ describe('ShaDa support code', function() nvim_command('qall') reset() eq({{'d'}, 'v'}, getreg('o')) - eq({NIL, ''}, getreg('t')) + eq({{}, ''}, getreg('t')) end) it('does limit number of lines according to < rather then "', function() @@ -125,7 +125,7 @@ describe('ShaDa support code', function() reset() eq({{'d'}, 'v'}, getreg('o')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('t')) - eq({NIL, ''}, getreg('h')) + eq({{}, ''}, getreg('h')) end) it('dumps and loads register correctly when &encoding is not UTF-8', |