diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 04f88d8058..9e09bd4121 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1357,7 +1357,7 @@ 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 (window, buffer or tab) does not exist, the next-higher +working directory (tab, window or buffer) does not exist, the next-higher scope in the hierarchy applies. *:cd* *E747* *E472* @@ -1388,7 +1388,7 @@ scope in the hierarchy applies. *:tc* *:tcd* :tc[d][!] {path} Like |:cd|, but only set the directory for the current - tab. The current buffer and window will also use this + 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 @@ -1401,15 +1401,10 @@ scope in the hierarchy applies. *:tch* *:tchdir* :tch[dir][!] Same as |:tcd|. - *:bc* *:bcd* -:bc[d][!] {path} Like |:cd|, but only set the current directory for the - current buffer. The current directory for other - windows or buffers or tabs is not changed. - *:lc* *:lcd* :lc[d][!] {path} Like |:cd|, but only set the current directory for the current window. The current directory for other - windows, tabs, or buffers is not changed. + tabs, windows, or buffers is not changed. *:lch* *:lchdir* :lch[dir][!] Same as |:lcd|. @@ -1418,6 +1413,18 @@ scope in the hierarchy applies. :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. + + *:bcd-* +:bc[d][!] - Change to the previous buffer-local current directory + (before the last ":bcd [path]" command). + + *:bch* *:bchdir* +:bch[dir][!] Same as |:bcd|. + *:pw* *:pwd* *E187* :pw[d] Print the current directory name. Also see |getcwd()|. @@ -1429,24 +1436,34 @@ scope in the hierarchy applies. :verbose pwd [global] /path/to/current - " Set by :lcd - :verbose pwd - [window] /path/to/current - " Set by :tcd :verbose pwd [tabpage] /path/to/current -So long as no |:lcd| or |:tcd| command has been used, all windows share the -same current directory. Using a command to jump to another window doesn't -change anything for the current directory. + " Set by :lcd + :verbose pwd + [window] /path/to/current + + " Set by :bcd + :verbose pwd + [buffer] /path/to/current + +So long as no |:tcd|, |:lcd| or |:bcd| 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 is changed to the last specified local current -directory. If none was specified, the global or tab-local directory is used. -When creating a new window it inherits the local directory of the current window. +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 local current directory. If none was specified, the buffer-local, +tab-local or global directory is used. When creating a new window it +inherits the local directory of the current window. + +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. A new buffer created with |:edit| or |:enew| +inherits the buffer-local directory of the previous current buffer. When changing tabs the same behaviour applies. If the current tab has no local working directory the global working directory is used. diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index ee667bed3e..e7b4470ba8 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1181,6 +1181,8 @@ Tag Command Action ~ |:ball| :ba[ll] open a window for each buffer in the buffer list |:badd| :bad[d] add buffer to the buffer list |:balt| :balt like ":badd" but also set the alternate file +|:bcd| :bc[d] change directory for buffer +|:bchdir| :bch[dir] change directory for buffer |:bdelete| :bd[elete] remove a buffer from the buffer list |:belowright| :bel[owright] make split window appear right or below |:bfirst| :bf[irst] go to first buffer in the buffer list diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 698be950d0..ee33bf2242 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -312,6 +312,7 @@ Command-line: is open. The |cmdwin-char| is shown via 'statuscolumn'. Commands: +- |:bcd| - |:checkhealth| - |:connect| - |:detach| diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index a7e840d093..38be4b38f3 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -3774,9 +3774,8 @@ getcwd([{winnr} [, {tabnr} [, {bufnr}]]]) directory of the window indicated by {winnr}. If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the - working directory of that buffer is returned. If {bufnr} is - -1, it is ignored, and the global working directory is - returned. + working directory of that buffer is returned. An argument may + be -1 only if all preceding arguments are -1. Examples of buffer usage: >vim getcwd(-1, -1, 0) " Get current buffer's directory getcwd(-1, -1, 3) " Get directory of buffer #3 @@ -5013,15 +5012,15 @@ has_key({dict}, {key}) *has_key()* (`0|1`) haslocaldir([{winnr} [, {tabnr} [, {bufnr} ]]]) *haslocaldir()* - Checks whether the window, tabpage or buffer has set a local + Checks whether the tabpage, window or buffer has set a local working directory. Returns 1 when the window has set a local path via |:lcd|, or when {winnr} is -1 and the tabpage has set a local path via |:tcd|, or when {winnr} and {tabnr} are -1 and {bufnr} has set a local path via |:bcd|, otherwise 0. Tabs, windows and buffers are identified by their respective - numbers, 0 means current tab or window. Missing argument - implies 0. Thus the following are equivalent: >vim + numbers, 0 means current tab, window or buffer. Missing + argument implies 0. Thus the following are equivalent: >vim echo haslocaldir() echo haslocaldir(0) echo haslocaldir(0, 0) @@ -5030,7 +5029,8 @@ haslocaldir([{winnr} [, {tabnr} [, {bufnr} ]]]) *haslocaldir()* {winnr} is a |window-number| or |window-ID|. If {winnr} is -1 it is ignored, only the tab is resolved. If {bufnr} is provided, {winnr} and {tabnr} must be -1 and - only the buffer is resolved. + only the buffer is resolved. An argument may be -1 only if + all preceding arguments are -1. Examples of buffer usage: >vim haslocaldir(-1, -1, 0) " Current buf has a local directory? haslocaldir(-1, -1, 3) " Buf #3 has a local directory? diff --git a/runtime/lua/vim/_meta/vimfn.gen.lua b/runtime/lua/vim/_meta/vimfn.gen.lua index 6112415d09..1806838eb4 100644 --- a/runtime/lua/vim/_meta/vimfn.gen.lua +++ b/runtime/lua/vim/_meta/vimfn.gen.lua @@ -3341,9 +3341,8 @@ function vim.fn.getcursorcharpos(winid) end --- directory of the window indicated by {winnr}. --- --- If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the ---- working directory of that buffer is returned. If {bufnr} is ---- -1, it is ignored, and the global working directory is ---- returned. +--- working directory of that buffer is returned. An argument may +--- be -1 only if all preceding arguments are -1. --- Examples of buffer usage: >vim --- getcwd(-1, -1, 0) " Get current buffer's directory --- getcwd(-1, -1, 3) " Get directory of buffer #3 @@ -4462,15 +4461,15 @@ function vim.fn.has(feature) end --- @return 0|1 function vim.fn.has_key(dict, key) end ---- Checks whether the window, tabpage or buffer has set a local +--- Checks whether the tabpage, window or buffer has set a local --- working directory. Returns 1 when the window has set a local --- path via |:lcd|, or when {winnr} is -1 and the tabpage has set --- a local path via |:tcd|, or when {winnr} and {tabnr} are -1 --- and {bufnr} has set a local path via |:bcd|, otherwise 0. --- --- Tabs, windows and buffers are identified by their respective ---- numbers, 0 means current tab or window. Missing argument ---- implies 0. Thus the following are equivalent: >vim +--- numbers, 0 means current tab, window or buffer. Missing +--- argument implies 0. Thus the following are equivalent: >vim --- echo haslocaldir() --- echo haslocaldir(0) --- echo haslocaldir(0, 0) @@ -4479,7 +4478,8 @@ function vim.fn.has_key(dict, key) end --- {winnr} is a |window-number| or |window-ID|. --- If {winnr} is -1 it is ignored, only the tab is resolved. --- If {bufnr} is provided, {winnr} and {tabnr} must be -1 and ---- only the buffer is resolved. +--- only the buffer is resolved. An argument may be -1 only if +--- all preceding arguments are -1. --- Examples of buffer usage: >vim --- haslocaldir(-1, -1, 0) " Current buf has a local directory? --- haslocaldir(-1, -1, 3) " Buf #3 has a local directory? diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 9384b03933..4df067481e 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4116,9 +4116,8 @@ M.funcs = { directory of the window indicated by {winnr}. If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the - working directory of that buffer is returned. If {bufnr} is - -1, it is ignored, and the global working directory is - returned. + working directory of that buffer is returned. An argument may + be -1 only if all preceding arguments are -1. Examples of buffer usage: >vim getcwd(-1, -1, 0) " Get current buffer's directory getcwd(-1, -1, 3) " Get directory of buffer #3 @@ -5429,15 +5428,15 @@ M.funcs = { args = { 0, 3 }, base = 1, desc = [=[ - Checks whether the window, tabpage or buffer has set a local + Checks whether the tabpage, window or buffer has set a local working directory. Returns 1 when the window has set a local path via |:lcd|, or when {winnr} is -1 and the tabpage has set a local path via |:tcd|, or when {winnr} and {tabnr} are -1 and {bufnr} has set a local path via |:bcd|, otherwise 0. Tabs, windows and buffers are identified by their respective - numbers, 0 means current tab or window. Missing argument - implies 0. Thus the following are equivalent: >vim + numbers, 0 means current tab, window or buffer. Missing + argument implies 0. Thus the following are equivalent: >vim echo haslocaldir() echo haslocaldir(0) echo haslocaldir(0, 0) @@ -5446,7 +5445,8 @@ M.funcs = { {winnr} is a |window-number| or |window-ID|. If {winnr} is -1 it is ignored, only the tab is resolved. If {bufnr} is provided, {winnr} and {tabnr} must be -1 and - only the buffer is resolved. + only the buffer is resolved. An argument may be -1 only if + all preceding arguments are -1. Examples of buffer usage: >vim haslocaldir(-1, -1, 0) " Current buf has a local directory? haslocaldir(-1, -1, 3) " Buf #3 has a local directory? diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c index 22ec8c7087..6826ea4a39 100644 --- a/src/nvim/eval/fs.c +++ b/src/nvim/eval/fs.c @@ -681,6 +681,16 @@ static bool getcwd_scope_args(typval_T *argvars, CdScope default_scope, CdScope } } + // An argument may only be -1 if all preceding arguments are -1: `(2, 3, -1)` is an error. + bool explicit_scope = false; + 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.")); + return false; + } + } + // Narrowest requested scope. Imagine X >= 0: switch (argc) { case 0: @@ -705,7 +715,7 @@ static bool getcwd_scope_args(typval_T *argvars, CdScope default_scope, CdScope if (argv[kBufArg] >= 0) { *scope = kCdScopeBuffer; // (-1, -1, X) } else { - *scope = kCdScopeGlobal; // (..., ..., -1) + *scope = kCdScopeGlobal; // (-1, -1, -1) } break; } @@ -737,10 +747,6 @@ 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[kTabArg] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return false; - } if (argv[kWinArg] > 0) { *win = find_win_by_nr(&argvars[0], *tp); if (*win == NULL) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 71412d548d..1da6864edd 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6222,7 +6222,7 @@ static char **get_prevdir(CdScope scope) /// Deal with the side effects of changing the current directory. /// -/// @param scope Scope of the function call (global, tab, buffer or window). +/// @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. @@ -6275,9 +6275,9 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) } } -/// Change directory function used by :cd/:tcd/:bcd/:lcd Ex commands and the chdir() function. +/// 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, buffer or window). +/// @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) { @@ -6333,7 +6333,7 @@ bool changedir_func(char *new_dir, CdScope scope) return true; } -/// ":cd", ":tcd", ":bcd", ":lcd", ":chdir", "tchdir", ":bchdir", and ":lchdir". +/// ":cd", ":tcd", ":lcd", ":bcd", ":chdir", ":tchdir", ":lchdir", and ":bchdir". void ex_cd(exarg_T *eap) { char *new_dir = eap->arg; diff --git a/src/nvim/vim_defs.h b/src/nvim/vim_defs.h index ae33e4f2cd..41015e226e 100644 --- a/src/nvim/vim_defs.h +++ b/src/nvim/vim_defs.h @@ -45,7 +45,7 @@ typedef enum { /// What caused the current directory to change. typedef enum { kCdCauseOther = -1, - kCdCauseManual, ///< Using `:cd`, `:bcd`, `:tcd`, `:lcd` or `chdir()`. + kCdCauseManual, ///< Using `:cd`, `:tcd`, `:lcd`, `:bcd` or `chdir()`. kCdCauseWindow, ///< Switching to another window. kCdCauseBuffer, ///< Switching to another buffer. kCdCauseAuto, ///< On 'autochdir'. diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 2057866d09..6385b6f93f 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -438,32 +438,32 @@ for _, cmd in ipairs { 'getcwd', 'haslocaldir' } do end) it('validation', function() - local err474 = 'Vim(call):E474: Invalid argument' - eq(err474, pcall_err(command, ('call %s("some string")'):format(cmd))) - eq(err474, pcall_err(command, ('call %s(1.0)'):format(cmd))) - eq(err474, pcall_err(command, ('call %s([1, 2])'):format(cmd))) - eq(err474, pcall_err(command, ('call %s({"key": "value"})'):format(cmd))) - eq(err474, pcall_err(command, ('call %s(function("tr"))'):format(cmd))) - eq(err474, pcall_err(command, ('call %s(-2)'):format(cmd))) + local err474 = 'Vim:E474: Invalid argument' + eq(err474, pcall_err(call, cmd, 'some string')) + eq(err474, pcall_err(call, cmd, 1.5)) + eq(err474, pcall_err(call, cmd, { 1, 2 })) + eq(err474, pcall_err(call, cmd, { key = 'value' })) + eq(err474, pcall_err(call, cmd, -2)) + -- Funcref is not representable over RPC. + eq( + 'Vim(call):E474: Invalid argument', + pcall_err(command, ('call %s(function("tr"))'):format(cmd)) + ) -- -1 preceded by an argument >= 0 + local err5001 = 'Vim:E5001: Higher scope cannot be -1 if lower scope 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)) + eq(err5001, pcall_err(call, cmd, 0, -1, 0)) + -- Buffer scope requires window and tab args to be -1. + local err5006 = 'Vim:E5006: Window and tab scope must be -1 when using buffer scope' + eq(err5006, pcall_err(call, cmd, 0, 0, 0)) + eq(err5006, pcall_err(call, cmd, 1, 2, 3)) + eq('Vim:E5007: Cannot find buffer number.', pcall_err(call, cmd, -1, -1, 99999)) eq( - 'Vim(call):E5001: Higher scope cannot be -1 if lower scope is >= 0.', - pcall_err(command, ('call %s(0, -1)'):format(cmd)) - ) - -- Buffer scope requires window and tab arguments of -1. - local err5006 = 'Vim(call):E5006: Window and tab scope must be -1 when using buffer scope' - eq(err5006, pcall_err(command, ('call %s(0, 0, 0)'):format(cmd))) - eq(err5006, pcall_err(command, ('call %s(1, 2, 3)'):format(cmd))) - -- Nonexistent buffer. - eq( - 'Vim(call):E5007: Cannot find buffer number.', - pcall_err(command, ('call %s(-1, -1, 99999)'):format(cmd)) - ) - -- Too many arguments. - eq( - ('Vim(call):E118: Too many arguments for function: %s'):format(cmd), - pcall_err(command, ('call %s(0, 0, 0, 0)'):format(cmd)) + ('Vim:E118: Too many arguments for function: %s'):format(cmd), + pcall_err(call, cmd, 0, 0, 0, 0) ) end) end)