export-1779215918511.mp4
Automatic Korean ↔ English IME switching for Korean–English Neovim, VSCode Vim plugin users on macOS.
Fixes the most common frustration when writing Korean in Vim:
Korean insert mode → press Esc → normal-mode keys (hjkl, w, :, /) work immediately
Re-enter insert mode → the IME you were last typing in is restored automatically
┌─────────────────────────────────────────────────────────┐
│ KoVim.app (macOS menu bar agent) │
│ │
│ • Global Esc / Ctrl-[ watcher (all terminals + VSCode) │
│ • macOS IME controller (TIS APIs) │
│ • Local HTTP API http://127.0.0.1:57321 │
└──────────────────┬──────────────────┬───────────────────┘
│ │
┌─────────┘ └──────────┐
▼ ▼
┌───────────────┐ ┌─────────────────────┐
│ Neovim plugin │ │ VSCode extension │
│ (Lua) │ │ (TypeScript) │
│ │ │ │
│ InsertLeave │ │ Esc / Ctrl-[ │
│ → /mode/normal│ │ → /mode/normal │
│ InsertEnter │ │ i / a / o / s / c │
│ → /mode/insert│ │ → /mode/insert │
└───────────────┘ └─────────────────────┘
Even without any editor plugin, the global key watcher alone handles:
| Scenario | Works |
|---|---|
| Neovim in iTerm2, Terminal, Warp, Alacritty, WezTerm, Kitty, Ghostty | ✓ |
| VSCode + VSCodeVim | ✓ |
| Neovim over SSH in any of those terminals | ✓ |
Editor plugins add reliable insert-mode restoration — when you press i/a/o the IME from your last insert session comes back: Korean if you were typing Korean, English if you were typing English.
git clone https://github.com/donny-son/kovim.git
cd kovim
bash scripts/install.shThis will:
- Build
KoVim.appandkovim-imCLI - Install
kovim-imto~/.local/bin/ - Install the Neovim plugin and wire it into your plugin manager (see below)
- Copy
KoVim.appto/Applications/ - Launch the agent
The VSCode extension is not installed by install.sh — run
scripts/install-vscode.sh separately if you use VSCode.
KoVim.app on its own already switches to English on Esc / Ctrl-[ in
every supported terminal and in VSCode — no plugin required. The editor plugins
add exactly one thing: restoring your previous IME (e.g. Korean) when you
re-enter insert mode, which a global key watcher cannot detect reliably.
After the first launch, two permissions are required:
| Permission | Where |
|---|---|
| Accessibility | System Settings → Privacy & Security → Accessibility |
| Input Monitoring | System Settings → Privacy & Security → Input Monitoring |
Enable both for KoVim, then the ⌨︎ KoVim icon will appear in your menu bar.
scripts/install.sh installs the plugin for you. To (re)install it on its own:
bash scripts/install-nvim.shThis copies the plugin to ~/.local/share/nvim/site/pack/kovim/start/kovim, then:
- lazy.nvim detected → writes a spec to
~/.config/nvim/lua/plugins/kovim.lua. lazy.nvim manages its own runtimepath and does not auto-loadsite/packplugins, so this step is required — without itsetup()never runs. - No plugin manager → Neovim auto-loads the plugin from
site/pack; it self-initializes viaplugin/kovim.lua, nothing else to do.
Restart Neovim, then run :KovimHealth to verify the autocmds and agent.
If the installer can't locate your config, add the plugin yourself.
lazy.nvim — create ~/.config/nvim/lua/plugins/kovim.lua:
return {
{
dir = vim.fn.expand("~/.local/share/nvim/site/pack/kovim/start/kovim"),
name = "kovim",
lazy = false,
config = function()
require("kovim").setup()
end,
},
}packer.nvim:
use {
"~/.local/share/nvim/site/pack/kovim/start/kovim",
as = "kovim",
config = function() require("kovim").setup() end,
}No plugin manager — nothing to add; the plugin auto-loads. To pass options,
put require("kovim").setup({ ... }) in your init.lua.
require("kovim").setup({
endpoint = "http://127.0.0.1:57321", -- kovim-agent API
enabled = true,
restore_on_insert = true, -- restore previous IME on InsertEnter
force_english_on_insert_leave = true, -- switch to English on InsertLeave
force_english_on_cmdline_enter = true, -- switch to English on : / ?
debug = false, -- print debug notifications
})| Command | Effect |
|---|---|
:KovimEnglish |
Immediately switch to English |
:KovimRestore |
Restore previous IME |
:KovimHealth |
Diagnose agent connectivity and autocmd setup |
The global Esc watcher fires locally, so IME switches to English even when you are inside a remote Neovim session over SSH. No configuration is needed for the basic case.
For full mode-aware restoration over SSH (restoring your previous IME when entering insert mode remotely), forward the agent port:
# One-time
ssh -R 57321:127.0.0.1:57321 user@server
# Or in ~/.ssh/config
Host myserver
HostName example.com
User youruser
RemoteForward 57321 127.0.0.1:57321The remote Neovim plugin will then call the local macOS agent transparently.
Requires the VSCodeVim extension.
bash scripts/install-vscode.shThis builds the extension (npm install + npm run compile) and installs it
into ~/.vscode/extensions/. It needs Node.js (npm) and also checks that
VSCodeVim is present.
Because the extension only uses the vscode API and Node built-ins, it is
installed as a plain folder — no .vsix packaging or Marketplace publisher
account required. Reload VSCode afterwards
(Cmd+Shift+P → Developer: Reload Window) to activate it.
{
"kovim.enabled": true,
"kovim.endpoint": "http://127.0.0.1:57321",
"kovim.restorePreviousOnInsert": true,
"kovim.debug": false
}The status bar shows ⌨︎ KoVim when the agent is connected, ⌨︎ KoVim ✕ when it is offline.
The agent config lives at ~/.config/kovim/config.json and is created with defaults on first run.
{
"allowedBundleIds": [
"com.googlecode.iterm2",
"com.apple.Terminal",
"dev.warp.Warp-Stable",
"dev.warp.Warp",
"org.alacritty",
"io.alacritty",
"com.github.wez.wezterm",
"com.github.wez.WezTerm",
"com.mitchellh.ghostty",
"net.kovidgoyal.kitty",
"com.microsoft.VSCode",
"com.microsoft.VSCodeInsiders",
"com.visualstudio.code.oss"
],
"debugLogging": false,
"enableLocalServer": true,
"englishInputSourceId": null,
"port": 57321,
"restorePreviousOnInsert": true,
"switchOnControlBracket": true,
"switchOnEscape": true
}Set englishInputSourceId explicitly if the agent cannot auto-detect your English layout.
Run kovim-im sources to list all available IDs.
kovim-im is a small helper that ships with the agent for diagnostics:
kovim-im current # print current input source ID
kovim-im english # print the likely English input source ID
kovim-im sources # list all selectable input sources
kovim-im select <id> # switch to a specific input sourceThe agent exposes a simple HTTP API on 127.0.0.1:57321:
| Method | Path | Effect |
|---|---|---|
GET |
/health |
Health check + current state |
GET |
/ime/current |
Current IME state |
GET |
/ime/sources |
List selectable input sources |
POST |
/mode/normal |
Snapshot the insert-mode IME → switch to English |
POST |
/mode/insert |
Restore the snapshotted IME (English or Korean) |
POST |
/ime/english |
Force English |
POST |
/ime/restore |
Restore previous |
Example:
curl -s http://127.0.0.1:57321/health | jq
curl -s -X POST http://127.0.0.1:57321/mode/normalkovim/
cli/ kovim-im.swift IME CLI helper
Sources/KovimAgent/ Swift macOS agent
KovimAgent.swift AppDelegate, entry point
InputSourceManager.swift
EventTap.swift Global key watcher
FrontmostAppWatcher.swift
LocalServer.swift HTTP API (NW framework)
Config.swift
Logger.swift
nvim/ Neovim Lua plugin
lua/kovim/
init.lua setup()
client.lua HTTP client
config.lua defaults
plugin/kovim.lua
vscode/ VSCode extension (TypeScript)
src/
extension.ts
kovimClient.ts
scripts/
build-app.sh builds KoVim.app
install.sh full install (app + CLI + Neovim plugin)
install-nvim.sh Neovim plugin only
install-vscode.sh VSCode extension only
MIT