diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index ef4ea17450..cd87ee1d22 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -9,7 +9,7 @@ Editing files *edit-files* Type |gO| to see the table of contents. ============================================================================== -1. Introduction *edit-intro* +Introduction *edit-intro* Editing a file with Vim means: @@ -169,7 +169,7 @@ If you want to keep the changed buffer without saving it, switch on the 'hidden' is not set, check the help for the command. ============================================================================== -2. Editing a file *edit-a-file* +Editing a file *edit-a-file* *:e* *:edit* *reload* :e[dit][!] [++opt] [+cmd] @@ -615,7 +615,7 @@ filename-expansion phase, so some parts of `path` are still expanded: >lua < ============================================================================== -3. The argument list *argument-list* *arglist* +The argument list *argument-list* *arglist* If you give more than one file name when starting Vim, this list is remembered as the argument list. You can jump to each file in this list. @@ -929,7 +929,7 @@ flag is used for the ":substitute" command to avoid an error for files where "my_foo" isn't used. ":update" writes the file only if changes were made. ============================================================================== -4. Writing *writing* *save-file* +Writing *writing* *save-file* Note: When the 'write' option is off, you are not able to write any file. @@ -1155,7 +1155,7 @@ For MS-Windows the device is detected by its name: The names can be in upper- or lowercase. ============================================================================== -5. Writing and quitting *write-quit* +Writing and quitting *write-quit* *:q* *:quit* :q[uit] Quit the current window. Quit Vim if this is the last @@ -1268,7 +1268,7 @@ MULTIPLE WINDOWS AND BUFFERS *window-exit* which cannot be written for another reason, Vim will not quit. ============================================================================== -6. Dialogs *edit-dialogs* +Dialogs *edit-dialogs* *:confirm* *:conf* :conf[irm] {command} Execute {command}, and use a dialog when an @@ -1346,92 +1346,114 @@ use has("browsefilter"): > endif ============================================================================== -7. The current directory *current-directory* +The current directory *current-directory* -There are four current-directory "scopes": global, tab, window and buffer. -You can use |:cd|, |:tcd|, |:lcd| and |:bcd| to change to another directory. -This affects anything that operates on relative paths, e.g. ":edit ./foo.txt" -or shell commands like ":!ls" or ":te ls". +The "effective working directory" affects anything that operates on relative +paths, e.g. ":edit ./foo.txt" or shell commands like ":!ls" or ":te ls". There +are four current-directory "scopes": global, tab, window and buffer. You can +use |:cd|, |:tcd|, |:lcd| and |:bcd| to change to a directory at a given scope. -Use |getcwd()| to get the effective directory from the current scope. The -window-local working directory takes precedence over the buffer-local working -directory, which takes precedence over the tab-local working directory, which -in turn takes precedence over the global working directory. If a local -working directory (tab, window or buffer) does not exist, the next-higher -scope in the hierarchy applies. +The precedence of scopes is ordered from narrowest to widest: > - *:cd* *E747* *E472* -:cd[!] On non-Unix systems when 'cdhome' is off: Print the - current directory name. - Otherwise: Change the current directory to the home - directory. Clear any window-local directory. - Use |:pwd| to print the current directory on all - systems. + buffer > window > tab > global + +Use |getcwd()| to get the effective directory, and `:verbose pwd` to also see +which scope it came from. + +Setting a directory at a higher scope clears the lower ones: |:tcd| clears the +window-local directory and |:cd| clears both. But the buffer-local directory +is never cleared, except by |:bcd| or |:bcd!|. + + +:chd[ir] *:cd* *:chd* *:chdir* *E747* *E472* +:cd Change the current directory to the home directory, + unsetting the same directories as ":cd {path}". + + If 'cdhome' is disabled this only prints the name. + Use |:pwd| to print it regardless of 'cdhome'. + +:cd {path} Change the current directory to {path}, resolved + against 'cdpath' (if {path} is relative). On + MS-Windows this also changes the active drive. + + Clears the window-local and tab-local directories (on + the current window/tab), but not the buffer-local one. -:cd[!] {path} Change the current directory to {path}. - If {path} is relative, it is searched for in the - directories listed in 'cdpath'. - Clear any window-local directory. - Does not change the meaning of an already opened file, - because its full path name is remembered. Files from - the |arglist| may change though! - On MS-Windows this also changes the active drive. To change to the directory of the current file: > :cd %:h < + Does not change the meaning of an already opened file, + because its full path name is remembered. Files from + the |arglist| may change though! + *:cd-* *E186* -:cd[!] - Change to the previous current directory (before the - previous ":cd {path}" command). +:cd - Change to previous current-directory (before the last + ":cd {path}" command). - *:chd* *:chdir* -:chd[ir][!] [path] Same as |:cd|. +:tch[dir] *:tc* *:tcd* *:tch* *:tchdir* +:tc[d] [path] Like |:cd|, but only set the directory for the current + tab. Clears the window-local directory of the current + window, but not the buffer-local one. - *:tc* *:tcd* -:tc[d][!] {path} Like |:cd|, but only set the directory for the current - tab. The current window and buffer will also use this - directory. The current directory is not changed for - windows in other tabs and for windows in the current - tab that have their own window-local directory, or for - buffers that have their own buffer-local directory. - - *:tcd-* -:tc[d][!] - 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 - tabs, windows, or buffers is not changed. - - *:lch* *:lchdir* -:lch[dir][!] Same as |:lcd|. - - *:lcd-* -:lc[d][!] - Change to the previous current directory (before the - previous ":lcd {path}" command). - - *:bc* *:bcd* -:bc[d][!] [path] Like |:cd|, but only set the current directory for the - current buffer. The current directory for other - tabs, windows, or buffers is not changed. - - Unlike :lcd and :tcd, this is not "sticky": a new - buffer or window will not "inherit" a buffer-local + Does not change the current directory for other tabs, + nor any other window or buffer with its own local directory. - *:bcd-* -:bc[d][!] - Change to the previous buffer-local current directory - (before the last ":bcd [path]" command). + "Sticky" behavior: a new tab inherits the current + tab-local directory. - *:bch* *:bchdir* -:bch[dir][!] Same as |:bcd|. + *:tcd-* +:tc[d] - Change to previous tab-local directory (before the + last ":tcd {path}" command). + +:lch[dir] *:lc* *:lcd* *:lch* *:lchdir* +:lc[d] [path] Like |:cd|, but only set the directory for the current + window. Does not change the current directory for + other tabs, windows or buffers. + + The specified directory becomes the current directory + whenever that window is entered, unless its current + buffer has a buffer-local (:bcd) directory. + + "Sticky" behavior: a new window inherits the current + window-local directory. + + + *:lcd-* +:lc[d] - Change to previous window-local directory (before the + last ":lcd {path}" command). + +:bch[dir] *:bc* *:bcd* *:bch* *:bchdir* +:bc[d] [path] Like |:cd|, but only set the directory for the current + buffer. Does not change the current directory for + tabs, windows or other buffers. + + Not "sticky": unlike :lcd and :tcd, a new window or + buffer (|:edit|, |:enew|, …) will not "inherit" the + current buffer-local directory. Use |:tcd| or |:lcd| + if you want "sticky" behavior. + + See also |project-dir|. + + *:bcd-* +:bc[d] - Change to previous buffer-local directory (before the + last ":bcd {path}" command). + + *:lcd!* *:tcd!* *:bcd!* +:lc[d]! Unset the window-local current-directory. +:tc[d]! Unset the tab-local current-directory. +:bc[d]! Unset the buffer-local current-directory. + + Each unsets only its own scope, so the next-higher + scope takes effect. No-op if the scope has no local + directory. + + The global directory always exists, so ":cd!" is the + same as |:cd|. *:pw* *:pwd* *E187* :pw[d] Print the current directory name. - Also see |getcwd()|. + See also |getcwd()|. *:pwd-verbose* When 'verbose' is non-zero, |:pwd| will also display what scope the current directory was set. Example: > @@ -1444,52 +1466,9 @@ scope in the hierarchy applies. :verbose pwd [tabpage] /path/to/current - " Set by :lcd - :verbose pwd - [window] /path/to/current - " Set by :bcd - :verbose pwd - [buffer] /path/to/current - -So long as |:tcd|, |:lcd| or |:bcd| has not been used, all buffers and windows -share the same 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 buffer-local, tab-local or global directory. When -jumping to another window the current directory is changed to the last -specified window-local directory. If none was specified, the buffer-local, -tab-local or global directory is used. When creating a new window it inherits -the current window-local directory ("sticky" behavior). - -When |:bcd| has been used for a buffer, the specified directory becomes the -current directory whenever that buffer is entered, unless the window has its -own window-local directory. The buffer-local directory is never inherited -(not "sticky"): a buffer created with |:edit|, |:enew|, etc., ignores the -current buffer-local (:bcd) directory, if any. Use |:lcd| if you want "sticky" -behavior. - -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 -using the full path name is that the file names currently in use will remain -referring to the same file. Example: If you have a file a:test and a -directory a:vim the commands ":e test" ":cd vim" ":w" will overwrite the file -a:test and not write a:vim/test. But if you do ":w test" the file a:vim/test -will be written, because you gave a new file name and did not refer to a -filename before the ":cd". - - -PROJECT DIRECTORY *project-dir* *workspace-dir* +------------------------------------------------------------------------------ +Project directory *project-dir* *workspace-dir* You can use |:bcd| to assign a "workspace" (or "project directory") to each buffer, so commands like |:make|, |:grep| and |:terminal| always run relative @@ -1511,7 +1490,7 @@ See also: - |terminal-osc7| ============================================================================== -8. Editing binary files *edit-binary* +Editing binary files *edit-binary* Although Vim was made to edit text files, it is possible to edit binary files. The |-b| Vim argument (b for binary) makes Vim do file I/O in binary @@ -1545,7 +1524,7 @@ There are a few things to remember when editing binary files: in the text). ============================================================================== -9. Encryption *encryption* +Encryption *encryption* *:X* *E817* *E818* *E819* *E820* Support for editing encrypted files has been removed. @@ -1553,7 +1532,7 @@ Support for editing encrypted files has been removed. https://github.com/neovim/neovim/issues/701 ============================================================================== -10. Timestamps *timestamp* *timestamps* +Timestamps *timestamp* *timestamps* Vim remembers the modification timestamp, mode and size of a file when you begin editing it. This is used to avoid that you have two different versions @@ -1669,7 +1648,7 @@ in the Win32 libraries that confuses Vim about the hour time difference. The problem goes away the next day. ============================================================================== -11. File Searching *file-searching* +File Searching *file-searching* The file searching is currently used for the 'path', 'cdpath' and 'tags' options, for |finddir()| and |findfile()|. Other commands use |wildcards| @@ -1768,7 +1747,7 @@ There are three different types of searching: with depth limiter (/usr/**2) or upward search (;) notations. ============================================================================== -12. Trusted Files *trust* +Trusted Files *trust* Nvim executes arbitrary code found on the filesystem if 'exrc' is enabled. To prevent executing malicious code, only "trusted files" are executed. You can @@ -1798,7 +1777,7 @@ mark a file as trusted or untrusted using the |:trust| command or the trust or deny it. ============================================================================== -13. Log Files *log-files* +Log Files *log-files* Nvim keeps log files in `stdpath("log")`. See |standard-path| for where that location is on your system. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ab53f55fbe..49110b9c28 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -251,6 +251,7 @@ EDITOR • |:bcd| sets a buffer-local directory. |getcwd()| and |haslocaldir()| take `bufnr` as a third parameter. +• |:lcd!| |:tcd!| |:bcd!| unsets the directory of that scope. • |:command-completion-customlist| can return a list of dictionaries with kind/menu/info/abbr for the popup menu. • |cmdwin| (|q:|, |q/|, |q?|, |c_CTRL-F|) is implemented as a "normal" diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index 004740e445..9de3ebbaa1 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -1110,31 +1110,22 @@ charidx({string}, {idx} [, {countcc} [, {utf16}]]) *charidx()* (`integer`) chdir({dir} [, {scope}]) *chdir()* - Lua: Prefer |nvim_set_current_dir()| for the global directory; - tab-local, window-local, and return semantics differ. + Sets the |current-directory| of the given {scope}: + - "buffer" Changes the buffer-local directory. |:bcd| + - "window" Changes the window-local directory. |:lcd| + - "tabpage" Changes the tabpage-local directory. |:tcd| + - "global" Changes the global directory. |:cd| - Changes the current working directory to {dir}. The scope of - the change is determined as follows: - If {scope} is not present, the current working directory is - changed to the scope of the current directory: - - If the window local directory (|:lcd|) is set, it - changes the current working directory for that scope. - - If the buffer local directory (|:bcd|) is set, it - changes the current working directory for that scope. - - Otherwise, if the tabpage local directory (|:tcd|) is - set, it changes the current directory for that scope. - - Otherwise, changes the global directory for that scope. + If {scope} is not given it is decided as follows: + - If buffer-local directory (|:bcd|) is set, scope is + "buffer". + - If the window-local directory (|:lcd|) is set, scope is + "window". + - If the tabpage-local directory (|:tcd|) is set, scope is + "tabpage". + - Otherwise, scope is "global". - If {scope} is present, changes the current working directory - for the specified scope: - "buffer" Changes the buffer local directory. |:bcd| - "window" Changes the window local directory. |:lcd| - "tabpage" Changes the tabpage local directory. |:tcd| - "global" Changes the global directory. |:cd| - - {dir} must be a String. - If successful, returns the previous working directory. Pass - this to another chdir() to restore the directory. + If successful, returns the previous working directory. On failure, returns an empty string. Example: >vim @@ -3750,44 +3741,39 @@ getcursorcharpos([{winid}]) *getcursorcharpos()* Return: ~ (`any`) - *getcwd()* *E5000* *E5001* *E5002* *E5006* *E5007* + *getcwd()* *E5000* *E5002* *E5006* *E5007* getcwd([{winnr} [, {tabnr} [, {bufnr}]]]) - Lua: Prefer |uv.cwd()| for the global working directory; - tab-local and window-local scopes differ. + Without arguments, returns the effective |current-directory|. + With {winnr} (|window-number| or |window-ID|), {tabnr} or + {bufnr} the working directory of that scope is returned, + ignoring 'autochdir'. - With no arguments, returns the name of the effective - |current-directory|. With {winnr} or {tabnr} or {bufnr} the - working directory of that scope is returned, and 'autochdir' - is ignored. + - If {winnr} is -1: gets the tabpage directory. + - If {winnr} and {tabnr} are both -1: gets the global + directory. + - Note: Vim returns an empty string when {tabnr} is -1. + - If {bufnr} is given: gets the buffer-local directory. + ({winnr} and {tabnr} must be -1.) + - An argument may be -1 only if preceding args are -1. *E5001* Tabs, windows and buffers are identified by their respective numbers, 0 means current tab/window/buffer. Missing {tabnr} implies 0 (missing {bufnr} does not; see below). Thus the following are equivalent: >vim - getcwd(0) - getcwd(0, 0) -< If {winnr} is -1 it is ignored, only the tab is resolved. - {winnr} is a |window-number| or |window-ID|. + getcwd(0) + getcwd(0, 0) +< + Each form reports its own scope or "wider", so e.g. {winnr} + never reports a buffer-local directory. The {bufnr} form + falls back to the global directory, because a buffer belongs + to no particular window or tabpage. - If both {winnr} and {tabnr} are -1 and {bufnr} is missing the - global working directory is returned. - - Note: When {tabnr} is -1 Vim returns an empty string to - signal that it is invalid, whereas Nvim returns either the - global working directory if {winnr} is -1 or the working - directory of the window indicated by {winnr}. - - If {bufnr} is provided, {winnr} and {tabnr} must be -1, then - the buffer-local working directory is returned. - - An argument may be -1 only if all preceding arguments are -1. - - Examples of buffer usage: >vim + Examples: >vim getcwd(-1, -1, 0) " Get current buffer's directory - getcwd(-1, -1, 3) " Get directory of buffer #3 + getcwd(-1, -1, 3) " Get directory of buffer 3 getcwd(-1, -1, -1) " Get global directory getcwd(-1, -1) " Get global directory -< Throw error if the arguments are invalid. +< Parameters: ~ • {winnr} (`integer?`) diff --git a/runtime/lua/vim/_meta/vimfn.gen.lua b/runtime/lua/vim/_meta/vimfn.gen.lua index 2ef0211f90..857a5dfedb 100644 --- a/runtime/lua/vim/_meta/vimfn.gen.lua +++ b/runtime/lua/vim/_meta/vimfn.gen.lua @@ -947,30 +947,22 @@ function vim.fn.charcol(expr, winid) end --- @return integer function vim.fn.charidx(string, idx, countcc, utf16) end ---- Lua: Prefer |nvim_set_current_dir()| for the global directory; tab-local, window-local, and return semantics differ. +--- Sets the |current-directory| of the given {scope}: +--- - "buffer" Changes the buffer-local directory. |:bcd| +--- - "window" Changes the window-local directory. |:lcd| +--- - "tabpage" Changes the tabpage-local directory. |:tcd| +--- - "global" Changes the global directory. |:cd| --- ---- Changes the current working directory to {dir}. The scope of ---- the change is determined as follows: ---- If {scope} is not present, the current working directory is ---- changed to the scope of the current directory: ---- - If the window local directory (|:lcd|) is set, it ---- changes the current working directory for that scope. ---- - If the buffer local directory (|:bcd|) is set, it ---- changes the current working directory for that scope. ---- - Otherwise, if the tabpage local directory (|:tcd|) is ---- set, it changes the current directory for that scope. ---- - Otherwise, changes the global directory for that scope. +--- If {scope} is not given it is decided as follows: +--- - If buffer-local directory (|:bcd|) is set, scope is +--- "buffer". +--- - If the window-local directory (|:lcd|) is set, scope is +--- "window". +--- - If the tabpage-local directory (|:tcd|) is set, scope is +--- "tabpage". +--- - Otherwise, scope is "global". --- ---- If {scope} is present, changes the current working directory ---- for the specified scope: ---- "buffer" Changes the buffer local directory. |:bcd| ---- "window" Changes the window local directory. |:lcd| ---- "tabpage" Changes the tabpage local directory. |:tcd| ---- "global" Changes the global directory. |:cd| ---- ---- {dir} must be a String. ---- If successful, returns the previous working directory. Pass ---- this to another chdir() to restore the directory. +--- If successful, returns the previous working directory. --- On failure, returns an empty string. --- --- Example: >vim @@ -3321,41 +3313,37 @@ function vim.fn.getcurpos(winid) end --- @return any function vim.fn.getcursorcharpos(winid) end ---- Lua: Prefer |uv.cwd()| for the global working directory; tab-local and window-local scopes differ. +--- Without arguments, returns the effective |current-directory|. +--- With {winnr} (|window-number| or |window-ID|), {tabnr} or +--- {bufnr} the working directory of that scope is returned, +--- ignoring 'autochdir'. --- ---- With no arguments, returns the name of the effective ---- |current-directory|. With {winnr} or {tabnr} or {bufnr} the ---- working directory of that scope is returned, and 'autochdir' ---- is ignored. +--- - If {winnr} is -1: gets the tabpage directory. +--- - If {winnr} and {tabnr} are both -1: gets the global +--- directory. +--- - Note: Vim returns an empty string when {tabnr} is -1. +--- - If {bufnr} is given: gets the buffer-local directory. +--- ({winnr} and {tabnr} must be -1.) +--- - An argument may be -1 only if preceding args are -1. *E5001* --- --- Tabs, windows and buffers are identified by their respective --- numbers, 0 means current tab/window/buffer. Missing {tabnr} --- implies 0 (missing {bufnr} does not; see below). Thus the --- following are equivalent: >vim ---- getcwd(0) ---- getcwd(0, 0) ---- vim +--- Examples: >vim --- getcwd(-1, -1, 0) " Get current buffer's directory ---- getcwd(-1, -1, 3) " Get directory of buffer #3 +--- getcwd(-1, -1, 3) " Get directory of buffer 3 --- getcwd(-1, -1, -1) " Get global directory --- getcwd(-1, -1) " Get global directory ---- vim @@ -1291,9 +1285,6 @@ M.funcs = { params = { { 'dir', 'string' }, { 'scope', 'string' } }, returns = 'string', signature = 'chdir({dir} [, {scope}])', - see_lua = { - '|nvim_set_current_dir()| for the global directory; tab-local, window-local, and return semantics differ', - }, }, cindent = { args = 1, @@ -4098,48 +4089,43 @@ M.funcs = { args = { 0, 3 }, base = 1, desc = [=[ - With no arguments, returns the name of the effective - |current-directory|. With {winnr} or {tabnr} or {bufnr} the - working directory of that scope is returned, and 'autochdir' - is ignored. + Without arguments, returns the effective |current-directory|. + With {winnr} (|window-number| or |window-ID|), {tabnr} or + {bufnr} the working directory of that scope is returned, + ignoring 'autochdir'. + + - If {winnr} is -1: gets the tabpage directory. + - If {winnr} and {tabnr} are both -1: gets the global + directory. + - Note: Vim returns an empty string when {tabnr} is -1. + - If {bufnr} is given: gets the buffer-local directory. + ({winnr} and {tabnr} must be -1.) + - An argument may be -1 only if preceding args are -1. *E5001* Tabs, windows and buffers are identified by their respective numbers, 0 means current tab/window/buffer. Missing {tabnr} implies 0 (missing {bufnr} does not; see below). Thus the following are equivalent: >vim - getcwd(0) - getcwd(0, 0) - vim + Examples: >vim getcwd(-1, -1, 0) " Get current buffer's directory - getcwd(-1, -1, 3) " Get directory of buffer #3 + getcwd(-1, -1, 3) " Get directory of buffer 3 getcwd(-1, -1, -1) " Get global directory getcwd(-1, -1) " Get global directory - w_localdir != NULL) { - scope = kCdScopeWindow; } else if (curbuf->b_localdir != NULL) { scope = kCdScopeBuffer; + } else if (curwin->w_localdir != NULL) { + scope = kCdScopeWindow; } else if (curtab->tp_localdir != NULL) { scope = kCdScopeTabpage; } - if (!changedir_func(argvars[0].vval.v_string, scope)) { + if (!do_chdir(argvars[0].vval.v_string, scope)) { // Directory change failed XFREE_CLEAR(rettv->vval.v_string); } @@ -688,7 +688,7 @@ static bool getcwd_scope_args(typval_T *argvars, CdScope default_scope, CdScope for (int i = 0; i < argc; i++) { explicit_scope = explicit_scope || argv[i] >= 0; if (explicit_scope && argv[i] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + emsg(_("E5001: Argument cannot be -1 if preceding argument is >= 0.")); return false; } } @@ -748,16 +748,12 @@ static bool getcwd_scope_args(typval_T *argvars, CdScope default_scope, CdScope } // Find the window in `tp` by number. - if (argv[kWinArg] >= 0) { - if (argv[kWinArg] > 0) { - *win = find_win_by_nr(&argvars[0], *tp); - if (*win == NULL) { - emsg(_("E5002: Cannot find window number.")); - return false; - } + if (argv[kWinArg] > 0) { + *win = find_win_by_nr(&argvars[0], *tp); + if (*win == NULL) { + emsg(_("E5002: Cannot find window number.")); + return false; } - // Window scope may fall through to the buffer shown in that window. - *buf = (*win)->w_buffer; } return true; @@ -797,14 +793,16 @@ void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) break; } FALLTHROUGH; - case kCdScopeBuffer: - from = buf->b_localdir; + case kCdScopeTabpage: + from = tp->tp_localdir; if (from) { break; } FALLTHROUGH; - case kCdScopeTabpage: - from = tp->tp_localdir; + case kCdScopeBuffer: + // Only a buffer target reaches this with `scope` unchanged: a buffer belongs to no particular + // window or tabpage, so it skips those and falls back to global dir. + from = scope == kCdScopeBuffer ? buf->b_localdir : NULL; if (from) { break; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 42c5535992..9504b78a13 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6225,18 +6225,21 @@ static char **get_prevdir(CdScope scope) /// @param scope Scope of the function call (global, tab, window or buffer). static void post_chdir(CdScope scope, bool trigger_dirchanged) { - // Always overwrite the window-local CWD. - XFREE_CLEAR(curwin->w_localdir); + // Only :bcd overwrites the buffer-local CWD. + if (scope == kCdScopeBuffer) { + XFREE_CLEAR(curbuf->b_localdir); + } + + // Overwrite the window-local CWD for :cd, :tcd, :lcd. + if (scope >= kCdScopeWindow) { + XFREE_CLEAR(curwin->w_localdir); + } // Overwrite the tab-local CWD for :cd, :tcd. if (scope >= kCdScopeTabpage) { XFREE_CLEAR(curtab->tp_localdir); } - if (scope >= kCdScopeBuffer) { - XFREE_CLEAR(curbuf->b_localdir); - } - if (scope < kCdScopeGlobal) { char *pdir = *get_prevdir(scope); // If still in global directory, set CWD as the global directory. @@ -6252,7 +6255,11 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) switch (scope) { case kCdScopeGlobal: // We are now in the global directory, no need to remember its name. + // Unless a buffer-local CWD is active, then it is in effect and must be remembered. XFREE_CLEAR(globaldir); + if (curbuf->b_localdir != NULL) { + globaldir = xstrdup(cwd); + } break; case kCdScopeTabpage: curtab->tp_localdir = xstrdup(cwd); @@ -6275,11 +6282,13 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) } } -/// Change directory function used by :cd/:tcd/:lcd/:bcd Ex commands and the chdir() function. -/// @param new_dir The directory to change to. -/// @param scope Scope of the function call (global, tab, window or buffer). -/// @return true if the directory is successfully changed. -bool changedir_func(char *new_dir, CdScope scope) +/// Performs explicit (user) chdir (:cd/:tcd/:lcd/:bcd/chdir()/nvim_set_current_dir()). Unlike +/// do_autochdir() and update_cwd(), this sets the `scope` dir and reports kCdCauseManual. +/// +/// @param new_dir Directory to change to. +/// @param scope Set the directory on this scope. +/// @return true if the directory was successfully changed. +bool do_chdir(char *new_dir, CdScope scope) { if (new_dir == NULL || allbuf_locked()) { return false; @@ -6312,9 +6321,16 @@ bool changedir_func(char *new_dir, CdScope scope) new_dir = TO_SLASH_SAVE(new_dir); + // Buffer-local CWD is never "cleared" by :lcd/:tcd/:cd, so it stays in effect. + const bool bcd_active = scope != kCdScopeBuffer && curbuf->b_localdir != NULL; + bool dir_differs = pdir == NULL || pathcmp(pdir, new_dir, -1) != 0; if (dir_differs) { - do_autocmd_dirchanged(new_dir, scope, kCdCauseManual, true); + if (!bcd_active) { + do_autocmd_dirchanged(new_dir, scope, kCdCauseManual, true); + } + // Enter `new_dir` even if it will not apply (because of buf-local dir): + // post_chdir() reads back its resolved name. if (vim_chdir(new_dir) != 0) { emsg(_(e_failed)); xfree(new_dir); @@ -6328,21 +6344,37 @@ bool changedir_func(char *new_dir, CdScope scope) xfree(*pp); *pp = pdir; - post_chdir(scope, dir_differs); + post_chdir(scope, dir_differs && !bcd_active); + if (bcd_active) { + os_chdir(curbuf->b_localdir); + shorten_fnames(true); + } ctx_did_chdir(); return true; } +/// ":lcd!", ":tcd!", ":bcd!": Clears the scope-local directory. No-op if scope has no local dir. +static void chdir_clear(CdScope scope) +{ + char **localdir = &curtab->tp_localdir; + if (scope == kCdScopeBuffer) { + localdir = &curbuf->b_localdir; + } else if (scope == kCdScopeWindow) { + localdir = &curwin->w_localdir; + } + if (*localdir == NULL) { + return; + } + XFREE_CLEAR(*localdir); + update_cwd(kCdCauseManual); + ctx_did_chdir(); +} + /// ":cd", ":tcd", ":lcd", ":bcd", ":chdir", ":tchdir", ":lchdir", and ":bchdir". void ex_cd(exarg_T *eap) { char *new_dir = eap->arg; - // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set - if (*new_dir == NUL && !p_cdh) { - ex_pwd(NULL); - return; - } CdScope scope = kCdScopeGlobal; switch (eap->cmdidx) { @@ -6361,7 +6393,23 @@ void ex_cd(exarg_T *eap) default: break; } - if (changedir_func(new_dir, scope)) { + + // ":lcd!", ":tcd!", ":bcd!": unset that scope. + if (eap->forceit && *new_dir == NUL && scope != kCdScopeGlobal) { + chdir_clear(scope); + if (KeyTyped || p_verbose >= 5) { + ex_pwd(eap); + } + return; + } + + // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set + if (*new_dir == NUL && !p_cdh) { + ex_pwd(NULL); + return; + } + + if (do_chdir(new_dir, scope)) { // Echo the new current directory if the command was typed. if (KeyTyped || p_verbose >= 5) { ex_pwd(eap); @@ -6380,10 +6428,10 @@ static void ex_pwd(exarg_T *eap) char *context = "global"; if (last_chdir_reason != NULL) { context = last_chdir_reason; - } else if (curwin->w_localdir != NULL) { - context = "window"; } else if (curbuf->b_localdir != NULL) { context = "buffer"; + } else if (curwin->w_localdir != NULL) { + context = "window"; } else if (curtab->tp_localdir != NULL) { context = "tabpage"; } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 4320e431b0..aaf36af061 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -764,10 +764,8 @@ static int makeopens(FILE *fd, char *dirnow) tab_topframe = topframe; } - // Restore the tab-local working directory while the tab still shows its empty "placeholder" - // buffer: ":tcd" clears the b_localdir of the current buffer, so emitting it before any file is - // loaded keeps it away from directories restored by the ":bcd" handlers above. Do this before - // the windows and buffers, so the win-local / buf-local dir can override the tab-local dir. + // Restore the tab-local working directory before the windows and buffers, so that the + // win-local / buf-local dir can override the tab-local dir. if ((ssop_flags & kOptSsopFlagCurdir) && tp->tp_localdir != NULL) { if (need_tabnext && put_line(fd, "tabnext") == FAIL) { return FAIL; diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 8b14ddc64b..9790c0e6c6 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -648,15 +648,11 @@ static int nlua_with(lua_State *L) TRY_WRAP(&err, { CtxSwitch cs = { 0 }; bool switched = true; - CtxSwitchFlags dirs = keepcwd ? kCtxKeepDirs : kCtxKeepCwd; - if (win) { - tabpage_T *tabpage = win_find_tabpage(win); - switched = ctx_switch(&cs, win, tabpage, NULL, kCtxNoDisplay | kCtxValidate | dirs); - } else if (buf) { - ctx_switch(&cs, NULL, NULL, buf, dirs); - } else if (keepcwd) { - ctx_switch(&cs, NULL, NULL, NULL, kCtxKeepDirs); + if (win || buf || keepcwd) { + CtxSwitchFlags dirs = keepcwd ? kCtxKeepDirs : kCtxKeepCwd; + tabpage_T *tab = win ? win_find_tabpage(win) : NULL; + switched = ctx_switch(&cs, win, tab, buf, kCtxNoDisplay | dirs | (win ? kCtxValidate : 0)); } if (switched) { diff --git a/src/nvim/vim_defs.h b/src/nvim/vim_defs.h index 41015e226e..4b12571e3f 100644 --- a/src/nvim/vim_defs.h +++ b/src/nvim/vim_defs.h @@ -29,15 +29,11 @@ typedef enum { kBroken, } WorkingStatus; -/// The scope of a working-directory command like `:cd`. -/// -/// Scopes are enumerated from lowest to highest. When adding a scope make sure -/// to update all functions using scopes as well, such as the implementation of -/// `getcwd()`. +/// The scope of a working-directory command like `:cd`. Enumerated from lowest to highest. typedef enum { kCdScopeInvalid = -1, - kCdScopeWindow, ///< Affects one window. kCdScopeBuffer, ///< Affects one buffer. + kCdScopeWindow, ///< Affects one window. kCdScopeTabpage, ///< Affects one tab page. kCdScopeGlobal, ///< Affects the entire Nvim instance. } CdScope; diff --git a/src/nvim/window.c b/src/nvim/window.c index 6d083c3dab..18cb9cc48e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5362,12 +5362,12 @@ void update_cwd(CdCause cause) char *new_dir; CdScope scope; - if (curwin->w_localdir) { - new_dir = curwin->w_localdir; - scope = kCdScopeWindow; - } else if (curbuf->b_localdir) { + if (curbuf->b_localdir) { new_dir = curbuf->b_localdir; scope = kCdScopeBuffer; + } else if (curwin->w_localdir) { + new_dir = curwin->w_localdir; + scope = kCdScopeWindow; } else if (curtab->tp_localdir) { new_dir = curtab->tp_localdir; scope = kCdScopeTabpage; diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 3ba9c56030..11fdf96c52 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -72,42 +72,60 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) - command('bcd ' .. dirs[2]) - eq({ directory = dirs[2], scope = 'buffer', changed_window = false }, eval('g:evpre')) - eq({ cwd = dirs[2], scope = 'buffer', changed_window = false }, eval('g:ev')) - eq('buffer', eval('g:amatchpre')) - eq('buffer', eval('g:amatch')) + command('tcd ' .. dirs[2]) + eq({ directory = dirs[2], scope = 'tabpage', changed_window = false }, eval('g:evpre')) + eq({ cwd = dirs[2], scope = 'tabpage', changed_window = false }, eval('g:ev')) + eq('tabpage', eval('g:amatchpre')) + eq('tabpage', eval('g:amatch')) eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) - command('tcd ' .. dirs[3]) - eq({ directory = dirs[3], scope = 'tabpage', changed_window = false }, eval('g:evpre')) - eq({ cwd = dirs[3], scope = 'tabpage', changed_window = false }, eval('g:ev')) - eq('tabpage', eval('g:amatchpre')) - eq('tabpage', eval('g:amatch')) + command('cd ' .. dirs[3]) + eq({ directory = dirs[3], scope = 'global', changed_window = false }, eval('g:evpre')) + eq({ cwd = dirs[3], scope = 'global', changed_window = false }, eval('g:ev')) + eq('global', eval('g:amatchpre')) + eq('global', eval('g:amatch')) eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) - command('cd ' .. dirs[4]) - eq({ directory = dirs[4], scope = 'global', changed_window = false }, eval('g:evpre')) - eq({ cwd = dirs[4], scope = 'global', changed_window = false }, eval('g:ev')) - eq('global', eval('g:amatchpre')) - eq('global', eval('g:amatch')) + command('bcd ' .. dirs[4]) + eq({ directory = dirs[4], scope = 'buffer', changed_window = false }, eval('g:evpre')) + eq({ cwd = dirs[4], scope = 'buffer', changed_window = false }, eval('g:ev')) + eq('buffer', eval('g:amatchpre')) + eq('buffer', eval('g:amatch')) eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) + + -- Buffer-local dir is NOT cleared by a wider scope. + assert_no_event('lcd ' .. dirs[1]) + assert_no_event('tcd ' .. dirs[2]) + assert_no_event('cd ' .. dirs[3]) + + -- ":bcd!" reports the scope that becomes effective, not the one that was unset. + -- The overridden ":cd" above still reset the window/tab scopes, so that is the global dir. + command('bcd!') + eq({ cwd = dirs[3], scope = 'global', changed_window = false }, eval('g:ev')) + eq('global', eval('g:amatch')) + + -- Same for ":lcd!", landing on the tab-local dir. + command('tcd ' .. dirs[2]) + command('lcd ' .. dirs[1]) + command('lcd!') + eq({ cwd = dirs[2], scope = 'tabpage', changed_window = false }, eval('g:ev')) + eq('tabpage', eval('g:amatch')) end) it('DirChanged set getcwd() during event #6260', function() command('lcd ' .. dirs[1]) eq(dirs[1], eval('g:getcwd')) - command('bcd ' .. dirs[2]) + command('tcd ' .. dirs[2]) eq(dirs[2], eval('g:getcwd')) - command('tcd ' .. dirs[3]) + command('cd ' .. dirs[3]) eq(dirs[3], eval('g:getcwd')) - command('cd ' .. dirs[4]) + command('bcd ' .. dirs[4]) eq(dirs[4], eval('g:getcwd')) end) @@ -192,7 +210,8 @@ describe('autocmd DirChanged and DirChangedPre', function() it('not triggered if directory has not changed', function() local scopes = { lcd = 'window', bcd = 'buffer', tcd = 'tabpage', cd = 'global' } - for i, cmd in ipairs({ 'lcd', 'bcd', 'tcd', 'cd' }) do + -- ":bcd" last: a buffer-local dir overrides the other scopes. + for i, cmd in ipairs({ 'lcd', 'tcd', 'cd', 'bcd' }) do local scope = scopes[cmd] command(('%s %s'):format(cmd, dirs[i])) eq({ directory = dirs[i], scope = scope, changed_window = false }, eval('g:evpre')) @@ -207,6 +226,7 @@ describe('autocmd DirChanged and DirChangedPre', function() end end + command('bcd!') -- 5th event: the global dir becomes effective again. command('set autochdir') command(('split %s/foo'):format(dirs[2])) @@ -214,8 +234,8 @@ describe('autocmd DirChanged and DirChangedPre', function() eq({ cwd = dirs[2], scope = 'window', changed_window = false }, eval('g:ev')) eq('auto', eval('g:amatchpre')) eq('auto', eval('g:amatch')) - eq(5, eval('g:cdprecount')) - eq(5, eval('g:cdcount')) + eq(6, eval('g:cdprecount')) + eq(6, eval('g:cdcount')) assert_no_event(('split %s/bar'):format(dirs[2])) if is_os('win') then assert_no_event(('split %s/baz'):format(win_dirs[2])) diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 3ebdeeb08f..801a257bb8 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -29,7 +29,7 @@ local function join(...) return table.concat({ ... }, pathsep) end --- Shorthand writing to get the current working directory +-- Get the current working directory. local cwd = function(...) return call('getcwd', ...) end -- effective working dir @@ -110,16 +110,16 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(1, lwd(localwin, tabnr)) command('tabnew') - -- From new tab page, original window reports global dir + -- From new tabpage, original window reports global dir eq(globalDir, cwd(globalwin, tabnr)) eq(0, lwd(globalwin, tabnr)) - -- From new tab page, local window reports as such + -- From new tabpage, local window reports as such eq(globalDir .. pathsep .. directories.window, cwd(localwin, tabnr)) eq(1, lwd(localwin, tabnr)) end) - it('for tab page', function() + it('for tabpage', function() local globalDir = startdir local globaltab = call('tabpagenr') @@ -133,7 +133,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do command('silent t' .. cmd .. ' ' .. directories.tab) local localtab = call('tabpagenr') - -- From local tab page, original tab reports globalDir + -- From local tabpage, original tab reports globalDir eq(globalDir, cwd(-1, globaltab)) eq(0, lwd(-1, globaltab)) @@ -144,7 +144,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(1, lwd(-1, localtab)) command('tabnext') - -- From original tab page, local reports as such + -- From original tabpage, local reports as such eq(globalDir .. pathsep .. directories.tab, cwd(-1, localtab)) eq(1, lwd(-1, localtab)) end) @@ -183,13 +183,23 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(0, blwd()) eq(globalDir, cwd()) - -- getcwd({winnr}) falls through to the buffer shown in that window, not the current - -- buffer. + -- getcwd({winnr}) reports the window's own scope chain: the buffer scope is separate, and + -- visible only via the {bufnr} form. command(('split %s%s%s2'):format(directories.buffer, pathsep, tmpfile)) + local bufnr = call('winbufnr', 1) command('wincmd p') eq(globalDir, cwd()) - eq(join(globalDir, directories.buffer), cwd(1)) - eq(globalDir, cwd(2)) + eq({ globalDir, globalDir }, { cwd(1), cwd(2) }) + eq(join(globalDir, directories.buffer), cwd(-1, -1, bufnr)) + + -- The {bufnr} form skips window and tab: a buffer belongs to no particular window/tabpage. + command('lcd ' .. directories.window) + command('tcd ' .. join('..', directories.tab)) + eq( + { join(globalDir, directories.tab), join(globalDir, directories.tab) }, + { cwd(0), tcwd() } + ) + eq(globalDir, cwd(-1, -1, 0)) end) end) @@ -199,19 +209,19 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(0, lwd(-1, -1)) end) - it('works with tab-local pwd', function() + it('with tab-local dir', function() command('silent t' .. cmd .. ' ' .. directories.tab) eq(startdir, cwd(-1, -1)) eq(0, lwd(-1, -1)) end) - it('works with window-local pwd', function() + it('with window-local dir', function() command('silent l' .. cmd .. ' ' .. directories.window) eq(startdir, cwd(-1, -1)) eq(0, lwd(-1, -1)) end) - it('works with buffer-local pwd', function() + it('with buffer-local dir', function() command(('silent b%s %s'):format(cmd, directories.buffer)) eq(startdir, cwd(-1, -1)) eq(0, lwd(-1, -1)) @@ -327,7 +337,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(join(globalDir, directories.tab), cwd()) eq(cwd(), tcwd()) -- Working directory matches tab directory eq(1, tlwd()) - eq(cwd(), bcwd()) -- Still no buffer-directory + eq(globalDir, bcwd()) -- Still no buffer-directory: the buffer form skips the tab scope eq(0, blwd()) -- Change buffer 2's buffer-local directory @@ -369,21 +379,24 @@ for _, cmd in ipairs { 'cd', 'chdir' } do eq(join(globalDir, directories.buffer), cwd()) eq(cwd(), bcwd()) -- Working directory matches buffer directory eq(1, blwd()) - eq(cwd(), wcwd()) -- Still no window-directory + eq(globalDir, wcwd()) -- Still no window-directory eq(0, wlwd()) - -- Change window-local directory to test `:lcd` + -- :lcd sets the window-local dir WITHOUT clearing the narrower buffer-local one. command(('silent l%s ../%s'):format(cmd, directories.window)) - eq(join(globalDir, directories.window), cwd()) - eq(join(globalDir, directories.buffer), bcwd()) - eq(1, blwd()) + eq(join(globalDir, directories.buffer), cwd()) + eq({ 1, 1 }, { blwd(), wlwd() }) + eq(join(globalDir, directories.window), wcwd()) - -- Verify buffer has buffer-local directory in original window + -- Window-local dir applies if the buffer-local one is unset. + command(('silent b%s!'):format(cmd)) + eq(0, blwd()) + eq(join(globalDir, directories.window), cwd()) + + -- Verify other window is unaffected. command('wincmd w') command('b ' .. tmpfile) - eq(join(globalDir, directories.buffer), cwd()) - - -- Verify going to second window uses window-local directory + eq(globalDir, cwd()) command('wincmd w') eq(join(globalDir, directories.window), cwd()) end) @@ -424,9 +437,68 @@ for _, cmd in ipairs { 'bcd', 'bchdir' } do eq(startdir, cwd()) eq(0, blwd()) end) + + it('is not cleared or overridden by :lcd/:tcd/:cd', function() + local bufdir = join(startdir, directories.buffer) + + command('edit ' .. tmpfile) + command(('%s %s'):format(cmd, directories.buffer)) + -- Paths are relative to `bufdir`, which stays in effect throughout. + command('lcd ' .. join('..', directories.window)) + eq({ 1, bufdir, join(startdir, directories.window) }, { blwd(), cwd(), wcwd() }) + + command('tcd ' .. join('..', directories.tab)) + eq({ 1, bufdir, join(startdir, directories.tab) }, { blwd(), cwd(), tcwd() }) + + command('cd ..') + eq({ 1, bufdir, startdir }, { blwd(), cwd(), cwd(-1, -1) }) + + -- The overridden ":cd" still reset the window/tab scopes, so ":bcd!" lands on the global dir. + command(('%s!'):format(cmd)) + eq({ 0, 0, 0, startdir }, { blwd(), wlwd(), tlwd(), cwd() }) + end) end) end +describe(':lcd!/:tcd!/:bcd! (bang)', function() + it('clear only their own scope', function() + local bufdir = join(startdir, directories.buffer) + local windir = join(startdir, directories.window) + local tabdir = join(startdir, directories.tab) + + command('tcd ' .. directories.tab) + command('lcd ' .. join('..', directories.window)) + command('bcd ' .. join('..', directories.buffer)) + eq({ 1, 1, 1 }, { blwd(), wlwd(), tlwd() }) + eq(bufdir, cwd()) + + command('bcd!') -- Buffer scope gone: the window-local dir applies. + eq({ 0, 1, 1 }, { blwd(), wlwd(), tlwd() }) + eq(windir, cwd()) + + command('lcd!') -- Window scope gone: the tab-local dir applies. + eq({ 0, 0, 1 }, { blwd(), wlwd(), tlwd() }) + eq(tabdir, cwd()) + + command('tcd!') -- Tab scope gone: back to the global dir. + eq({ 0, 0, 0 }, { blwd(), wlwd(), tlwd() }) + eq(startdir, cwd()) + + command('lcd!') -- No-op when the scope has no local directory. + eq(startdir, cwd()) + + -- :cd! does not unset. + command('lcd ' .. directories.window) + command('cd! ' .. join('..', directories.global)) + eq(join(startdir, directories.global), cwd()) + eq(0, wlwd()) -- ":cd" cleared it, as always. + + -- Legacy: bang WITH arg ":lcd! {path}" is just ":lcd {path}". + command('lcd! ' .. join('..', directories.window)) + eq({ 1, join(startdir, directories.window) }, { wlwd(), cwd() }) + end) +end) + describe('cd during temp context-switch', function() it(':bcd/:tcd/:lcd persists in target scope, does not leak into original context', function() local exec_lua = n.exec_lua @@ -503,7 +575,7 @@ for _, cmd in ipairs { 'getcwd', 'haslocaldir' } do ) -- -1 preceded by an argument >= 0 - local err5001 = 'Vim:E5001: Higher scope cannot be -1 if lower scope is >= 0.' + local err5001 = 'Vim:E5001: Argument cannot be -1 if preceding argument is >= 0.' eq(err5001, pcall_err(call, cmd, 0, -1)) eq(err5001, pcall_err(call, cmd, 2, 3, -1)) eq(err5001, pcall_err(call, cmd, -1, 0, -1)) diff --git a/test/old/testdir/test_cd.vim b/test/old/testdir/test_cd.vim index 0a502ea980..212ecff0f0 100644 --- a/test/old/testdir/test_cd.vim +++ b/test/old/testdir/test_cd.vim @@ -104,10 +104,11 @@ func Test_chdir_func() call assert_match('^\[global\]', trim(execute('verbose pwd'))) call chdir('.', 'tabpage') call assert_match('^\[tabpage\]', trim(execute('verbose pwd'))) - call chdir('.', 'buffer') - call assert_match('^\[buffer\]', trim(execute('verbose pwd'))) call chdir('.', 'window') call assert_match('^\[window\]', trim(execute('verbose pwd'))) + " Nvim: buffer is the narrowest scope and shadows the others, so it goes last. + call chdir('.', 'buffer') + call assert_match('^\[buffer\]', trim(execute('verbose pwd'))) " Error case call assert_fails("call chdir('dir-abcd')", 'E344:')