Modal vim-style editing for Discord's chat input, as a Vencord userplugin. Normal / insert / visual / replace modes, motions, operators, text objects, registers, repeat - right in the message box.
Vencord userplugins require building from source:
- Clone and set up Vencord:
git clone https://github.com/Vendicated/Vencord cd Vencord pnpm i - Clone this repo into
src/userplugins/vimcord:git clone https://github.com/burneikis/vimcord src/userplugins/vimcord
- Build and inject:
pnpm build pnpm inject
- Enable vimcord in Vencord's plugin settings and restart/reload Discord (Ctrl+R).
The vim logic is covered by a vitest suite that drives the real key
dispatcher against a fake Slate editor (test/fakeEditor.ts), which
applies the plugin's synthetic beforeinput events to an in-memory
buffer the way Discord's Slate instance does. Expectations in
test/vim-parity.spec.ts assert vim's behavior, so failures mean a
divergence from vim (or a Slate-emulation gap in the fake).
npm install
npm test # or: npm run test:watchNot covered: the Discord/Slate integration itself (editor.ts event
timing, cursor/statusline rendering) - test those manually in Discord.
Focus the chat box and press Escape to enter NORMAL mode. A mode
indicator (badge and/or colored border, configurable) shows the current
mode, and a block cursor is drawn in NORMAL mode.
Modes
Escape- NORMAL mode;iaIAoO- enter INSERT modev- charwise VISUAL modeR- replace (overtype) mode,r<char>- replace single char
Motions (all accept counts, e.g. 3w, 5j)
hjkl,0^$wbe/WBEggGfFtTand;,
Operators / edits
dcy+ motion or text object (dw,ce,yy,dd,di",ca(,daw, ...)xXsDCSpPpaste from the yank register~toggle case,Jjoin linesu/Ctrl-rundo/redo (forwarded to Discord's native history).repeat last change
Text objects: iw aw and quote/bracket pairs (i", a", i(, a(,
etc.).
- Default mode on focus - insert (default), normal, or remember last mode per editor
- Escape in NORMAL mode - swallow, pass through to Discord (closes reply/edit), or require double-Escape
- Mode indicator style - badge, border, both, or none
- Yank to clipboard - also copy yanked/deleted text to the system clipboard (on by default; yanks always go to the internal register)
Discord's chat box is a Slate.js contenteditable. vimcord installs a single
capturing keydown listener; when the focused element is a Discord Slate
textbox and the key should be handled (NORMAL mode, or Escape in INSERT),
the event is consumed and fed into a vim state machine. Edits go through
input events / execCommand so Slate's model stays in sync - no raw DOM
mutation, no fragile webpack patches.
Mode state is tracked per editor, so the main chat box, message-edit boxes, etc. each keep their own mode.
- Autocomplete popups (mentions, emoji) are not specially handled yet; in NORMAL mode keys are consumed by vimcord. Use INSERT mode to interact with them.
- Insert-mode typing is not recorded by
.repeat; only the change command itself is replayed. - Rebuild Vencord (
pnpm build) and reload Discord after updating the plugin.
GPL-3.0, matching Vencord.