summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt22
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--src/evalfunc.c41
-rw-r--r--src/ex_docmd.c180
-rw-r--r--src/if_py_both.h2
-rw-r--r--src/proto/ex_docmd.pro3
-rw-r--r--src/structs.h7
-rw-r--r--src/testdir/test_cd.vim43
-rw-r--r--src/version.c2
9 files changed, 220 insertions, 81 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index a39f72b55..84001b7a0 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2273,6 +2273,7 @@ ch_status({handle} [, {options}])
String status of channel {handle}
changenr() Number current change number
char2nr({expr} [, {utf8}]) Number ASCII/UTF8 value of first char in {expr}
+chdir({dir}) String change current working directory
cindent({lnum}) Number C indent for line {lnum}
clearmatches([{win}]) none clear all matches
col({expr}) Number column nr of cursor or mark
@@ -3469,6 +3470,27 @@ char2nr({expr} [, {utf8}]) *char2nr()*
let list = map(split(str, '\zs'), {_, val -> char2nr(val)})
< Result: [65, 66, 67]
+chdir({dir}) *chdir()*
+ Change the current working directory to {dir}. The scope of
+ the directory change depends on the directory of the current
+ window:
+ - If the current window has a window-local directory
+ (|:lcd|), then changes the window local directory.
+ - Otherwise, if the current tabpage has a local
+ directory (|:tcd|) then changes the tabpage local
+ directory.
+ - Otherwise, changes the global directory.
+ If successful, returns the previous working directory. Pass
+ this to another chdir() to restore the directory.
+ On failure, returns an empty string.
+
+ Example: >
+ let save_dir = chdir(newdir)
+ if save_dir
+ " ... do some work
+ call chdir(save_dir)
+ endif
+<
cindent({lnum}) *cindent()*
Get the amount of indent for line {lnum} according the C
indenting rules, as with 'cindent'.
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index cef6fd793..f874b073b 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -769,6 +769,7 @@ System functions and manipulation of files:
haslocaldir() check if current window used |:lcd| or |:tcd|
tempname() get the name of a temporary file
mkdir() create a new directory
+ chdir() change current working directory
delete() delete a file
rename() rename a file
system() get the result of a shell command as a string
diff --git a/src/evalfunc.c b/src/evalfunc.c
index ba3db95c9..ca412f754 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -107,6 +107,7 @@ static void f_ch_status(typval_T *argvars, typval_T *rettv);
#endif
static void f_changenr(typval_T *argvars, typval_T *rettv);
static void f_char2nr(typval_T *argvars, typval_T *rettv);
+static void f_chdir(typval_T *argvars, typval_T *rettv);
static void f_cindent(typval_T *argvars, typval_T *rettv);
static void f_clearmatches(typval_T *argvars, typval_T *rettv);
static void f_col(typval_T *argvars, typval_T *rettv);
@@ -597,6 +598,7 @@ static struct fst
#endif
{"changenr", 0, 0, f_changenr},
{"char2nr", 1, 2, f_char2nr},
+ {"chdir", 1, 1, f_chdir},
{"cindent", 1, 1, f_cindent},
{"clearmatches", 0, 1, f_clearmatches},
{"col", 1, 1, f_col},
@@ -2491,6 +2493,45 @@ f_char2nr(typval_T *argvars, typval_T *rettv)
}
/*
+ * "chdir(dir)" function
+ */
+ static void
+f_chdir(typval_T *argvars, typval_T *rettv)
+{
+ char_u *cwd;
+ cdscope_T scope = CDSCOPE_GLOBAL;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (argvars[0].v_type != VAR_STRING)
+ return;
+
+ // Return the current directory
+ cwd = alloc(MAXPATHL);
+ if (cwd != NULL)
+ {
+ if (mch_dirname(cwd, MAXPATHL) != FAIL)
+ {
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(cwd);
+#endif
+ rettv->vval.v_string = vim_strsave(cwd);
+ }
+ vim_free(cwd);
+ }
+
+ if (curwin->w_localdir != NULL)
+ scope = CDSCOPE_WINDOW;
+ else if (curtab->tp_localdir != NULL)
+ scope = CDSCOPE_TABPAGE;
+
+ if (!changedir_func(argvars[0].vval.v_string, TRUE, scope))
+ // Directory change failed
+ VIM_CLEAR(rettv->vval.v_string);
+}
+
+/*
* "cindent(lnum)" function
*/
static void
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 1393d0e2f..a2b302d92 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -7513,17 +7513,17 @@ free_cd_dir(void)
/*
* Deal with the side effects of changing the current directory.
- * When "tablocal" is TRUE then this was after an ":tcd" command.
- * When "winlocal" is TRUE then this was after an ":lcd" command.
+ * When 'scope' is CDSCOPE_TABPAGE then this was after an ":tcd" command.
+ * When 'scope' is CDSCOPE_WINDOW then this was after an ":lcd" command.
*/
void
-post_chdir(int tablocal, int winlocal)
+post_chdir(cdscope_T scope)
{
- if (!winlocal)
+ if (scope != CDSCOPE_WINDOW)
// Clear tab local directory for both :cd and :tcd
VIM_CLEAR(curtab->tp_localdir);
VIM_CLEAR(curwin->w_localdir);
- if (winlocal || tablocal)
+ if (scope != CDSCOPE_GLOBAL)
{
/* If still in global directory, need to remember current
* directory as global directory. */
@@ -7532,7 +7532,7 @@ post_chdir(int tablocal, int winlocal)
/* Remember this local directory for the window. */
if (mch_dirname(NameBuff, MAXPATHL) == OK)
{
- if (tablocal)
+ if (scope == CDSCOPE_TABPAGE)
curtab->tp_localdir = vim_strsave(NameBuff);
else
curwin->w_localdir = vim_strsave(NameBuff);
@@ -7548,102 +7548,126 @@ post_chdir(int tablocal, int winlocal)
shorten_fnames(TRUE);
}
-
/*
- * ":cd", ":tcd", ":lcd", ":chdir" ":tchdir" and ":lchdir".
+ * Change directory function used by :cd/:tcd/:lcd Ex commands and the
+ * chdir() function. If 'winlocaldir' is TRUE, then changes the window-local
+ * directory. If 'tablocaldir' is TRUE, then changes the tab-local directory.
+ * Otherwise changes the global directory.
+ * Returns TRUE if the directory is successfully changed.
*/
- void
-ex_cd(exarg_T *eap)
+ int
+changedir_func(
+ char_u *new_dir,
+ int forceit,
+ cdscope_T scope)
{
- char_u *new_dir;
char_u *tofree;
int dir_differs;
+ int retval = FALSE;
- new_dir = eap->arg;
-#if !defined(UNIX) && !defined(VMS)
- /* for non-UNIX ":cd" means: print current directory */
- if (*new_dir == NUL)
- ex_pwd(NULL);
- else
-#endif
+ if (allbuf_locked())
+ return FALSE;
+
+ if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged() && !forceit)
{
- if (allbuf_locked())
- return;
- if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged()
- && !eap->forceit)
- {
- emsg(_("E747: Cannot change directory, buffer is modified (add ! to override)"));
- return;
- }
+ emsg(_("E747: Cannot change directory, buffer is modified (add ! to override)"));
+ return FALSE;
+ }
- /* ":cd -": Change to previous directory */
- if (STRCMP(new_dir, "-") == 0)
+ // ":cd -": Change to previous directory
+ if (STRCMP(new_dir, "-") == 0)
+ {
+ if (prev_dir == NULL)
{
- if (prev_dir == NULL)
- {
- emsg(_("E186: No previous directory"));
- return;
- }
- new_dir = prev_dir;
+ emsg(_("E186: No previous directory"));
+ return FALSE;
}
+ new_dir = prev_dir;
+ }
- /* Save current directory for next ":cd -" */
- tofree = prev_dir;
- if (mch_dirname(NameBuff, MAXPATHL) == OK)
- prev_dir = vim_strsave(NameBuff);
- else
- prev_dir = NULL;
+ // Save current directory for next ":cd -"
+ tofree = prev_dir;
+ if (mch_dirname(NameBuff, MAXPATHL) == OK)
+ prev_dir = vim_strsave(NameBuff);
+ else
+ prev_dir = NULL;
#if defined(UNIX) || defined(VMS)
- /* for UNIX ":cd" means: go to home directory */
- if (*new_dir == NUL)
- {
- /* use NameBuff for home directory name */
+ // for UNIX ":cd" means: go to home directory
+ if (*new_dir == NUL)
+ {
+ // use NameBuff for home directory name
# ifdef VMS
- char_u *p;
+ char_u *p;
- p = mch_getenv((char_u *)"SYS$LOGIN");
- if (p == NULL || *p == NUL) /* empty is the same as not set */
- NameBuff[0] = NUL;
- else
- vim_strncpy(NameBuff, p, MAXPATHL - 1);
+ p = mch_getenv((char_u *)"SYS$LOGIN");
+ if (p == NULL || *p == NUL) // empty is the same as not set
+ NameBuff[0] = NUL;
+ else
+ vim_strncpy(NameBuff, p, MAXPATHL - 1);
# else
- expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
+ expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
# endif
- new_dir = NameBuff;
- }
+ new_dir = NameBuff;
+ }
#endif
- dir_differs = new_dir == NULL || prev_dir == NULL
- || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
- if (new_dir == NULL || (dir_differs && vim_chdir(new_dir)))
- emsg(_(e_failed));
- else
+ dir_differs = new_dir == NULL || prev_dir == NULL
+ || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
+ if (new_dir == NULL || (dir_differs && vim_chdir(new_dir)))
+ emsg(_(e_failed));
+ else
+ {
+ char_u *acmd_fname;
+
+ post_chdir(scope);
+
+ if (dir_differs)
{
- char_u *acmd_fname;
- int is_winlocal_chdir = eap->cmdidx == CMD_lcd
- || eap->cmdidx == CMD_lchdir;
- int is_tablocal_chdir = eap->cmdidx == CMD_tcd
- || eap->cmdidx == CMD_tchdir;
+ if (scope == CDSCOPE_WINDOW)
+ acmd_fname = (char_u *)"window";
+ else if (scope == CDSCOPE_TABPAGE)
+ acmd_fname = (char_u *)"tabpage";
+ else
+ acmd_fname = (char_u *)"global";
+ apply_autocmds(EVENT_DIRCHANGED, acmd_fname, new_dir, FALSE,
+ curbuf);
+ }
+ retval = TRUE;
+ }
+ vim_free(tofree);
+
+ return retval;
+}
+
+/*
+ * ":cd", ":tcd", ":lcd", ":chdir" ":tchdir" and ":lchdir".
+ */
+ void
+ex_cd(exarg_T *eap)
+{
+ char_u *new_dir;
- post_chdir(is_tablocal_chdir, is_winlocal_chdir);
+ new_dir = eap->arg;
+#if !defined(UNIX) && !defined(VMS)
+ // for non-UNIX ":cd" means: print current directory
+ if (*new_dir == NUL)
+ ex_pwd(NULL);
+ else
+#endif
+ {
+ cdscope_T scope = CDSCOPE_GLOBAL;
- /* Echo the new current directory if the command was typed. */
+ if (eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir)
+ scope = CDSCOPE_WINDOW;
+ else if (eap->cmdidx == CMD_tcd || eap->cmdidx == CMD_tchdir)
+ scope = CDSCOPE_TABPAGE;
+
+ if (changedir_func(new_dir, eap->forceit, scope))
+ {
+ // Echo the new current directory if the command was typed.
if (KeyTyped || p_verbose >= 5)
ex_pwd(eap);
-
- if (dir_differs)
- {
- if (is_winlocal_chdir)
- acmd_fname = (char_u *)"window";
- else if (is_tablocal_chdir)
- acmd_fname = (char_u *)"tabpage";
- else
- acmd_fname = (char_u *)"global";
- apply_autocmds(EVENT_DIRCHANGED, acmd_fname,
- new_dir, FALSE, curbuf);
- }
}
- vim_free(tofree);
}
}
diff --git a/src/if_py_both.h b/src/if_py_both.h
index ede2f5cde..498972d54 100644
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -1032,7 +1032,7 @@ _VimChdir(PyObject *_chdir, PyObject *args, PyObject *kwargs)
Py_DECREF(newwd);
Py_XDECREF(todecref);
- post_chdir(FALSE, FALSE);
+ post_chdir(CDSCOPE_GLOBAL);
if (VimTryEnd())
{
diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro
index 9934d60fc..6714b38c4 100644
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -37,7 +37,8 @@ void ex_splitview(exarg_T *eap);
void tabpage_new(void);
void do_exedit(exarg_T *eap, win_T *old_curwin);
void free_cd_dir(void);
-void post_chdir(int tablocal, int winlocal);
+void post_chdir(cdscope_T cdscope);
+int changedir_func(char_u *new_dir, int forceit, cdscope_T cdscope);
void ex_cd(exarg_T *eap);
void do_sleep(long msec);
void ex_may_print(exarg_T *eap);
diff --git a/src/structs.h b/src/structs.h
index fa8a7655f..ca678f83b 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3555,3 +3555,10 @@ typedef struct {
varnumber_T vv_count;
varnumber_T vv_count1;
} vimvars_save_T;
+
+// Scope for changing directory
+typedef enum {
+ CDSCOPE_GLOBAL, // :cd
+ CDSCOPE_TABPAGE, // :tcd
+ CDSCOPE_WINDOW // :lcd
+} cdscope_T;
diff --git a/src/testdir/test_cd.vim b/src/testdir/test_cd.vim
index c63f0060f..31859542e 100644
--- a/src/testdir/test_cd.vim
+++ b/src/testdir/test_cd.vim
@@ -1,4 +1,4 @@
-" Test for :cd
+" Test for :cd and chdir()
func Test_cd_large_path()
" This used to crash with a heap write overflow.
@@ -65,3 +65,44 @@ func Test_cd_with_cpo_chdir()
set cpo&
bw!
endfunc
+
+" Test for chdir()
+func Test_chdir_func()
+ let topdir = getcwd()
+ call mkdir('Xdir/y/z', 'p')
+
+ " Create a few tabpages and windows with different directories
+ new
+ cd Xdir
+ tabnew
+ tcd y
+ below new
+ below new
+ lcd z
+
+ tabfirst
+ call chdir('..')
+ call assert_equal('y', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
+ tabnext | wincmd t
+ call chdir('..')
+ call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
+ call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
+ 3wincmd w
+ call chdir('..')
+ call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('y', fnamemodify(getcwd(3, 2), ':t'))
+ call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
+
+ " Error case
+ call assert_fails("call chdir('dir-abcd')", 'E472:')
+ silent! let d = chdir("dir_abcd")
+ call assert_equal("", d)
+
+ only | tabonly
+ exe 'cd ' . topdir
+ call delete('Xdir', 'rf')
+endfunc
diff --git a/src/version.c b/src/version.c
index 676c38306..09bb1be85 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1291,
+/**/
1290,
/**/
1289,