Skip to content

Instantly share code, notes, and snippets.

@imjonathan
Last active March 14, 2025 15:21
Show Gist options
  • Save imjonathan/20112f04608e5e7fa371dd117f4d8801 to your computer and use it in GitHub Desktop.
Save imjonathan/20112f04608e5e7fa371dd117f4d8801 to your computer and use it in GitHub Desktop.
Emacs Keyboard Macro Support in VS Codium
/*
====================================================================
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
Copy link
Author

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

@whitphx
Copy link

whitphx commented Mar 14, 2025

@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

@tshino
Copy link

tshino commented Mar 14, 2025

@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