ShadowVim provides a Vim mode within Xcode powered by a background Neovim instance.
- This is not a Vim emulation, but real Neovim with macros,
.
, Ex commands, etc. - Vim plugins (without UI) work out of the box. Hello
vim-surround
,argtextobj.vim
and whatnot. - Add key mappings to trigger native Xcode features from Neovim (e.g. "Jump to Definition" on
gd
).
See the changelog for the list of upcoming features waiting to be released.
ShadowVim uses macOS's Accessibility API to keep Xcode and Neovim synchronized. It works by:
- intercepting key and focus events in Xcode
- forwarding them to a background Neovim instance
- applying the changes back to Xcode's source editors
Check out the latest release for pre-built binaries for macOS.
ShadowVim | macOS | Xcode | Neovim |
---|---|---|---|
0.2+ | 13.0 | 14 | 0.9 |
0.1 | 13.0 | 14 | 0.8 |
Since many Vim plugins can cause issues with ShadowVim, it is recommended to start from an empty init.vim
.
To determine if Neovim is running in ShadowVim, add to your init.vim
:
if exists('g:shadowvim')
" ShadowVim-specific statements
endif
Or to your init.lua
:
if vim.g.shadowvim then
-- ShadowVim-specific statements
end
To conditionally activate plugins, vim-plug
has a
few solutions.
☝️ The default Neovim indent files for Swift are not great. For a better alternative, install keith/swift.vim
with your Neovim package manager.
Only ⌃/C--based keyboard shortcuts can be customized in Neovim. ⌘-based hotkeys are handled directly by Xcode.
The SVPress
user command triggers a keyboard shortcut or mouse click in Xcode. This is convenient to bind Neovim commands to Xcode features, such as:
" Jump to Definition (⌃⌘J).
map gd <Cmd>SVPress <LT>C-D-j><CR>
" Find Selected Symbol in Workspace (⌃⇧⌘J).
map gr <Cmd>SVPress <LT>C-S-D-f><CR>
" Show the Quick Help pop-up for the symbol at the caret location (<kbd>⌥ + Left Click</kbd>).
map K <Cmd>SVPress <LT>M-LeftMouse><CR>
<
needs to be escaped as <LT>
when calling SVPress
from a key binding.
Modifier | macOS | Nvim |
---|---|---|
control | ⌃ | C- |
option | ⌥ | M- or A- |
shift | ⇧ | S- |
command | ⌘ | D- |
Take a look at the Tips and tricks section for a collection of useful bindings.
ShadowVim adds a new menu bar icon (🅽) with a couple of useful features which can also be triggered with global hotkeys:
- Reset ShadowVim (⌃⌥⌘⎋) kills Neovim and resets the synchronization. This might be useful if you get stuck.
The following commands are available in your bindings when Neovim is run by ShadowVim.
SVPress
triggers a keyboard shortcut or mouse click in Xcode. The syntax is the same as Neovim's key bindings, e.g.SVPress <D-s>
to save the current file. Mouse clicks are performed at the current caret location.SVOpenTUI
launches a Terminal window with a Neovim text user interface of the embedded Neovim instance.- This is useful to solve issues with Neovim such as a blocking prompt.
SVReset
kills Neovim and resets the synchronization. This might be useful if you get stuck.SVSetInputUI
lets Xcode handle all key events. Press Esc to cancel.SVSetInputNvim
forwards key events to Neovim, even in Insert mode. Press Esc to cancel.SVSynchronizeUI
requests Xcode to reset the current file to the state of the Neovim buffer. You should not need to call this manually.SVSynchronizeNvim
requests Neovim to reset the current buffer to the state of the Xcode file. You should not need to call this manually.
Neovim is in read-only mode, so :w
won't do anything. Use the usual ⌘S to save your files.
All keyboard shortcuts that are not using the ⌘ modifier are sent to Neovim. This means that if you have a global hot key (e.g. ⌥` to open iTerm), it won't work when Xcode is focused.
As a workaround, you can add a custom mapping to your init.vim
to retrigger your hot key globally.
map <A-`> <Cmd>SVPress <LT>A-`><CR>
Cross-buffers navigation is not yet supported with ShadowVim. Therefore, it is recommended to override the C-o and C-i mappings to use Xcode's navigation instead.
map <C-o> <Cmd>SVPress <LT>C-D-Left><CR>
map <C-i> <Cmd>SVPress <LT>C-D-Right><CR>
Unfortunately, this won't work in read-only source editors. As a workaround, you can rebind Go back to ⌃O and Go forward to ⌃I in Xcode's Key Bindings preferences, then in Neovim:
map <C-o> <Cmd>SVPress <LT>C-o><CR>
map <C-i> <Cmd>SVPress <LT>C-i><CR>
As SVPress
is not recursive, this will perform the native Xcode navigation.
Here are some useful bindings simulating mouse clicks.
" Show the Quick Help pop-up for the symbol at the caret location (<kbd>⌥ + Left Click</kbd>).
map K <Cmd>SVPress <LT>M-LeftMouse><CR>
" Perform a right click at the caret location.
map gR <Cmd>SVPress <LT>RightMouse><CR>
Use the following bindings to manage Xcode's source editor with the usual C-w-based keyboard shortcuts.
" Split vertically.
map <C-w>v <Cmd>SVPress <LT>C-D-t><CR>
" Split horizontally.
map <C-w>s <Cmd>SVPress <LT>C-M-D-t><CR>
" Close the focused editor.
" Note: Xcode 14 doesn't focus the previous one... As a workaround, ⌃C is triggered to focus the first one.
map <C-w>c <Cmd>SVPress <LT>C-S-D-w><CR><Cmd>SVPress <LT>C-`><CR>
" Close all other source editors.
map <C-w>o <Cmd>SVPress <LT>C-S-M-D-w><CR>
" Focus the editor on the left.
map <C-w>h <Cmd>SVPress <LT>D-j><CR><Cmd>SVPress h<CR><Cmd>SVPress <LT>CR><CR>
" Focus the editor below.
map <C-w>j <Cmd>SVPress <LT>D-j><CR><Cmd>SVPress j<CR><Cmd>SVPress <LT>CR><CR>
" Focus the editor above.
map <C-w>k <Cmd>SVPress <LT>D-j><CR><Cmd>SVPress k<CR><Cmd>SVPress <LT>CR><CR>
" Focus the editor on the right.
map <C-w>l <Cmd>SVPress <LT>D-j><CR><Cmd>SVPress l<CR><Cmd>SVPress <LT>CR><CR>
" Rotate the source editors.
map <C-w>w <Cmd>SVPress <LT>C-`><CR>
To emulate the Vim zz
command, you will need to set a custom keyboard shortcut for the Center Selection in Visual Area command in the Xcode Key Bindings preferences. For example, ⌃⌥⇧⌘L.
Then, add the following binding in your Neovim config:
map zz <Cmd>SVPress <LT>C-M-S-D-l><CR>
Xcode's folding capabilities are limited, but you get the basics with these bindings:
map zc <Cmd>SVPress <LT>M-D-Left><CR>
map zo <Cmd>SVPress <LT>M-D-Right><CR>
map zM <Cmd>SVPress <LT>M-S-D-Left><CR>
map zR <Cmd>SVPress <LT>M-S-D-Right><CR>
You can get pretty creative with key bindings. Here's one opening Sourcetree with <leader>st for the current Git repository, using !
to execute a shell command and %
to get the path of the edited file.
map <leader>st <Cmd>silent !stree "%"<CR>
This keybinding opens a new iTerm tab at the root of the Git repository for the current buffer.
map <leader>sh <Cmd>silent !open -a iTerm `(cd "%:p:h"; git rev-parse --show-toplevel)`<CR>
As the default Xcode shortcut to trigger the completion (⎋) is already used in Neovim to go back to the normal mode, you might want to set a different one in Xcode's Key Bindings preferences. ⌘P is a good candidate, who needs to print their code anyway?
Thanks to kindaVim and SketchyVim for showing me this was possible.