aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiPhish <hiphish@Aleksandars-iMac.local>2015-08-24 12:58:28 +0200
committerHiPhish <hiphish@Aleksandars-iMac.local>2016-04-20 12:52:31 +0200
commitec71d87b8140ae097158f7e219b26701aed8158e (patch)
treeed9420d1d76c3b2ece7be3f8d89333604aa049f7 /src
parenta7a0bf54e3f339f7bf1f97dee44546cdcd926b1d (diff)
downloadrneovim-ec71d87b8140ae097158f7e219b26701aed8158e.tar.gz
rneovim-ec71d87b8140ae097158f7e219b26701aed8158e.tar.bz2
rneovim-ec71d87b8140ae097158f7e219b26701aed8158e.zip
Implement tab-local working directory feature.
New ex commands: 'tcd', 'tchdir' Changed Vimscript functions: 'haslocaldir', 'getcwd' The ex-commands ':tcd' and ':tchdir' are the tab-local equivalents of ':lcd' and ':lchdir'. There are no new Vimscript functions introduced, instead the functions 'haslocaldir' and 'getcwd' take in optional arguments. See the documentation for details Since there is now different levels of local directory a simple boolean at source level is no longer sufficient; a new enumeration type is used for the scope-level from now on. The documentation has been accommodated for these new commands and functional tests have been written to test the feature.
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/buffer_defs.h10
-rw-r--r--src/nvim/eval.c247
-rw-r--r--src/nvim/ex_cmds.lua12
-rw-r--r--src/nvim/ex_docmd.c81
-rw-r--r--src/nvim/ex_docmd.h12
-rw-r--r--src/nvim/window.c22
7 files changed, 332 insertions, 54 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 10110b0f62..243806b3f1 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -291,7 +291,7 @@ void vim_change_directory(String dir, Error *err)
return;
}
- post_chdir(false);
+ post_chdir(kCdScopeGlobal);
try_end(err);
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 86e63eb52c..ecd13d5ee0 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -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
};
/*
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index e9b1df6367..9579a8ea75 100644
--- a/src/nvim/eval.c
+++ b/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,144 @@ 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;
+
+ // 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);
- if (os_dirname(cwd, MAXPATHL) != FAIL) {
- rettv->vval.v_string = vim_strsave(cwd);
+
+ // 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;
+ }
+ }
+
+ // 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(_("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: A higher-level scope cannot be -1 if a lower-level"
+ " 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);
+ slash_adjust(rettv->vval.v_string);
#endif
- }
+
+end:
xfree(cwd);
}
@@ -10592,12 +10714,99 @@ 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: A higher-level scope cannot be -1 if a lower-level"
+ " 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;
+ }
}
/*
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 6c8835b5c5..04fd88cc8d 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2575,6 +2575,18 @@ return {
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),
addr_type=ADDR_LINES,
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 6391e023c3..2d316cb106 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -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)
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index a5a4edbbbf..7af3ee233c 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -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
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1b8c953596..93a7a58678 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -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. */