{"id":13478986,"url":"https://github.com/mickael-menu/ShadowVim","last_synced_at":"2025-03-27T08:31:15.050Z","repository":{"id":82797890,"uuid":"586268952","full_name":"mickael-menu/ShadowVim","owner":"mickael-menu","description":"Neovim 𝘪𝘯𝘴𝘪𝘥𝘦 Xcode, for real.","archived":false,"fork":false,"pushed_at":"2023-07-09T13:10:17.000Z","size":401,"stargazers_count":355,"open_issues_count":1,"forks_count":2,"subscribers_count":13,"default_branch":"develop","last_synced_at":"2025-03-22T21:56:57.982Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mickael-menu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-01-07T14:43:51.000Z","updated_at":"2025-03-15T04:40:00.000Z","dependencies_parsed_at":"2024-01-16T06:20:34.563Z","dependency_job_id":"eac3ef23-6972-44eb-a9a3-6a7f019a5eaa","html_url":"https://github.com/mickael-menu/ShadowVim","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mickael-menu%2FShadowVim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mickael-menu%2FShadowVim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mickael-menu%2FShadowVim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mickael-menu%2FShadowVim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mickael-menu","download_url":"https://codeload.github.com/mickael-menu/ShadowVim/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245809815,"owners_count":20676061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T16:02:07.159Z","updated_at":"2025-03-27T08:31:15.024Z","avatar_url":"https://github.com/mickael-menu.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\u003ch1\u003eShadowVim\u003c/h1\u003e\n\u003ch4\u003eNeovim \u003cem\u003einside\u003c/em\u003e Xcode, for real.\u003c/h4\u003e\n\n\u003cimg width=\"800\" src=\"https://user-images.githubusercontent.com/58686775/219877113-367a0880-de31-46e3-b6a7-bf305077ec8c.gif\"/\u003e\n\u003c/div\u003e\n\n## Description\n\nShadowVim provides a Vim mode within Xcode powered by a background Neovim instance.\n\n:warning: **Still experimental.** Keep a backup of your files before editing them with ShadowVim.\n\n### Highlights\n\n* This is not a Vim emulation, but real Neovim with macros, `.`, Ex commands, etc.\n* 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.\n* Add key mappings to trigger native Xcode features from Neovim (e.g. \"Jump to Definition\" on `gd`).\n\n[See the changelog](CHANGELOG.md) for the list of upcoming features waiting to be released.\n\n### How does it work?\n\nShadowVim uses macOS's Accessibility API to keep Xcode and Neovim synchronized. It works by:\n\n1. intercepting key and focus events in Xcode\n2. forwarding them to a background Neovim instance\n3. applying the changes back to Xcode's source editors\n\n## Install\n\n[Check out the latest release](https://github.com/mickael-menu/ShadowVim/releases) for pre-built binaries for macOS.\n\n### Minimum Requirements\n\n| ShadowVim  | macOS | Xcode | Neovim |\n|------------|-------|-------|--------|\n| 0.2+       | 13.0  | 14    | 0.9    |\n| 0.1        | 13.0  | 14    | 0.8    |\n\n## Setup\n\n### Neovim configuration\n\nSince many Vim plugins can cause issues with ShadowVim, it is recommended to start from an empty `init.vim`.\n\nTo determine if Neovim is running in ShadowVim, add to your `init.vim`:\n\n```vim\nif exists('g:shadowvim')\n    \" ShadowVim-specific statements\nendif\n```\n\nOr to your `init.lua`:\n\n```lua\nif vim.g.shadowvim then\n    -- ShadowVim-specific statements\nend\n```\n\nTo conditionally activate plugins, `vim-plug` has a\n[few solutions](https://github.com/junegunn/vim-plug/wiki/tips#conditional-activation).\n\n: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.\n\n#### Adding key bindings\n\nOnly \u003ckbd\u003e⌃\u003c/kbd\u003e/\u003ckbd\u003eC-\u003c/kbd\u003e-based keyboard shortcuts can be customized in Neovim. \u003ckbd\u003e⌘\u003c/kbd\u003e-based hotkeys are handled directly by Xcode.\n\nThe `SVPress` user command triggers a keyboard shortcut or mouse click in Xcode. This is convenient to bind Neovim commands to Xcode features, such as:\n\n```viml\n\" Jump to Definition (⌃⌘J).\nmap gd \u003cCmd\u003eSVPress \u003cLT\u003eC-D-j\u003e\u003cCR\u003e\n\n\" Find Selected Symbol in Workspace (⌃⇧⌘J).\nmap gr \u003cCmd\u003eSVPress \u003cLT\u003eC-S-D-f\u003e\u003cCR\u003e\n\n\" Show the Quick Help pop-up for the symbol at the caret location (\u003ckbd\u003e⌥ + Left Click\u003c/kbd\u003e).\nmap K \u003cCmd\u003eSVPress \u003cLT\u003eM-LeftMouse\u003e\u003cCR\u003e\n```\n\n:warning: The first `\u003c` needs to be escaped as `\u003cLT\u003e` when calling `SVPress` from a key binding.\n\n| Modifier | macOS        | Nvim                           |\n|----------|--------------|--------------------------------|\n| control  | \u003ckbd\u003e⌃\u003c/kbd\u003e | \u003ckbd\u003eC-\u003c/kbd\u003e                  |\n| option   | \u003ckbd\u003e⌥\u003c/kbd\u003e | \u003ckbd\u003eM-\u003c/kbd\u003e or \u003ckbd\u003eA-\u003c/kbd\u003e |\n| shift    | \u003ckbd\u003e⇧\u003c/kbd\u003e | \u003ckbd\u003eS-\u003c/kbd\u003e                  |\n| command  | \u003ckbd\u003e⌘\u003c/kbd\u003e | \u003ckbd\u003eD-\u003c/kbd\u003e                  |\n\nTake a look at the [Tips and tricks](#tips-and-tricks) section for a collection of useful bindings.\n\n## Usage\n\n### Menu bar icon\n\nShadowVim adds a new menu bar icon (🅽) with a couple of useful features which can also be triggered with global hotkeys:\n\n* **Reset ShadowVim** (\u003ckbd\u003e⌃⌥⌘⎋\u003c/kbd\u003e) kills Neovim and resets the synchronization. This might be useful if you get stuck.\n\n### Neovim user commands\n\nThe following commands are available in your bindings when Neovim is run by ShadowVim.\n\n* `SVPress` triggers a keyboard shortcut or mouse click in Xcode. The syntax is the same as Neovim's key bindings, e.g. `SVPress \u003cD-s\u003e` to save the current file. Mouse clicks are performed at the current caret location.\n* `SVOpenTUI` launches a Terminal window with a Neovim text user interface of the embedded Neovim instance.\n    * This is useful to solve issues with Neovim such as a blocking prompt.\n* `SVReset` kills Neovim and resets the synchronization. This might be useful if you get stuck.\n* `SVSetInputUI` lets Xcode handle all key events. Press \u003ckbd\u003eEsc\u003c/kbd\u003e to cancel.\n* `SVSetInputNvim` forwards key events to Neovim, even in Insert mode. Press \u003ckbd\u003eEsc\u003c/kbd\u003e to cancel.\n* `SVSynchronizeUI` requests Xcode to reset the current file to the state of the Neovim buffer. You should not need to call this manually.\n* `SVSynchronizeNvim` requests Neovim to reset the current buffer to the state of the Xcode file. You should not need to call this manually.\n\n## Tips and tricks\n\n### Don't use `:w`\n\nNeovim is in read-only mode, so `:w` won't do anything. Use the usual \u003ckbd\u003e⌘S\u003c/kbd\u003e to save your files.\n\n### Custom passthrough for hot keys\n\nAll keyboard shortcuts that are not using the \u003ckbd\u003e⌘\u003c/kbd\u003e modifier are sent to Neovim. This means that if you have a global hot key (e.g. \u003ckbd\u003e⌥\\`\u003c/kbd\u003e to open iTerm), it won't work when Xcode is focused.\n\nAs a workaround, you can add a custom mapping to your `init.vim` to retrigger your hot key globally.\n\n```viml\nmap \u003cA-`\u003e \u003cCmd\u003eSVPress \u003cLT\u003eA-`\u003e\u003cCR\u003e\n```\n\n### Navigation with \u003ckbd\u003eC-o\u003c/kbd\u003e and \u003ckbd\u003eC-i\u003c/kbd\u003e\n\nCross-buffers navigation is not yet supported with ShadowVim. Therefore, it is recommended to override the \u003ckbd\u003eC-o\u003c/kbd\u003e and \u003ckbd\u003eC-i\u003c/kbd\u003e mappings to use Xcode's navigation instead.\n\n```viml\nmap \u003cC-o\u003e \u003cCmd\u003eSVPress \u003cLT\u003eC-D-Left\u003e\u003cCR\u003e\nmap \u003cC-i\u003e \u003cCmd\u003eSVPress \u003cLT\u003eC-D-Right\u003e\u003cCR\u003e\n```\n\nUnfortunately, this won't work in read-only source editors. As a workaround, you can rebind **Go back** to \u003ckbd\u003e⌃O\u003c/kbd\u003e and **Go forward** to \u003ckbd\u003e⌃I\u003c/kbd\u003e in Xcode's **Key Bindings** preferences, then in Neovim:\n\n```viml\nmap \u003cC-o\u003e \u003cCmd\u003eSVPress \u003cLT\u003eC-o\u003e\u003cCR\u003e\nmap \u003cC-i\u003e \u003cCmd\u003eSVPress \u003cLT\u003eC-i\u003e\u003cCR\u003e\n```\n\nAs `SVPress` is not recursive, this will perform the native Xcode navigation.\n\n### Mouse clicks\n\nHere are some useful bindings simulating mouse clicks.\n\n```viml\n\" Show the Quick Help pop-up for the symbol at the caret location (\u003ckbd\u003e⌥ + Left Click\u003c/kbd\u003e).\nmap K \u003cCmd\u003eSVPress \u003cLT\u003eM-LeftMouse\u003e\u003cCR\u003e\n\n\" Perform a right click at the caret location.\nmap gR \u003cCmd\u003eSVPress \u003cLT\u003eRightMouse\u003e\u003cCR\u003e\n```\n\n### Window management\n\nUse the following bindings to manage Xcode's source editor with the usual \u003ckbd\u003eC-w\u003c/kbd\u003e-based keyboard shortcuts.\n\n```viml\n\" Split vertically.\nmap \u003cC-w\u003ev \u003cCmd\u003eSVPress \u003cLT\u003eC-D-t\u003e\u003cCR\u003e\n\" Split horizontally.\nmap \u003cC-w\u003es \u003cCmd\u003eSVPress \u003cLT\u003eC-M-D-t\u003e\u003cCR\u003e\n\n\" Close the focused editor.\n\" Note: Xcode 14 doesn't focus the previous one... As a workaround, ⌃C is triggered to focus the first one.\nmap \u003cC-w\u003ec \u003cCmd\u003eSVPress \u003cLT\u003eC-S-D-w\u003e\u003cCR\u003e\u003cCmd\u003eSVPress \u003cLT\u003eC-`\u003e\u003cCR\u003e\n\" Close all other source editors.\nmap \u003cC-w\u003eo \u003cCmd\u003eSVPress \u003cLT\u003eC-S-M-D-w\u003e\u003cCR\u003e\n\n\" Focus the editor on the left.\nmap \u003cC-w\u003eh \u003cCmd\u003eSVPress \u003cLT\u003eD-j\u003e\u003cCR\u003e\u003cCmd\u003eSVPress h\u003cCR\u003e\u003cCmd\u003eSVPress \u003cLT\u003eCR\u003e\u003cCR\u003e\n\" Focus the editor below.\nmap \u003cC-w\u003ej \u003cCmd\u003eSVPress \u003cLT\u003eD-j\u003e\u003cCR\u003e\u003cCmd\u003eSVPress j\u003cCR\u003e\u003cCmd\u003eSVPress \u003cLT\u003eCR\u003e\u003cCR\u003e\n\" Focus the editor above.\nmap \u003cC-w\u003ek \u003cCmd\u003eSVPress \u003cLT\u003eD-j\u003e\u003cCR\u003e\u003cCmd\u003eSVPress k\u003cCR\u003e\u003cCmd\u003eSVPress \u003cLT\u003eCR\u003e\u003cCR\u003e\n\" Focus the editor on the right.\nmap \u003cC-w\u003el \u003cCmd\u003eSVPress \u003cLT\u003eD-j\u003e\u003cCR\u003e\u003cCmd\u003eSVPress l\u003cCR\u003e\u003cCmd\u003eSVPress \u003cLT\u003eCR\u003e\u003cCR\u003e\n\" Rotate the source editors.\nmap \u003cC-w\u003ew \u003cCmd\u003eSVPress \u003cLT\u003eC-`\u003e\u003cCR\u003e\n```\n\n### Center cursor line with `zz`\n\nTo 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, \u003ckbd\u003e⌃⌥⇧⌘L\u003c/kbd\u003e.\n\nThen, add the following binding in your Neovim config:\n\n```viml\nmap zz \u003cCmd\u003eSVPress \u003cLT\u003eC-M-S-D-l\u003e\u003cCR\u003e\n```\n\n### Folds\n\nXcode's folding capabilities are limited, but you get the basics with these bindings:\n\n```viml\nmap zc \u003cCmd\u003eSVPress \u003cLT\u003eM-D-Left\u003e\u003cCR\u003e\nmap zo \u003cCmd\u003eSVPress \u003cLT\u003eM-D-Right\u003e\u003cCR\u003e\nmap zM \u003cCmd\u003eSVPress \u003cLT\u003eM-S-D-Left\u003e\u003cCR\u003e\nmap zR \u003cCmd\u003eSVPress \u003cLT\u003eM-S-D-Right\u003e\u003cCR\u003e\n```\n\n### Opening third-party applications\n\nYou can get pretty creative with key bindings. Here's one opening [Sourcetree](https://www.sourcetreeapp.com/) with \u003ckbd\u003e\u0026lt;leader\u003est\u003c/kbd\u003e for the current Git repository, using `!` to execute a shell command and `%` to get the path of the edited file.\n\n```viml\nmap \u003cleader\u003est \u003cCmd\u003esilent !stree \"%\"\u003cCR\u003e\n```\n\nThis keybinding opens a new iTerm tab at the root of the Git repository for the current buffer.\n\n```viml\nmap \u003cleader\u003esh \u003cCmd\u003esilent !open -a iTerm `(cd \"%:p:h\"; git rev-parse --show-toplevel)`\u003cCR\u003e\n```\n\n### Triggering Xcode's completion\n\nAs the default Xcode shortcut to trigger the completion (\u003ckbd\u003e⎋\u003c/kbd\u003e) 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. \u003ckbd\u003e⌘P\u003c/kbd\u003e is a good candidate, who needs to print their code anyway?\n\n## Attributions\n\nThanks to [kindaVim](https://kindavim.app/) and [SketchyVim](https://github.com/FelixKratz/SketchyVim) for showing me this was possible.\n\n* [Neovim](https://github.com/neovim/neovim), duh.\n* [Sparkle](https://sparkle-project.org/) provides the auto-updater.\n* [MessagePack.swift](https://github.com/a2/MessagePack.swift) is used to communicate with Neovim using MessagePack-RPC.\n* [Sauce](https://github.com/Clipy/Sauce) helps supporting virtual keyboard layouts.\n* [NSLogger](https://github.com/fpillet/NSLogger) for keeping me sane while debugging.\n* [XcodeGen](https://github.com/yonaskolb/XcodeGen) generates the project, because `.xcodeproj` is meh.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmickael-menu%2FShadowVim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmickael-menu%2FShadowVim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmickael-menu%2FShadowVim/lists"}