vim-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 vim/vim#4358)
1063f3d200

Also includes some documentation changes from patch 8.1.1218.
This commit is contained in:
zeertzjq 2021-10-17 22:04:53 +08:00
parent 57651df9c1
commit 8727d38012
9 changed files with 191 additions and 55 deletions

View File

@ -1275,10 +1275,12 @@ exist, the next-higher scope in the hierarchy applies.
*:chd* *:chdir* *:chd* *:chdir*
:chd[ir][!] [path] Same as |:cd|. :chd[ir][!] [path] Same as |:cd|.
*:tc* *:tcd* *E5000* *E5001* *E5002* *:tc* *:tcd*
:tc[d][!] {path} Like |:cd|, but set the current directory for the :tc[d][!] {path} Like |:cd|, but only set the directory for the current
current tab and window. The current directory for tab. The current window will also use this directory.
other tabs and windows is not changed. The current directory is not changed for windows in
other tabs and for windows in the current tab that
have their own window-local directory.
*:tcd-* *:tcd-*
:tc[d][!] - Change to the previous current directory (before the :tc[d][!] - Change to the previous current directory (before the
@ -1303,8 +1305,8 @@ exist, the next-higher scope in the hierarchy applies.
:pw[d] Print the current directory name. :pw[d] Print the current directory name.
Also see |getcwd()|. Also see |getcwd()|.
So long as no |:tcd| or |:lcd| command has been used, all windows share the So long as no |:lcd| or |:tcd| command has been used, all windows share the
same "current directory". Using a command to jump to another window doesn't same current directory. Using a command to jump to another window doesn't
change anything for the current directory. change anything for the current directory.
When |:lcd| has been used for a window, the specified directory becomes the When |:lcd| has been used for a window, the specified directory becomes the

View File

@ -2316,6 +2316,7 @@ chansend({id}, {data}) Number Writes {data} to channel
char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
charidx({string}, {idx} [, {countcc}]) charidx({string}, {idx} [, {countcc}])
Number char index of byte {idx} in {string} Number char index of byte {idx} in {string}
chdir({dir}) String change current working directory
cindent({lnum}) Number C indent for line {lnum} cindent({lnum}) Number C indent for line {lnum}
clearmatches([{win}]) none clear all matches clearmatches([{win}]) none clear all matches
col({expr}) Number column nr of cursor or mark col({expr}) Number column nr of cursor or mark
@ -2461,6 +2462,7 @@ has({feature}) Number |TRUE| if feature {feature} supported
has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key} has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key}
haslocaldir([{winnr} [, {tabnr}]]) haslocaldir([{winnr} [, {tabnr}]])
Number |TRUE| if current window executed |:lcd| Number |TRUE| if current window executed |:lcd|
or |:tcd|
hasmapto({what} [, {mode} [, {abbr}]]) hasmapto({what} [, {mode} [, {abbr}]])
Number |TRUE| if mapping to {what} exists Number |TRUE| if mapping to {what} exists
histadd({history}, {item}) String add an item to a history histadd({history}, {item}) String add an item to a history
@ -3280,6 +3282,27 @@ charidx({string}, {idx} [, {countcc}])
echo charidx('áb́ć', 6, 1) returns 4 echo charidx('áb́ć', 6, 1) returns 4
echo charidx('áb́ć', 16) returns -1 echo charidx('áb́ć', 16) returns -1
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()* cindent({lnum}) *cindent()*
Get the amount of indent for line {lnum} according the C Get the amount of indent for line {lnum} according the C
indenting rules, as with 'cindent'. indenting rules, as with 'cindent'.
@ -4987,6 +5010,8 @@ getcwd([{winnr}[, {tabnr}]]) *getcwd()*
getcwd(0, 0) getcwd(0, 0)
< If {winnr} is -1 it is ignored, only the tab is resolved. < If {winnr} is -1 it is ignored, only the tab is resolved.
{winnr} can be the window number or the |window-ID|. {winnr} can be the window number or the |window-ID|.
If both {winnr} and {tabnr} are -1 the global working
directory is returned.
Can also be used as a |method|: > Can also be used as a |method|: >
GetWinnr()->getcwd() GetWinnr()->getcwd()

View File

@ -202,14 +202,28 @@ the other window. This is called a local directory. >
:pwd :pwd
/home/Bram/VeryLongFileName /home/Bram/VeryLongFileName
So long as no ":lcd" command has been used, all windows share the same current So long as no `:lcd` command has been used, all windows share the same current
directory. Doing a ":cd" command in one window will also change the current directory. Doing a `:cd` command in one window will also change the current
directory of the other window. directory of the other window.
For a window where ":lcd" has been used a different current directory is For a window where `:lcd` has been used a different current directory is
remembered. Using ":cd" or ":lcd" in other windows will not change it. remembered. Using `:cd` or `:lcd` in other windows will not change it.
When using a ":cd" command in a window that uses a different current When using a `:cd` command in a window that uses a different current
directory, it will go back to using the shared directory. directory, it will go back to using the shared directory.
TAB LOCAL DIRECTORY
When you open a new tab page, it uses the directory of the window in the
previous tab page from which the new tab page was opened. You can change the
directory of the current tab page using the `:tcd` command. All the windows in
a tab page share this directory except for windows with a window-local
directory. Any new windows opened in this tab page will use this directory as
the current working directory. Using a `:cd` command in a tab page will not
change the working directory of tab pages which have a tab local directory.
When the global working directory is changed using the ":cd" command in a tab
page, it will also change the current tab page working directory.
============================================================================== ==============================================================================
*22.3* Finding a file *22.3* Finding a file

View File

@ -785,9 +785,10 @@ System functions and manipulation of files:
isdirectory() check if a directory exists isdirectory() check if a directory exists
getfsize() get the size of a file getfsize() get the size of a file
getcwd() get the current working directory getcwd() get the current working directory
haslocaldir() check if current window used |:lcd| haslocaldir() check if current window used |:lcd| or |:tcd|
tempname() get the name of a temporary file tempname() get the name of a temporary file
mkdir() create a new directory mkdir() create a new directory
chdir() change current working directory
delete() delete a file delete() delete a file
rename() rename a file rename() rename a file
system() get the result of a shell command as a string system() get the result of a shell command as a string

View File

@ -197,6 +197,10 @@ Functions:
|stdpath()| |stdpath()|
|system()|, |systemlist()| can run {cmd} directly (without 'shell') |system()|, |systemlist()| can run {cmd} directly (without 'shell')
|matchadd()| can be called before highlight group is defined |matchadd()| can be called before highlight group is defined
|getcwd()| and |haslocaldir()| may throw errors. *E5000* *E5001* *E5002*
|haslocaldir()|'s only possible return values are 0 and 1, it never returns 2.
`getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global
working directory. Use `getcwd(-1, -1)` to get the global working directory.
Highlight groups: Highlight groups:
|highlight-blend| controls blend level for a highlight group |highlight-blend| controls blend level for a highlight group

View File

@ -72,6 +72,7 @@ return {
chansend={args=2}, chansend={args=2},
char2nr={args={1, 2}, base=1}, char2nr={args={1, 2}, base=1},
charidx={args={2, 3}}, charidx={args={2, 3}},
chdir={args=1, base=1},
cindent={args=1, base=1}, cindent={args=1, base=1},
clearmatches={args={0, 1}, base=1}, clearmatches={args={0, 1}, base=1},
col={args=1, base=1}, col={args=1, base=1},

View File

@ -28,6 +28,7 @@
#include "nvim/file_search.h" #include "nvim/file_search.h"
#include "nvim/fileio.h" #include "nvim/fileio.h"
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/globals.h"
#include "nvim/if_cscope.h" #include "nvim/if_cscope.h"
#include "nvim/indent.h" #include "nvim/indent.h"
#include "nvim/indent_c.h" #include "nvim/indent_c.h"
@ -1062,6 +1063,43 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = len > 0 ? len - 1 : 0; rettv->vval.v_number = len > 0 ? len - 1 : 0;
} }
// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
char_u *cwd;
CdScope scope = kCdScopeGlobal;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
if (argvars[0].v_type != VAR_STRING) {
return;
}
// Return the current directory
cwd = xmalloc(MAXPATHL);
if (cwd != NULL) {
if (os_dirname(cwd, MAXPATHL) != FAIL) {
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(cwd);
#endif
rettv->vval.v_string = vim_strsave(cwd);
}
xfree(cwd);
}
if (curwin->w_localdir != NULL) {
scope = kCdScopeWindow;
} else if (curtab->tp_localdir != NULL) {
scope = kCdScopeTab;
}
if (!changedir_func(argvars[0].vval.v_string, scope)) {
// Directory change failed
XFREE_CLEAR(rettv->vval.v_string);
}
}
/* /*
* "cindent(lnum)" function * "cindent(lnum)" function
*/ */

View File

@ -7753,51 +7753,68 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
} }
} }
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. /// Change directory function used by :cd/:tcd/:lcd Ex commands and the chdir() function.
/// @return true if the directory is successfully changed.
bool changedir_func(char_u *new_dir, CdScope scope)
{
char_u *tofree;
bool retval = false;
if (allbuf_locked()) {
return false;
}
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
if (prev_dir == NULL) {
EMSG(_("E186: No previous directory"));
return false;
}
new_dir = prev_dir;
}
// Save current directory for next ":cd -"
tofree = prev_dir;
if (os_dirname(NameBuff, MAXPATHL) == OK) {
prev_dir = vim_strsave(NameBuff);
} else {
prev_dir = NULL;
}
#if defined(UNIX)
// On Unix ":cd" means: go to home directory.
if (*new_dir == NUL) {
// Use NameBuff for home directory name.
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
new_dir = NameBuff;
}
#endif
bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
if (dir_differs && vim_chdir(new_dir)) {
EMSG(_(e_failed));
} else {
post_chdir(scope, dir_differs);
retval = true;
}
xfree(tofree);
return retval;
}
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
void ex_cd(exarg_T *eap) void ex_cd(exarg_T *eap)
{ {
char_u *new_dir; char_u *new_dir;
char_u *tofree;
new_dir = eap->arg; new_dir = eap->arg;
#if !defined(UNIX) #if !defined(UNIX) && !defined(VMS)
// for non-UNIX ":cd" means: print current directory // for non-UNIX ":cd" means: print current directory
if (*new_dir == NUL) { if (*new_dir == NUL) {
ex_pwd(NULL); ex_pwd(NULL);
} else } else
#endif #endif
{ {
if (allbuf_locked()) { CdScope scope = kCdScopeGlobal;
return;
}
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
if (prev_dir == NULL) {
EMSG(_("E186: No previous directory"));
return;
}
new_dir = prev_dir;
}
// Save current directory for next ":cd -"
tofree = prev_dir;
if (os_dirname(NameBuff, MAXPATHL) == OK) {
prev_dir = vim_strsave(NameBuff);
} else {
prev_dir = NULL;
}
#if defined(UNIX)
// On Unix ":cd" means: go to home directory.
if (*new_dir == NUL) {
// Use NameBuff for home directory name.
expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
new_dir = NameBuff;
}
#endif
CdScope scope = kCdScopeGlobal; // Depends on command invoked
switch (eap->cmdidx) { switch (eap->cmdidx) {
case CMD_tcd: case CMD_tcd:
case CMD_tchdir: case CMD_tchdir:
@ -7810,19 +7827,12 @@ void ex_cd(exarg_T *eap)
default: default:
break; break;
} }
if (changedir_func(new_dir, scope)) {
bool dir_differs = prev_dir == NULL || pathcmp((char *)prev_dir, (char *)new_dir, -1) != 0;
if (dir_differs && vim_chdir(new_dir)) {
EMSG(_(e_failed));
} else {
post_chdir(scope, dir_differs);
// Echo the new current directory if the command was typed. // Echo the new current directory if the command was typed.
if (KeyTyped || p_verbose >= 5) { if (KeyTyped || p_verbose >= 5) {
ex_pwd(eap); ex_pwd(eap);
} }
} }
xfree(tofree);
} }
} }

View File

@ -69,6 +69,47 @@ func Test_cd_with_cpo_chdir()
bw! bw!
endfunc 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
func Test_cd_from_non_existing_dir() func Test_cd_from_non_existing_dir()
CheckNotMSWindows CheckNotMSWindows