mirror of
https://github.com/neovim/neovim.git
synced 2026-08-20 06:11:52 +00:00
Merge #33320 :bcd (buffer-local directory)
This commit is contained in:
@@ -615,13 +615,14 @@ DiffUpdated After diffs have been updated. Depending on
|
||||
*DirChanged*
|
||||
DirChanged After the |current-directory| was changed.
|
||||
The pattern can be:
|
||||
"window" to trigger on `:lcd`
|
||||
"tabpage" to trigger on `:tcd`
|
||||
"global" to trigger on `:cd`
|
||||
"auto" to trigger on 'autochdir'.
|
||||
"auto" triggered on 'autochdir'.
|
||||
"buffer" triggered on `:bcd`.
|
||||
"global" triggered on `:cd`.
|
||||
"tabpage" triggered on `:tcd`.
|
||||
"window" triggered on `:lcd`.
|
||||
Sets these |v:event| keys:
|
||||
cwd: current working directory
|
||||
scope: "global", "tabpage", "window"
|
||||
scope: "global", "tabpage", "window", "buffer"
|
||||
changed_window: v:true if we fired the event
|
||||
switching window (or tab)
|
||||
<afile> is set to the new directory name.
|
||||
@@ -633,7 +634,7 @@ DirChangedPre When the |current-directory| is going to be
|
||||
The pattern is like with |DirChanged|.
|
||||
Sets these |v:event| keys:
|
||||
directory: new working directory
|
||||
scope: "global", "tabpage", "window"
|
||||
scope: "global", "tabpage", "window", "buffer"
|
||||
changed_window: v:true if we fired the event
|
||||
switching window (or tab)
|
||||
<afile> is set to the new directory name.
|
||||
|
||||
@@ -1348,15 +1348,16 @@ use has("browsefilter"): >
|
||||
==============================================================================
|
||||
7. The current directory *current-directory*
|
||||
|
||||
There are three current-directory "scopes": global, tab and window. You can
|
||||
use |:cd|, |:tcd| and |:lcd| 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".
|
||||
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".
|
||||
|
||||
Use |getcwd()| to get the effective directory from the current scope. The
|
||||
window-local working directory takes precedence over the tab-local working
|
||||
directory, which in turn takes precedence over the global working directory.
|
||||
If a local working directory (tab or window) does not exist, the next-higher
|
||||
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.
|
||||
|
||||
*:cd* *E747* *E472*
|
||||
@@ -1387,10 +1388,11 @@ scope in the hierarchy applies.
|
||||
|
||||
*:tc* *:tcd*
|
||||
:tc[d][!] {path} Like |:cd|, but only set the directory for the current
|
||||
tab. The current window 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.
|
||||
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
|
||||
@@ -1402,7 +1404,7 @@ scope in the hierarchy applies.
|
||||
*:lc* *:lcd*
|
||||
:lc[d][!] {path} Like |:cd|, but only set the current directory for the
|
||||
current window. The current directory for other
|
||||
windows or tabs is not changed.
|
||||
tabs, windows, or buffers is not changed.
|
||||
|
||||
*:lch* *:lchdir*
|
||||
:lch[dir][!] Same as |:lcd|.
|
||||
@@ -1411,6 +1413,22 @@ 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.
|
||||
|
||||
Unlike :lcd and :tcd, this is not "sticky": a new
|
||||
buffer or window will not "inherit" a buffer-local
|
||||
directory.
|
||||
|
||||
*: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()|.
|
||||
@@ -1422,24 +1440,36 @@ 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
|
||||
" 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 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 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -247,6 +247,8 @@ DIAGNOSTICS
|
||||
|
||||
EDITOR
|
||||
|
||||
• |:bcd| sets a buffer-local directory. |getcwd()| and |haslocaldir()| take
|
||||
`bufnr` as a third parameter.
|
||||
• |: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"
|
||||
|
||||
@@ -1323,7 +1323,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
'cdhome' 'cdh' boolean (default on on Unix, off on Windows)
|
||||
global
|
||||
Disallowed in |modeline|. |no-modeline-option|
|
||||
When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
|
||||
When on, |:cd|, |:tcd|, |:lcd| and |:bcd| without an argument changes the
|
||||
current working directory to the |$HOME| directory like in Unix.
|
||||
When off, those commands just print the current directory name.
|
||||
|
||||
@@ -1332,7 +1332,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
global
|
||||
Disallowed in |modeline|. |no-modeline-option|
|
||||
This is a list of directories which will be searched when using the
|
||||
|:cd|, |:tcd| and |:lcd| commands, provided that the directory being
|
||||
|:cd|, |:tcd|, |:lcd| and |:bcd| commands, provided that the directory being
|
||||
searched for has a relative path, not an absolute part starting with
|
||||
"/", "./" or "../", the 'cdpath' option is not used then.
|
||||
The 'cdpath' option's value has the same form and semantics as
|
||||
@@ -2058,7 +2058,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
following occurrence.
|
||||
*cpo-~*
|
||||
~ When included, don't resolve symbolic links when
|
||||
changing directory with |:cd|, |:lcd|, or |:tcd|.
|
||||
changing directory with |:cd|, |:tcd|, |:lcd|, or |:bcd|.
|
||||
This preserves the symbolic link path in buffer names
|
||||
and when displaying the current directory. When
|
||||
excluded (default), symbolic links are resolved to
|
||||
|
||||
@@ -312,6 +312,7 @@ Command-line:
|
||||
is open. The |cmdwin-char| is shown via 'statuscolumn'.
|
||||
|
||||
Commands:
|
||||
- |:bcd|
|
||||
- |:checkhealth|
|
||||
- |:connect|
|
||||
- |:detach|
|
||||
@@ -695,8 +696,8 @@ Vimscript compatibility:
|
||||
Working directory (Vim implemented some of these after Nvim):
|
||||
- |DirChanged| and |DirChangedPre| can be triggered when switching to another
|
||||
window or tab.
|
||||
- |getcwd()| and |haslocaldir()| may throw errors if the tabpage or window
|
||||
cannot be found. *E5000* *E5001* *E5002*
|
||||
- |getcwd()| and |haslocaldir()| may throw errors if the tabpage, window, or
|
||||
buffer cannot be found.
|
||||
- |haslocaldir()| checks for tab-local directory if and only if -1 is passed as
|
||||
window number, and its only possible returns values are 0 and 1.
|
||||
- `getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global
|
||||
|
||||
@@ -1117,12 +1117,15 @@ chdir({dir} [, {scope}]) *chdir()*
|
||||
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 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|
|
||||
@@ -3745,31 +3748,49 @@ getcursorcharpos([{winid}]) *getcursorcharpos()*
|
||||
Return: ~
|
||||
(`any`)
|
||||
|
||||
getcwd([{winnr} [, {tabnr}]]) *getcwd()*
|
||||
*getcwd()* *E5000* *E5001* *E5002* *E5006* *E5007*
|
||||
getcwd([{winnr} [, {tabnr} [, {bufnr}]]])
|
||||
Lua: Prefer |uv.cwd()| for the global working directory;
|
||||
tab-local and window-local scopes differ.
|
||||
|
||||
With no arguments, returns the name of the effective
|
||||
|current-directory|. With {winnr} or {tabnr} the working
|
||||
directory of that scope is returned, and 'autochdir' is
|
||||
ignored. Tabs and windows are identified by their respective
|
||||
numbers, 0 means current tab or window. Missing tab number
|
||||
implies 0. Thus the following are equivalent: >vim
|
||||
|current-directory|. With {winnr} or {tabnr} or {bufnr} the
|
||||
working directory of that scope is returned, and 'autochdir'
|
||||
is ignored.
|
||||
|
||||
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|.
|
||||
If both {winnr} and {tabnr} are -1 the global working
|
||||
directory is returned.
|
||||
|
||||
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}.
|
||||
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
|
||||
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
|
||||
getcwd(-1, -1, 0) " Get current buffer's directory
|
||||
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?`)
|
||||
• {tabnr} (`integer?`)
|
||||
• {bufnr} (`integer?`)
|
||||
|
||||
Return: ~
|
||||
(`string`)
|
||||
@@ -4994,14 +5015,16 @@ has_key({dict}, {key}) *has_key()*
|
||||
Return: ~
|
||||
(`0|1`)
|
||||
|
||||
haslocaldir([{winnr} [, {tabnr}]]) *haslocaldir()*
|
||||
Checks whether the window or tabpage 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|, otherwise 0.
|
||||
haslocaldir([{winnr} [, {tabnr} [, {bufnr} ]]]) *haslocaldir()*
|
||||
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 and windows are identified by their respective numbers,
|
||||
0 means current tab or window. Missing argument implies 0.
|
||||
Tabs, windows and buffers are identified by their respective
|
||||
numbers, 0 means current tab/window/buffer. Missing {winnr}
|
||||
or {tabnr} implies 0 (missing {bufnr} does not; see below).
|
||||
Thus the following are equivalent: >vim
|
||||
echo haslocaldir()
|
||||
echo haslocaldir(0)
|
||||
@@ -5010,11 +5033,19 @@ haslocaldir([{winnr} [, {tabnr}]]) *haslocaldir()*
|
||||
With {winnr} and {tabnr} use the window in that tabpage.
|
||||
{winnr} is a |window-number| or |window-ID|.
|
||||
If {winnr} is -1 it is ignored, only the tab is resolved.
|
||||
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
If {bufnr} is provided, {winnr} and {tabnr} must be -1 and
|
||||
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?
|
||||
< Throw error if the arguments are invalid.
|
||||
|E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
|
||||
Parameters: ~
|
||||
• {winnr} (`integer?`)
|
||||
• {tabnr} (`integer?`)
|
||||
• {bufnr} (`integer?`)
|
||||
|
||||
Return: ~
|
||||
(`0|1`)
|
||||
|
||||
6
runtime/lua/vim/_meta/options.gen.lua
generated
6
runtime/lua/vim/_meta/options.gen.lua
generated
@@ -734,7 +734,7 @@ vim.o.cmp = vim.o.casemap
|
||||
vim.go.casemap = vim.o.casemap
|
||||
vim.go.cmp = vim.go.casemap
|
||||
|
||||
--- When on, `:cd`, `:tcd` and `:lcd` without an argument changes the
|
||||
--- When on, `:cd`, `:tcd`, `:lcd` and `:bcd` without an argument changes the
|
||||
--- current working directory to the `$HOME` directory like in Unix.
|
||||
--- When off, those commands just print the current directory name.
|
||||
---
|
||||
@@ -745,7 +745,7 @@ vim.go.cdhome = vim.o.cdhome
|
||||
vim.go.cdh = vim.go.cdhome
|
||||
|
||||
--- This is a list of directories which will be searched when using the
|
||||
--- `:cd`, `:tcd` and `:lcd` commands, provided that the directory being
|
||||
--- `:cd`, `:tcd`, `:lcd` and `:bcd` commands, provided that the directory being
|
||||
--- searched for has a relative path, not an absolute part starting with
|
||||
--- "/", "./" or "../", the 'cdpath' option is not used then.
|
||||
--- The 'cdpath' option's value has the same form and semantics as
|
||||
@@ -1572,7 +1572,7 @@ vim.bo.ci = vim.bo.copyindent
|
||||
--- following occurrence.
|
||||
--- *cpo-~*
|
||||
--- ~ When included, don't resolve symbolic links when
|
||||
--- changing directory with `:cd`, `:lcd`, or `:tcd`.
|
||||
--- changing directory with `:cd`, `:tcd`, `:lcd`, or `:bcd`.
|
||||
--- This preserves the symbolic link path in buffer names
|
||||
--- and when displaying the current directory. When
|
||||
--- excluded (default), symbolic links are resolved to
|
||||
|
||||
64
runtime/lua/vim/_meta/vimfn.gen.lua
generated
64
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -952,12 +952,15 @@ function vim.fn.charidx(string, idx, countcc, utf16) end
|
||||
--- 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 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|
|
||||
@@ -3318,27 +3321,44 @@ function vim.fn.getcursorcharpos(winid) end
|
||||
--- Lua: Prefer |uv.cwd()| for the global working directory; tab-local and window-local scopes differ.
|
||||
---
|
||||
--- With no arguments, returns the name of the effective
|
||||
--- |current-directory|. With {winnr} or {tabnr} the working
|
||||
--- directory of that scope is returned, and 'autochdir' is
|
||||
--- ignored. Tabs and windows are identified by their respective
|
||||
--- numbers, 0 means current tab or window. Missing tab number
|
||||
--- implies 0. Thus the following are equivalent: >vim
|
||||
--- |current-directory|. With {winnr} or {tabnr} or {bufnr} the
|
||||
--- working directory of that scope is returned, and 'autochdir'
|
||||
--- is ignored.
|
||||
---
|
||||
--- 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|.
|
||||
--- If both {winnr} and {tabnr} are -1 the global working
|
||||
--- directory is returned.
|
||||
---
|
||||
--- 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}.
|
||||
--- Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
---
|
||||
--- 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
|
||||
--- getcwd(-1, -1, 0) " Get current buffer's directory
|
||||
--- 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
|
||||
--- @param bufnr? integer
|
||||
--- @return string
|
||||
function vim.fn.getcwd(winnr, tabnr) end
|
||||
function vim.fn.getcwd(winnr, tabnr, bufnr) end
|
||||
|
||||
--- Lua: Prefer |vim.env|.
|
||||
---
|
||||
@@ -4445,13 +4465,15 @@ function vim.fn.has(feature) end
|
||||
--- @return 0|1
|
||||
function vim.fn.has_key(dict, key) end
|
||||
|
||||
--- Checks whether the window or tabpage 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|, otherwise 0.
|
||||
--- 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 and windows are identified by their respective numbers,
|
||||
--- 0 means current tab or window. Missing argument implies 0.
|
||||
--- Tabs, windows and buffers are identified by their respective
|
||||
--- numbers, 0 means current tab/window/buffer. Missing {winnr}
|
||||
--- or {tabnr} implies 0 (missing {bufnr} does not; see below).
|
||||
--- Thus the following are equivalent: >vim
|
||||
--- echo haslocaldir()
|
||||
--- echo haslocaldir(0)
|
||||
@@ -4460,12 +4482,20 @@ function vim.fn.has_key(dict, key) end
|
||||
--- With {winnr} and {tabnr} use the window in that tabpage.
|
||||
--- {winnr} is a |window-number| or |window-ID|.
|
||||
--- If {winnr} is -1 it is ignored, only the tab is resolved.
|
||||
--- Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
--- If {bufnr} is provided, {winnr} and {tabnr} must be -1 and
|
||||
--- 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?
|
||||
--- <Throw error if the arguments are invalid.
|
||||
--- |E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
---
|
||||
--- @param winnr? integer
|
||||
--- @param tabnr? integer
|
||||
--- @param bufnr? integer
|
||||
--- @return 0|1
|
||||
function vim.fn.haslocaldir(winnr, tabnr) end
|
||||
function vim.fn.haslocaldir(winnr, tabnr, bufnr) end
|
||||
|
||||
--- Checks whether a mapping exists whose rhs contains {what}.
|
||||
--- Returns TRUE if there is such a mapping in one of the modes
|
||||
|
||||
@@ -38,9 +38,6 @@ local dup_allowed = {
|
||||
E482 = 3,
|
||||
E484 = 2,
|
||||
E488 = 2,
|
||||
E5000 = 2,
|
||||
E5001 = 2,
|
||||
E5002 = 2,
|
||||
E5009 = 3,
|
||||
E502 = 2,
|
||||
E503 = 3,
|
||||
|
||||
@@ -98,7 +98,7 @@ local function split(x, sep)
|
||||
end
|
||||
|
||||
--- @param f string
|
||||
--- @param params [string,string][]|true
|
||||
--- @param params [string,string,string?][]|true
|
||||
--- @return string
|
||||
local function render_fun_sig(f, params)
|
||||
local param_str --- @type string
|
||||
@@ -107,7 +107,7 @@ local function render_fun_sig(f, params)
|
||||
else
|
||||
param_str = table.concat(
|
||||
vim.tbl_map(
|
||||
--- @param v [string,string]
|
||||
--- @param v [string,string,string?]
|
||||
--- @return string
|
||||
function(v)
|
||||
return luaescape(v[1])
|
||||
@@ -126,8 +126,8 @@ local function render_fun_sig(f, params)
|
||||
end
|
||||
|
||||
--- Uniquify names
|
||||
--- @param params [string,string,string][]
|
||||
--- @return [string,string,string][]
|
||||
--- @param params [string,string,string?][]
|
||||
--- @return [string,string,string?][]
|
||||
local function process_params(params)
|
||||
local seen = {} --- @type table<string,true>
|
||||
local sfx = 1
|
||||
@@ -192,7 +192,7 @@ local function get_api_meta()
|
||||
sees[#sees + 1] = see.desc
|
||||
end
|
||||
|
||||
local params = {} --- @type [string,string][]
|
||||
local params = {} --- @type [string,string,string?][]
|
||||
for _, p in ipairs(fun.params) do
|
||||
params[#params + 1] = {
|
||||
p.name,
|
||||
|
||||
@@ -938,6 +938,9 @@ bool buf_freeall(buf_T *buf, int flags)
|
||||
}
|
||||
syntax_clear(&buf->b_s); // reset syntax info
|
||||
buf->b_flags &= ~BF_READERR; // a read error is no longer relevant
|
||||
|
||||
XFREE_CLEAR(buf->b_localdir);
|
||||
XFREE_CLEAR(buf->b_prevdir);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1023,6 +1026,8 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
|
||||
map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings
|
||||
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
|
||||
XFREE_CLEAR(buf->b_start_fenc);
|
||||
XFREE_CLEAR(buf->b_localdir);
|
||||
XFREE_CLEAR(buf->b_prevdir);
|
||||
|
||||
buf_free_callbacks(buf);
|
||||
}
|
||||
@@ -1771,6 +1776,9 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
|
||||
if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) {
|
||||
terminal_check_size(prevbuf->terminal);
|
||||
}
|
||||
|
||||
// Maybe cd to buffer-local directory
|
||||
update_cwd(kCdCauseBuffer);
|
||||
}
|
||||
|
||||
/// Enter a new current buffer.
|
||||
|
||||
@@ -767,6 +767,9 @@ struct file_buffer {
|
||||
|
||||
// The number for times the current line has been flushed in the memline.
|
||||
int flush_count;
|
||||
|
||||
char *b_localdir; ///< Absolute path of local cwd or NULL.
|
||||
char *b_prevdir; ///< Previous directory.
|
||||
};
|
||||
|
||||
// Stuff for diff mode.
|
||||
|
||||
@@ -256,6 +256,7 @@ static void ctx_cwd_save(CtxSwitch *cs, win_T *wp, tabpage_T *tp)
|
||||
char cwd[MAXPATHL];
|
||||
if (curwin != wp
|
||||
&& (curwin->w_localdir != NULL || (wp != NULL && wp->w_localdir != NULL)
|
||||
|| curbuf->b_localdir != NULL || (wp != NULL && wp->w_buffer->b_localdir != NULL)
|
||||
|| (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL))
|
||||
|| p_acd)) {
|
||||
cs->cs_cwd_status = os_dirname(cwd, MAXPATHL);
|
||||
@@ -342,9 +343,12 @@ static win_T *ctx_win_prep(CtxSwitch *cs, buf_T *buf)
|
||||
buf->b_nwindows++;
|
||||
win_init_empty(cw_win); // set cursor and topline to safe values
|
||||
|
||||
// Make sure w_localdir, tp_localdir, globaldir are NULL: the switched-to code runs in the actual
|
||||
// cwd (no chdir on switch), and a pooled tmp-window must not carry a stale w_localdir.
|
||||
// Make sure w_localdir, b_localdir, tp_localdir, globaldir are NULL: the switched-to code runs
|
||||
// in the actual cwd (no chdir on switch), and a pooled tmp-window must not carry a stale
|
||||
// w_localdir.
|
||||
XFREE_CLEAR(cw_win->w_localdir);
|
||||
cs->cs_b_localdir = buf->b_localdir;
|
||||
buf->b_localdir = NULL;
|
||||
cs->cs_tp_localdir = curtab->tp_localdir;
|
||||
curtab->tp_localdir = NULL;
|
||||
cs->cs_globaldir = globaldir;
|
||||
@@ -577,9 +581,15 @@ void ctx_restore(CtxSwitch *cs)
|
||||
hash_init(&cwp->w_vars->dv_hashtab); // re-use the hashtab
|
||||
|
||||
// If :lcd has been used in the autocommand window, correct current
|
||||
// directory before restoring tp_localdir and globaldir.
|
||||
// directory before restoring b_localdir, tp_localdir and globaldir.
|
||||
if (cwp->w_localdir != NULL) {
|
||||
win_fix_current_dir();
|
||||
update_cwd(kCdCauseWindow);
|
||||
}
|
||||
if (bufref_valid(&cs->cs_new_curbuf)) {
|
||||
xfree(cs->cs_new_curbuf.br_buf->b_localdir);
|
||||
cs->cs_new_curbuf.br_buf->b_localdir = cs->cs_b_localdir;
|
||||
} else {
|
||||
xfree(cs->cs_b_localdir);
|
||||
}
|
||||
xfree(curtab->tp_localdir);
|
||||
curtab->tp_localdir = cs->cs_tp_localdir;
|
||||
|
||||
@@ -82,6 +82,7 @@ typedef struct {
|
||||
handle_T cs_target_win; ///< the window switched to
|
||||
pos_T cs_target_old_pos; ///< its cursor before the switch
|
||||
// State kept across the switch:
|
||||
char *cs_b_localdir; ///< saved b_localdir of the target buffer (autocmd window)
|
||||
char *cs_tp_localdir; ///< saved tp_localdir (autocmd window)
|
||||
char *cs_globaldir; ///< saved globaldir (autocmd window)
|
||||
char *cs_cwd; ///< saved cwd (kCtxKeepCwd; allocated on demand)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
--- @field generics? string[] Used to write `---@generic` annotations over a function.
|
||||
--- @field signature? string
|
||||
--- @field desc? string
|
||||
--- @field params [string, string, string][]
|
||||
--- @field params [string, string, string?][] Each entry is `{name, type, desc?}`; `desc` is only present in API-meta entries.
|
||||
--- @field notes? string[]
|
||||
--- @field see? string[]
|
||||
---
|
||||
@@ -1257,12 +1257,15 @@ M.funcs = {
|
||||
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 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|
|
||||
@@ -4089,32 +4092,48 @@ M.funcs = {
|
||||
signature = 'getcursorcharpos([{winid}])',
|
||||
},
|
||||
getcwd = {
|
||||
args = { 0, 2 },
|
||||
args = { 0, 3 },
|
||||
base = 1,
|
||||
desc = [=[
|
||||
With no arguments, returns the name of the effective
|
||||
|current-directory|. With {winnr} or {tabnr} the working
|
||||
directory of that scope is returned, and 'autochdir' is
|
||||
ignored. Tabs and windows are identified by their respective
|
||||
numbers, 0 means current tab or window. Missing tab number
|
||||
implies 0. Thus the following are equivalent: >vim
|
||||
|current-directory|. With {winnr} or {tabnr} or {bufnr} the
|
||||
working directory of that scope is returned, and 'autochdir'
|
||||
is ignored.
|
||||
|
||||
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|.
|
||||
If both {winnr} and {tabnr} are -1 the global working
|
||||
directory is returned.
|
||||
|
||||
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}.
|
||||
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
|
||||
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
|
||||
getcwd(-1, -1, 0) " Get current buffer's directory
|
||||
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' } },
|
||||
params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' }, { 'bufnr', 'integer' } },
|
||||
returns = 'string',
|
||||
signature = 'getcwd([{winnr} [, {tabnr}]])',
|
||||
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',
|
||||
},
|
||||
@@ -5410,16 +5429,18 @@ M.funcs = {
|
||||
see_lua = { '`dict[key] ~= nil`' },
|
||||
},
|
||||
haslocaldir = {
|
||||
args = { 0, 2 },
|
||||
args = { 0, 3 },
|
||||
base = 1,
|
||||
desc = [=[
|
||||
Checks whether the window or tabpage 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|, otherwise 0.
|
||||
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 and windows are identified by their respective numbers,
|
||||
0 means current tab or window. Missing argument implies 0.
|
||||
Tabs, windows and buffers are identified by their respective
|
||||
numbers, 0 means current tab/window/buffer. Missing {winnr}
|
||||
or {tabnr} implies 0 (missing {bufnr} does not; see below).
|
||||
Thus the following are equivalent: >vim
|
||||
echo haslocaldir()
|
||||
echo haslocaldir(0)
|
||||
@@ -5428,13 +5449,20 @@ M.funcs = {
|
||||
With {winnr} and {tabnr} use the window in that tabpage.
|
||||
{winnr} is a |window-number| or |window-ID|.
|
||||
If {winnr} is -1 it is ignored, only the tab is resolved.
|
||||
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
|
||||
If {bufnr} is provided, {winnr} and {tabnr} must be -1 and
|
||||
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?
|
||||
<Throw error if the arguments are invalid.
|
||||
|E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
|
||||
]=],
|
||||
name = 'haslocaldir',
|
||||
params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
|
||||
params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' }, { 'bufnr', 'integer' } },
|
||||
returns = '0|1',
|
||||
signature = 'haslocaldir([{winnr} [, {tabnr}]])',
|
||||
signature = 'haslocaldir([{winnr} [, {tabnr} [, {bufnr} ]]])',
|
||||
},
|
||||
hasmapto = {
|
||||
args = { 1, 3 },
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "auto/config.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/cmdexpand.h"
|
||||
@@ -419,6 +420,8 @@ void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
}
|
||||
} else if (curwin->w_localdir != NULL) {
|
||||
scope = kCdScopeWindow;
|
||||
} else if (curbuf->b_localdir != NULL) {
|
||||
scope = kCdScopeBuffer;
|
||||
} else if (curtab->tp_localdir != NULL) {
|
||||
scope = kCdScopeTabpage;
|
||||
}
|
||||
@@ -641,98 +644,164 @@ void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
xfree(fbuf);
|
||||
}
|
||||
|
||||
/// `getcwd([{win}[, {tab}]])` function
|
||||
/// Resolves the `[{winnr}[, {tabnr}[, {bufnr}]]]` arguments of `getcwd()`/`haslocaldir()` to the
|
||||
/// requested scope and the scope objects to inspect. In an argument, `0` means the current object
|
||||
/// and `-1` skips the scope (requesting the next broader one). Note that the argument order
|
||||
/// differs from the `CdScope` order.
|
||||
///
|
||||
/// @param default_scope Scope when no arguments were given.
|
||||
///
|
||||
/// @return false if an error message was emitted.
|
||||
static bool getcwd_scope_args(typval_T *argvars, CdScope default_scope, CdScope *scope, win_T **win,
|
||||
tabpage_T **tp, buf_T **buf)
|
||||
{
|
||||
enum { kWinArg, kTabArg, kBufArg, };
|
||||
int argv[] = { 0, 0, 0 };
|
||||
int argc = 0;
|
||||
|
||||
*scope = default_scope;
|
||||
*win = curwin;
|
||||
*tp = curtab;
|
||||
*buf = curbuf;
|
||||
|
||||
for (; argc < 3; argc++) {
|
||||
// If there is no argument there are no more scopes after it, break out.
|
||||
if (argvars[argc].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[argc].v_type != VAR_NUMBER) {
|
||||
emsg(_(e_invarg));
|
||||
return false;
|
||||
}
|
||||
argv[argc] = (int)argvars[argc].vval.v_number;
|
||||
// It is an error for the scope number to be less than `-1`.
|
||||
if (argv[argc] < -1) {
|
||||
emsg(_(e_invarg));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
break;
|
||||
case 1:
|
||||
if (argv[kWinArg] >= 0) {
|
||||
*scope = kCdScopeWindow; // (X)
|
||||
} else {
|
||||
*scope = kCdScopeTabpage; // (-1)
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (argv[kWinArg] >= 0) {
|
||||
*scope = kCdScopeWindow; // (X, ...)
|
||||
} else if (argv[kTabArg] >= 0) {
|
||||
*scope = kCdScopeTabpage; // (-1, X)
|
||||
} else {
|
||||
*scope = kCdScopeGlobal; // (-1, -1)
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (argv[kBufArg] >= 0) {
|
||||
*scope = kCdScopeBuffer; // (-1, -1, X)
|
||||
} else {
|
||||
*scope = kCdScopeGlobal; // (-1, -1, -1)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (*scope == kCdScopeBuffer) {
|
||||
if (argv[kWinArg] >= 0 || argv[kTabArg] >= 0) {
|
||||
emsg(_("E5006: Window and tab scope must be -1 when using buffer scope"));
|
||||
return false;
|
||||
}
|
||||
if (argv[kBufArg] > 0) {
|
||||
Error err = ERROR_INIT;
|
||||
*buf = find_buffer_by_handle(argv[kBufArg], &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(_("E5007: Cannot find buffer number."));
|
||||
xfree(err.msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the tabpage by number.
|
||||
if (argv[kTabArg] > 0) {
|
||||
*tp = find_tabpage(argv[kTabArg]);
|
||||
if (*tp == NULL) {
|
||||
emsg(_("E5000: Cannot find tab number."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Window scope may fall through to the buffer shown in that window.
|
||||
*buf = (*win)->w_buffer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// `getcwd([{win}[, {tab}[, {buf}]]])` function
|
||||
///
|
||||
/// Every scope not specified implies the currently selected scope object.
|
||||
///
|
||||
/// @pre The arguments must be of type number.
|
||||
/// @pre There may not be more than two arguments.
|
||||
/// @pre There may not be more than three arguments.
|
||||
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
||||
///
|
||||
/// @post The return value will be a string.
|
||||
void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
// Possible scope of working directory to return.
|
||||
CdScope scope = kCdScopeInvalid;
|
||||
|
||||
// Numbers of the scope objects (window, tab) we want the working directory
|
||||
// of. A `-1` means to skip this scope, a `0` means the current object.
|
||||
int scope_number[] = {
|
||||
[kCdScopeWindow] = 0, // Number of window to look at.
|
||||
[kCdScopeTabpage] = 0, // Number of tab to look at.
|
||||
};
|
||||
|
||||
char *cwd = NULL; // Current working directory to print
|
||||
char *from = NULL; // The original string to copy
|
||||
|
||||
tabpage_T *tp = curtab; // The tabpage to look at.
|
||||
win_T *win = curwin; // The window to look at.
|
||||
CdScope scope;
|
||||
win_T *win;
|
||||
tabpage_T *tp;
|
||||
buf_T *buf;
|
||||
|
||||
rettv->v_type = VAR_STRING;
|
||||
rettv->vval.v_string = NULL;
|
||||
|
||||
// Pre-conditions and scope extraction together
|
||||
for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
|
||||
// If there is no argument there are no more scopes after it, break out.
|
||||
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[i].v_type != VAR_NUMBER) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
scope_number[i] = (int)argvars[i].vval.v_number;
|
||||
// It is an error for the scope number to be less than `-1`.
|
||||
if (scope_number[i] < -1) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
// Use the narrowest scope the user requested
|
||||
if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
|
||||
// The scope is the current iteration step.
|
||||
scope = i;
|
||||
} else if (scope_number[i] < 0) {
|
||||
scope = i + 1;
|
||||
}
|
||||
// Without arguments (kCdScopeInvalid), the effective working directory is returned.
|
||||
if (!getcwd_scope_args(argvars, kCdScopeInvalid, &scope, &win, &tp, &buf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (scope_number[kCdScopeTabpage] > 0) {
|
||||
tp = find_tabpage(scope_number[kCdScopeTabpage]);
|
||||
if (!tp) {
|
||||
emsg(_("E5000: Cannot find tab number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (scope_number[kCdScopeWindow] >= 0) {
|
||||
if (scope_number[kCdScopeTabpage] < 0) {
|
||||
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope_number[kCdScopeWindow] > 0) {
|
||||
win = find_win_by_nr(&argvars[0], tp);
|
||||
if (!win) {
|
||||
emsg(_("E5002: Cannot find window number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cwd = xmalloc(MAXPATHL);
|
||||
char *from = NULL; // The local directory to copy.
|
||||
char *cwd = xmalloc(MAXPATHL);
|
||||
|
||||
switch (scope) {
|
||||
case kCdScopeWindow:
|
||||
assert(win);
|
||||
from = win->w_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case kCdScopeBuffer:
|
||||
from = buf->b_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case kCdScopeTabpage:
|
||||
assert(tp);
|
||||
from = tp->tp_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
@@ -959,96 +1028,40 @@ void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
rettv->vval.v_string = pat == NULL ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false);
|
||||
}
|
||||
|
||||
/// `haslocaldir([{win}[, {tab}]])` function
|
||||
/// `haslocaldir([{win}[, {tab}[, {buf}]]])` function
|
||||
///
|
||||
/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
|
||||
/// scope object is not specified the current one is implied. This function
|
||||
/// share a lot of code with `f_getcwd`.
|
||||
/// scope object is not specified the current one is implied.
|
||||
///
|
||||
/// @pre The arguments must be of type number.
|
||||
/// @pre There may not be more than two arguments.
|
||||
/// @pre There may not be more than three arguments.
|
||||
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
||||
///
|
||||
/// @post The return value will be either the number `1` or `0`.
|
||||
void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
// Possible scope of working directory to return.
|
||||
CdScope scope = kCdScopeInvalid;
|
||||
|
||||
// Numbers of the scope objects (window, tab) we want the working directory
|
||||
// of. A `-1` means to skip this scope, a `0` means the current object.
|
||||
int scope_number[] = {
|
||||
[kCdScopeWindow] = 0, // Number of window to look at.
|
||||
[kCdScopeTabpage] = 0, // Number of tab to look at.
|
||||
};
|
||||
|
||||
tabpage_T *tp = curtab; // The tabpage to look at.
|
||||
win_T *win = curwin; // The window to look at.
|
||||
CdScope scope;
|
||||
win_T *win;
|
||||
tabpage_T *tp;
|
||||
buf_T *buf;
|
||||
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = 0;
|
||||
|
||||
// Pre-conditions and scope extraction together
|
||||
for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
|
||||
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[i].v_type != VAR_NUMBER) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
scope_number[i] = (int)argvars[i].vval.v_number;
|
||||
if (scope_number[i] < -1) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
// Use the narrowest scope the user requested
|
||||
if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
|
||||
// The scope is the current iteration step.
|
||||
scope = i;
|
||||
} else if (scope_number[i] < 0) {
|
||||
scope = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If the user didn't specify anything, default to window scope
|
||||
if (scope == kCdScopeInvalid) {
|
||||
scope = MIN_CD_SCOPE;
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (scope_number[kCdScopeTabpage] > 0) {
|
||||
tp = find_tabpage(scope_number[kCdScopeTabpage]);
|
||||
if (!tp) {
|
||||
emsg(_("E5000: Cannot find tab number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (scope_number[kCdScopeWindow] >= 0) {
|
||||
if (scope_number[kCdScopeTabpage] < 0) {
|
||||
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope_number[kCdScopeWindow] > 0) {
|
||||
win = find_win_by_nr(&argvars[0], tp);
|
||||
if (!win) {
|
||||
emsg(_("E5002: Cannot find window number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Without arguments, default to window scope.
|
||||
if (!getcwd_scope_args(argvars, kCdScopeWindow, &scope, &win, &tp, &buf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case kCdScopeWindow:
|
||||
assert(win);
|
||||
rettv->vval.v_number = win->w_localdir ? 1 : 0;
|
||||
rettv->vval.v_number = win->w_localdir != NULL;
|
||||
break;
|
||||
case kCdScopeBuffer:
|
||||
rettv->vval.v_number = buf->b_localdir != NULL;
|
||||
break;
|
||||
case kCdScopeTabpage:
|
||||
assert(tp);
|
||||
rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
|
||||
rettv->vval.v_number = tp->tp_localdir != NULL;
|
||||
break;
|
||||
case kCdScopeGlobal:
|
||||
// The global scope never has a local directory
|
||||
|
||||
@@ -2937,6 +2937,9 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
|
||||
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
|
||||
}
|
||||
|
||||
// Update to the working directory of the new current buffer.
|
||||
update_cwd(kCdCauseBuffer);
|
||||
|
||||
// Change directories when the 'acd' option is set.
|
||||
do_autochdir();
|
||||
|
||||
|
||||
@@ -192,6 +192,18 @@ M.cmds = {
|
||||
addr_type = 'ADDR_NONE',
|
||||
func = 'ex_edit',
|
||||
},
|
||||
{
|
||||
command = 'bcd',
|
||||
flags = bit.bor(BANG, FILE1, TRLBAR, BUFLOCK_OK, LOCK_OK),
|
||||
addr_type = 'ADDR_NONE',
|
||||
func = 'ex_cd',
|
||||
},
|
||||
{
|
||||
command = 'bchdir',
|
||||
flags = bit.bor(BANG, FILE1, TRLBAR, BUFLOCK_OK, LOCK_OK),
|
||||
addr_type = 'ADDR_NONE',
|
||||
func = 'ex_cd',
|
||||
},
|
||||
{
|
||||
command = 'bdelete',
|
||||
flags = bit.bor(BANG, RANGE, BUFNAME, COUNT, EXTRA, TRLBAR),
|
||||
|
||||
@@ -6205,24 +6205,24 @@ void free_cd_dir(void)
|
||||
|
||||
#endif
|
||||
|
||||
/// Get the previous directory for the given chdir scope.
|
||||
static char *get_prevdir(CdScope scope)
|
||||
/// Gets the previous-directory slot for the given chdir scope.
|
||||
static char **get_prevdir(CdScope scope)
|
||||
{
|
||||
switch (scope) {
|
||||
case kCdScopeTabpage:
|
||||
return curtab->tp_prevdir;
|
||||
break;
|
||||
return &curtab->tp_prevdir;
|
||||
case kCdScopeBuffer:
|
||||
return &curbuf->b_prevdir;
|
||||
case kCdScopeWindow:
|
||||
return curwin->w_prevdir;
|
||||
break;
|
||||
return &curwin->w_prevdir;
|
||||
default:
|
||||
return prev_dir;
|
||||
return &prev_dir;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deal with the side effects of changing the current directory.
|
||||
///
|
||||
/// @param scope Scope of the function call (global, tab 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.
|
||||
@@ -6233,8 +6233,12 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged)
|
||||
XFREE_CLEAR(curtab->tp_localdir);
|
||||
}
|
||||
|
||||
if (scope >= kCdScopeBuffer) {
|
||||
XFREE_CLEAR(curbuf->b_localdir);
|
||||
}
|
||||
|
||||
if (scope < kCdScopeGlobal) {
|
||||
char *pdir = get_prevdir(scope);
|
||||
char *pdir = *get_prevdir(scope);
|
||||
// If still in global directory, set CWD as the global directory.
|
||||
if (globaldir == NULL && pdir != NULL) {
|
||||
globaldir = xstrdup(pdir);
|
||||
@@ -6253,6 +6257,9 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged)
|
||||
case kCdScopeTabpage:
|
||||
curtab->tp_localdir = xstrdup(cwd);
|
||||
break;
|
||||
case kCdScopeBuffer:
|
||||
curbuf->b_localdir = xstrdup(cwd);
|
||||
break;
|
||||
case kCdScopeWindow:
|
||||
curwin->w_localdir = xstrdup(cwd);
|
||||
break;
|
||||
@@ -6268,9 +6275,9 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged)
|
||||
}
|
||||
}
|
||||
|
||||
/// Change directory function used by :cd/:tcd/: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 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)
|
||||
{
|
||||
@@ -6281,7 +6288,7 @@ bool changedir_func(char *new_dir, CdScope scope)
|
||||
char *pdir = NULL;
|
||||
// ":cd -": Change to previous directory
|
||||
if (strcmp(new_dir, "-") == 0) {
|
||||
pdir = get_prevdir(scope);
|
||||
pdir = *get_prevdir(scope);
|
||||
if (pdir == NULL) {
|
||||
emsg(_("E186: No previous directory"));
|
||||
return false;
|
||||
@@ -6317,17 +6324,7 @@ bool changedir_func(char *new_dir, CdScope scope)
|
||||
}
|
||||
xfree(new_dir);
|
||||
|
||||
char **pp;
|
||||
switch (scope) {
|
||||
case kCdScopeTabpage:
|
||||
pp = &curtab->tp_prevdir;
|
||||
break;
|
||||
case kCdScopeWindow:
|
||||
pp = &curwin->w_prevdir;
|
||||
break;
|
||||
default:
|
||||
pp = &prev_dir;
|
||||
}
|
||||
char **pp = get_prevdir(scope);
|
||||
xfree(*pp);
|
||||
*pp = pdir;
|
||||
|
||||
@@ -6336,7 +6333,7 @@ bool changedir_func(char *new_dir, CdScope scope)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
|
||||
/// ":cd", ":tcd", ":lcd", ":bcd", ":chdir", ":tchdir", ":lchdir", and ":bchdir".
|
||||
void ex_cd(exarg_T *eap)
|
||||
{
|
||||
char *new_dir = eap->arg;
|
||||
@@ -6352,6 +6349,10 @@ void ex_cd(exarg_T *eap)
|
||||
case CMD_tchdir:
|
||||
scope = kCdScopeTabpage;
|
||||
break;
|
||||
case CMD_bcd:
|
||||
case CMD_bchdir:
|
||||
scope = kCdScopeBuffer;
|
||||
break;
|
||||
case CMD_lcd:
|
||||
case CMD_lchdir:
|
||||
scope = kCdScopeWindow;
|
||||
@@ -6380,6 +6381,8 @@ static void ex_pwd(exarg_T *eap)
|
||||
context = last_chdir_reason;
|
||||
} else if (curwin->w_localdir != NULL) {
|
||||
context = "window";
|
||||
} else if (curbuf->b_localdir != NULL) {
|
||||
context = "buffer";
|
||||
} else if (curtab->tp_localdir != NULL) {
|
||||
context = "tabpage";
|
||||
}
|
||||
|
||||
@@ -675,6 +675,31 @@ static int makeopens(FILE *fd, char *dirnow)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Restore buffer-local CWD (:bcd) via one-shot BufEnter handlers: there is no reliable point
|
||||
// during session-load where all buffers are entered. Each buffer's directory is applied when the
|
||||
// buffer is first entered, which may happen after session-load for buffers that stay hidden.
|
||||
if (ssop_flags & kOptSsopFlagCurdir) {
|
||||
FOR_ALL_BUFFERS(buf) {
|
||||
if (buf->b_localdir == NULL || buf->b_fname == NULL || !buf->b_p_bl
|
||||
|| (only_save_windows && buf->b_nwindows == 0)
|
||||
|| (buf->b_help && !(ssop_flags & kOptSsopFlagHelp))
|
||||
|| (bt_terminal(buf) && !(ssop_flags & kOptSsopFlagTerminal))) {
|
||||
continue;
|
||||
}
|
||||
// `bufadd()` finds the buffer by exact (literal) name.
|
||||
if (fputs("lua vim.api.nvim_create_autocmd('BufEnter', { once = true, "
|
||||
"buffer = vim.fn.bufadd([==[", fd) < 0
|
||||
|| fputs(ses_get_fname(buf, &ssop_flags), fd) < 0
|
||||
|| fputs("]==]), callback = function() vim.cmd.bcd({ [==[", fd) < 0
|
||||
|| fputs(buf->b_localdir, fd) < 0
|
||||
|| fputs("]==], magic = { file = false, bar = false } }) end })\n", fd) < 0) {
|
||||
return FAIL;
|
||||
}
|
||||
// Cwd may change when a handler fires: filenames must be written absolute from here on.
|
||||
did_lcd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ssop_flags & kOptSsopFlagResize) {
|
||||
// Note: after the restore we still check it worked!
|
||||
if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n",
|
||||
@@ -739,6 +764,23 @@ 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.
|
||||
if ((ssop_flags & kOptSsopFlagCurdir) && tp->tp_localdir != NULL) {
|
||||
if (need_tabnext && put_line(fd, "tabnext") == FAIL) {
|
||||
return FAIL;
|
||||
}
|
||||
need_tabnext = false;
|
||||
if (fputs("tcd ", fd) < 0
|
||||
|| ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
|
||||
|| put_eol(fd) == FAIL) {
|
||||
return FAIL;
|
||||
}
|
||||
did_lcd = true;
|
||||
}
|
||||
|
||||
// Before creating the window layout, try loading one file. If this
|
||||
// is aborted we don't end up with a number of useless windows.
|
||||
// This may have side effects! (e.g., compressed or network file).
|
||||
@@ -822,18 +864,6 @@ static int makeopens(FILE *fd, char *dirnow)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Restore the tab-local working directory if specified
|
||||
// Do this before the windows, so that the window-local directory can
|
||||
// override the tab-local directory.
|
||||
if ((ssop_flags & kOptSsopFlagCurdir) && tp->tp_localdir != NULL) {
|
||||
if (fputs("tcd ", fd) < 0
|
||||
|| ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
|
||||
|| put_eol(fd) == FAIL) {
|
||||
return FAIL;
|
||||
}
|
||||
did_lcd = true;
|
||||
}
|
||||
|
||||
// Restore the view of the window (options, file, cursor, etc.).
|
||||
for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
|
||||
if (!ses_do_win(wp)) {
|
||||
|
||||
@@ -1854,6 +1854,9 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre
|
||||
case kCdScopeTabpage:
|
||||
snprintf(buf, sizeof(buf), "tabpage");
|
||||
break;
|
||||
case kCdScopeBuffer:
|
||||
snprintf(buf, sizeof(buf), "buffer");
|
||||
break;
|
||||
case kCdScopeWindow:
|
||||
snprintf(buf, sizeof(buf), "window");
|
||||
break;
|
||||
@@ -1874,6 +1877,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre
|
||||
switch (cause) {
|
||||
case kCdCauseManual:
|
||||
case kCdCauseWindow:
|
||||
case kCdCauseBuffer:
|
||||
break;
|
||||
case kCdCauseAuto:
|
||||
snprintf(buf, sizeof(buf), "auto");
|
||||
|
||||
@@ -1093,7 +1093,7 @@ local options = {
|
||||
doc = [[on on Unix, off on Windows]],
|
||||
},
|
||||
desc = [=[
|
||||
When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
|
||||
When on, |:cd|, |:tcd|, |:lcd| and |:bcd| without an argument changes the
|
||||
current working directory to the |$HOME| directory like in Unix.
|
||||
When off, those commands just print the current directory name.
|
||||
]=],
|
||||
@@ -1113,7 +1113,7 @@ local options = {
|
||||
deny_duplicates = true,
|
||||
desc = [=[
|
||||
This is a list of directories which will be searched when using the
|
||||
|:cd|, |:tcd| and |:lcd| commands, provided that the directory being
|
||||
|:cd|, |:tcd|, |:lcd| and |:bcd| commands, provided that the directory being
|
||||
searched for has a relative path, not an absolute part starting with
|
||||
"/", "./" or "../", the 'cdpath' option is not used then.
|
||||
The 'cdpath' option's value has the same form and semantics as
|
||||
@@ -2119,7 +2119,7 @@ local options = {
|
||||
following occurrence.
|
||||
*cpo-~*
|
||||
~ When included, don't resolve symbolic links when
|
||||
changing directory with |:cd|, |:lcd|, or |:tcd|.
|
||||
changing directory with |:cd|, |:tcd|, |:lcd|, or |:bcd|.
|
||||
This preserves the symbolic link path in buffer names
|
||||
and when displaying the current directory. When
|
||||
excluded (default), symbolic links are resolved to
|
||||
|
||||
@@ -33,23 +33,21 @@ typedef enum {
|
||||
///
|
||||
/// 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()`. When using scopes as limits (e.g. in loops) don't use the scopes
|
||||
/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
|
||||
/// `getcwd()`.
|
||||
typedef enum {
|
||||
kCdScopeInvalid = -1,
|
||||
kCdScopeWindow, ///< Affects one window.
|
||||
kCdScopeBuffer, ///< Affects one buffer.
|
||||
kCdScopeTabpage, ///< Affects one tab page.
|
||||
kCdScopeGlobal, ///< Affects the entire Nvim instance.
|
||||
} CdScope;
|
||||
|
||||
#define MIN_CD_SCOPE kCdScopeWindow
|
||||
#define MAX_CD_SCOPE kCdScopeGlobal
|
||||
|
||||
/// What caused the current directory to change.
|
||||
typedef enum {
|
||||
kCdCauseOther = -1,
|
||||
kCdCauseManual, ///< Using `:cd`, `: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'.
|
||||
} CdCause;
|
||||
|
||||
|
||||
@@ -5300,7 +5300,7 @@ static void win_enter_ext(win_T *const wp, const int flags)
|
||||
win_fix_cursor(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL));
|
||||
}
|
||||
|
||||
win_fix_current_dir();
|
||||
update_cwd(kCdCauseWindow);
|
||||
|
||||
entering_window(curwin);
|
||||
// Careful: autocommands may close the window and make "wp" invalid
|
||||
@@ -5352,18 +5352,37 @@ static void win_enter_ext(win_T *const wp, const int flags)
|
||||
do_autochdir();
|
||||
}
|
||||
|
||||
/// Used after making another window the current one: change directory if needed.
|
||||
void win_fix_current_dir(void)
|
||||
/// Applies the effective current-directory of the current window (its window-local, buffer-local,
|
||||
/// tab-local or the global directory). Called after setting curbuf/curwin.
|
||||
///
|
||||
/// @param cause What caused the switch, reported by the DirChanged event.
|
||||
void update_cwd(CdCause cause)
|
||||
{
|
||||
// New directory is either the local directory of the window, tab or NULL.
|
||||
char *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->tp_localdir;
|
||||
// New directory is either the local directory of the window, buffer, tab or NULL.
|
||||
char *new_dir;
|
||||
CdScope scope;
|
||||
|
||||
if (curwin->w_localdir) {
|
||||
new_dir = curwin->w_localdir;
|
||||
scope = kCdScopeWindow;
|
||||
} else if (curbuf->b_localdir) {
|
||||
new_dir = curbuf->b_localdir;
|
||||
scope = kCdScopeBuffer;
|
||||
} else if (curtab->tp_localdir) {
|
||||
new_dir = curtab->tp_localdir;
|
||||
scope = kCdScopeTabpage;
|
||||
} else {
|
||||
new_dir = NULL;
|
||||
scope = kCdScopeGlobal;
|
||||
}
|
||||
|
||||
char cwd[MAXPATHL];
|
||||
if (os_dirname(cwd, MAXPATHL) != OK) {
|
||||
cwd[0] = NUL;
|
||||
}
|
||||
|
||||
if (new_dir) {
|
||||
// Window/tab has a local directory: Save current directory as global
|
||||
// Window/buffer/tab has a local directory: Save current directory as global
|
||||
// (unless that was done already) and change to the local directory.
|
||||
if (globaldir == NULL) {
|
||||
if (cwd[0] != NUL) {
|
||||
@@ -5372,27 +5391,25 @@ void win_fix_current_dir(void)
|
||||
}
|
||||
bool dir_differs = pathcmp(new_dir, cwd, -1) != 0;
|
||||
if (!p_acd && dir_differs) {
|
||||
do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
|
||||
kCdCauseWindow, true);
|
||||
do_autocmd_dirchanged(new_dir, scope, cause, true);
|
||||
}
|
||||
if (os_chdir(new_dir) == 0) {
|
||||
if (!p_acd && dir_differs) {
|
||||
do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
|
||||
kCdCauseWindow, false);
|
||||
do_autocmd_dirchanged(new_dir, scope, cause, false);
|
||||
}
|
||||
}
|
||||
last_chdir_reason = NULL;
|
||||
shorten_fnames(true);
|
||||
} else if (globaldir != NULL) {
|
||||
// Window doesn't have a local directory and we are not in the global
|
||||
// Window nor buffer have a local directory and we are not in the global
|
||||
// directory: Change to the global directory.
|
||||
bool dir_differs = pathcmp(globaldir, cwd, -1) != 0;
|
||||
if (!p_acd && dir_differs) {
|
||||
do_autocmd_dirchanged(globaldir, kCdScopeGlobal, kCdCauseWindow, true);
|
||||
do_autocmd_dirchanged(globaldir, kCdScopeGlobal, cause, true);
|
||||
}
|
||||
if (os_chdir(globaldir) == 0) {
|
||||
if (!p_acd && dir_differs) {
|
||||
do_autocmd_dirchanged(globaldir, kCdScopeGlobal, kCdCauseWindow, false);
|
||||
do_autocmd_dirchanged(globaldir, kCdScopeGlobal, cause, false);
|
||||
}
|
||||
}
|
||||
XFREE_CLEAR(globaldir);
|
||||
|
||||
@@ -6,7 +6,6 @@ local describe, it, before_each, setup, teardown =
|
||||
local clear = n.clear
|
||||
local command = n.command
|
||||
local eq = t.eq
|
||||
local pcall_err = t.pcall_err
|
||||
local eval = n.eval
|
||||
local request = n.request
|
||||
local is_os = t.is_os
|
||||
@@ -17,6 +16,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
|
||||
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',
|
||||
curdir .. '/Xtest-functional-autocmd-dirchanged.dir3',
|
||||
curdir .. '/Xtest-functional-autocmd-dirchanged.dir4',
|
||||
}
|
||||
local win_dirs = {
|
||||
curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR1',
|
||||
@@ -62,32 +62,43 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
eq(1, eval('g:cdprecount'))
|
||||
eq(1, eval('g:cdcount'))
|
||||
|
||||
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'))
|
||||
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'))
|
||||
eq(2, eval('g:cdprecount'))
|
||||
eq(2, eval('g:cdcount'))
|
||||
|
||||
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'))
|
||||
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'))
|
||||
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'))
|
||||
eq(4, eval('g:cdprecount'))
|
||||
eq(4, eval('g:cdcount'))
|
||||
end)
|
||||
|
||||
it('DirChanged set getcwd() during event #6260', function()
|
||||
command('lcd ' .. dirs[1])
|
||||
eq(dirs[1], eval('g:getcwd'))
|
||||
|
||||
command('tcd ' .. dirs[2])
|
||||
command('bcd ' .. dirs[2])
|
||||
eq(dirs[2], eval('g:getcwd'))
|
||||
|
||||
command('cd ' .. dirs[3])
|
||||
command('tcd ' .. dirs[3])
|
||||
eq(dirs[3], eval('g:getcwd'))
|
||||
|
||||
command('cd ' .. dirs[4])
|
||||
eq(dirs[4], eval('g:getcwd'))
|
||||
end)
|
||||
|
||||
it('disallow recursion', function()
|
||||
@@ -150,7 +161,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
eq('E344:', string.match(err3, 'E%d*:'))
|
||||
end)
|
||||
|
||||
it("are triggered by 'autochdir'", function()
|
||||
it("triggered by 'autochdir'", function()
|
||||
command('set autochdir')
|
||||
|
||||
command('split ' .. dirs[1] .. '/foo')
|
||||
@@ -169,7 +180,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
eq(2, eval('g:cdcount'))
|
||||
end)
|
||||
|
||||
it('do not trigger if directory has not changed', function()
|
||||
it('not triggered if directory has not changed', function()
|
||||
command('lcd ' .. dirs[1])
|
||||
eq({ directory = dirs[1], scope = 'window', changed_window = false }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[1], scope = 'window', changed_window = false }, eval('g:ev'))
|
||||
@@ -216,55 +227,78 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
eq(2, eval('g:cdcount'))
|
||||
end
|
||||
|
||||
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:amatch'))
|
||||
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'))
|
||||
eq(3, eval('g:cdprecount'))
|
||||
eq(3, eval('g:cdcount'))
|
||||
command('let g:evpre = {}')
|
||||
command('let g:ev = {}')
|
||||
command('cd ' .. dirs[3])
|
||||
command('tcd ' .. dirs[3])
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(3, eval('g:cdprecount'))
|
||||
eq(3, eval('g:cdcount'))
|
||||
|
||||
if is_os('win') then
|
||||
command('cd ' .. win_dirs[3])
|
||||
command('tcd ' .. win_dirs[3])
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(3, eval('g:cdprecount'))
|
||||
eq(3, eval('g:cdcount'))
|
||||
end
|
||||
|
||||
command('set autochdir')
|
||||
|
||||
command('split ' .. dirs[1] .. '/foo')
|
||||
eq({ directory = dirs[1], scope = 'window', changed_window = false }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[1], scope = 'window', changed_window = false }, eval('g:ev'))
|
||||
eq('auto', eval('g:amatchpre'))
|
||||
eq('auto', eval('g:amatch'))
|
||||
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:amatch'))
|
||||
eq(4, eval('g:cdprecount'))
|
||||
eq(4, eval('g:cdcount'))
|
||||
command('let g:evpre = {}')
|
||||
command('let g:ev = {}')
|
||||
command('split ' .. dirs[1] .. '/bar')
|
||||
command('cd ' .. dirs[4])
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(4, eval('g:cdprecount'))
|
||||
eq(4, eval('g:cdcount'))
|
||||
|
||||
if is_os('win') then
|
||||
command('split ' .. win_dirs[1] .. '/baz')
|
||||
command('cd ' .. dirs[4])
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(4, eval('g:cdprecount'))
|
||||
eq(4, eval('g:cdcount'))
|
||||
end
|
||||
|
||||
command('set autochdir')
|
||||
|
||||
command(('split %s/foo'):format(dirs[2]))
|
||||
eq({ directory = dirs[2], scope = 'window', changed_window = false }, eval('g:evpre'))
|
||||
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'))
|
||||
command('let g:evpre = {}')
|
||||
command('let g:ev = {}')
|
||||
command(('split %s/bar'):format(dirs[2]))
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(5, eval('g:cdprecount'))
|
||||
eq(5, eval('g:cdcount'))
|
||||
|
||||
if is_os('win') then
|
||||
command(('split %s/baz'):format(win_dirs[2]))
|
||||
eq({}, eval('g:evpre'))
|
||||
eq({}, eval('g:ev'))
|
||||
eq(5, eval('g:cdprecount'))
|
||||
eq(5, eval('g:cdcount'))
|
||||
end
|
||||
end)
|
||||
|
||||
it('are triggered by switching to win/tab with different CWD #6054', function()
|
||||
it('triggered by switching to win/tab with different CWD #6054', function()
|
||||
command('lcd ' .. dirs[3]) -- window 3
|
||||
command('split ' .. dirs[2] .. '/foo') -- window 2
|
||||
command('lcd ' .. dirs[2])
|
||||
@@ -341,7 +375,143 @@ describe('autocmd DirChanged and DirChangedPre', function()
|
||||
end
|
||||
end)
|
||||
|
||||
it('are triggered by nvim_set_current_dir()', function()
|
||||
it('triggered by switching to buf/tab with different CWD', function()
|
||||
local files = {
|
||||
dirs[1] .. '/file',
|
||||
dirs[2] .. '/file',
|
||||
dirs[3] .. '/file',
|
||||
}
|
||||
|
||||
command('e ' .. files[3]) -- buffer 3
|
||||
command('e ' .. files[2]) -- buffer 2
|
||||
command('e ' .. files[1]) -- buffer 1
|
||||
|
||||
command('bcd ' .. dirs[1])
|
||||
command('b ' .. files[2]) -- Switch to buffer 2
|
||||
command('bcd ' .. dirs[2])
|
||||
command('b ' .. files[3]) -- Switch to buffer 3
|
||||
command('bcd ' .. dirs[3])
|
||||
|
||||
eq({ directory = dirs[3], scope = 'buffer', changed_window = false }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[3], scope = 'buffer', changed_window = false }, eval('g:ev'))
|
||||
eq('buffer', eval('g:amatchpre'))
|
||||
eq('buffer', eval('g:amatch'))
|
||||
|
||||
eq(5, eval('g:cdprecount'))
|
||||
eq(5, eval('g:cdcount'))
|
||||
command('tabnew') -- tab 2: its new empty buffer has no local CWD, reverts to global
|
||||
eq({ cwd = curdir, scope = 'global', changed_window = false }, eval('g:ev'))
|
||||
eq(6, eval('g:cdprecount'))
|
||||
eq(6, eval('g:cdcount'))
|
||||
command('tcd ' .. dirs[2])
|
||||
command('tabnext') -- tab 1 (no tab-local CWD)
|
||||
eq({ directory = dirs[3], scope = 'buffer', changed_window = true }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[3], scope = 'buffer', changed_window = true }, eval('g:ev'))
|
||||
eq('buffer', eval('g:amatchpre'))
|
||||
eq('buffer', eval('g:amatch'))
|
||||
command('tabnext') -- tab 2
|
||||
eq({ directory = dirs[2], scope = 'tabpage', changed_window = true }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[2], scope = 'tabpage', changed_window = true }, eval('g:ev'))
|
||||
eq('tabpage', eval('g:amatchpre'))
|
||||
eq('tabpage', eval('g:amatch'))
|
||||
eq(9, eval('g:cdprecount'))
|
||||
eq(9, eval('g:cdcount'))
|
||||
|
||||
command('tabnext') -- tab 1
|
||||
command('b ' .. files[2]) -- buffer 2
|
||||
eq(11, eval('g:cdprecount'))
|
||||
eq(11, eval('g:cdcount'))
|
||||
command('tabnext') -- tab 2 (has the *same* CWD)
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
|
||||
if is_os('win') then
|
||||
command('tabnew') -- tab 3
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tcd ' .. dirs[2])
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabnext') -- tab 1
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabprevious') -- tab 3
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabprevious') -- tab 2
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabprevious') -- tab 1
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('bcd ' .. dirs[2]) -- buffer 2
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabnext') -- tab 2
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabnext') -- tab 3
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabnext') -- tab 1
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command('tabprevious') -- tab 3
|
||||
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
end
|
||||
end)
|
||||
|
||||
it('triggered by switching to buf/win with different CWD', function()
|
||||
command('lcd ' .. dirs[3]) -- window 3
|
||||
command(('split %s/file'):format(dirs[2])) -- window 2
|
||||
command('lcd ' .. dirs[2])
|
||||
command(('split %s/file'):format(dirs[1])) -- window 1
|
||||
command('lcd ' .. dirs[1])
|
||||
|
||||
-- All windows have a window-local CWD
|
||||
|
||||
command('2wincmd w') -- window 2
|
||||
eq({ directory = dirs[2], scope = 'window', changed_window = true }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[2], scope = 'window', changed_window = true }, eval('g:ev'))
|
||||
eq('window', eval('g:amatchpre'))
|
||||
eq('window', eval('g:amatch'))
|
||||
|
||||
eq(4, eval('g:cdprecount'))
|
||||
eq(4, eval('g:cdcount'))
|
||||
command('bcd ' .. dirs[1]) -- window 2 now has a buffer with buffer-local CWD (and no window-local CWD)
|
||||
eq({ directory = dirs[1], scope = 'buffer', changed_window = false }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[1], scope = 'buffer', changed_window = false }, eval('g:ev'))
|
||||
eq('buffer', eval('g:amatchpre'))
|
||||
eq('buffer', eval('g:amatch'))
|
||||
eq(5, eval('g:cdprecount'))
|
||||
eq(5, eval('g:cdcount'))
|
||||
command('3wincmd w') -- window 3 (window-local CWD)
|
||||
eq(6, eval('g:cdprecount'))
|
||||
eq(6, eval('g:cdcount'))
|
||||
command('bcd ' .. dirs[2]) -- window 3 now has a buffer with buffer-local CWD (and no window-local CWD)
|
||||
command('1wincmd w') -- window 1 (window-local CWD)
|
||||
eq({ directory = dirs[1], scope = 'window', changed_window = true }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[1], scope = 'window', changed_window = true }, eval('g:ev'))
|
||||
eq('window', eval('g:amatchpre'))
|
||||
eq('window', eval('g:amatch'))
|
||||
command('2wincmd w') -- window 2 (buffer-local CWD)
|
||||
eq({ directory = dirs[1], scope = 'window', changed_window = true }, eval('g:evpre')) -- buffer-local CWD
|
||||
eq({ cwd = dirs[1], scope = 'window', changed_window = true }, eval('g:ev'))
|
||||
eq('window', eval('g:amatchpre'))
|
||||
eq('window', eval('g:amatch'))
|
||||
eq(8, eval('g:cdprecount'))
|
||||
eq(8, eval('g:cdcount'))
|
||||
|
||||
command('1wincmd w') -- window 1 (window-local cwd)
|
||||
eq(8, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
|
||||
eq(8, eval('g:cdcount')) -- same CWD, no DirChanged event
|
||||
command(('b %s/file'):format(dirs[2])) -- buffer 2 (has buffer-local cwd)
|
||||
eq(8, eval('g:cdprecount')) -- no DirChangedPre event, window-local CWD has higher priority
|
||||
eq(8, eval('g:cdcount')) -- no DirChanged event, window-local CWD has higher priority
|
||||
end)
|
||||
|
||||
it('triggered by nvim_set_current_dir()', function()
|
||||
request('nvim_set_current_dir', dirs[1])
|
||||
eq({ directory = dirs[1], scope = 'global', changed_window = false }, eval('g:evpre'))
|
||||
eq({ cwd = dirs[1], scope = 'global', changed_window = false }, eval('g:ev'))
|
||||
|
||||
@@ -18,9 +18,16 @@ local mkdir = t.mkdir
|
||||
local directories = {
|
||||
tab = 'Xtest-functional-ex_cmds-cd_spec.tab', -- Tab
|
||||
window = 'Xtest-functional-ex_cmds-cd_spec.window', -- Window
|
||||
buffer = 'Xtest-functional-ex_cmds-cd_spec.buffer', -- Buffer
|
||||
global = 'Xtest-functional-ex_cmds-cd_spec.global', -- New global
|
||||
}
|
||||
|
||||
local tmpfile = 'Xtest-functional-ex_cmds-cd_spec-tmpfile'
|
||||
|
||||
local function join(...)
|
||||
return table.concat({ ... }, pathsep)
|
||||
end
|
||||
|
||||
-- Shorthand writing to get the current working directory
|
||||
local cwd = function(...)
|
||||
return call('getcwd', ...)
|
||||
@@ -28,6 +35,9 @@ end -- effective working dir
|
||||
local wcwd = function()
|
||||
return cwd(0)
|
||||
end -- window dir
|
||||
local bcwd = function()
|
||||
return cwd(-1, -1, 0)
|
||||
end -- buffer dir
|
||||
local tcwd = function()
|
||||
return cwd(-1, 0)
|
||||
end -- tab dir
|
||||
@@ -39,27 +49,33 @@ end -- effective working dir
|
||||
local wlwd = function()
|
||||
return lwd(0)
|
||||
end -- window dir
|
||||
local blwd = function()
|
||||
return lwd(-1, -1, 0)
|
||||
end -- buffer dir
|
||||
local tlwd = function()
|
||||
return lwd(-1, 0)
|
||||
end -- tab dir
|
||||
--local glwd = function() return eval('haslocaldir(-1, -1)') end -- global dir
|
||||
|
||||
local function before_test()
|
||||
clear()
|
||||
for _, d in pairs(directories) do
|
||||
mkdir(d)
|
||||
end
|
||||
directories.start = cwd()
|
||||
end
|
||||
|
||||
local function remove_dirs()
|
||||
for _, d in pairs(directories) do
|
||||
vim.uv.fs_rmdir(d)
|
||||
end
|
||||
end
|
||||
|
||||
-- Test both the `cd` and `chdir` variants
|
||||
for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
describe(':' .. cmd, function()
|
||||
before_each(function()
|
||||
clear()
|
||||
for _, d in pairs(directories) do
|
||||
mkdir(d)
|
||||
end
|
||||
directories.start = cwd()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
for _, d in pairs(directories) do
|
||||
vim.uv.fs_rmdir(d)
|
||||
end
|
||||
end)
|
||||
before_each(before_test)
|
||||
after_each(remove_dirs)
|
||||
|
||||
describe('using explicit scope', function()
|
||||
it('for window', function()
|
||||
@@ -134,6 +150,49 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
eq(globalDir .. pathsep .. directories.tab, cwd(-1, localtab))
|
||||
eq(1, lwd(-1, localtab))
|
||||
end)
|
||||
|
||||
it('for buffer', function()
|
||||
local globalDir = directories.start
|
||||
-- Create two buffers
|
||||
command(('e %s1'):format(tmpfile))
|
||||
command(('e %s%s%s2'):format(directories.buffer, pathsep, tmpfile))
|
||||
|
||||
-- Initially matches globalDir
|
||||
eq(globalDir, cwd())
|
||||
eq(0, lwd())
|
||||
|
||||
-- Change buffer-local directory to subdirectory
|
||||
command('bcd ' .. directories.buffer)
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
eq(1, blwd())
|
||||
|
||||
-- ":bcd -" changes to the previous directory, still buffer-scoped.
|
||||
command('bcd -')
|
||||
eq(globalDir, cwd())
|
||||
eq(1, blwd())
|
||||
command('bcd -')
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
eq(1, blwd())
|
||||
|
||||
-- Verify Other buffer is unchanged
|
||||
command('b# ')
|
||||
eq(globalDir, cwd())
|
||||
eq(0, blwd())
|
||||
|
||||
-- A new buffer created with :edit does not inherit the buffer-local directory.
|
||||
command('b# ')
|
||||
command(('e %s3'):format(tmpfile))
|
||||
eq(0, blwd())
|
||||
eq(globalDir, cwd())
|
||||
|
||||
-- getcwd({winnr}) falls through to the buffer shown in that window, not the current
|
||||
-- buffer.
|
||||
command(('split %s%s%s2'):format(directories.buffer, pathsep, tmpfile))
|
||||
command('wincmd p')
|
||||
eq(globalDir, cwd())
|
||||
eq(join(globalDir, directories.buffer), cwd(1))
|
||||
eq(globalDir, cwd(2))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('getcwd(-1, -1)', function()
|
||||
@@ -153,6 +212,16 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
eq(directories.start, cwd(-1, -1))
|
||||
eq(0, lwd(-1, -1))
|
||||
end)
|
||||
|
||||
it('works with buffer-local pwd', function()
|
||||
command(('silent b%s %s'):format(cmd, directories.buffer))
|
||||
eq(directories.start, cwd(-1, -1))
|
||||
eq(0, lwd(-1, -1))
|
||||
|
||||
-- Must behave the same if bufnr is -1
|
||||
eq(directories.start, cwd(-1, -1, -1))
|
||||
eq(0, lwd(-1, -1, -1))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Local directory gets inherited', function()
|
||||
@@ -239,6 +308,127 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
eq(0, tlwd())
|
||||
eq(globalDir .. pathsep .. directories.window, wcwd())
|
||||
end)
|
||||
|
||||
it('works when mixing tab-local and buffer-local directories', function()
|
||||
local globalDir = directories.start
|
||||
|
||||
-- Create two buffers for testing. One in each tab
|
||||
command(('e %s1'):format(tmpfile))
|
||||
command('tabnew')
|
||||
command(('e %s2'):format(tmpfile))
|
||||
|
||||
-- Verify that buffer 2 has the same working directory
|
||||
eq(globalDir, cwd())
|
||||
eq(globalDir, tcwd()) -- Has no tab-local directory
|
||||
eq(0, tlwd())
|
||||
eq(globalDir, bcwd()) -- Has no buffer-local directory
|
||||
eq(0, blwd())
|
||||
|
||||
-- Change tab-local working directory and verify it is different
|
||||
command(('silent t%s %s'):format(cmd, directories.tab))
|
||||
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(0, blwd())
|
||||
|
||||
-- Change buffer 2's buffer-local directory
|
||||
command(('silent b%s ..%s%s'):format(cmd, pathsep, directories.buffer))
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
eq(join(globalDir, directories.tab), tcwd())
|
||||
eq(cwd(), bcwd()) -- Has no buffer-directory
|
||||
eq(1, blwd())
|
||||
|
||||
-- Verify the first tab has no local-directory
|
||||
command('tabfirst')
|
||||
eq(0, tlwd())
|
||||
|
||||
-- Verify buffer 2 has buffer-local directory even in first tab
|
||||
command(('b %s2'):format(tmpfile)) -- Switch to buffer 2
|
||||
eq(join(globalDir, directories.buffer), bcwd())
|
||||
eq(0, tlwd()) -- Still no tab-local directory
|
||||
|
||||
-- Verify buffer 1 did not have its directory changed
|
||||
command('b#') -- Switch to buffer 1
|
||||
eq(globalDir, cwd())
|
||||
eq(0, blwd()) -- No window-buffer directory
|
||||
end)
|
||||
it('works when mixing window local and buffer local directories', function()
|
||||
local globalDir = directories.start
|
||||
-- Create a new window first and verify that is has the same working directory
|
||||
command('new')
|
||||
eq(globalDir, cwd())
|
||||
eq(globalDir, wcwd()) -- Has no window-local directory
|
||||
eq(0, tlwd())
|
||||
eq(globalDir, bcwd()) -- Has no buffer-local directory
|
||||
eq(0, blwd())
|
||||
|
||||
-- Create a buffer in current window
|
||||
command('e ' .. tmpfile)
|
||||
|
||||
-- Change buffer-local working directory and verify it is different
|
||||
command(('silent b%s %s'):format(cmd, directories.buffer))
|
||||
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(0, wlwd())
|
||||
|
||||
-- Change window-local directory to test `:lcd`
|
||||
command(('silent l%s ../%s'):format(cmd, directories.window))
|
||||
eq(join(globalDir, directories.window), cwd())
|
||||
eq(join(globalDir, directories.buffer), bcwd())
|
||||
eq(1, blwd())
|
||||
|
||||
-- Verify buffer has buffer-local directory in original window
|
||||
command('wincmd w')
|
||||
command('b ' .. tmpfile)
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
|
||||
-- Verify going to second window uses window-local directory
|
||||
command('wincmd w')
|
||||
eq(join(globalDir, directories.window), cwd())
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
for _, cmd in ipairs { 'bcd', 'bchdir' } do
|
||||
describe(':' .. cmd, function()
|
||||
before_each(before_test)
|
||||
after_each(remove_dirs)
|
||||
|
||||
it('works after deleting the only buffer', function()
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
command('bd') -- delete buffer
|
||||
end)
|
||||
|
||||
it('buffer-local directory is NOT sticky/inherited', function()
|
||||
local bufdir = join(directories.start, directories.buffer)
|
||||
|
||||
command('edit ' .. tmpfile)
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
eq(bufdir, cwd())
|
||||
|
||||
-- A new buffer starts without a buffer-local directory.
|
||||
command('new')
|
||||
eq(directories.start, cwd())
|
||||
eq(0, blwd())
|
||||
command('close')
|
||||
eq(bufdir, cwd())
|
||||
command('enew')
|
||||
eq(directories.start, cwd())
|
||||
eq(0, blwd())
|
||||
command('b# ')
|
||||
eq(bufdir, cwd())
|
||||
|
||||
-- Recycling an empty unnamed buffer (:edit) drops its directory with it.
|
||||
command('enew')
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
eq(bufdir, cwd())
|
||||
command('edit ' .. tmpfile .. '2')
|
||||
eq(directories.start, cwd())
|
||||
eq(0, blwd())
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -249,37 +439,34 @@ for _, cmd in ipairs { 'getcwd', 'haslocaldir' } do
|
||||
clear()
|
||||
end)
|
||||
|
||||
-- Test invalid argument types
|
||||
local err474 = 'Vim(call):E474: Invalid argument'
|
||||
it('fails on string', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '("some string")'))
|
||||
end)
|
||||
it('fails on float', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '(1.0)'))
|
||||
end)
|
||||
it('fails on list', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '([1, 2])'))
|
||||
end)
|
||||
it('fails on dictionary', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '({"key": "value"})'))
|
||||
end)
|
||||
it('fails on funcref', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '(function("tr"))'))
|
||||
end)
|
||||
it('validation', function()
|
||||
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))
|
||||
)
|
||||
|
||||
-- Test invalid numbers
|
||||
it('fails on number less than -1', function()
|
||||
eq(err474, pcall_err(command, 'call ' .. cmd .. '(-2)'))
|
||||
end)
|
||||
local err5001 = 'Vim(call):E5001: Higher scope cannot be -1 if lower scope is >= 0.'
|
||||
it('fails on -1 if previous arg is >=0', function()
|
||||
eq(err5001, pcall_err(command, 'call ' .. cmd .. '(0, -1)'))
|
||||
end)
|
||||
|
||||
-- Test wrong number of arguments
|
||||
local err118 = 'Vim(call):E118: Too many arguments for function: ' .. cmd
|
||||
it('fails to parse more than one argument', function()
|
||||
eq(err118, pcall_err(command, 'call ' .. cmd .. '(0, 0, 0)'))
|
||||
-- -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:E118: Too many arguments for function: %s'):format(cmd),
|
||||
pcall_err(call, cmd, 0, 0, 0, 0)
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -23,16 +23,19 @@ local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
|
||||
|
||||
describe(':mksession', function()
|
||||
local session_file = file_prefix .. '.vim'
|
||||
local tab_dir = file_prefix .. '.d'
|
||||
local tab_dir = file_prefix .. '.tab.d'
|
||||
local buf_dir = file_prefix .. '.buf.d'
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
mkdir(tab_dir)
|
||||
mkdir(buf_dir)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(session_file)
|
||||
rmdir(tab_dir)
|
||||
rmdir(buf_dir)
|
||||
end)
|
||||
|
||||
it('restores same :terminal buf in splits', function()
|
||||
@@ -362,4 +365,69 @@ describe(':mksession', function()
|
||||
-- contains all splits
|
||||
eq(3, #api.nvim_tabpage_list_wins(0))
|
||||
end)
|
||||
|
||||
it('restores buffer-local working directories', function()
|
||||
-- "'" and " " in the names must survive the session file's string quoting and escaping.
|
||||
local tmpfile_base = file_prefix .. "-tmp'file"
|
||||
local quote_dir = file_prefix .. ".b'uf d"
|
||||
local session_file2 = session_file .. '2'
|
||||
local cwd_dir = fn.getcwd()
|
||||
mkdir(quote_dir)
|
||||
finally(function()
|
||||
rmdir(quote_dir)
|
||||
os.remove(session_file2)
|
||||
end)
|
||||
|
||||
command(('edit %s1'):format(tmpfile_base))
|
||||
command(('edit %s2'):format(tmpfile_base))
|
||||
command('bcd ' .. fn.fnameescape(quote_dir))
|
||||
command('b# ')
|
||||
command('mksession ' .. session_file)
|
||||
command('set sessionoptions-=curdir')
|
||||
command('mksession ' .. session_file2)
|
||||
|
||||
-- Create a new test instance of Nvim.
|
||||
clear()
|
||||
|
||||
command('silent source ' .. session_file)
|
||||
command(('b %s%s2'):format(get_pathsep(), tmpfile_base))
|
||||
eq(('%s%s%s'):format(cwd_dir, get_pathsep(), quote_dir), fn.getcwd())
|
||||
|
||||
-- Without "curdir" in 'sessionoptions' no buffer-local directory is restored.
|
||||
clear()
|
||||
command('silent source ' .. session_file2)
|
||||
command(('b %s%s2'):format(get_pathsep(), tmpfile_base))
|
||||
eq(cwd_dir, fn.getcwd())
|
||||
eq(0, fn.haslocaldir(-1, -1, 0))
|
||||
end)
|
||||
|
||||
it('restores buffer-local working directories when used with tabs', function()
|
||||
local tmpfile_base = file_prefix .. '-tmpfile'
|
||||
local cwd_dir = fn.getcwd()
|
||||
|
||||
command(('edit %s1'):format(tmpfile_base))
|
||||
command('tabnew ')
|
||||
command(('edit %s2'):format(tmpfile_base))
|
||||
command('tcd ' .. tab_dir)
|
||||
command('bcd ..')
|
||||
command('bcd ' .. buf_dir)
|
||||
command('tabfirst')
|
||||
command('mksession ' .. session_file)
|
||||
|
||||
-- Create a new test instance of Nvim.
|
||||
clear()
|
||||
|
||||
command('source ' .. session_file)
|
||||
-- The ":tcd" during restore must not clear the buffer-local directory of the tab's
|
||||
-- first-loaded buffer: it must be set before that tab is ever (re)visited.
|
||||
eq(1, fn.haslocaldir(-1, -1, fn.bufnr(('%s2'):format(tmpfile_base))))
|
||||
-- First tab should have the original working directory.
|
||||
command('tabnext 1')
|
||||
eq(cwd_dir, fn.getcwd())
|
||||
-- Second tab should have the tab-local working directory.
|
||||
command('tabnext 2')
|
||||
eq(('%s%s%s'):format(cwd_dir, get_pathsep(), tab_dir), fn.getcwd(-1, 0))
|
||||
-- Buffer in second tab should have buffer-local working directory
|
||||
eq(('%s%s%s'):format(cwd_dir, get_pathsep(), buf_dir), fn.getcwd(-1, -1, 0))
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user