Skip to content

Latest commit

 

History

History
77 lines (47 loc) · 5.27 KB

DESIGN.md

File metadata and controls

77 lines (47 loc) · 5.27 KB

Design

Recording keystrokes vs. recording commands

First of all, we don't have any VS Code API that allows us to capture command executions at this moment. But, we could imagine that we have an event API for that. Let's name it onDidExecuteCommand.

If we could capture all the command executions, is it possible to reproduce the scenario by simply executing all the captured commands by vscode.commands.executeCommand API?

No, probably not.

A command may trigger another command. It happens by directly invoking vscode.commands.executeCommand in a command or in some way indirectly like making side-effects like document change. So we must distinguish them to execute only the commands that are triggered directly by the user.

On the other hand, capturing keystrokes could be a good solution for reproducing recorded scenarios since a keystroke is always made directly by the user. A keystroke does not trigger another keystroke.

However, we don't have any VS Code API to capture keystrokes directly.

Capturing keystrokes

We have Keybindings on VS Code. Using that feature we can associate keystrokes with commands.

However, we can't associate every possible keystroke by defining a single keybinding rule (imagine kind of using wildcard "key": "*").

So we end up defining a bunch of wrapper keybindings to capture the whole set of the default keybindings of VS Code.

Wrapper keybindings

A wrapper keybinding associates a particular combination of key and when with the kb-macro.wrap command with args parameter that specifies the target command to be invoked. The kb-macro.wrap command executes the target command. This indirect execution makes it possible to capture the command that has been triggered by user's keystrokes.

    {
        "key": "ctrl+shift+a",
        "command": "kb-macro.wrap",
        "args": {
            "command": "editor.action.selectToBracket"
        },
        "when": "kb-macro.recording && editorTextFocus"
    }

We use the wrapper command only when the macro recording is ongoing. So we add the kb-macro.recording context to every wrapper keybinding.

Why don't we use the wrapper keybindings always to simplify things? Because we want to keep the original behavior of each command for the keybindings as much as possible. It is not clear but the indirect execution may not be perfectly transparent.

Default wrapper keybindings

This extension defines a large set of keybindings to capture all the default keyboard shortcuts of VS Code.

The list of default wrapper keybindings is defined in the package.json of this extension. The list is automatically generated by a script generator/gen_wrapper.js. The script takes JSON files where each one contains the default keybindings of VS Code for Windows, Linux, and macOS respectively, and combines all the keybindings in them with additional context such as isWindows, isLinux, or isMac as needed, and convert them to wrappers and write them into keybindings section of the package.json.

The script also performs some optimization work to reduce the amount of wrapper keybinding rules.

Related files
generator/default-keybindings-win.json the default keybindings of VS Code for Windows
generator/default-keybindings-linux.json the default keybindings of VS Code for Linux
generator/default-keybindings-mac.json the default keybindings of VS Code for macOS
generator/gen_wrapper.js a script to generate default wrapper keybindings and write them in package.json
generator/verify_wrapper.js a script to verify the output of gen_wrapper.js

The default-keybindings-*.json files are retrieved by running the Open Default Keyboard Shortcuts (JSON) command on VS Code on each OS. In order to mitigate manual work to retrieve these three files, an automated workflow on GitHub Actions is used.

The following command updates the default wrapper keybindings in the package.json based on the default keybindings files.

npm run gen-wrapper

Capturing typed characters

On VS Code, typed characters in text editors are treated differently than other keystrokes. We don't put every possible character in the keybindings. When you type characters in a text editor, for each character, the type built-in command is invoked internally. The type command performs inserting each character into the document.

As far as I know, an extension is allowed to override the type built-in command using vscode.commands.registerCommand API. Actually, the VSCodeVim extension seems to do that to customize the behavior for typed characters.

It was not clear whether overriding the type command to capture typed characters is a good way for this extension. Especially if you use this extension combined with another extension that is overriding the type command too, there would be a conflict, and likely they will not work correctly. See vscode#13441.

So this extension took another way to capture typed characters. That is to listen to the events on changes on the text document. Basically this is possible through the vscode.workspace.onDidChangeTextDocument event.