mirror of
https://github.com/neovim/neovim.git
synced 2026-08-14 11:29:31 +00:00
refactor(editor): cleanup change-directory (:bcd) logic
This commit is contained in:
@@ -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 (window, buffer or tab) does not exist, the next-higher
|
||||
scope in the hierarchy applies.
|
||||
|
||||
*:cd* *E747* *E472*
|
||||
|
||||
@@ -696,7 +696,7 @@ 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, window, or
|
||||
buffer cannot be found. *E5000* *E5001* *E5002* *E5006* *E5007*
|
||||
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
|
||||
|
||||
@@ -3748,7 +3748,8 @@ getcursorcharpos([{winid}]) *getcursorcharpos()*
|
||||
Return: ~
|
||||
(`any`)
|
||||
|
||||
getcwd([{winnr} [, {tabnr} [, {bufnr}]]]) *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.
|
||||
|
||||
@@ -3782,7 +3783,6 @@ getcwd([{winnr} [, {tabnr} [, {bufnr}]]]) *getcwd()*
|
||||
getcwd(-1, -1, -1) " Get global directory
|
||||
getcwd(-1, -1) " Get global directory
|
||||
< Throw error if the arguments are invalid.
|
||||
|E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
|
||||
Parameters: ~
|
||||
• {winnr} (`integer?`)
|
||||
|
||||
1
runtime/lua/vim/_meta/vimfn.gen.lua
generated
1
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -3350,7 +3350,6 @@ function vim.fn.getcursorcharpos(winid) end
|
||||
--- getcwd(-1, -1, -1) " Get global directory
|
||||
--- getcwd(-1, -1) " Get global directory
|
||||
--- <Throw error if the arguments are invalid.
|
||||
--- |E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
---
|
||||
--- @param winnr? integer
|
||||
--- @param tabnr? integer
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -20,8 +20,6 @@ EXTERN const char e_invalid_buffer_name_str[] INIT(= N_("E158: Invalid buffer na
|
||||
EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
|
||||
EXTERN const char e_buffer_nr_invalid_buffer_number[] INIT(= N_("E680: <buffer=%d>: invalid buffer number"));
|
||||
EXTERN const char e_buffer_is_not_loaded[] INIT(= N_("E681: Buffer is not loaded"));
|
||||
EXTERN const char e_getcwd_buffer_scope_requires_minus_one[] INIT(= N_("E5006: Window and tab scope must be -1 when using buffer scope"));
|
||||
EXTERN const char e_cannot_find_buffer_number[] INIT(= N_("E5007: Cannot find buffer number."));
|
||||
EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
|
||||
EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
|
||||
EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
|
||||
|
||||
@@ -4125,12 +4125,12 @@ M.funcs = {
|
||||
getcwd(-1, -1, -1) " Get global directory
|
||||
getcwd(-1, -1) " Get global directory
|
||||
<Throw error if the arguments are invalid.
|
||||
|E5000| |E5001| |E5002| |E5006| |E5007|
|
||||
]=],
|
||||
name = 'getcwd',
|
||||
params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' }, { 'bufnr', 'integer' } },
|
||||
returns = 'string',
|
||||
signature = 'getcwd([{winnr} [, {tabnr} [, {bufnr}]]])',
|
||||
tags = { 'E5000', 'E5001', 'E5002', 'E5006', 'E5007' },
|
||||
see_lua = {
|
||||
'|uv.cwd()| for the global working directory; tab-local and window-local scopes differ',
|
||||
},
|
||||
|
||||
@@ -644,6 +644,117 @@ void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
xfree(fbuf);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
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[kTabArg] < 0) {
|
||||
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
return false;
|
||||
}
|
||||
if (argv[kWinArg] > 0) {
|
||||
*win = find_win_by_nr(&argvars[0], *tp);
|
||||
if (*win == NULL) {
|
||||
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.
|
||||
@@ -655,149 +766,36 @@ void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
/// @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, buffer, tab) we want the working
|
||||
// directory of. A `-1` means to skip this scope, a `0` means the current object.
|
||||
|
||||
// getcwd() takes arguments in this order: (window, tab, buffer)
|
||||
// Note that this is different from the order of CdScope
|
||||
enum {
|
||||
WINDOW_IDX = 0,
|
||||
TABPAGE_IDX = 1,
|
||||
BUFFER_IDX = 2,
|
||||
};
|
||||
|
||||
int argv[] = { // arguments passed to getcwd().
|
||||
0, // Number of window to look at.
|
||||
0, // Number of tab to look at.
|
||||
0, // Number of buffer to look at.
|
||||
};
|
||||
int argc = 0; // number of arguments passed to getcwd().
|
||||
|
||||
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.
|
||||
buf_T *buf = curbuf; // The buffer 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
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// Without arguments (kCdScopeInvalid), the effective working directory is returned.
|
||||
if (!getcwd_scope_args(argvars, kCdScopeInvalid, &scope, &win, &tp, &buf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scope extraction
|
||||
// Imagine X >= 0
|
||||
switch (argc) {
|
||||
case 0:
|
||||
scope = kCdScopeInvalid; // getcwd()
|
||||
break;
|
||||
case 1:
|
||||
if (argv[WINDOW_IDX] > -1) {
|
||||
scope = kCdScopeWindow; // getcwd(X)
|
||||
} else {
|
||||
scope = kCdScopeTabpage; // getcwd(-1)
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (argv[WINDOW_IDX] > -1) {
|
||||
scope = kCdScopeWindow; // getcwd(X, ...)
|
||||
} else if (argv[TABPAGE_IDX] > -1) {
|
||||
scope = kCdScopeTabpage; // getcwd(-1, X)
|
||||
} else {
|
||||
scope = kCdScopeGlobal; // getcwd(-1, -1)
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (argv[BUFFER_IDX] > -1) {
|
||||
scope = kCdScopeBuffer; // getcwd(..., ..., X)
|
||||
} else {
|
||||
scope = kCdScopeGlobal; // getcwd(..., ..., -1)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// getcwd(-1, -1, X)
|
||||
if (scope == kCdScopeBuffer) {
|
||||
if (argv[WINDOW_IDX] >= 0 || argv[TABPAGE_IDX] >= 0) {
|
||||
emsg(_(e_getcwd_buffer_scope_requires_minus_one));
|
||||
return;
|
||||
}
|
||||
if (argv[BUFFER_IDX] > 0) {
|
||||
Error err = ERROR_INIT;
|
||||
buf = find_buffer_by_handle(argv[BUFFER_IDX], &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(_(e_cannot_find_buffer_number));
|
||||
xfree(err.msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (argv[TABPAGE_IDX] > 0) {
|
||||
tp = find_tabpage(argv[TABPAGE_IDX]);
|
||||
if (!tp) {
|
||||
emsg(_("E5000: Cannot find tab number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (argv[WINDOW_IDX] >= 0) {
|
||||
if (argv[TABPAGE_IDX] < 0) {
|
||||
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv[WINDOW_IDX] > 0) {
|
||||
win = find_win_by_nr(&argvars[0], tp);
|
||||
if (!win) {
|
||||
emsg(_("E5002: Cannot find window number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Window scope may fall through to the buffer shown in that window.
|
||||
buf = win->w_buffer;
|
||||
}
|
||||
|
||||
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:
|
||||
assert(buf);
|
||||
from = buf->b_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case kCdScopeTabpage:
|
||||
assert(tp);
|
||||
from = tp->tp_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
@@ -1027,148 +1025,37 @@ void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
/// `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.
|
||||
|
||||
// haslocaldir() takes arguments in this order: (window, tab, buffer)
|
||||
// Note that this is different from the order of CdScope
|
||||
enum {
|
||||
WINDOW_IDX = 0,
|
||||
TABPAGE_IDX = 1,
|
||||
BUFFER_IDX = 2,
|
||||
};
|
||||
|
||||
int argv[] = { // arguments passed to haslocaldir().
|
||||
0, // Number of window to look at.
|
||||
0, // Number of tab to look at.
|
||||
0, // Number of buffer to look at.
|
||||
};
|
||||
int argc = 0; // number of arguments passed to haslocaldir.
|
||||
|
||||
tabpage_T *tp = curtab; // The tabpage to look at.
|
||||
win_T *win = curwin; // The window to look at.
|
||||
buf_T *buf = curbuf; // The buffer 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
|
||||
for (; argc < 3; argc++) {
|
||||
if (argvars[argc].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[argc].v_type != VAR_NUMBER) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
argv[argc] = (int)argvars[argc].vval.v_number;
|
||||
if (argv[argc] < -1) {
|
||||
emsg(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Scope extraction
|
||||
// Imagine X >= 0
|
||||
switch (argc) {
|
||||
case 0:
|
||||
// If the user didn't specify anything, default to window scope
|
||||
scope = kCdScopeWindow; // haslocaldir()
|
||||
break;
|
||||
case 1:
|
||||
if (argv[0] > -1) {
|
||||
scope = kCdScopeWindow; // haslocaldir(X)
|
||||
} else {
|
||||
scope = kCdScopeTabpage; // haslocaldir(-1)
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (argv[0] > -1) {
|
||||
scope = kCdScopeWindow; // haslocaldir(X, ...)
|
||||
} else if (argv[1] > -1) {
|
||||
scope = kCdScopeTabpage; // haslocaldir(-1, X)
|
||||
} else {
|
||||
scope = kCdScopeGlobal; // haslocaldir(-1, -1)
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (argv[2] > -1) {
|
||||
scope = kCdScopeBuffer; // haslocaldir(..., ..., X)
|
||||
} else {
|
||||
scope = kCdScopeGlobal; // haslocaldir(..., ..., -1)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// haslocaldir(-1, -1, X)
|
||||
if (scope == kCdScopeBuffer) {
|
||||
if (argv[WINDOW_IDX] >= 0 || argv[TABPAGE_IDX] >= 0) {
|
||||
emsg(_(e_getcwd_buffer_scope_requires_minus_one));
|
||||
return;
|
||||
}
|
||||
if (argv[BUFFER_IDX] > 0) {
|
||||
Error err = ERROR_INIT;
|
||||
buf = find_buffer_by_handle(argv[BUFFER_IDX], &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(_(e_cannot_find_buffer_number));
|
||||
xfree(err.msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (argv[TABPAGE_IDX] >= 0) {
|
||||
if (argv[TABPAGE_IDX] > 0) {
|
||||
tp = find_tabpage(argv[TABPAGE_IDX]);
|
||||
if (!tp) {
|
||||
emsg(_("E5000: Cannot find tab number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (argv[WINDOW_IDX] >= 0) {
|
||||
if (argv[TABPAGE_IDX] < 0) {
|
||||
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv[WINDOW_IDX] > 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:
|
||||
assert(buf);
|
||||
rettv->vval.v_number = buf->b_localdir ? 1 : 0;
|
||||
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
|
||||
|
||||
@@ -6205,21 +6205,18 @@ 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;
|
||||
break;
|
||||
return &curbuf->b_prevdir;
|
||||
case kCdScopeWindow:
|
||||
return curwin->w_prevdir;
|
||||
break;
|
||||
return &curwin->w_prevdir;
|
||||
default:
|
||||
return prev_dir;
|
||||
return &prev_dir;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6241,7 +6238,7 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged)
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -6291,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;
|
||||
@@ -6327,20 +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;
|
||||
case kCdScopeBuffer:
|
||||
pp = &curbuf->b_prevdir;
|
||||
break;
|
||||
default:
|
||||
pp = &prev_dir;
|
||||
}
|
||||
char **pp = get_prevdir(scope);
|
||||
xfree(*pp);
|
||||
*pp = pdir;
|
||||
|
||||
|
||||
@@ -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,43 +864,6 @@ static int makeopens(FILE *fd, char *dirnow)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Restore the tab-local working directory if specified. Do this before the buffers and windows,
|
||||
// so the buf-local / win-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 buffer-local CWD (:bcd) via a one-shot BufEnter handler: there is no reliable point
|
||||
// during session-load where all buffers are entered. Re-applied per tab, AFTER the ":tcd"
|
||||
// above: ":tcd" clears the b_localdir of the then-current buffer, which may be a buffer whose
|
||||
// autocmd already fired.
|
||||
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 the autocmd fires: filenames must be written absolute from here on.
|
||||
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)) {
|
||||
|
||||
@@ -5355,12 +5355,7 @@ static void win_enter_ext(win_T *const wp, const int flags)
|
||||
/// Used after making another window or buffer the current one: change directory if needed.
|
||||
void fix_current_dir(bool caused_by_win)
|
||||
{
|
||||
CdCause cause;
|
||||
if (caused_by_win) {
|
||||
cause = kCdCauseWindow;
|
||||
} else {
|
||||
cause = kCdCauseBuffer;
|
||||
}
|
||||
CdCause cause = caused_by_win ? kCdCauseWindow : kCdCauseBuffer;
|
||||
|
||||
// New directory is either the local directory of the window, buffer, tab or NULL.
|
||||
char *new_dir;
|
||||
|
||||
@@ -24,6 +24,10 @@ local directories = {
|
||||
|
||||
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', ...)
|
||||
@@ -53,22 +57,25 @@ local tlwd = function()
|
||||
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()
|
||||
@@ -156,7 +163,15 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Change buffer-local directory to subdirectory
|
||||
command('bcd ' .. directories.buffer)
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
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
|
||||
@@ -169,7 +184,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
command('b# ')
|
||||
command(('e %s3'):format(tmpfile))
|
||||
eq(1, blwd())
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
command(('e ..%s%s1'):format(pathsep, tmpfile))
|
||||
eq(0, blwd())
|
||||
eq(globalDir, cwd())
|
||||
@@ -179,7 +194,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
command(('split %s%s%s2'):format(directories.buffer, pathsep, tmpfile))
|
||||
command('wincmd p')
|
||||
eq(globalDir, cwd())
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd(1))
|
||||
eq(join(globalDir, directories.buffer), cwd(1))
|
||||
eq(globalDir, cwd(2))
|
||||
end)
|
||||
end)
|
||||
@@ -315,7 +330,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Change tab-local working directory and verify it is different
|
||||
command(('silent t%s %s'):format(cmd, directories.tab))
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.tab), cwd())
|
||||
eq(join(globalDir, directories.tab), cwd())
|
||||
eq(cwd(), tcwd()) -- Working directory matches tab directory
|
||||
eq(1, tlwd())
|
||||
eq(cwd(), bcwd()) -- Still no buffer-directory
|
||||
@@ -323,8 +338,8 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Change buffer 2's buffer-local directory
|
||||
command(('silent b%s ..%s%s'):format(cmd, pathsep, directories.buffer))
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.tab), tcwd())
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
eq(join(globalDir, directories.tab), tcwd())
|
||||
eq(cwd(), bcwd()) -- Has no buffer-directory
|
||||
eq(1, blwd())
|
||||
|
||||
@@ -334,7 +349,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Verify buffer 2 has buffer-local directory even in first tab
|
||||
command(('b %s2'):format(tmpfile)) -- Switch to buffer 2
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), bcwd())
|
||||
eq(join(globalDir, directories.buffer), bcwd())
|
||||
eq(0, tlwd()) -- Still no tab-local directory
|
||||
|
||||
-- Verify buffer 1 did not have its directory changed
|
||||
@@ -357,7 +372,7 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Change buffer-local working directory and verify it is different
|
||||
command(('silent b%s %s'):format(cmd, directories.buffer))
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
eq(cwd(), bcwd()) -- Working directory matches buffer directory
|
||||
eq(1, blwd())
|
||||
eq(cwd(), wcwd()) -- Still no window-directory
|
||||
@@ -365,68 +380,52 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
|
||||
|
||||
-- Change window-local directory to test `:lcd`
|
||||
command(('silent l%s ../%s'):format(cmd, directories.window))
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.window), cwd())
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), bcwd())
|
||||
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(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(join(globalDir, directories.buffer), cwd())
|
||||
|
||||
-- Verify going to second window uses window-local directory
|
||||
command('wincmd w')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.window), cwd())
|
||||
eq(join(globalDir, directories.window), cwd())
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
for _, cmd in ipairs { 'bcd', 'bchdir' } 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)
|
||||
|
||||
it('works after deleting the only buffer', function()
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
command('bd') -- delete buffer
|
||||
end)
|
||||
|
||||
it('makes :new use the buffer-local directory', function()
|
||||
local globalDir = directories.start
|
||||
it('makes :new/:vnew/:enew use the buffer-local directory', function()
|
||||
local bufdir = join(directories.start, directories.buffer)
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
|
||||
command(':new')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(bufdir, cwd())
|
||||
command('wincmd x') -- close :new window
|
||||
|
||||
command(':vnew')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(bufdir, cwd())
|
||||
command('wincmd x') -- close :vnew window
|
||||
|
||||
command(':enew')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
end)
|
||||
|
||||
it('makes :enew use the buffer-local directory in a split', function()
|
||||
local globalDir = directories.start
|
||||
command(('%s %s'):format(cmd, directories.buffer))
|
||||
eq(bufdir, cwd())
|
||||
|
||||
-- Also in a split.
|
||||
command(':vsplit')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
|
||||
eq(bufdir, cwd())
|
||||
command(':enew')
|
||||
eq(('%s%s%s'):format(globalDir, pathsep, directories.buffer), cwd())
|
||||
eq(bufdir, cwd())
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -438,46 +437,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(call):E474: Invalid argument'
|
||||
eq(err474, pcall_err(command, ('call %s("some string")'):format(cmd)))
|
||||
eq(err474, pcall_err(command, ('call %s(1.0)'):format(cmd)))
|
||||
eq(err474, pcall_err(command, ('call %s([1, 2])'):format(cmd)))
|
||||
eq(err474, pcall_err(command, ('call %s({"key": "value"})'):format(cmd)))
|
||||
eq(err474, pcall_err(command, ('call %s(function("tr"))'):format(cmd)))
|
||||
eq(err474, pcall_err(command, ('call %s(-2)'):format(cmd)))
|
||||
|
||||
-- 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.'
|
||||
local err5006 = 'Vim(call):E5006: Window and tab scope must be -1 when using buffer scope'
|
||||
local err5007 = 'Vim(call):E5007: Cannot find buffer number.'
|
||||
it('fails on -1 if previous arg is >=0', function()
|
||||
eq(err5001, pcall_err(command, 'call ' .. cmd .. '(0, -1)'))
|
||||
end)
|
||||
it('fails when 1st and 2nd args != -1 when 3rd arg > -1', function()
|
||||
-- -1 preceded by an argument >= 0
|
||||
eq(
|
||||
'Vim(call):E5001: Higher scope cannot be -1 if lower scope is >= 0.',
|
||||
pcall_err(command, ('call %s(0, -1)'):format(cmd))
|
||||
)
|
||||
-- Buffer scope requires window and tab arguments of -1.
|
||||
local err5006 = 'Vim(call):E5006: Window and tab scope must be -1 when using buffer scope'
|
||||
eq(err5006, pcall_err(command, ('call %s(0, 0, 0)'):format(cmd)))
|
||||
eq(err5006, pcall_err(command, ('call %s(1, 2, 3)'):format(cmd)))
|
||||
end)
|
||||
it('fails when passing a bufnr that does not exist', function()
|
||||
eq(err5007, pcall_err(command, ('call %s(-1, -1, 99999)'):format(cmd)))
|
||||
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 %s(0, 0, 0, 0)'):format(cmd)))
|
||||
-- Nonexistent buffer.
|
||||
eq(
|
||||
'Vim(call):E5007: Cannot find buffer number.',
|
||||
pcall_err(command, ('call %s(-1, -1, 99999)'):format(cmd))
|
||||
)
|
||||
-- Too many arguments.
|
||||
eq(
|
||||
('Vim(call):E118: Too many arguments for function: %s'):format(cmd),
|
||||
pcall_err(command, ('call %s(0, 0, 0, 0)'):format(cmd))
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -418,6 +418,9 @@ describe(':mksession', function()
|
||||
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())
|
||||
|
||||
Reference in New Issue
Block a user