Advanced Text Editing Using Karabiner & macOS KeyBindings
Categories: Productivity
I’ve always wanted some of the fancy keybindings I have in VS Code across my entire macOS experience. Additionally, ever since I discovered back/forward for code navigation in VS Code I wanted to bind my mouse keys to these shortcuts.
I ended up digging into Karabiner and the native macOS keybindings. Here are my notes! Most of the resulting code is here.
macOS Keybindings
Here are some notes about what I learned about this hidden macOS feature:
- There are a set of special commands that control the native cocoa text system. You can combine these commands and tie them to keyboard shortcuts, but they only work in apps that use the native cocoa text system (not Chrome, for example!).
- In Karabiner there is not a
application_windows
command, but there is amission_control
. Holding cntrl while executingmission_control
(which I have bound to my middle button) will execute "application windows" and holding cmd executes show desktop. - All of the standard macOS keybindings are located
/System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict
. However, this file is not a plain text dict. You can read it using plist buddyplistbuddy -c "print" /System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict
- There was some strange output encoding issues that caused many of the bindings to not display properly.
- Any user defined keybindings override system-level keybindings.
- You don’t need to add the shift modifier (
$
) to a keybinding if shift is modifying a letter that can be uppercased. - It looks like macOS has a emacs-like kill-ring that is managed separately from the system clipboard.
Resources
- Official documentation on keybindings
- Another good KeyBindings example
- Yet another KeyBindings example
- List of default macOS shortcuts
- Here’s the best description and examples of how this magical keybinding dict works.
Karabiner Key Modifications
- You can use karabiner to convert any mouse/keyboard input into other mouse/keyboard input. The benefit of this approach is this works across any applications, and you apply filters (only activate when a specific application is active, etc) to keyboard shortcuts.
- If you are adding a new rule entry to a complex modification you already have, you’ll need to explicitly add it in the karabiner complex modifications UI.
- Bundle identifiers can be specified as a regex.
Resources
- List of Karabiner keycodes lists out all of the possible ‘inputs’ into your custom karabiner mappings
- Example of shortcut based on application condition
- Really interesting project which turns the caps lock key into a modifier. It is strange how caps lock is such a big key that you basically never use. Using this for something better is a great idea.
- Karabiner has a great site with community keybindings
- Example mapping mouse buttons to back/forward
Delete Line Using Karabiner
The delete line command in VS Code is so convenient. For a while, I’ve wanted to bring a similar command to all of macOS. Here’s the stackoverflow post that pointed me in the right direction.
However, this was slightly glitchy in some applications (like gdocs).
{
"title": "cmd+shift+k delete line",
"rules": [
{
"description": "cmd+shift+k delete line",
"manipulators": [
{
"type": "basic",
"from": {
"modifiers": { "mandatory": ["left_command", "left_shift"] },
"key_code": "k"
},
"to": [
{ "repeat": false, "key_code": "a", "modifiers": ["left_control"] },
{ "repeat": false, "key_code": "k", "modifiers": ["left_control"] },
{ "repeat": false, "key_code": "delete_or_backspace" }
]
}
]
}
}
Duplicate Line Using Karabiner
{
"title": "duplicate line",
"rules": [
{
"description": "opt+shift+down duplicate line",
"manipulators": [
{
"type": "basic",
"conditions": [
{
"bundle_identifiers": [
"com\.microsoft\.VSCode",
"com\.microsoft\.VSCodeInsiders"
],
"type": "frontmost_application_unless"
}
],
"from": {
"modifiers": { "mandatory": ["left_option", "left_shift"] },
"key_code": "down_arrow"
},
"to": [
{ "repeat": false, "key_code": "right_arrow", "modifiers": ["left_command"] },
{ "repeat": false, "key_code": "up_arrow", "modifiers": ["left_shift", "left_option"] },
{ "repeat": false, "key_code": "c", "modifiers": ["left_command"] },
{ "repeat": false, "key_code": "right_arrow" },
{ "repeat": false, "key_code": "return_or_enter" },
{ "repeat": false, "key_code": "v", "modifiers": ["left_command"] }
]
}
]
}
]
}
Back & Forward Code Navigation in VS Code Using Karabiner
I have a logitech MX vertical mouse (which I’ve enjoyed). I wanted to conditionally map the back/forward buttons to the VS code keyboard shortcut for back/forward code navigation.
{
"title": "Back/Forward in Visual Studio Code",
"rules": [
{
"description": "Change mouse button 4/5 to navigate back/forward in VSCode",
"manipulators": [
{
"type": "basic",
"conditions": [
{
"bundle_identifiers": [
"^com\.microsoft\.VSCode"
],
"type": "frontmost_application_if"
}
],
"from": {
"pointing_button": "button4"
},
"to": [
{
"key_code": "hyphen",
"modifiers": [
"left_control"
]
}
]
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\.microsoft\.VSCode"
],
"type": "frontmost_application_if"
}
],
"from": {
"pointing_button": "button5"
},
"to": [
{
"key_code": "hyphen",
"modifiers": [
"left_control",
"left_shift"
]
}
],
"type": "basic"
}
]
},
{
"description": "When VS Code is not active, use standard browser back/forward commands",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": [
"^com\.microsoft\.VSCode"
],
"type": "frontmost_application_unless"
}
],
"from": {
"pointing_button": "button4"
},
"to": [
{
"key_code": "open_bracket",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\.microsoft\.VSCode"
],
"type": "frontmost_application_unless"
}
],
"from": {
"pointing_button": "button5"
},
"to": [
{
"key_code": "close_bracket",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
}
]
}
]
}
Duplicate Line, Move Line, Delete Line using macOS KeyBindings
The Karabiner versions work well enough, but they are a bit glitchy in some applications. An alternative is implementing these functions in macOS keybindings. The downside is they do not work on applications that do not use the native text input and are glitchy in applications that overload the native text input in strange ways.
{
// duplicate paragraph, opt + shift + down
"~$UF701" = (setMark:, moveToBeginningOfParagraph:, moveToEndOfParagraphAndModifySelection:, copy:, swapWithMark:, moveToEndOfParagraph:,moveRight:,insertNewline:,moveLeft:, paste:);
// delete line/paragraph, cmd + shift + k
"@K" = (selectParagraph:, delete:, moveToBeginningOfParagraph:);
// Move line up
"~UF700" = (selectParagraph:, setMark:, deleteToMark:, moveLeft:, moveToBeginningOfParagraph:, yank:, moveLeft:, selectToMark:, moveLeft:);
// Move line down
"~UF701" = (selectParagraph:, setMark:, deleteToMark:, moveToEndOfParagraph:, moveRight:, setMark:, yank:, moveLeft:, selectToMark:);
}
Open Questions
- On my Logitech mouse, there is a fourth button (not conveniently located) that doesn’t show up in the Karabiner event viewer. I wonder if there’s any way to map this via karabiner?
- How exactly how the emacs kill ring work in macOS?
- Can zsh/tmux be customized to use similar shortcuts?