mirror of
https://github.com/neovim/neovim.git
synced 2025-10-04 08:56:29 +00:00
Merge #3229 ':tcd'
This commit is contained in:
@@ -291,7 +291,7 @@ void vim_change_directory(String dir, Error *err)
|
||||
return;
|
||||
}
|
||||
|
||||
post_chdir(false);
|
||||
post_chdir(kCdScopeGlobal);
|
||||
try_end(err);
|
||||
}
|
||||
|
||||
|
@@ -816,10 +816,12 @@ struct tabpage_S {
|
||||
was set */
|
||||
diff_T *tp_first_diff;
|
||||
buf_T *(tp_diffbuf[DB_COUNT]);
|
||||
int tp_diff_invalid; /* list of diffs is outdated */
|
||||
frame_T *(tp_snapshot[SNAP_COUNT]); /* window layout snapshots */
|
||||
dictitem_T tp_winvar; /* variable for "t:" Dictionary */
|
||||
dict_T *tp_vars; /* internal variables, local to tab page */
|
||||
int tp_diff_invalid; ///< list of diffs is outdated */
|
||||
frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots
|
||||
dictitem_T tp_winvar; ///< variable for "t:" Dictionary
|
||||
dict_T *tp_vars; ///< internal variables, local to tab page
|
||||
char_u *localdir; ///< Absolute path of local directory or
|
||||
///< NULL
|
||||
};
|
||||
|
||||
/*
|
||||
|
249
src/nvim/eval.c
249
src/nvim/eval.c
@@ -6656,7 +6656,7 @@ static struct fst {
|
||||
} functions[] =
|
||||
{
|
||||
{ "abs", 1, 1, f_abs },
|
||||
{ "acos", 1, 1, f_acos }, // WJMc
|
||||
{ "acos", 1, 1, f_acos }, // WJMc
|
||||
{ "add", 2, 2, f_add },
|
||||
{ "and", 2, 2, f_and },
|
||||
{ "append", 2, 2, f_append },
|
||||
@@ -6673,9 +6673,9 @@ static struct fst {
|
||||
{ "browse", 4, 4, f_browse },
|
||||
{ "browsedir", 2, 2, f_browsedir },
|
||||
{ "bufexists", 1, 1, f_bufexists },
|
||||
{ "buffer_exists", 1, 1, f_bufexists }, // obsolete
|
||||
{ "buffer_name", 1, 1, f_bufname }, // obsolete
|
||||
{ "buffer_number", 1, 1, f_bufnr }, // obsolete
|
||||
{ "buffer_exists", 1, 1, f_bufexists }, // obsolete
|
||||
{ "buffer_name", 1, 1, f_bufname }, // obsolete
|
||||
{ "buffer_number", 1, 1, f_bufnr }, // obsolete
|
||||
{ "buflisted", 1, 1, f_buflisted },
|
||||
{ "bufloaded", 1, 1, f_bufloaded },
|
||||
{ "bufname", 1, 1, f_bufname },
|
||||
@@ -6719,7 +6719,7 @@ static struct fst {
|
||||
{ "expand", 1, 3, f_expand },
|
||||
{ "extend", 2, 3, f_extend },
|
||||
{ "feedkeys", 1, 2, f_feedkeys },
|
||||
{ "file_readable", 1, 1, f_filereadable }, // obsolete
|
||||
{ "file_readable", 1, 1, f_filereadable }, // obsolete
|
||||
{ "filereadable", 1, 1, f_filereadable },
|
||||
{ "filewritable", 1, 1, f_filewritable },
|
||||
{ "filter", 2, 2, f_filter },
|
||||
@@ -6749,7 +6749,7 @@ static struct fst {
|
||||
{ "getcmdtype", 0, 0, f_getcmdtype },
|
||||
{ "getcmdwintype", 0, 0, f_getcmdwintype },
|
||||
{ "getcurpos", 0, 0, f_getcurpos },
|
||||
{ "getcwd", 0, 0, f_getcwd },
|
||||
{ "getcwd", 0, 2, f_getcwd },
|
||||
{ "getfontname", 0, 1, f_getfontname },
|
||||
{ "getfperm", 1, 1, f_getfperm },
|
||||
{ "getfsize", 1, 1, f_getfsize },
|
||||
@@ -6773,7 +6773,7 @@ static struct fst {
|
||||
{ "globpath", 2, 5, f_globpath },
|
||||
{ "has", 1, 1, f_has },
|
||||
{ "has_key", 2, 2, f_has_key },
|
||||
{ "haslocaldir", 0, 0, f_haslocaldir },
|
||||
{ "haslocaldir", 0, 2, f_haslocaldir },
|
||||
{ "hasmapto", 1, 3, f_hasmapto },
|
||||
{ "highlightID", 1, 1, f_hlID }, // obsolete
|
||||
{ "highlight_exists", 1, 1, f_hlexists }, // obsolete
|
||||
@@ -9758,22 +9758,143 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv)
|
||||
rettv->vval.v_string[0] = cmdwin_type;
|
||||
}
|
||||
|
||||
/*
|
||||
* "getcwd()" function
|
||||
*/
|
||||
/// `getcwd([{win}[, {tab}]])` 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 An argument may not be -1 if preceding arguments are not all -1.
|
||||
///
|
||||
/// @post The return value will be a string.
|
||||
static void f_getcwd(typval_T *argvars, typval_T *rettv)
|
||||
{
|
||||
char_u *cwd;
|
||||
// Possible scope of working directory to return.
|
||||
CdScope scope = kCdScopeWindow;
|
||||
|
||||
// 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.
|
||||
[kCdScopeTab ] = 0, // Number of tab to look at.
|
||||
};
|
||||
|
||||
char_u *cwd = NULL; // Current working directory to print
|
||||
char_u *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.
|
||||
|
||||
rettv->v_type = VAR_STRING;
|
||||
rettv->vval.v_string = NULL;
|
||||
cwd = xmalloc(MAXPATHL);
|
||||
if (os_dirname(cwd, MAXPATHL) != FAIL) {
|
||||
rettv->vval.v_string = vim_strsave(cwd);
|
||||
#ifdef BACKSLASH_IN_FILENAME
|
||||
slash_adjust(rettv->vval.v_string);
|
||||
#endif
|
||||
|
||||
// Pre-conditions and scope extraction together
|
||||
for (int i = 0; i < kCdScopeGlobal; i++) {
|
||||
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[i].v_type != VAR_NUMBER) {
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
scope_number[i] = argvars[i].vval.v_number;
|
||||
// The scope is the current iteration step.
|
||||
scope = i;
|
||||
|
||||
if (scope_number[i] < -1) {
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate and initialize the string to return.
|
||||
cwd = xmalloc(MAXPATHL);
|
||||
|
||||
// Get the scope and numbers from the arguments
|
||||
for (int i = 0; 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;
|
||||
}
|
||||
scope_number[i] = argvars[i].vval.v_number;
|
||||
// The scope is the current iteration step.
|
||||
scope = i;
|
||||
// It is an error for the scope number to be less than `-1`.
|
||||
if (scope_number[i] < -1) {
|
||||
EMSG(_(e_invarg));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
// If the deepest scope number is `-1` advance the scope.
|
||||
if (scope_number[scope] < 0) {
|
||||
scope++;
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (scope_number[kCdScopeTab] == -1) {
|
||||
tp = NULL;
|
||||
} else if (scope_number[kCdScopeTab] > 0) {
|
||||
tp = find_tabpage(scope_number[kCdScopeTab]);
|
||||
if (!tp) {
|
||||
EMSG(_("E5000: Cannot find tab number."));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (scope_number[kCdScopeWindow] == -1) {
|
||||
win = NULL;
|
||||
} else if (scope_number[kCdScopeWindow] >= 0) {
|
||||
if (!tp) {
|
||||
EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (scope_number[kCdScopeWindow] > 0) {
|
||||
win = find_win_by_nr(&argvars[0], curtab);
|
||||
if (!win) {
|
||||
EMSG(_("E5002: Cannot find window number."));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case kCdScopeWindow:
|
||||
from = win->w_localdir;
|
||||
if (from) {
|
||||
break;
|
||||
}
|
||||
case kCdScopeTab: // FALLTHROUGH
|
||||
from = tp->localdir;
|
||||
if (from) {
|
||||
break;
|
||||
}
|
||||
case kCdScopeGlobal: // FALLTHROUGH
|
||||
// The `globaldir` variable is not always set.
|
||||
if (globaldir) {
|
||||
from = globaldir;
|
||||
} else {
|
||||
// Copy the OS path directly into output string and jump to the end.
|
||||
if (os_dirname(cwd, MAXPATHL) == FAIL) {
|
||||
EMSG(_("E41: Could not display path."));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (from) {
|
||||
xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
|
||||
}
|
||||
|
||||
rettv->vval.v_string = vim_strsave(cwd);
|
||||
#ifdef BACKSLASH_IN_FILENAME
|
||||
slash_adjust(rettv->vval.v_string);
|
||||
#endif
|
||||
|
||||
end:
|
||||
xfree(cwd);
|
||||
}
|
||||
|
||||
@@ -10593,12 +10714,98 @@ static void f_has_key(typval_T *argvars, typval_T *rettv)
|
||||
get_tv_string(&argvars[1]), -1) != NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* "haslocaldir()" function
|
||||
*/
|
||||
/// `haslocaldir([{win}[, {tab}]])` 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`.
|
||||
///
|
||||
/// @pre The arguments must be of type number.
|
||||
/// @pre There may not be more than two 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`.
|
||||
static void f_haslocaldir(typval_T *argvars, typval_T *rettv)
|
||||
{
|
||||
rettv->vval.v_number = (curwin->w_localdir != NULL);
|
||||
// Possible scope of working directory to return.
|
||||
CdScope scope = kCdScopeWindow;
|
||||
|
||||
// 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.
|
||||
[kCdScopeTab ] = 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.
|
||||
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = 0;
|
||||
|
||||
// Pre-conditions and scope extraction together
|
||||
for (int i = 0; i < kCdScopeGlobal; i++) {
|
||||
if (argvars[i].v_type == VAR_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
if (argvars[i].v_type != VAR_NUMBER) {
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
scope_number[i] = argvars[i].vval.v_number;
|
||||
// The scope is the current iteration step.
|
||||
scope = i;
|
||||
if (scope_number[i] < -1) {
|
||||
EMSG(_(e_invarg));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It the deepest scope number is `-1` advance the scope by one.
|
||||
if (scope_number[scope] < 0) {
|
||||
++scope;
|
||||
}
|
||||
|
||||
// Find the tabpage by number
|
||||
if (scope_number[kCdScopeTab] == -1) {
|
||||
tp = NULL;
|
||||
} else if (scope_number[kCdScopeTab] > 0) {
|
||||
tp = find_tabpage(scope_number[kCdScopeTab]);
|
||||
if (!tp) {
|
||||
EMSG(_("5000: Cannot find tab number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the window in `tp` by number, `NULL` if none.
|
||||
if (scope_number[kCdScopeWindow] == -1) {
|
||||
win = NULL;
|
||||
} else if (scope_number[kCdScopeWindow] >= 0) {
|
||||
if (!tp) {
|
||||
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], curtab);
|
||||
if (!win) {
|
||||
EMSG(_("E5002: Cannot find window number."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case kCdScopeWindow:
|
||||
rettv->vval.v_number = win->w_localdir ? 1 : 0;
|
||||
break;
|
||||
case kCdScopeTab:
|
||||
rettv->vval.v_number = tp->localdir ? 1 : 0;
|
||||
break;
|
||||
case kCdScopeGlobal:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -2574,6 +2574,18 @@ return {
|
||||
addr_type=ADDR_LINES,
|
||||
func='ex_copymove',
|
||||
},
|
||||
{
|
||||
command='tcd',
|
||||
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
|
||||
addr_type=ADDR_LINES,
|
||||
func='ex_cd',
|
||||
},
|
||||
{
|
||||
command='tchdir',
|
||||
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
|
||||
addr_type=ADDR_LINES,
|
||||
func='ex_cd',
|
||||
},
|
||||
{
|
||||
command='tNext',
|
||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||
|
@@ -2958,8 +2958,11 @@ set_one_cmd_context (
|
||||
case CMD_chdir:
|
||||
case CMD_lcd:
|
||||
case CMD_lchdir:
|
||||
if (xp->xp_context == EXPAND_FILES)
|
||||
case CMD_tcd:
|
||||
case CMD_tchdir:
|
||||
if (xp->xp_context == EXPAND_FILES) {
|
||||
xp->xp_context = EXPAND_DIRECTORIES;
|
||||
}
|
||||
break;
|
||||
case CMD_help:
|
||||
xp->xp_context = EXPAND_HELP;
|
||||
@@ -6814,36 +6817,55 @@ void free_cd_dir(void)
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Deal with the side effects of changing the current directory.
|
||||
* When "local" is TRUE then this was after an ":lcd" command.
|
||||
*/
|
||||
void post_chdir(int local)
|
||||
/// Deal with the side effects of changing the current directory.
|
||||
///
|
||||
/// @param scope Scope of the function call (global, tab or window).
|
||||
void post_chdir(CdScope scope)
|
||||
{
|
||||
// The local directory of the current window is always overwritten.
|
||||
xfree(curwin->w_localdir);
|
||||
curwin->w_localdir = NULL;
|
||||
if (local) {
|
||||
/* If still in global directory, need to remember current
|
||||
* directory as global directory. */
|
||||
if (globaldir == NULL && prev_dir != NULL)
|
||||
|
||||
// Overwrite the local directory of the current tab page for `cd` and `tcd`
|
||||
if (scope >= kCdScopeTab) {
|
||||
xfree(curtab->localdir);
|
||||
curtab->localdir = NULL;
|
||||
}
|
||||
|
||||
if (scope < kCdScopeGlobal) {
|
||||
// If still in global directory, need to remember current directory as
|
||||
// global directory.
|
||||
if (globaldir == NULL && prev_dir != NULL) {
|
||||
globaldir = vim_strsave(prev_dir);
|
||||
/* Remember this local directory for the window. */
|
||||
if (os_dirname(NameBuff, MAXPATHL) == OK)
|
||||
curwin->w_localdir = vim_strsave(NameBuff);
|
||||
} else {
|
||||
/* We are now in the global directory, no need to remember its
|
||||
* name. */
|
||||
}
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case kCdScopeGlobal:
|
||||
// We are now in the global directory, no need to remember its name.
|
||||
xfree(globaldir);
|
||||
globaldir = NULL;
|
||||
break;
|
||||
case kCdScopeTab:
|
||||
// Remember this local directory for the tab page.
|
||||
if (os_dirname(NameBuff, MAXPATHL) == OK) {
|
||||
curtab->localdir = vim_strsave(NameBuff);
|
||||
}
|
||||
break;
|
||||
case kCdScopeWindow:
|
||||
// Remember this local directory for the window.
|
||||
if (os_dirname(NameBuff, MAXPATHL) == OK) {
|
||||
curwin->w_localdir = vim_strsave(NameBuff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
shorten_fnames(TRUE);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ":cd", ":lcd", ":chdir" and ":lchdir".
|
||||
*/
|
||||
|
||||
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
|
||||
void ex_cd(exarg_T *eap)
|
||||
{
|
||||
char_u *new_dir;
|
||||
@@ -6884,10 +6906,25 @@ void ex_cd(exarg_T *eap)
|
||||
new_dir = NameBuff;
|
||||
}
|
||||
#endif
|
||||
if (new_dir == NULL || vim_chdir(new_dir))
|
||||
if (vim_chdir(new_dir)) {
|
||||
EMSG(_(e_failed));
|
||||
else {
|
||||
post_chdir(eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir);
|
||||
} else {
|
||||
CdScope scope = kCdScopeGlobal; // Depends on command invoked
|
||||
|
||||
switch (eap->cmdidx) {
|
||||
case CMD_tcd:
|
||||
case CMD_tchdir:
|
||||
scope = kCdScopeTab;
|
||||
break;
|
||||
case CMD_lcd:
|
||||
case CMD_lchdir:
|
||||
scope = kCdScopeWindow;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
post_chdir(scope);
|
||||
|
||||
/* Echo the new current directory if the command was typed. */
|
||||
if (KeyTyped || p_verbose >= 5)
|
||||
|
@@ -19,6 +19,18 @@
|
||||
#define EXMODE_NORMAL 1
|
||||
#define EXMODE_VIM 2
|
||||
|
||||
/// The scope of a command.
|
||||
///
|
||||
/// The lower a number, the deeper the scope.
|
||||
typedef enum {
|
||||
kCdScopeWindow, ///< Affects one window.
|
||||
kCdScopeTab, ///< Affects one tab page.
|
||||
kCdScopeGlobal, ///< Affects the entire instance of NeoVim.
|
||||
} CdScope;
|
||||
|
||||
/// Last `:cd` scope defined.
|
||||
#define MAX_CD_SCOPE kCdScopeGlobal
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ex_docmd.h.generated.h"
|
||||
#endif
|
||||
|
@@ -2948,7 +2948,7 @@ void free_tabpage(tabpage_T *tp)
|
||||
unref_var_dict(tp->tp_vars);
|
||||
|
||||
|
||||
|
||||
xfree(tp->localdir); // Free tab-local working directory
|
||||
xfree(tp);
|
||||
}
|
||||
|
||||
@@ -3560,18 +3560,24 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri
|
||||
curwin->w_cursor.coladd = 0;
|
||||
changed_line_abv_curs(); /* assume cursor position needs updating */
|
||||
|
||||
if (curwin->w_localdir != NULL) {
|
||||
/* Window has a local directory: Save current directory as global
|
||||
* directory (unless that was done already) and change to the local
|
||||
* directory. */
|
||||
// The new directory is either the local directory of the window, of the tab
|
||||
// or NULL.
|
||||
char_u *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->localdir;
|
||||
|
||||
if (new_dir) {
|
||||
// Window/tab has a local directory: Save current directory as global
|
||||
// directory (unless that was done already) and change to the local
|
||||
// directory.
|
||||
if (globaldir == NULL) {
|
||||
char_u cwd[MAXPATHL];
|
||||
|
||||
if (os_dirname(cwd, MAXPATHL) == OK)
|
||||
if (os_dirname(cwd, MAXPATHL) == OK) {
|
||||
globaldir = vim_strsave(cwd);
|
||||
}
|
||||
}
|
||||
if (os_chdir((char *)new_dir) == 0) {
|
||||
shorten_fnames(true);
|
||||
}
|
||||
if (os_chdir((char *)curwin->w_localdir) == 0)
|
||||
shorten_fnames(TRUE);
|
||||
} else if (globaldir != NULL) {
|
||||
/* Window doesn't have a local directory and we are not in the global
|
||||
* directory: Change to the global directory. */
|
||||
|
Reference in New Issue
Block a user