Custom Key Bindings and Macros

Now Available in Composer v 4.2!

Like most macOS applications, MultiMarkdown Composer has shortcuts for many of the menu bar commands, as well as a couple of extra commands for editing that do not appear in the menu bar.

If you want to customize the shortcuts for the menu commands, you can use the System Preferences to modify them. (System Preferences-> Keyboard-> Shortcuts-> App Shortcuts) However, this has to be done on each computer separately, and has to be done on a per app basis.

A new Pro Feature in version 4.2 enables customization of the keyboard shortcuts via a configuration file. Like Themes, CSS, and Expansion files, these can be synchronized across machines. And the goal is that they will be compatible with a few other applications that are planned for the future so that you share a familiar working environment across devices and applications.

What Does This Do?

This new functionality is used for a couple of things:

  1. The defined shortcuts are enabled when typing in Composer's editor. These can trigger menu bar commands or you can define your own custom commands.

  2. If the action matches that of a menu bar command, Composer reassigns the new shortcut to the menu bar. This allows you to customize the menu bar easily.

  3. You can define your own custom macros -- you can configure a shortcut to trigger a specified series of commands that allow you to basically add your own features to MultiMarkdown Composer. The primary limitation is your imagination!

File Format

The key bindings are defined in the file default.keybinding. It should be placed in the KeyBindings folder:

/Users/<UserName>/Library/Containers/com.multimarkdown.composer4.mac/Data/Library/Application Support/MultiMarkdown Composer 4/KeyBindings

As usual, you can use a button in the Pro Preferences to open this folder for you automatically.

The default.keybinding file uses the following JSON format:

[{
    "key": "P",
    "action": "togglePreview:",
    "modifiers" : ["cmd", "ctrl"],
    "title" : "Toggle Preview"
}, {
    "key": "I",
    "action": "toggleInfo:",
    "modifiers" : ["cmd", "shift"]
}]

It is pretty self-explanatory. The modifier options are cmd, ctrl, opt, and shift. You must use at least one of them.

The title key is used on iOS applications to show the name of the function on the iOS Discoverability HUD (Hold down the Command Key and an overlay appears showing the available keyboard shortcuts.) Keyboard shortcuts on iOS are only usable with a hardware keyboard since on screen keyboards do not offer the control key, option key, etc.

Troubleshooting

NOTE: It is recommend that you use a JSON validator to ensure that there are no typos in your file. A great one is https://jsonlint.com/. Additionally, you can use a JSON Schema validator to ensure that your file properly follows the key binding rules. The schema is here and a validator that I use is https://www.jsonschemavalidator.net/

Validating your file after making changes can save a lot of time spent troubleshooting!

Defaults

The default key bindings to ensure functionality of the commands that are not in the menu bar is:

[{
    "key": "{",
    "action": "decreaseLine:",
    "modifiers": ["cmd", "shift"]
}, {
    "key": "}",
    "action": "increaseLine:",
    "modifiers": ["cmd", "shift"]
}, {
    "key": "[",
    "action": "decreaseLine:",
    "modifiers": ["cmd", "shift"]
}, {
    "key": "]",
    "action": "increaseLine:",
    "modifiers": ["cmd", "shift"]
}, {
    "key": "\t",
    "action": "shiftLeft:",
    "modifiers": ["cmd"]
}, {
    "key": "[",
    "action": "shiftLeft:",
    "modifiers": ["cmd"]
}, {
    "key": "]",
    "action": "shiftRight:",
    "modifiers": ["cmd"]
}, {
    "key": "\\uF702",
    "action": "shiftLeft:",
    "modifiers": ["cmd", "ctrl"]
}, {
    "key": "\\uF703",
    "action": "shiftRight:",
    "modifiers": ["cmd", "ctrl"]
}]

(The seemingly redundant duplicate functions are to ensure proper behavior on both macOS and iOS)

A complete file to recreate the default menu bar shortcuts is here.

Special Keys

Most keys are set by simply typing the letter you want. A few require special changes, however:

  • Tab -- use \t
  • Up Arrow -- use \\uF700
  • Down Arrow -- use \\uF701
  • Left Arrow -- use \\uF702
  • Right Arrow -- use \\uF703

Advanced Action Sequences

Sometimes a single action step is not enough -- sometimes you just need more. You can also configure a single key binding to perform a series of actions in order (aka a "macro"):

{
    "key" : "U",
    "action": [
        "setMark:",
        "selectParagraph:",
        "uppercaseWord:",
        "swapWithMark:"
    ],
    "modifiers" : ["cmd", "ctrl"]
}

This configures the Cmd-Ctrl-U key to set a bookmark, select the entire paragraph, uppercase the entire paragraph, and return the selection back to the original point (bookmark).

Defining your own macro sequences like this makes it easy to customize the behavior of Composer to do what you want, when you want it.

Text Insertion

You can use the insertText: action followed by the text that you wish to insert:

{
    "key": "Y",
    "action": [
        "setMark:",
        "swapWithMark:",
        "delete:",
        "insertText:",
        "<!-- ",
        "yank:",
        "insertText:",
        " -->",
        "swapWithMark:",
        "moveRight:",
        "moveRight:",
        "moveRight:",
        "moveRight:",
        "moveRight:",
        "moveRight:"
    ],
    "modifiers": ["cmd"]
}

Note the text after the two insertText actions. This command wraps the selected text in <!-- and -->.

(This example from Brett Terpstra's project, linked to below. It wraps the selected text in HTML comment code.)

Selector White List

A limited number of actions are allowed for security reasons. The following Composer-specific actions are (or will be soon) allowed in Composer 4:

acceptCurrentCriticMarkup:
addEmph:
addStrong:
applyTitleCase:
cleanupFull:
cleanupHTMLEntities:
cleanupList:
cleanupMetadata:
cleanupSmartTypography:
cleanupTables:
copyAsHTML:
copyAsLaTeX:
copyAsRichText:
decreaseLine:
decreaseSelection:
diffToCriticMarkup:
increaseLine:
increaseSelection:
insertTOC:
learnAboutPro:
macAppStore:
makeBlockquote:
makeBulletedList:
makeEnumeratedList:
makeFencedBlock:
mmdSoftwareSite:
moveSelectionToNextCriticMarkup:
moveSelectionToPreviousCriticMarkup:
onlineHelp:
openWithMarked:
pasteAsBlockquote:
pasteAsBulletedList:
pasteAsEnumeratedList:
pasteAsTable:
pasteAsTableRows:
printPreview:
refreshPreview:
rejectCurrentCriticMarkup:
selectEditor:
selectLeftSidebar:
selectRightSidebar:
shiftDown:
shiftLeft:
shiftRight:
shiftUp:
showCheatSheet:
showIAPStore:
showPreferences:
stripMarkers:
submitProblem:
swapPreview:
swapVisiblePanels:
toggleAutoZoom:
toggleChangeTracking:
toggleCritic:
toggleInfo:
toggleList:
togglePreview:
toggleRef:
toggleShowInvisibleCharacters:
toggleShowLineNumbers:
toggleShowParagraphNumbers:
toggleTOC:
transcludeToNewDocument:
unwrapParagraphs:
wrapSelectionInCriticComment:
wrapSelectionInCriticHighlight:
zoomIn:
zoomOut:
zoomPreviewIn:
zoomPreviewOut:

The following are default actions provided by the operating system itself, and are also allowed (or will be soon):

arrangeInFront:
capitalizeWord:
centerSelectionInVisibleArea:
changeCaseOfLetter:
checkSpelling:
clearRecentDocuments:
complete:
copy:
cut:
delete:
deleteBackward:
deleteBackwardByDecomposingPreviousCharacter:
deleteForward:
deleteToBeginningOfLine:
deleteToBeginningOfParagraph:
deleteToEndOfLine:
deleteToEndOfParagraph:
deleteToMark:
deleteWordBackward:
deleteWordForward:
hide:
hideOtherApplications:
indent:
insertTabIgnoringFieldEditor:
insertBacktab:
insertDoubleQuoteIgnoringSubstitution:
insertNewline:
insertNewlineIgnoringFieldEditor:
insertSingleQuoteIgnoringSubstitution:
insertTab:
insertText:
lowercaseWord:
moveBackward:
moveBackwardAndModifySelection:
moveDown:
moveDownAndModifySelection:
moveForward:
moveForwardAndModifySelection:
moveLeft:
moveLeftAndModifySelection:
moveParagraphBackwardAndModifySelection:
moveParagraphForwardAndModifySelection:
moveRight:
moveRightAndModifySelection:
moveSelectionToNextParagraph:
moveSelectionToPreviousParagraph:
moveToBeginningOfDocument:
moveToBeginningOfDocumentAndModifySelection:
moveToBeginningOfLine:
moveToBeginningOfLineAndModifySelection:
moveToBeginningOfParagraph:
moveToBeginningOfParagraphAndModifySelection:
moveToEndOfDocument:
moveToEndOfDocumentAndModifySelection:
moveToEndOfLineAndModifySelection:
moveToEndOfParagraph:
moveToEndOfParagraphAndModifySelection:
moveToLeftEndOfLine:
moveToLeftEndOfLineAndModifySelection:
moveToRightEndOfLine:
moveToRightEndOfLineAndModifySelection:
moveUp:
moveUpAndModifySelection:
moveWordBackward:
moveWordBackwardAndModifySelection:
moveWordForward:
moveWordForwardAndModifySelection:
moveWordLeft:
moveWordLeftAndModifySelection:
moveWordRight:
moveWordRightAndModifySelection:
newDocument:
newWindowForTab:
openDocument:
openFolder:
orderFrontStandardAboutPanel:
orderFrontSubstitutionsPanel:
pageDown:
pageDownAndModifySelection:
pageUp:
pageUpAndModifySelection:
paste:
performClose:
performFindPanelAction:
performMiniaturize:
performZoom:
printDocument:
redo:
revertDocumentToSaved:
runPageLayout:
saveAs:
saveDocument:
saveDocumentAs:
saveDocumentTo:
scrollLineDown:
scrollLineUp:
scrollPageDown:
scrollPageUp:
scrollToBeginningOfDocument:
scrollToEndOfDocument:
selectAll:
selectLine:
selectNextTab:
selectParagraph:
selectPreviousTab:
selectToMark:
selectWord:
setMark:
showContextHelp:
showGuessPanel:
startSpeaking:
stopSpeaking:
swapWithMark:
terminate:
toggleAutomaticDashSubstitution:
toggleAutomaticDataDetection:
toggleAutomaticLinkDetection:
toggleAutomaticQuoteSubstitution:
toggleAutomaticSpellingCorrection:
toggleAutomaticTextReplacement:
toggleContinuousSpellChecking:
toggleFullScreen:
toggleGrammarChecking:
toggleSmartInsertDelete:
toggleTypewriterMode:
transpose:
transposeWords:
undo:
unhideAllApplications:
uppercaseWord:
yank:
yankAndSelect:

Not all of these commands will function as expected, but they are allowed. They will also not all work across all applications or on iOS. (For example, the macOS Find submenu uses a single action for all of the menu commands.)

Notes

Changes to the default.keybinding file are reflected in the hot keys for each new window (so close and reopen a document to see the effect).

The menu bar is only updated when the application launches, so you'll have to restart to see that change.

What Next?

The allowed actions are basically the same as what you might use in a custom DefaultKeyBinding.dict file. If you need some ideas for useful macros, Brett Terpstra is a master at this and has written about this previously.

Feel free to share your creations in the "Extras" discussions.

I'll continue to work on this feature as needed based on feedback and further testing. Let me know what you would like to see happen!