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
:help mapmode-t
]: "The terminal mappings are used in a terminal window, when typing keys for the job running in the terminal.".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..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.<C-v><Tab>
to insert an actual Tab character in that terminal window? If not, maybe configure a mapping for it withtnoremap <C-v><Tab> <Tab>
? That way you still have a way to enter a literal tab...tnoremap <S-Tab> <Tab>
withtmap <TAB> <C-w>w
and useShift
withTab
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 insertvi-mode
. I've noticed that in emacs that feature is available by default.