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[ir][!] [path] Same as |:cd|.
*:tc* *:tcd* *E5000* *E5001* *E5002*
:tc[d][!] {path} Like |:cd|, but set the current directory for the
current tab and window. The current directory for
other tabs and windows is not changed.
*:tc* *:tcd*
:tc[d][!] {path} Like |:cd|, but only set the directory for the current
tab. The current window will also use this directory.
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-*
: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.
Also see |getcwd()|.
So long as no |:tcd| or |:lcd| command has been used, all windows share the
same "current directory". Using a command to jump to another window doesn't
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
change anything for the current directory.
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}
charidx({string}, {idx} [, {countcc}])
Number char index of byte {idx} in {string}
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
@ -2461,6 +2462,7 @@ has({feature}) Number |TRUE| if feature {feature} supported
has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key}
haslocaldir([{winnr} [, {tabnr}]])
Number |TRUE| if current window executed |:lcd|
or |:tcd|
hasmapto({what} [, {mode} [, {abbr}]])
Number |TRUE| if mapping to {what} exists
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́ć', 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()*
Get the amount of indent for line {lnum} according the C
indenting rules, as with 'cindent'.
@ -4987,6 +5010,8 @@ getcwd([{winnr}[, {tabnr}]]) *getcwd()*
getcwd(0, 0)
< If {winnr} is -1 it is ignored, only the tab is resolved.
{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|: >
GetWinnr()->getcwd()

View File

@ -202,14 +202,28 @@ the other window. This is called a local directory. >
:pwd
/home/Bram/VeryLongFileName
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
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 of the other window.
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.
When using a ":cd" command in a window that uses a different current
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.
When using a `:cd` command in a window that uses a different current
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

View File

@ -785,9 +785,10 @@ System functions and manipulation of files:
isdirectory() check if a directory exists
getfsize() get the size of a file
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
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

View File

@ -197,6 +197,10 @@ Functions:
|stdpath()|
|system()|, |systemlist()| can run {cmd} directly (without 'shell')
|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-blend| controls blend level for a highlight group

View File

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

View File

@ -28,6 +28,7 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/globals.h"
#include "nvim/if_cscope.h"
#include "nvim/indent.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;
}
// "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
*/

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)
{
char_u *new_dir;
char_u *tofree;
new_dir = eap->arg;
#if !defined(UNIX)
#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;
}
// ":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
CdScope scope = kCdScopeGlobal;
switch (eap->cmdidx) {
case CMD_tcd:
case CMD_tchdir:
@ -7810,19 +7827,12 @@ void ex_cd(exarg_T *eap)
default:
break;
}
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);
if (changedir_func(new_dir, scope)) {
// Echo the new current directory if the command was typed.
if (KeyTyped || p_verbose >= 5) {
ex_pwd(eap);
}
}
xfree(tofree);
}
}

View File

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