visual-studio-codemonaco-editorvisual-studio-monacomonaco-languageserver

Programatically insert text with autoclose in Monaco


If a user types in a Monaco editor using their keyboard, Monaco is capable of autoclosing brackets, so typing the character '(' will produce '()'. Is there a way to cause this to happen programmatically?

const editor = monaco.editor.create(document.getElementById("container"), {
    "const x = ",
    language: "javascript",
    automaticLayout: true,
    matchBrackets: "always",
    autoClosingBrackets: "always",
});
const model = editor.getModel();
editor.trigger('keyboard', 'type', { text: "(", forceMoveMarkers: true });

This results in the text const x = ( being typed, instead of the desired const x = ().

Further details

Stepping through monaco/vscode internals I have reached a point where I suspect the autoclose behavior should be happening - editor.trigger eventually calls typeWithInterceptors, which calls an _isAutoclosingOvertype method, I assume to determine if it needs to autoclose. This looks for an autoclosing config for the configured language - in this case javascript - but it is empty, so _isAutoclsoingOvertype returns false. So I am wondering if there is an initialization step that I'm not doing, but I can't find anything in the documentation to support this.


Solution

  • Following your debug pointers, I found the exact point that generates the difference between the programatic type and real typing, which is here in the source code at the call to _getAutoClosingPairClose, and its cause is the difference in the values of config.autoClosingPairs at that point. The problem is that the property is initialized asynchronously, as part of the language configuration initialization, which can be verified by the fact that a simple delay (setTimeout of typically less that a second) of the simulated type event gets the autocomplete working.

    One can set an event handler that fires after the language configuration was finalized, using model.onDidChangeLanguageConfiguration:

    model.onDidChangeLanguageConfiguration(()=>{
        editor.trigger('keyboard', 'type', { text: "(", forceMoveMarkers: true });
    });
    

    Runnable example:

    const editor = monaco.editor.create(document.getElementById("container"), {
        value: "const x = ",
        language: "javascript",
        automaticLayout: true,
        matchBrackets: "always",
        autoClosingBrackets: "always",
    });
    
    const model = editor.getModel();
    model.onDidChangeLanguageConfiguration(()=>{
        editor.setPosition(new monaco.Position(1, editor.getValue().length+1));
        editor.trigger('keyboard', 'type', { text: "(", forceMoveMarkers: true })
    });
    <div id="container" style="height: 10vh;border:1px solid black"></div>
    <!-- script setup based on https://stackoverflow.com/a/63179814/16466946 -->
    <link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/editor/editor.main.min.css" />
    <script>var require = { paths: { 'vs': "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs"} }</script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/loader.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/editor/editor.main.nls.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/editor/editor.main.min.js"></script>