Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mickael-menu/shadowvim

Neovim ๐˜ช๐˜ฏ๐˜ด๐˜ช๐˜ฅ๐˜ฆ Xcode, for real.
https://github.com/mickael-menu/shadowvim

Last synced: 1 day ago
JSON representation

Neovim ๐˜ช๐˜ฏ๐˜ด๐˜ช๐˜ฅ๐˜ฆ Xcode, for real.

Awesome Lists containing this project

README

        


ShadowVim


Neovim inside Xcode, for real.


## Description

ShadowVim provides a Vim mode within Xcode powered by a background Neovim instance.

:warning: **Still experimental.** Keep a backup of your files before editing them with ShadowVim.

### Highlights

* 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`](https://github.com/tpope/vim-surround), [`argtextobj.vim`](https://github.com/vim-scripts/argtextobj.vim) and whatnot.
* Add key mappings to trigger native Xcode features from Neovim (e.g. "Jump to Definition" on `gd`).

[See the changelog](CHANGELOG.md) for the list of upcoming features waiting to be released.

### How does it work?

ShadowVim uses macOS's Accessibility API to keep Xcode and Neovim synchronized. It works by:

1. intercepting key and focus events in Xcode
2. forwarding them to a background Neovim instance
3. applying the changes back to Xcode's source editors

## Install

[Check out the latest release](https://github.com/mickael-menu/ShadowVim/releases) for pre-built binaries for macOS.

### Minimum Requirements

| ShadowVim | macOS | Xcode | Neovim |
|------------|-------|-------|--------|
| 0.2+ | 13.0 | 14 | 0.9 |
| 0.1 | 13.0 | 14 | 0.8 |

## Setup

### Neovim configuration

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`:

```vim
if exists('g:shadowvim')
" ShadowVim-specific statements
endif
```

Or to your `init.lua`:

```lua
if vim.g.shadowvim then
-- ShadowVim-specific statements
end
```

To conditionally activate plugins, `vim-plug` has a
[few solutions](https://github.com/junegunn/vim-plug/wiki/tips#conditional-activation).

:point_up: The default Neovim indent files for Swift are not great. For a better alternative, install [`keith/swift.vim`](https://github.com/keith/swift.vim) with your Neovim package manager.

#### Adding key bindings

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:

```viml
" Jump to Definition (โŒƒโŒ˜J).
map gd SVPress C-D-j>

" Find Selected Symbol in Workspace (โŒƒโ‡งโŒ˜J).
map gr SVPress C-S-D-f>

" Show the Quick Help pop-up for the symbol at the caret location (โŒฅ + Left Click).
map K SVPress M-LeftMouse>
```

:warning: The first `<` needs to be escaped as `` 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](#tips-and-tricks) section for a collection of useful bindings.

## Usage

### Menu bar icon

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.

### Neovim user commands

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 ` 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.

## Tips and tricks

### Don't use `:w`

Neovim is in read-only mode, so `:w` won't do anything. Use the usual โŒ˜S to save your files.

### Custom passthrough for hot keys

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.

```viml
map SVPress A-`>
```

### Navigation with C-o and C-i

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.

```viml
map SVPress C-D-Left>
map SVPress C-D-Right>
```

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:

```viml
map SVPress C-o>
map SVPress C-i>
```

As `SVPress` is not recursive, this will perform the native Xcode navigation.

### Mouse clicks

Here are some useful bindings simulating mouse clicks.

```viml
" Show the Quick Help pop-up for the symbol at the caret location (โŒฅ + Left Click).
map K SVPress M-LeftMouse>

" Perform a right click at the caret location.
map gR SVPress RightMouse>
```

### Window management

Use the following bindings to manage Xcode's source editor with the usual C-w-based keyboard shortcuts.

```viml
" Split vertically.
map v SVPress C-D-t>
" Split horizontally.
map s SVPress C-M-D-t>

" 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 SVPress C-S-D-w>SVPress C-`>
" Close all other source editors.
map o SVPress C-S-M-D-w>

" Focus the editor on the left.
map h SVPress D-j>SVPress hSVPress CR>
" Focus the editor below.
map j SVPress D-j>SVPress jSVPress CR>
" Focus the editor above.
map k SVPress D-j>SVPress kSVPress CR>
" Focus the editor on the right.
map l SVPress D-j>SVPress lSVPress CR>
" Rotate the source editors.
map w SVPress C-`>
```

### Center cursor line with `zz`

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:

```viml
map zz SVPress C-M-S-D-l>
```

### Folds

Xcode's folding capabilities are limited, but you get the basics with these bindings:

```viml
map zc SVPress M-D-Left>
map zo SVPress M-D-Right>
map zM SVPress M-S-D-Left>
map zR SVPress M-S-D-Right>
```

### Opening third-party applications

You can get pretty creative with key bindings. Here's one opening [Sourcetree](https://www.sourcetreeapp.com/) with <leader>st for the current Git repository, using `!` to execute a shell command and `%` to get the path of the edited file.

```viml
map st silent !stree "%"
```

This keybinding opens a new iTerm tab at the root of the Git repository for the current buffer.

```viml
map sh silent !open -a iTerm `(cd "%:p:h"; git rev-parse --show-toplevel)`
```

### Triggering Xcode's completion

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?

## Attributions

Thanks to [kindaVim](https://kindavim.app/) and [SketchyVim](https://github.com/FelixKratz/SketchyVim) for showing me this was possible.

* [Neovim](https://github.com/neovim/neovim), duh.
* [Sparkle](https://sparkle-project.org/) provides the auto-updater.
* [MessagePack.swift](https://github.com/a2/MessagePack.swift) is used to communicate with Neovim using MessagePack-RPC.
* [Sauce](https://github.com/Clipy/Sauce) helps supporting virtual keyboard layouts.
* [NSLogger](https://github.com/fpillet/NSLogger) for keeping me sane while debugging.
* [XcodeGen](https://github.com/yonaskolb/XcodeGen) generates the project, because `.xcodeproj` is meh.