Is there a way to use Cmd
key for Vim in terminal? Something MacVim does.
I have remapped Cmd+S
in iTerm2 to send Esc:w!<CR>
to save files in Vim, but this sounds a bit weak.
With all the power of iTerm and Vim there should be some way to do this right?
It is possible, but it takes some doing and has some downsides.
The first set of issues is that:
- although iTerm2 can accept the Command keys and pass them on to terminal programs, it can only do so by remapping them as something else, e.g. the Left Option or Control keys.
- it can only remap each modifier key everywhere in iTerm2; you can't do it just for certain windows, panes, or profiles.
- in order to remap a modifier within the terminal, you need to allow iTerm2 to control the computer in the Security & Privacy preference pane; when you configure iTerm2 to do remapping, it will direct you there automatically.
- once you've remapped Left Command, things like Left Command+N to open a new window no longer work. Luckily, though, you can remap e.g. Left Option to Left Command and still have your Right Command key available for Mac shortcuts; or you can switch around your Command and Option keys.
The remaining issue is that Vim isn't configured out of the box with Mac-like key bindings. Luckily, MacVim includes most of its Mac-style GUI bindings in /Applications/MacVim.app/Contents/Resources/vim/runtime/macmap.vim rather than coding them in Interface Builder or Objective-C source; so I was able to copy those and adapt them for the terminal. Here is a config that works pretty closely to MacVim (and other Mac apps):
" Keybindings for terminal vim; mostly to emulate MacVim (somewhat)
" See /Applications/MacVim.app/Contents/Resources/vim/runtime/macmap.vim (from
" which many of the following are taken); and menu.vim in the same directory.
" Since these all have native (Cmd-modified) versions in MacVim, don't bother
" defining them there.
" A utility function to help cover our bases when mapping.
"
" Example of use:
" call NvicoMapMeta('n', ':new<CR>', 1)
" is equivalent to:
" exec "set <M-n>=\<Esc>n"
" nnoremap <special> <Esc>n :new<CR>
" vnoremap <special> <Esc>n <Esc><Esc>ngv
" inoremap <special> <Esc>n <C-o><Esc>n
" cnoremap <special> <Esc>n <C-c><Esc>n
" onoremap <special> <Esc>n <Esc><Esc>n
function! NvicoMapMeta(key, cmd, add_gv)
" TODO: Make this detect whether key is something that has a Meta
" equivalent.
let l:keycode = "<M-" . a:key . ">"
let l:set_line = "set " . l:keycode . "=\<Esc>" . a:key
let l:nmap_line = 'nmap <silent> <special> ' . l:keycode . ' ' . a:cmd
let l:vnoremap_line = 'vnoremap <silent> <special> ' . l:keycode . ' <Esc>' . l:keycode
if(a:add_gv)
let l:vnoremap_line.='gv'
endif
let l:inoremap_line = 'inoremap <silent> <special> ' . l:keycode . ' <C-o>' . l:keycode
let l:cnoremap_line = 'cnoremap <special> ' . l:keycode . ' <C-c>' . l:keycode
let l:onoremap_line = 'onoremap <silent> <special> ' . l:keycode . ' <Esc>' . l:keycode
exec l:set_line
exec l:nmap_line
exec l:vnoremap_line
exec l:inoremap_line
exec l:cnoremap_line
exec l:onoremap_line
endfunction
" I can't think of a good function to assign to Meta+n, since in MacVim Cmd+N
" opens a whole new editing session.
" Meta+Shift+N
" No equivalent to this in standard MacVim. Here " it just opens a window on a
" new buffer.
call NvicoMapMeta('N', ':new<CR>', 1)
" Meta+o
" Open netrw file browser
call NvicoMapMeta('o', ':split %:p:h<CR>', 1)
" Meta+w
" Close window
call NvicoMapMeta('w', ':confirm close<CR>', 1)
" Meta+s
" Save buffer
call NvicoMapMeta('s', ':confirm w<CR>', 1)
" Meta+Shift+S
" Save as
" TODO: This is silent, so you can't tell it's waiting for input. If anyone can
" fix this, please do!
call NvicoMapMeta('S', ':confirm saveas ', 1)
" Meta+z
" Undo
call NvicoMapMeta('z', 'u', 1)
" Meta+Shift+Z
" Redo
call NvicoMapMeta('Z', '<C-r>', 1)
" Meta+x
" Cut to system clipboard (requires register +")
exec "set <M-x>=\<Esc>x"
vnoremap <special> <M-x> "+x
" Meta+c
" Copy to system clipboard (requires register +")
exec "set <M-c>=\<Esc>c"
vnoremap <special> <M-c> "+y
" Meta+v
" Paste from system clipboard (requires register +")
exec "set <M-v>=\<Esc>v"
nnoremap <silent> <special> <M-v> "+gP
cnoremap <special> <M-v> <C-r>+
execute 'vnoremap <silent> <script> <special> <M-v>' paste#paste_cmd['v']
execute 'inoremap <silent> <script> <special> <M-v>' paste#paste_cmd['i']
" Meta+a
" Select all
call NvicoMapMeta('a', ':if &slm != ""<Bar>exe ":norm gggH<C-o>G"<Bar> else<Bar>exe ":norm ggVG"<Bar>endif<CR>', 0)
" Meta+f
" Find regexp. NOTE: MacVim's Cmd+f does a non-regexp search.
call NvicoMapMeta('f', '/', 0)
" Meta+g
" Find again
call NvicoMapMeta('g', 'n', 0)
" Meta+Shift+G
" Find again, reverse direction
call NvicoMapMeta('G', 'N', 0)
" Meta+q
" Quit Vim
" Not quite identical to MacVim default (which is actually coded in the app
" itself rather than in macmap.vim)
call NvicoMapMeta('q', ':confirm qa<CR>', 0)
" Meta+Shift+{
" Switch tab left
call NvicoMapMeta('{', ':tabN<CR>', 0)
" Meta+Shift+}
" Switch tab right
call NvicoMapMeta('}', ':tabn<CR>', 0)
" Meta+t
" Create new tab
call NvicoMapMeta('t', ':tabnew<CR>', 0)
" Meta+Shift+T
" Open netrw file browser in new tab
call NvicoMapMeta('T', ':tab split %:p:h<CR>', 0)
" Meta+b
" Call :make
call NvicoMapMeta('b', ':make<CR>', 1)
" Meta+l
" Open error list
call NvicoMapMeta('l', ':cl<CR>', 1)
" TODO: We need to configure iTerm2 to be able to send Cmd+Ctrl+arrow keys, so
" we can duplicate the :cnext/:cprevious/:colder/:cnewer bindings to those keys
" in MacVim.
" There may be a few others I've missed, too.
The NvicoMakeMeta
function maps a meta-key-modified version of the key passed into it to the specified action; "meta-key" here is just a generic term for Command- or Option-modified. The "Nvico" in its name represents the fact that it maps in normal, visual, insert, command, and operator pending modes.
Due to the way way Vim works when interpreting the ESC
character (which is the beginning of all meta key sequences as well as arrow keys, etc.), if we simply mapped sequences of ESC
plus another character, we would end up with Vim waiting a noticeable amount of time after receiving the actual Esc keypress (e.g. to signal a desire to return to normal mode). The way my function avoids this is by using :set
to set a key code (see :help :set-termcap
).
To summarize, here's what you need to do:
- Put the above code in your .vimrc.
- Go to the general iTerm2 preferences (
iTerm
menu > Preferences…
); go to the Keys
tab; remap your whatever key you'd like to use as a modifier so that it sends Left Option or Right Option. Follow the advice to allow iTerm2 to control the computer; it should open up the right preference pane for you to do that. You'll have to click the lock and type in your password before you can check the box next to iTerm.
- Open the preferences for the terminal profile you'd like to use (e.g. by opening it in the app-wide
Preferences
dialog in the Profiles
tab) and ensure that +Esc
is checked next to Left option key acts as
(and/or Right option key acts as
, depending on whether you've made your meta key send Right Option).