2

I'm using vim and configured :terminal with bash and vi-mode. To do that I've configured .inputrc config from: https://vim.fandom.com/wiki/Use_vi_shortcuts_in_terminal. I've noticed that my keybindings don't cooperate with bindings in the terminal. Eg. I'm switching buffers with <TAB>. That keybinding doesn't distinguish whether bash in :terminal is in insert or normal mode so I need to type <C-w>w to go forward. When I use following mapping in my .vimrc:

tmap <TAB> <C-w>w

then I cannot use tab anymore when bash in my :terminal is in insert mode. Vim uses tmap mappings for both vi-modes insert and normal modes.

Is this possible to have <TAB> switching the buffer only when bash in :terminal is in normal mode and <TAB> doing autocompletion when bash in :terminal is in the insert mode?

5
  • "Vim uses tmap mappings for both :terminal insert and normal modes." No, it doesn't, only terminal insert mode. See [:help mapmode-t]: "The terminal mappings are used in a terminal window, when typing keys for the job running in the terminal."
    – filbranden
    May 16, 2020 at 22:38
  • @filbranden I wasn't clear about that in the description. Terminal mode has actually 3 states: [1] terminal normal mode - it allows using regular mappings, [2] terminal insert mode with bash in insert mode (can be configured in .inputrc), [3] terminal insert mode with bash in normal mode (can be configured in .inputrc). I don't know how to distinguish two last modes so my terminal mapping work only in the last mode.
    – Pankracy
    May 17, 2020 at 9:19
  • Ah I can't tell that at all from reading your question (well, only in that you mention .inputrc that doesn't matter at all for Vim.) Please edit your question to make it clear you're talking about "vi-mode" in bash. But to answer your question, Vim doesn't really have a direct way to know whether bash inside a :terminal is in Insert or Normal mode, so a :tmap in Vim will always execute the action when you press that key from that terminal window.
    – filbranden
    May 17, 2020 at 12:22
  • Does it work using <C-v><Tab> to insert an actual Tab character in that terminal window? If not, maybe configure a mapping for it with tnoremap <C-v><Tab> <Tab>? That way you still have a way to enter a literal tab...
    – filbranden
    May 17, 2020 at 12:23
  • I could do tnoremap <S-Tab> <Tab> with tmap <TAB> <C-w>w and use Shift with Tab whenever I want to use autocompletion in the terminal but that doesn't solve the problem. I've got many other keybindings that I would like to use when my bash is in insert vi-mode. I've noticed that in emacs that feature is available by default.
    – Pankracy
    May 17, 2020 at 14:09

1 Answer 1

2

Vim can't really tell whether the bash shell inside its terminal is running in vi-insert mode or in vi-normal mode. In fact, it can't even tell whether it's using vi or emacs mode. Or at some moments, while you're running a command inside bash, it doesn't even make sense to talk about whether bash is in insert or normal mode, since technically it isn't in either...

So to achieve this kind of integration, you'll need to have bash tell Vim about which mode it's in, or similarly, have bash interpret the key-bindings and call Vim commands from bash when the special keys are triggered.

Personally, I think the latter is easiest, doing the key bindings in bash, since last I looked there wasn't really a very reliable to hook into the mode change from bash, or even to reliably detect when a new command was about to be executed. (zsh seems to be much better than bash in this sense, it has more hooks and they're typically easier to use.)

The other piece missing here is to call Vim commands from the shell running inside Vim's terminal. You can do that using the terminal-api, which allows you to use an escape sequence from bash to call a function in Vim.

For the bash bindings, see the bind command, I believe you can pass it a -m to create a binding for a specific mode (such as vi-normal) and you can use -x to execute some code (or a function) when a specific key combination is entered.

For example, this will make <Tab> (which is the same as Ctrl+I) call function wincmd_next() when pressed in vi mode (which matches the "normal" vi mode in readline):

bind -m vi -x '"\C-i": wincmd_next'

For the Vim terminal API, you can export a Tapi_wincmd function that can be called from the shell. Then from bash you should be able to use:

echo -ne '\e]51;["call","Tapi_wincmd",["w"]]\a'

So just turn that into the wincmd_next function:

wincmd_next () {
    echo -ne '\e]51;["call","Tapi_wincmd",["w"]]\a'
}

And then you can write the corresponding Vim function:

function! Tapi_wincmd(bufnum, arglist)
    execute 'wincmd' a:arglist[0]
endfunction

This way, the bash command above will cause Vim to cycle to the next window.

The terminal API takes JSON encoded arguments. By default, only functions named using the Tapi_ prefix are allowed to be called from the terminal API. So you can decide how much and which specific features and parameters you want to expose to terminal applications from Vim.

I believe these pointers should be enough for you to put together everything you need to set up the specific scenario you described, which involves switching Vim windows when Tab is pressed, but only when the shell is in the vi-normal mode.


An alternative to this approach is to use :tnoremap to hook into keystrokes whenever they're being sent to the application running on the terminal, whether that's bash in vi-normal mode, bash in vi-insert mode or another application altogether.

As you mentioned in the question, you can configure that with:

tnoremap <Tab> <C-w>w

But that doesn't leave you a way to use an actual Tab in the terminal, which might be useful when doing completion in vi-insert (or emacs) mode.

You can work around that by mapping a different keystroke to send an actual Tab to the terminal application, for example, one of:

tnoremap <S-Tab> <Tab>      " Shift-Tab
tnoremap <C-v><Tab> <Tab>   " Ctrl-V, Tab
tnoremap <C-P> <Tab>        " Ctrl-P
4
  • Thanks a lot! I've noticed that Tapi_wincmd function should have following signature: function! Tapi_wincmd(bufnum, arglist) and body execute 'wincmd' a:arglist[0] and it works when I paste echo statement in my bash. But setting correct binding in .inputrc is still beyond my power. Any help?
    – Pankracy
    May 18, 2020 at 11:05
  • @Pankracy I updated the question with an example. You can't really configure this from inputrc, since you're calling a bash function/command, so you need bind -x instead. Anyways, see the update in the answer, see if that works for you!
    – filbranden
    May 18, 2020 at 13:52
  • 1
    Works perfect now! Thanks a lot. Also "\C-i" wasn't obvious for me.
    – Pankracy
    May 18, 2020 at 14:10
  • @Pankracy Glad this all worked fine! Yeah Tab == Ctrl+I. Not sure if there's a specific name for "Tab" in readline, there might be one, but this one I was sure was gonna work... Anyways, enjoy your new bash + Vim terminal integration! 😁
    – filbranden
    May 18, 2020 at 14:12

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.