2

I have the following code in my ~/.zshrc:

autoload -Uz edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line

It binds the edit-command-line zle widget to the keysequence C-x C-e. The widget is described in man zshcontrib (section ZLE FUNCTIONS, subsection Widgets):

edit-command-line Edit the command line using your visual editor, as in ksh.

          bindkey -M vicmd v edit-command-line

The purpose is to be able to edit the current shell command line, in the default editor, by pressing C-x C-e, similar to what the readline function edit-and-execute-command does (man bash, section READLINE, subsection Commands for Manipulating the History).

edit-and-execute-command (C-xC-e) Invoke an editor on the current command line, and execute the result as shell commands. Bash attempts to invoke $VISUAL, $EDITOR, and emacs as the editor, in that order.

My default editor is Vim 8.1 (patches 1-538 included). I have Vim mappings, using the keys C-h, C-j, C-k, C-l, to move the focus to neighbour windows. They can be boiled down to:

nnoremap <c-h> :<c-u>wincmd h<cr>
nnoremap <c-j> :<c-u>wincmd j<cr>
nnoremap <c-k> :<c-u>wincmd k<cr>
nnoremap <c-l> :<c-u>wincmd l<cr>

They all work as expected in a regular Vim instance (started by executing $ vim). But C-j doesn't work as expected when Vim was started by edit-command-line.

When I press C-x C-e while on the shell command line, zsh starts Vim. If I split the window by executing :split, then press C-k to move to the top window, I get two windows, and the focus is moved to the top one. But then, if I press C-j to get back to the bottom window, nothing happens.

I don't know whether it's the cause of the issue, but if I try to insert a literal C-j in a Vim buffer (by pressing C-v C-j), ^M is displayed (the caret notation for a carriage return). In a regular Vim instance (started by executing $ vim), inserting a literal C-j results in a character whose caret notation is ^@ (a NUL).

I can reproduce the issue with this minimal ~/.zshrc:

export EDITOR=vim
autoload -Uz edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line

And this minimal ~/.vimrc:

nnoremap <c-j> :echom 'C-j has been pressed'<cr>

Pressing C-x C-e on the zsh command line starts Vim, then pressing C-j should print and log the message:

C-j has been pressed

But nothing happens.

I can't reproduce the issue in bash, nor with Neovim (v0.3.2-752-g4d7c7f9). Besides, inserting a literal C-j in Vim, when the latter was started from bash after pressing C-x C-e, results in a NUL (^@). Same thing when Neovim is started from bash or zsh after pressing C-x C-e.

┌────────┬──────┬─────┐
│        │ bash │ zsh │
├────────┼──────┼─────┤
│ Vim    │ ^@   │ ^M  │
├────────┼──────┼─────┤
│ Neovim │ ^@   │ ^@  │
└────────┴──────┴─────┘

I thought that maybe some Vim terminal option was not properly configured, so I captured the output of:

:set termcap

from Vim started by $ vim, and from Vim started by C-x C-e. But the output is identical in both cases:

--- Terminal codes ---

  t_AL=^[[%p1%dL      t_DL=^[[%p1%dM      t_mr=^[[7m          t_se=^[[27m         t_us=^[[4m
  t_al=^[[L           t_dl=^[[M           t_ms=y              t_Sf=               t_ut=
  t_bc=               t_EC=               t_nd=^[[C           t_SH=               t_vb=^[g
  t_BE=               t_EI=^[[2 q         t_op=^[[39;49m      t_SI=^[[6 q         t_vi=^[[?25l
  t_BD=               t_fs=^G             t_RF=               t_Si=               t_VS=
  t_cd=^[[J           t_GP=               t_RB=               t_so=^[[7m          t_vs=^[[34l
  t_ce=^[[K           t_IE=               t_RC=               t_SR=^[[4 q         t_WP=
  t_cl=^[[H^[[J       t_IS=               t_RI=^[[%p1%dC      t_sr=^[M            t_WS=
  t_Ce=               t_ke=^[[?1l^[>      t_Ri=               t_ST=               t_xn=y
  t_Co=256            t_ks=^[[?1h^[=      t_RS=               t_Te=               t_xs=
  t_CS=               t_le=^H             t_RT=               t_Ts=               t_ZH=^[[3m
  t_CV=               t_mb=^[[5m          t_RV=               t_ts=^[]0;          t_ZR=^[[23m
  t_da=               t_md=^[[1m          t_Sb=               t_u7=               t_8f=
  t_db=               t_me=^[[0m          t_SC=               t_ue=^[[24m         t_8b=
  t_AB=^[[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m
  t_AF=^[[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m
  t_cm=^[[%i%p1%d;%p2%dH
  t_Cs=^[]12;%p1%s^G
  t_cs=^[[%i%p1%d;%p2%dr
  t_te=^[[2 q^[[?1004l^[[?1049l
  t_ti=^[[2 q^[[?1004h^[[?1049h
  t_ve=^[[34h^[[?25h

--- Terminal keys ---

t_#2 <S-Home>    ^[[1;2H   t_k6 <F6>        ^[[17~    t_kh <Home>      ^[[1~          <ð>        ^[p
t_#4 <S-Left>    ^[[1;2D   t_k7 <F7>        ^[[18~    t_kl <Left>      ^[OD           <ô>        ^[t
t_%i <S-Right>   ^[[1;2C   t_k8 <F8>        ^[[19~    t_kr <Right>     ^[OC           <õ>        ^[u
t_*7 <S-End>     ^[[1;2F   t_k9 <F9>        ^[[20~    t_ku <Up>        ^[OA           <ù>        ^[y
t_@7 <End>       ^[[4~     t_k; <F10>       ^[[21~         <á>        ^[a            <ú>        ^[z
t_F1 <F11>       ^[[23~    t_kB <S-Tab>     ^[[Z           <â>        ^[b            <Mouse>     ^[[M
t_F2 <F12>       ^[[24~    t_kD <Del>       ^[[3~          <ä>        ^[d            <S-F18>     ^[[O
t_k1 <F1>        ^[OP      t_kI <Insert>    ^[[2~          <å>        ^[e            <S-F19>     ^[[I
t_k2 <F2>        ^[OQ      t_kN <PageDown>  ^[[6~          <æ>        ^[f            <xUp>       ^[[1;*A
t_k3 <F3>        ^[OR      t_kP <PageUp>    ^[[5~          <ç>        ^[g            <xDown>     ^[[1;*B
t_k4 <F4>        ^[OS      t_kb <BS>        ^?             <í>        ^[m            <xLeft>     ^[[1;*D
t_k5 <F5>        ^[[15~    t_kd <Down>      ^[OB           <î>        ^[n            <xRight>    ^[[1;*C

The output of $ stty -a is also identical in bash and zsh:

speed 38400 baud; rows 33; columns 119; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc

I'm using zsh 5.6.2-dev-1 (x86_64-pc-linux-gnu).

Is there a Vim or zsh option which should be set to prevent Vim from translating C-j into C-m when started by the zle widget edit-command-line?

  • 1
    Zsh modifies stty settings while zle is active. Running stty from a prompt shows the settings when running commands, not the settings during command line edition. This looks like a bug where it doesn't properly restore stty settings when it runs a command from within command line edition. – Gilles Nov 28 '18 at 21:19
  • @Gilles, zsh does redirect stdin from /dev/null inside user-defined widgets, but edit-command-line does a exec < /dev/tty, but does not restore the non-zle settings, so the bug if more with that edit-command-line widget. – Stéphane Chazelas Dec 2 '18 at 14:51
2

If you set your $EDITOR to a script or function that does stty -a, you'll notice that the terminal settings at the time vim is called by that edit-command-line are those of zle (where inlcr which causes the terminal device driver to translate NL to CR is on among other things).

^J is the newline character, so binding it is a bit delicate.

Here, you can work around it by defining a vim function as:

vim() STTY=sane command vim "$@"

Or add edit the STTY=sane (or STTY=-inlcr) to edit-command-line.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.