Skip to content
Permalink
Browse files

patch 8.1.1291: not easy to change directory and restore

Problem:    Not easy to change directory and restore.
Solution:   Add the chdir() function. (Yegappan Lakshmanan, closes #4358)
  • Loading branch information...
brammool committed May 7, 2019
1 parent fd31e45 commit 1063f3d2008f22d02ccfa9dab83a23db52febbdc
Showing with 220 additions and 81 deletions.
  1. +22 −0 runtime/doc/eval.txt
  2. +1 −0 runtime/doc/usr_41.txt
  3. +41 −0 src/evalfunc.c
  4. +102 −78 src/ex_docmd.c
  5. +1 −1 src/if_py_both.h
  6. +2 −1 src/proto/ex_docmd.pro
  7. +7 −0 src/structs.h
  8. +42 −1 src/testdir/test_cd.vim
  9. +2 −0 src/version.c
@@ -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'.
@@ -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
@@ -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
*/
@@ -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);
}
}

@@ -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())
{
@@ -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);
@@ -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;

0 comments on commit 1063f3d

Please sign in to comment.
You can’t perform that action at this time.