Last active
March 14, 2025 15:21
-
-
Save imjonathan/20112f04608e5e7fa371dd117f4d8801 to your computer and use it in GitHub Desktop.
Emacs Keyboard Macro Support in VS Codium
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
==================================================================== | |
CUSTOM KEYBINDINGS FOR EMACS-LIKE BEHAVIOR | |
IN VS CODIUM WITH KEYBOARD MACROS SUPPORT | |
==================================================================== | |
This keybindings file is designed for use with the following extensions: | |
- **Awesome Emacs Keymap (emacs-mcx)**: Provides Emacs-like keybindings. | |
- **Keyboard Macros Beta (kb-macro)**: Enables macro recording and playback. | |
- **Multi Command (ryuta46.multi-command)**: Allows chaining multiple commands | |
(e.g., setting a mark THEN moving) in a single keybinding. | |
Goals of this configuration: | |
1. **Emacs Keybindings:** Replicates Emacs-style navigation, editing, and region selection. | |
2. **Keyboard Macros:** Ensures that macro recording captures all important Emacs-like commands. | |
3. **Prefix Arguments (`Esc n` support):** Allows `Esc 3 C-x e` (or any command) to repeat `n` times. | |
4. **Set Mark Before Moving:** Ensures selection works like Emacs. | |
- For `Esc >` / `Esc <`, or `Ctrl+Shift>.` / `Ctrl+Shift>,`, we use **Multi Command** to chain: | |
1) `emacs-mcx.setMarkCommand` | |
2) `emacs-mcx.endOfBuffer` OR `emacs-mcx.beginningOfBuffer` | |
5. **Fix Macro Playback Issues:** Ensures macro execution respects prefix arguments and records all commands. | |
==================================================================== | |
ISSUES ADDRESSED IN THIS FILE | |
==================================================================== | |
1️⃣ **Keyboard macros (`C-x e`) were not repeating with prefix args (`Esc n C-x e`).** | |
- Fixed by using `emacs-mcx.executeCommandWithPrefixArgument` to ensure prefix arguments work. | |
2️⃣ **Chaining multiple Emacs commands on a single chord** (e.g., mark + move). | |
- Fixed by using **Multi Command** so `setMarkCommand` and `endOfBuffer` happen together. | |
3️⃣ **Some commands (like `C-k`, `C-w`, etc.) did not record correctly in macros.** | |
- Fixed by wrapping them with `kb-macro.wrap` to ensure correct macro playback. | |
4️⃣ **Some keybindings were not captured properly during macro recording.** | |
- Fixed by ensuring `kb-macro.wrap` is applied when `kb-macro.active` is true. | |
==================================================================== | |
CONVENTIONS USED IN THIS FILE | |
==================================================================== | |
- Each Emacs-like command is defined **twice** if needed: | |
1. **Regular Binding** → Works when not recording a macro. | |
2. **Macro Wrapped Binding (`kb-macro.wrap`)** → Captured properly when macros are active. | |
- **Multi Command**: | |
- We use `"command": "extension.multiCommand.execute"` with a `"sequence"` of commands. | |
- Example: "setMarkCommand" → "endOfBuffer" in one keypress. | |
- **Prefix Argument Support (`Esc n command`)**: | |
- `emacs-mcx.executeCommandWithPrefixArgument` ensures `Esc 3 C-x e` repeats macros. | |
- **Macro Commands**: | |
- `C-x (`, `C-x )` → start/stop macro recording. | |
- `C-x e` → playback macros, respecting prefix arguments. | |
==================================================================== | |
*/ | |
[ | |
////////////////////////////////////////////////////////////////////////// | |
// 1) Keyboard Macro commands | |
////////////////////////////////////////////////////////////////////////// | |
{ | |
"key": "ctrl+x shift+9", | |
"command": "kb-macro.startRecording", | |
"when": "!kb-macro.recording" | |
}, | |
{ | |
"key": "ctrl+x shift+0", | |
"command": "kb-macro.finishRecording", | |
"when": "kb-macro.recording" | |
}, | |
{ | |
"key": "ctrl+x e", | |
"command": "emacs-mcx.executeCommandWithPrefixArgument", | |
"args": { | |
"command": "kb-macro.playback", | |
"prefixArgumentKey": "repeat" | |
}, | |
"when": "!kb-macro.recording" | |
}, | |
////////////////////////////////////////////////////////////////////////// | |
// 2) Emacs-like commands plus "wrap" versions for macro recording | |
////////////////////////////////////////////////////////////////////////// | |
// -------------------------- | |
// Esc < => beginning-of-buffer | |
// (Typically M-< in Emacs) | |
// -------------------------- | |
{ | |
"key": "escape shift+,", | |
"command": "emacs-mcx.setMarkCommand", | |
"when": "editorTextFocus" | |
}, | |
{ | |
"key": "escape shift+,", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.setMarkCommand", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// ========================= | |
// C-SHIFT-SPACE (Set Mark) | |
// ========================= | |
{ | |
"key": "ctrl+shift+space", | |
"command": "emacs-mcx.setMarkCommand", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+shift+space", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.setMarkCommand", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// Esc b => backward-word | |
// -------------------------- | |
{ | |
"key": "escape b", | |
"command": "emacs-mcx.backwardWord", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape b", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.backwardWord", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// Esc BACKSPACE => backward-kill-word | |
// -------------------------- | |
{ | |
"key": "escape backspace", | |
"command": "emacs-mcx.backwardKillWord", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape backspace", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.backwardKillWord", | |
"await": "document selection clipboard" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// Esc > => end-of-buffer (Typically M-> in Emacs) | |
// Marking is separate, so we rely on multi-command if we want both steps | |
// -------------------------- | |
{ | |
"key": "escape shift+.", | |
"command": "emacs-mcx.setMarkCommand", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape shift+.", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.setMarkCommand", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
{ | |
"key": "escape shift+.", | |
"command": "emacs-mcx.endOfBuffer", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape shift+.", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.endOfBuffer", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// Esc < => beginning-of-buffer (Typically M-< in Emacs) | |
// -------------------------- | |
{ | |
"key": "escape shift+,", | |
"command": "emacs-mcx.setMarkCommand", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape shift+,", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.setMarkCommand", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
{ | |
"key": "escape shift+,", | |
"command": "emacs-mcx.beginningOfBuffer", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "escape shift+,", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.beginningOfBuffer", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// C-w => kill-region | |
// -------------------------- | |
{ | |
"key": "ctrl+w", | |
"command": "emacs-mcx.killRegion", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+w", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.killRegion", | |
"await": "document selection clipboard" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// C-k => kill-line | |
// -------------------------- | |
{ | |
"key": "ctrl+k", | |
"command": "emacs-mcx.killLine", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+k", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.killLine", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// C-y => yank | |
// -------------------------- | |
{ | |
"key": "ctrl+y", | |
"command": "emacs-mcx.yank", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+y", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.yank", | |
"await": "document clipboard" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// C-a => move-beginning-of-line | |
// -------------------------- | |
{ | |
"key": "ctrl+a", | |
"command": "emacs-mcx.moveBeginningOfLine", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+a", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.moveBeginningOfLine", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// C-e => move-end-of-line | |
// -------------------------- | |
{ | |
"key": "ctrl+e", | |
"command": "emacs-mcx.moveEndOfLine", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "ctrl+e", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "emacs-mcx.moveEndOfLine", | |
"await": "document" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
// -------------------------- | |
// Arrow keys => simple cursor moves | |
// (Up, Down, Left, Right) | |
// -------------------------- | |
{ | |
"key": "up", | |
"command": "cursorUp", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "up", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "cursorUp", | |
"await": "selection" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
{ | |
"key": "down", | |
"command": "cursorDown", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "down", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "cursorDown", | |
"await": "selection" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
{ | |
"key": "left", | |
"command": "cursorLeft", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "left", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "cursorLeft", | |
"await": "selection" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
}, | |
{ | |
"key": "right", | |
"command": "cursorRight", | |
"when": "editorTextFocus && !kb-macro.active" | |
}, | |
{ | |
"key": "right", | |
"command": "kb-macro.wrap", | |
"args": { | |
"command": "cursorRight", | |
"await": "selection" | |
}, | |
"when": "kb-macro.active && editorTextFocus" | |
} | |
] | |
// Aborted effort to set mark when you hit C-> | |
// // YOU NEED TO ADD THIS TO SETTINGS FOR MULTI-COMMAND: | |
// // { | |
// // // ... other settings ... | |
// // | |
// // // Here we define named multi-commands: | |
// // "multiCommand.commands": [ | |
// // { | |
// // "command": "multiCommand.markThenEndOfBuffer", | |
// // "sequence": [ | |
// // "emacs-mcx.setMarkCommand", | |
// // "emacs-mcx.endOfBuffer" | |
// // ] | |
// // }, | |
// // { | |
// // "command": "multiCommand.markThenBeginningOfBuffer", | |
// // "sequence": [ | |
// // "emacs-mcx.setMarkCommand", | |
// // "emacs-mcx.beginningOfBuffer" | |
// // ] | |
// // } | |
// // // You can add more as needed | |
// // ] | |
// // } | |
// // ========================= | |
// // C-SHIFT-. (Mark + move to end-of-buffer) | |
// // Uses multi-command to chain both commands in one chord | |
// // ========================= | |
// // For normal usage (not recording) | |
// { | |
// "key": "ctrl+shift+.", | |
// "command": "multiCommand.markThenEndOfBuffer", | |
// "when": "editorTextFocus && !kb-macro.active" | |
// }, | |
// // For macro recording | |
// { | |
// "key": "ctrl+shift+.", | |
// "command": "kb-macro.wrap", | |
// "args": { | |
// "command": "multiCommand.markThenEndOfBuffer", | |
// "await": "document" | |
// }, | |
// "when": "editorTextFocus && kb-macro.active" | |
// }, | |
// // ========================= | |
// // C-SHIFT-, (Mark + Move to Beginning of Buffer) | |
// // Uses multi-command to chain both commands in one chord | |
// // ========================= | |
// // Normal usage (not recording macros) | |
// { | |
// "key": "ctrl+shift+,", | |
// "command": "multiCommand.markThenBeginningOfBuffer", | |
// "when": "editorTextFocus && !kb-macro.active" | |
// }, | |
// // Macro recording version | |
// { | |
// "key": "ctrl+shift+,", | |
// "command": "kb-macro.wrap", | |
// "args": { | |
// "command": "multiCommand.markThenBeginningOfBuffer", | |
// "await": "document" | |
// }, | |
// "when": "editorTextFocus && kb-macro.active" | |
// }, |
@imjonathan Thank you for sharing this very helpful resource!
I updated the README referring to this page https://github.com/whitphx/vscode-emacs-mcx?tab=readme-ov-file#i-want-to-use-keyboard-macro
@imjonathan Thank you for sharing! I'm happy to know people are using my software!
For your information, there is another keybinding file generated programmatically to use with the two extensions combined, which I think also works on platforms other than Mac.
https://github.com/tshino/vscode-kb-macro/blob/main/keymap-wrapper/tuttieee.emacs-mcx.json
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@whitphx @tshino Thank you guys SO much for easing the transition to VS Code / VS Codium. With the above config I won't have to keep switching back and forth multiple times every day. It took me way longer than it should have to make this work, so I've posted this here in hopes that it helps others.