Skip to content

Commit

Permalink
patch 8.2.2675: directory change in a terminal window shell is not fo…
Browse files Browse the repository at this point in the history
…llowed

Problem:    Directory change in a terminal window shell is not followed.
Solution:   Add the 'autoshelldir' option. (closes #6290)
  • Loading branch information
brammool committed Mar 29, 2021
1 parent 9dbe701 commit 8b9abfd
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 2 deletions.
9 changes: 9 additions & 0 deletions runtime/doc/options.txt
Expand Up @@ -749,6 +749,15 @@ A jump table for the options with a short description can be found at |Q_op|.
or selected.
Note: When this option is on some plugins may not work.

*'autoshelldir'* *'asd'* *'noautoshelldir'* *'noasd'*
'autoshelldir' 'asd' boolean (default off)
global
When on, Vim will change the current working directory whenever you
change the directory of the shell running in a terminal window. You
need proper setting-up, so whenever the shell's pwd changes an OSC 7
escape sequence will be emitted. For example, on Linux, you can source
/etc/profile.d/vte.sh in your shell profile if you use bash or zsh.

*'arabic'* *'arab'* *'noarabic'* *'noarab'*
'arabic' 'arab' boolean (default off)
local to window
Expand Down
1 change: 1 addition & 0 deletions runtime/doc/quickref.txt
Expand Up @@ -605,6 +605,7 @@ Short explanation of each option: *option-list*
'ambiwidth' 'ambw' what to do with Unicode chars of ambiguous width
'antialias' 'anti' Mac OS X: use smooth, antialiased fonts
'autochdir' 'acd' change directory to the file in the current window
'autoshelldir' 'asd' change directory to the shell's current directory
'arabic' 'arab' for Arabic as a default second language
'arabicshape' 'arshape' do shaping for Arabic characters
'autoindent' 'ai' take indent for new line from previous line
Expand Down
2 changes: 2 additions & 0 deletions runtime/optwin.vim
Expand Up @@ -266,6 +266,8 @@ if exists("+autochdir")
call <SID>AddOption("autochdir", gettext("change to directory of file in buffer"))
call <SID>BinOptionG("acd", &acd)
endif
call <SID>AddOption("autoshelldir", gettext("change to pwd of shell in terminal buffer"))
call <SID>BinOptionG("asd", &asd)
call <SID>AddOption("wrapscan", gettext("search commands wrap around the end of the buffer"))
call <SID>BinOptionG("ws", &ws)
call <SID>AddOption("incsearch", gettext("show match for partly typed search command"))
Expand Down
3 changes: 2 additions & 1 deletion src/charset.c
Expand Up @@ -2005,7 +2005,8 @@ hex2nr(int c)
return c - '0';
}

#if defined(FEAT_TERMRESPONSE) || defined(FEAT_GUI_GTK) || defined(PROTO)
#if defined(FEAT_TERMRESPONSE) || defined(FEAT_GUI_GTK) \
|| defined(PROTO) || defined(FEAT_AUTOSHELLDIR)
/*
* Convert two hex characters to a byte.
* Return -1 if one of the characters is not hex.
Expand Down
6 changes: 6 additions & 0 deletions src/feature.h
Expand Up @@ -1147,6 +1147,12 @@
# define FEAT_SYN_HL
#endif

/*
* +autoshelldir 'autoshelldir' option.
*/
#if defined(FEAT_TERMINAL)
# define FEAT_AUTOSHELLDIR
#endif
/*
* +textprop and +popupwin Text PROPerties and POPUP windows
*/
Expand Down
3 changes: 3 additions & 0 deletions src/option.h
Expand Up @@ -383,6 +383,9 @@ EXTERN char_u *p_ambw; // 'ambiwidth'
#ifdef FEAT_AUTOCHDIR
EXTERN int p_acd; // 'autochdir'
#endif
#ifdef FEAT_AUTOSHELLDIR
EXTERN int p_asd; // 'autoshelldir'
#endif
EXTERN int p_ai; // 'autoindent'
EXTERN int p_bin; // 'binary'
EXTERN int p_bomb; // 'bomb'
Expand Down
9 changes: 9 additions & 0 deletions src/optiondefs.h
Expand Up @@ -370,6 +370,15 @@ static struct vimoption options[] =
#else
(char_u *)NULL, PV_NONE,
{(char_u *)0L, (char_u *)0L}
#endif
SCTX_INIT},
{"autoshelldir", "asd", P_BOOL|P_VI_DEF,
#ifdef FEAT_AUTOSHELLDIR
(char_u *)&p_asd, PV_NONE,
{(char_u *)FALSE, (char_u *)0L}
#else
(char_u *)NULL, PV_NONE,
{(char_u *)0L, (char_u *)0L}
#endif
SCTX_INIT},
{"autoindent", "ai", P_BOOL|P_VI_DEF,
Expand Down
75 changes: 74 additions & 1 deletion src/terminal.c
Expand Up @@ -4292,6 +4292,73 @@ handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
ch_log(channel, "Calling function %s failed", func);
}

/*
* URL decoding (also know as Percent-encoding).
*
* Note this function currently is only used for decoding shell's
* OSC 7 escape sequence which we can assume all bytes are valid
* UTF-8 bytes. Thus we don't need to deal with invalid UTF-8
* encoding bytes like 0xfe, 0xff.
*/
static size_t
url_decode(const char *src, const size_t len, char_u *dst)
{
size_t i = 0, j = 0;

while (i < len)
{
if (src[i] == '%' && i + 2 < len)
{
dst[j] = hexhex2nr((char_u *)&src[i + 1]);
j++;
i += 3;
}
else
{
dst[j] = src[i];
i++;
j++;
}
}
dst[j] = '\0';
return j;
}

/*
* Sync terminal buffer's cwd with shell's pwd with the help of OSC 7.
*
* The OSC 7 sequence has the format of
* "\033]7;file://HOSTNAME/CURRENT/DIR\033\\"
* and what VTerm provides via VTermStringFragment is
* "file://HOSTNAME/CURRENT/DIR"
*/
static void
sync_shell_dir(VTermStringFragment *frag)
{
int offset = 7; // len of "file://" is 7
char *pos = (char *)frag->str + offset;
char_u *new_dir;

// remove HOSTNAME to get PWD
while (*pos != '/' && offset < frag->len)
{
offset += 1;
pos += 1;
}

if (offset >= frag->len)
{
semsg(_(e_failed_to_extract_pwd_from_str_check_your_shell_config),
frag->str);
return;
}

new_dir = alloc(frag->len - offset + 1);
url_decode(pos, frag->len-offset, new_dir);
changedir_func(new_dir, TRUE, CDSCOPE_WINDOW);
vim_free(new_dir);
}

/*
* Called by libvterm when it cannot recognize an OSC sequence.
* We recognize a terminal API command.
Expand All @@ -4306,7 +4373,13 @@ parse_osc(int command, VTermStringFragment frag, void *user)
: term->tl_job->jv_channel;
garray_T *gap = &term->tl_osc_buf;

// We recognize only OSC 5 1 ; {command}
// We recognize only OSC 5 1 ; {command} and OSC 7 ; {command}
if (p_asd && command == 7)
{
sync_shell_dir(&frag);
return 1;
}

if (command != 51)
return 0;

Expand Down
8 changes: 8 additions & 0 deletions src/testdir/check.vim
Expand Up @@ -92,6 +92,14 @@ func CheckLinux()
endif
endfunc

" Command to check for not running on a BSD system.
command CheckNotBSD call CheckNotBSD()
func CheckNotBSD()
if has('bsd')
throw 'Skipped: does not work on BSD'
endif
endfunc

" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
Expand Down
29 changes: 29 additions & 0 deletions src/testdir/test_terminal3.vim
Expand Up @@ -475,6 +475,35 @@ func Test_term_mouse()
call delete('Xbuf')
endfunc

" Test for sync buffer cwd with shell's pwd
func Test_terminal_sync_shell_dir()
CheckUnix
" The test always use sh (see src/testdir/unix.vim).
" However, BSD's sh doesn't seem to play well with OSC 7 escape sequence.
CheckNotBSD

set asd

This comment has been minimized.

Copy link
@Shane-XB-Qian

Shane-XB-Qian Mar 30, 2021

Contributor

should not recover to default value later?

This comment has been minimized.

Copy link
@brammool

brammool Mar 30, 2021

Author Contributor

That is better. But it doesn't interfere with tests currently.

" , is
" 1. a valid character for directory names
" 2. a reserved character in url-encoding
let chars = ",a"
" "," is url-encoded as '%2C'
let chars_url = "%2Ca"
let tmpfolder = fnamemodify(tempname(),':h').'/'.chars
let tmpfolder_url = fnamemodify(tempname(),':h').'/'.chars_url
call mkdir(tmpfolder, "p")
let buf = Run_shell_in_terminal({})
call term_sendkeys(buf, "echo -ne $'\\e\]7;file://".tmpfolder_url."\\a'\<CR>")
"call term_sendkeys(buf, "cd ".tmpfolder."\<CR>")
call TermWait(buf)
if has("mac")
let expected = "/private".tmpfolder
else
let expected = tmpfolder
endif
call assert_equal(expected, getcwd(winnr()))
endfunc

" Test for modeless selection in a terminal
func Test_term_modeless_selection()
CheckUnix
Expand Down
2 changes: 2 additions & 0 deletions src/version.c
Expand Up @@ -750,6 +750,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2675,
/**/
2674,
/**/
Expand Down

0 comments on commit 8b9abfd

Please sign in to comment.