Skip to content

Instantly share code, notes, and snippets.

@imjonathan
Last active May 31, 2025 16:57
Show Gist options
  • Select an option

  • Save imjonathan/20112f04608e5e7fa371dd117f4d8801 to your computer and use it in GitHub Desktop.

Select an option

Save imjonathan/20112f04608e5e7fa371dd117f4d8801 to your computer and use it in GitHub Desktop.
Emacs Keyboard Macro Support in VS Codium
/*
YOU SHOULD USE THIS INSTEAD:
https://github.com/tshino/vscode-kb-macro/blob/main/keymap-wrapper/tuttieee.emacs-mcx.json
====================================================================
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 You should probably remove me from the faq @tshino 's https://github.com/tshino/vscode-kb-macro/blob/main/keymap-wrapper/tuttieee.emacs-mcx.json works better than mine. Thanks again to both of you!

@whitphx
Copy link

whitphx commented May 31, 2025

@imjonathan Thanks I updated the README!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment