mirror of
https://github.com/neovim/neovim.git
synced 2026-08-17 21:02:27 +00:00
feat(cwd)!: :lcd! (bang), rearrange :bcd/:lcd/… scope precedence #41194
Problem: - buf-local CWD scope is lower priority than :lcd, which is weird. ``` win > buf > tab > global ``` - No way to clear current CWD at a given scope. Solution: - Rerrange scope precedence to: ``` buf > win > tab > global ``` - Introduce "bang" variants (`:bcd!`/`:lcd!`/`:tcd!`) which clears the local CWD for the given scope.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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?`)
|
||||
|
||||
82
runtime/lua/vim/_meta/vimfn.gen.lua
generated
82
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -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)
|
||||
--- <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.
|
||||
--- <
|
||||
---
|
||||
--- @param winnr? integer
|
||||
--- @param tabnr? integer
|
||||
|
||||
@@ -690,7 +690,7 @@ void nvim_set_current_dir(String dir, Error *err)
|
||||
});
|
||||
|
||||
TRY_WRAP(err, {
|
||||
changedir_func(dir.data, kCdScopeGlobal);
|
||||
do_chdir(dir.data, kCdScopeGlobal);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1254,28 +1254,22 @@ M.funcs = {
|
||||
args = { 1, 2 },
|
||||
base = 1,
|
||||
desc = [=[
|
||||
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.
|
||||
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|
|
||||
|
||||
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|
|
||||
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".
|
||||
|
||||
{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
|
||||
@@ -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)
|
||||
<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.
|
||||
<
|
||||
]=],
|
||||
name = 'getcwd',
|
||||
params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' }, { 'bufnr', 'integer' } },
|
||||
returns = 'string',
|
||||
signature = 'getcwd([{winnr} [, {tabnr} [, {bufnr}]]])',
|
||||
tags = { 'E5000', 'E5001', 'E5002', 'E5006', 'E5007' },
|
||||
see_lua = {
|
||||
'|uv.cwd()| for the global working directory; tab-local and window-local scopes differ',
|
||||
},
|
||||
tags = { 'E5000', 'E5002', 'E5006', 'E5007' },
|
||||
},
|
||||
getenv = {
|
||||
args = 1,
|
||||
|
||||
@@ -420,15 +420,15 @@ void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
semsg(_(e_invargNval), "scope", s);
|
||||
return;
|
||||
}
|
||||
} else if (curwin->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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:')
|
||||
|
||||
Reference in New Issue
Block a user