]> granicus.if.org Git - vim/commitdiff
patch 8.1.1291: not easy to change directory and restore v8.1.1291
authorBram Moolenaar <Bram@vim.org>
Tue, 7 May 2019 20:06:52 +0000 (22:06 +0200)
committerBram Moolenaar <Bram@vim.org>
Tue, 7 May 2019 20:06:52 +0000 (22:06 +0200)
Problem:    Not easy to change directory and restore.
Solution:   Add the chdir() function. (Yegappan Lakshmanan, closes #4358)

runtime/doc/eval.txt
runtime/doc/usr_41.txt
src/evalfunc.c
src/ex_docmd.c
src/if_py_both.h
src/proto/ex_docmd.pro
src/structs.h
src/testdir/test_cd.vim
src/version.c

index a39f72b551153032e7f76688b406a5cd5593d3bd..84001b7a021748d75436b4dd8efb4e0c9e4a5b4d 100644 (file)
@@ -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'.
index cef6fd793a66eb9ad965215a5fd7bb431ccebba8..f874b073b93bcd22c452d12768e680c6b249cc4f 100644 (file)
@@ -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
index ba3db95c9fd6d54748b95412347068b2fec69edc..ca412f7549311d59ec4547791562853a54e7d8da 100644 (file)
@@ -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},
@@ -2490,6 +2492,45 @@ f_char2nr(typval_T *argvars, typval_T *rettv)
        rettv->vval.v_number = tv_get_string(&argvars[0])[0];
 }
 
+/*
+ * "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
  */
index 1393d0e2f75af205bf66157a9bb2b0094c63392d..a2b302d92250b7cf4c662ab3f5e60a503730f4c9 100644 (file)
@@ -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);
     }
 }
 
index ede2f5cde41255760624885c034f5823e403ea96..498972d548caa92dbf9a8cf09ac8590de6a9f79b 100644 (file)
@@ -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())
     {
index 9934d60fcb8d14e083c956d5d3a1aa873119e077..6714b38c4e45aa70a8800cbc11529448847abb0f 100644 (file)
@@ -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);
index fa8a7655fa7115afbbe673295513f8f6c52c525b..ca678f83b1784d6c578681778c48fb726a7bcf5d 100644 (file)
@@ -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;
index c63f0060f83d65f0d0a9951a06aac4467eb29d38..31859542e5214e0c3e8c99b9edf3da1d0f52c232 100644 (file)
@@ -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
index 676c383062da8d6278cb27d3b7bd5a6df28fdc59..09bb1be8538f1e860bffc0e86be68c6a844c9b10 100644 (file)
@@ -767,6 +767,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1291,
 /**/
     1290,
 /**/