Skip to content

Commit 1063f3d

Browse files
committed
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)
1 parent fd31e45 commit 1063f3d

File tree

9 files changed

+220
-81
lines changed

9 files changed

+220
-81
lines changed

runtime/doc/eval.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,7 @@ ch_status({handle} [, {options}])
22732273
String status of channel {handle}
22742274
changenr() Number current change number
22752275
char2nr({expr} [, {utf8}]) Number ASCII/UTF8 value of first char in {expr}
2276+
chdir({dir}) String change current working directory
22762277
cindent({lnum}) Number C indent for line {lnum}
22772278
clearmatches([{win}]) none clear all matches
22782279
col({expr}) Number column nr of cursor or mark
@@ -3469,6 +3470,27 @@ char2nr({expr} [, {utf8}]) *char2nr()*
34693470
let list = map(split(str, '\zs'), {_, val -> char2nr(val)})
34703471
< Result: [65, 66, 67]
34713472

3473+
chdir({dir}) *chdir()*
3474+
Change the current working directory to {dir}. The scope of
3475+
the directory change depends on the directory of the current
3476+
window:
3477+
- If the current window has a window-local directory
3478+
(|:lcd|), then changes the window local directory.
3479+
- Otherwise, if the current tabpage has a local
3480+
directory (|:tcd|) then changes the tabpage local
3481+
directory.
3482+
- Otherwise, changes the global directory.
3483+
If successful, returns the previous working directory. Pass
3484+
this to another chdir() to restore the directory.
3485+
On failure, returns an empty string.
3486+
3487+
Example: >
3488+
let save_dir = chdir(newdir)
3489+
if save_dir
3490+
" ... do some work
3491+
call chdir(save_dir)
3492+
endif
3493+
<
34723494
cindent({lnum}) *cindent()*
34733495
Get the amount of indent for line {lnum} according the C
34743496
indenting rules, as with 'cindent'.

runtime/doc/usr_41.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ System functions and manipulation of files:
769769
haslocaldir() check if current window used |:lcd| or |:tcd|
770770
tempname() get the name of a temporary file
771771
mkdir() create a new directory
772+
chdir() change current working directory
772773
delete() delete a file
773774
rename() rename a file
774775
system() get the result of a shell command as a string

src/evalfunc.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ static void f_ch_status(typval_T *argvars, typval_T *rettv);
107107
#endif
108108
static void f_changenr(typval_T *argvars, typval_T *rettv);
109109
static void f_char2nr(typval_T *argvars, typval_T *rettv);
110+
static void f_chdir(typval_T *argvars, typval_T *rettv);
110111
static void f_cindent(typval_T *argvars, typval_T *rettv);
111112
static void f_clearmatches(typval_T *argvars, typval_T *rettv);
112113
static void f_col(typval_T *argvars, typval_T *rettv);
@@ -597,6 +598,7 @@ static struct fst
597598
#endif
598599
{"changenr", 0, 0, f_changenr},
599600
{"char2nr", 1, 2, f_char2nr},
601+
{"chdir", 1, 1, f_chdir},
600602
{"cindent", 1, 1, f_cindent},
601603
{"clearmatches", 0, 1, f_clearmatches},
602604
{"col", 1, 1, f_col},
@@ -2490,6 +2492,45 @@ f_char2nr(typval_T *argvars, typval_T *rettv)
24902492
rettv->vval.v_number = tv_get_string(&argvars[0])[0];
24912493
}
24922494

2495+
/*
2496+
* "chdir(dir)" function
2497+
*/
2498+
static void
2499+
f_chdir(typval_T *argvars, typval_T *rettv)
2500+
{
2501+
char_u *cwd;
2502+
cdscope_T scope = CDSCOPE_GLOBAL;
2503+
2504+
rettv->v_type = VAR_STRING;
2505+
rettv->vval.v_string = NULL;
2506+
2507+
if (argvars[0].v_type != VAR_STRING)
2508+
return;
2509+
2510+
// Return the current directory
2511+
cwd = alloc(MAXPATHL);
2512+
if (cwd != NULL)
2513+
{
2514+
if (mch_dirname(cwd, MAXPATHL) != FAIL)
2515+
{
2516+
#ifdef BACKSLASH_IN_FILENAME
2517+
slash_adjust(cwd);
2518+
#endif
2519+
rettv->vval.v_string = vim_strsave(cwd);
2520+
}
2521+
vim_free(cwd);
2522+
}
2523+
2524+
if (curwin->w_localdir != NULL)
2525+
scope = CDSCOPE_WINDOW;
2526+
else if (curtab->tp_localdir != NULL)
2527+
scope = CDSCOPE_TABPAGE;
2528+
2529+
if (!changedir_func(argvars[0].vval.v_string, TRUE, scope))
2530+
// Directory change failed
2531+
VIM_CLEAR(rettv->vval.v_string);
2532+
}
2533+
24932534
/*
24942535
* "cindent(lnum)" function
24952536
*/

src/ex_docmd.c

Lines changed: 102 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7513,17 +7513,17 @@ free_cd_dir(void)
75137513

75147514
/*
75157515
* Deal with the side effects of changing the current directory.
7516-
* When "tablocal" is TRUE then this was after an ":tcd" command.
7517-
* When "winlocal" is TRUE then this was after an ":lcd" command.
7516+
* When 'scope' is CDSCOPE_TABPAGE then this was after an ":tcd" command.
7517+
* When 'scope' is CDSCOPE_WINDOW then this was after an ":lcd" command.
75187518
*/
75197519
void
7520-
post_chdir(int tablocal, int winlocal)
7520+
post_chdir(cdscope_T scope)
75217521
{
7522-
if (!winlocal)
7522+
if (scope != CDSCOPE_WINDOW)
75237523
// Clear tab local directory for both :cd and :tcd
75247524
VIM_CLEAR(curtab->tp_localdir);
75257525
VIM_CLEAR(curwin->w_localdir);
7526-
if (winlocal || tablocal)
7526+
if (scope != CDSCOPE_GLOBAL)
75277527
{
75287528
/* If still in global directory, need to remember current
75297529
* directory as global directory. */
@@ -7532,7 +7532,7 @@ post_chdir(int tablocal, int winlocal)
75327532
/* Remember this local directory for the window. */
75337533
if (mch_dirname(NameBuff, MAXPATHL) == OK)
75347534
{
7535-
if (tablocal)
7535+
if (scope == CDSCOPE_TABPAGE)
75367536
curtab->tp_localdir = vim_strsave(NameBuff);
75377537
else
75387538
curwin->w_localdir = vim_strsave(NameBuff);
@@ -7548,102 +7548,126 @@ post_chdir(int tablocal, int winlocal)
75487548
shorten_fnames(TRUE);
75497549
}
75507550

7551-
75527551
/*
7553-
* ":cd", ":tcd", ":lcd", ":chdir" ":tchdir" and ":lchdir".
7552+
* Change directory function used by :cd/:tcd/:lcd Ex commands and the
7553+
* chdir() function. If 'winlocaldir' is TRUE, then changes the window-local
7554+
* directory. If 'tablocaldir' is TRUE, then changes the tab-local directory.
7555+
* Otherwise changes the global directory.
7556+
* Returns TRUE if the directory is successfully changed.
75547557
*/
7555-
void
7556-
ex_cd(exarg_T *eap)
7558+
int
7559+
changedir_func(
7560+
char_u *new_dir,
7561+
int forceit,
7562+
cdscope_T scope)
75577563
{
7558-
char_u *new_dir;
75597564
char_u *tofree;
75607565
int dir_differs;
7566+
int retval = FALSE;
75617567

7562-
new_dir = eap->arg;
7563-
#if !defined(UNIX) && !defined(VMS)
7564-
/* for non-UNIX ":cd" means: print current directory */
7565-
if (*new_dir == NUL)
7566-
ex_pwd(NULL);
7567-
else
7568-
#endif
7568+
if (allbuf_locked())
7569+
return FALSE;
7570+
7571+
if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged() && !forceit)
75697572
{
7570-
if (allbuf_locked())
7571-
return;
7572-
if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged()
7573-
&& !eap->forceit)
7574-
{
7575-
emsg(_("E747: Cannot change directory, buffer is modified (add ! to override)"));
7576-
return;
7577-
}
7573+
emsg(_("E747: Cannot change directory, buffer is modified (add ! to override)"));
7574+
return FALSE;
7575+
}
75787576

7579-
/* ":cd -": Change to previous directory */
7580-
if (STRCMP(new_dir, "-") == 0)
7577+
// ":cd -": Change to previous directory
7578+
if (STRCMP(new_dir, "-") == 0)
7579+
{
7580+
if (prev_dir == NULL)
75817581
{
7582-
if (prev_dir == NULL)
7583-
{
7584-
emsg(_("E186: No previous directory"));
7585-
return;
7586-
}
7587-
new_dir = prev_dir;
7582+
emsg(_("E186: No previous directory"));
7583+
return FALSE;
75887584
}
7585+
new_dir = prev_dir;
7586+
}
75897587

7590-
/* Save current directory for next ":cd -" */
7591-
tofree = prev_dir;
7592-
if (mch_dirname(NameBuff, MAXPATHL) == OK)
7593-
prev_dir = vim_strsave(NameBuff);
7594-
else
7595-
prev_dir = NULL;
7588+
// Save current directory for next ":cd -"
7589+
tofree = prev_dir;
7590+
if (mch_dirname(NameBuff, MAXPATHL) == OK)
7591+
prev_dir = vim_strsave(NameBuff);
7592+
else
7593+
prev_dir = NULL;
75967594

75977595
#if defined(UNIX) || defined(VMS)
7598-
/* for UNIX ":cd" means: go to home directory */
7599-
if (*new_dir == NUL)
7600-
{
7601-
/* use NameBuff for home directory name */
7596+
// for UNIX ":cd" means: go to home directory
7597+
if (*new_dir == NUL)
7598+
{
7599+
// use NameBuff for home directory name
76027600
# ifdef VMS
7603-
char_u *p;
7601+
char_u *p;
76047602

7605-
p = mch_getenv((char_u *)"SYS$LOGIN");
7606-
if (p == NULL || *p == NUL) /* empty is the same as not set */
7607-
NameBuff[0] = NUL;
7608-
else
7609-
vim_strncpy(NameBuff, p, MAXPATHL - 1);
7603+
p = mch_getenv((char_u *)"SYS$LOGIN");
7604+
if (p == NULL || *p == NUL) // empty is the same as not set
7605+
NameBuff[0] = NUL;
7606+
else
7607+
vim_strncpy(NameBuff, p, MAXPATHL - 1);
76107608
# else
7611-
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
7609+
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
76127610
# endif
7613-
new_dir = NameBuff;
7614-
}
7611+
new_dir = NameBuff;
7612+
}
76157613
#endif
7616-
dir_differs = new_dir == NULL || prev_dir == NULL
7617-
|| pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
7618-
if (new_dir == NULL || (dir_differs && vim_chdir(new_dir)))
7619-
emsg(_(e_failed));
7620-
else
7614+
dir_differs = new_dir == NULL || prev_dir == NULL
7615+
|| pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
7616+
if (new_dir == NULL || (dir_differs && vim_chdir(new_dir)))
7617+
emsg(_(e_failed));
7618+
else
7619+
{
7620+
char_u *acmd_fname;
7621+
7622+
post_chdir(scope);
7623+
7624+
if (dir_differs)
76217625
{
7622-
char_u *acmd_fname;
7623-
int is_winlocal_chdir = eap->cmdidx == CMD_lcd
7624-
|| eap->cmdidx == CMD_lchdir;
7625-
int is_tablocal_chdir = eap->cmdidx == CMD_tcd
7626-
|| eap->cmdidx == CMD_tchdir;
7626+
if (scope == CDSCOPE_WINDOW)
7627+
acmd_fname = (char_u *)"window";
7628+
else if (scope == CDSCOPE_TABPAGE)
7629+
acmd_fname = (char_u *)"tabpage";
7630+
else
7631+
acmd_fname = (char_u *)"global";
7632+
apply_autocmds(EVENT_DIRCHANGED, acmd_fname, new_dir, FALSE,
7633+
curbuf);
7634+
}
7635+
retval = TRUE;
7636+
}
7637+
vim_free(tofree);
7638+
7639+
return retval;
7640+
}
7641+
7642+
/*
7643+
* ":cd", ":tcd", ":lcd", ":chdir" ":tchdir" and ":lchdir".
7644+
*/
7645+
void
7646+
ex_cd(exarg_T *eap)
7647+
{
7648+
char_u *new_dir;
76277649

7628-
post_chdir(is_tablocal_chdir, is_winlocal_chdir);
7650+
new_dir = eap->arg;
7651+
#if !defined(UNIX) && !defined(VMS)
7652+
// for non-UNIX ":cd" means: print current directory
7653+
if (*new_dir == NUL)
7654+
ex_pwd(NULL);
7655+
else
7656+
#endif
7657+
{
7658+
cdscope_T scope = CDSCOPE_GLOBAL;
76297659

7630-
/* Echo the new current directory if the command was typed. */
7660+
if (eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir)
7661+
scope = CDSCOPE_WINDOW;
7662+
else if (eap->cmdidx == CMD_tcd || eap->cmdidx == CMD_tchdir)
7663+
scope = CDSCOPE_TABPAGE;
7664+
7665+
if (changedir_func(new_dir, eap->forceit, scope))
7666+
{
7667+
// Echo the new current directory if the command was typed.
76317668
if (KeyTyped || p_verbose >= 5)
76327669
ex_pwd(eap);
7633-
7634-
if (dir_differs)
7635-
{
7636-
if (is_winlocal_chdir)
7637-
acmd_fname = (char_u *)"window";
7638-
else if (is_tablocal_chdir)
7639-
acmd_fname = (char_u *)"tabpage";
7640-
else
7641-
acmd_fname = (char_u *)"global";
7642-
apply_autocmds(EVENT_DIRCHANGED, acmd_fname,
7643-
new_dir, FALSE, curbuf);
7644-
}
76457670
}
7646-
vim_free(tofree);
76477671
}
76487672
}
76497673

src/if_py_both.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ _VimChdir(PyObject *_chdir, PyObject *args, PyObject *kwargs)
10321032
Py_DECREF(newwd);
10331033
Py_XDECREF(todecref);
10341034

1035-
post_chdir(FALSE, FALSE);
1035+
post_chdir(CDSCOPE_GLOBAL);
10361036

10371037
if (VimTryEnd())
10381038
{

src/proto/ex_docmd.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ void ex_splitview(exarg_T *eap);
3737
void tabpage_new(void);
3838
void do_exedit(exarg_T *eap, win_T *old_curwin);
3939
void free_cd_dir(void);
40-
void post_chdir(int tablocal, int winlocal);
40+
void post_chdir(cdscope_T cdscope);
41+
int changedir_func(char_u *new_dir, int forceit, cdscope_T cdscope);
4142
void ex_cd(exarg_T *eap);
4243
void do_sleep(long msec);
4344
void ex_may_print(exarg_T *eap);

src/structs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3555,3 +3555,10 @@ typedef struct {
35553555
varnumber_T vv_count;
35563556
varnumber_T vv_count1;
35573557
} vimvars_save_T;
3558+
3559+
// Scope for changing directory
3560+
typedef enum {
3561+
CDSCOPE_GLOBAL, // :cd
3562+
CDSCOPE_TABPAGE, // :tcd
3563+
CDSCOPE_WINDOW // :lcd
3564+
} cdscope_T;

0 commit comments

Comments
 (0)