Tio.run Monaco Editor

Tio.run with Monaco Editor.

// ==UserScript==
// @name         Tio.run Monaco Editor
// @namespace    https://greasyfork.org/en/scripts/432456-tio-run-monaco-editor
// @version      0.3
// @description  Tio.run with Monaco Editor.
// @author       cgiosy
// @match        https://tio.run/*
// @run-at       document-body
// ==/UserScript==

'use strict';

let mainExecuted = false;

const main = () => {
    if (mainExecuted) return;
    mainExecuted = true;

    const head = document.getElementsByTagName('head')[0];

    const monacoStylesheet = document.createElement('style');
    monacoStylesheet.textContent = `
        * { cursor: inherit; }
        .monaco-editor .inputarea { background: none; }
    `;
    head.appendChild(monacoStylesheet);

    const monacoLoaderScript = document.createElement('script');
    monacoLoaderScript.defer = 'true';
    monacoLoaderScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js';
    monacoLoaderScript.onload = () => {
        const getLanguage = (languageId) => {
            const languages = ['abap', 'aes', 'apex', 'azcli', 'bat', 'bicep', 'c', 'cameligo', 'clojure', 'coffeescript', 'cpp', 'csharp', 'csp', 'css', 'dart', 'dockerfile', 'ecl', 'elixir', 'fsharp', 'go', 'graphql', 'handlebars', 'hcl', 'html', 'ini', 'java', 'javascript', 'json', 'julia', 'kotlin', 'less', 'lexon', 'liquid', 'lua', 'm3', 'markdown', 'mips', 'msdax', 'mysql', 'objective-c', 'pascal', 'pascaligo', 'perl', 'pgsql', 'php', 'plaintext', 'postiats', 'powerquery', 'powershell', 'pug', 'python', 'qsharp', 'r', 'razor', 'redis', 'redshift', 'restructuredtext', 'ruby', 'rust', 'sb', 'scala', 'scheme', 'scss', 'shell', 'sol', 'sparql', 'sql', 'st', 'swift', 'systemverilog', 'tcl', 'twig', 'typescript', 'vb', 'verilog', 'xml', 'yaml'];
            let max = 0, lang = 'cpp';
            if (!languageId) return lang;
            for (const language of languages) {
                let common = 0;
                if (languageId === language)
                    common += 10000000;
                if (languageId.split('-')[0] === language || languageId.indexOf(language) === 0)
                    common += 1000;
                for (let i = 0, j = 0; i < languageId.length; i++, j++) {
                    while (j < language.length && languageId[i] !== language[j]) j++;
                    if (j === language.length) break;
                    common++;
                }
                if (max < common) {
                    max = common;
                    lang = language;
                }
            }
            return lang;
        };

        const fn = () => {
            self.MonacoEnvironment = {
                baseUrl: 'https://cdn.jsdelivr.net/npm/[email protected]/min/'
            };
            importScripts('https://cdn.jsdelivr.net/npm/[email protected]/min/vs/base/worker/workerMain.js');
        };
        window.MonacoEnvironment = {
            getWorkerUrl: (workerId, label) => 'data:text/javascript;charset=utf-8,' + encodeURIComponent(`(${fn})()`),
        };
        require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' }});

        require(['vs/editor/editor.main'], () => {
            const prevCodeEditor = document.getElementById('code');
            const prevOnInput = prevCodeEditor.oninput;
            const text = prevCodeEditor.value;

            const codeEditor = document.createElement('div');
            codeEditor.id = 'code';
            codeEditor.style = 'width: 100%;';
            codeEditor.dataset.mask = 'null';
            prevCodeEditor.replaceWith(codeEditor);

            let prevLanguage = languageId;
            const monacoEditor = monaco.editor.create(codeEditor, {
                value: text,
                language: getLanguage(prevLanguage),
                insertSpaces: false,
                tabSize: 4,
                automaticLayout: true,
                wordWrap: 'on',
                minimap: {
                    enabled: false,
                },
                scrollbar: {
                    vertical: 'hidden',
                    horizontal: 'hidden',
                    handleMouseWheel: false,
                },
                scrollBeyondLastLine: false,
                hideCursorInOverviewRuler: true,
                overviewRulerBorder: false,
                overviewRulerLanes: 0,
            });
            Object.defineProperty(codeEditor, 'value', {
                get: () => monacoEditor.getValue(),
                set: (value) => monacoEditor.setValue(value),
            });
            monacoEditor.onDidContentSizeChange(() => {
                codeEditor.style.height = monacoEditor.getContentHeight() + 'px';
                monacoEditor.layout();
            });

            const monacoEditorModel = monacoEditor.getModel();
            codeEditor.oninput = (() => {
                prevOnInput();
                if (prevLanguage === languageId) return;
                monaco.editor.setModelLanguage(monacoEditorModel, getLanguage(languageId));
                prevLanguage = languageId;
            });
            monacoEditorModel.onDidChangeContent(codeEditor.oninput);

            window.removeEventListener("beforeunload", saveState);
            byteStringToTextArea = (byteString, textArea) => {
                textArea.value = byteStringToText(byteString);
                if (textArea !== codeEditor)
                    resize(textArea);
            };
            saveState = (saveIfEmpty) => {
                if (!languageId)
                    return;
                var stateString = languageId;
                var saveTextArea = function(textArea) {
                    if (textArea.readOnly)
                        return;
                    stateString += fieldSeparator + textToByteString(textArea.value);
                }
                iterate($$("#interpreter > textarea, #code, #interpreter > :not([data-mask]) textarea"), saveTextArea);
                iterate($$("#interpreter > [data-mask=false]"), function(element) {
                    if ($("textarea", element) === null)
                        return;
                    stateString += startOfExtraFields + (element.dataset.if || element.dataset.ifNot);
                    iterate($$("textarea", element), saveTextArea);
                });
                var settings = getSettings();
                if (settings != "/")
                    stateString += startOfSettings + settings.slice(1,-1);
                if (saveIfEmpty || ! rEmptyStateString.test(stateString))
                    history.replaceState({}, "", "##" + byteStringToBase64(byteArrayToByteString(deflate(stateString))));
            };
            window.addEventListener("beforeunload", saveState);
        });

        const keyPressed = {};
        window.addEventListener('keyup', (e) => {
            keyPressed[e.key.toLowerCase()] = false;
        });
        window.addEventListener('keydown', (e) => {
            keyPressed[e.key.toLowerCase()] = true;
            if (keyPressed['control'] === true && keyPressed['s'] === true) {
                e.preventDefault();
                saveState();
                document.getElementById('run').click();
            }
        });
    };
    head.appendChild(monacoLoaderScript);
};

const scripts = [...document.querySelectorAll('script[src^="/static/"]')];
let scriptLoadCount = 0;

scripts.forEach((script) => {
    script.onload = () => ++scriptLoadCount === scripts.length && main();
});

window.onload = () => main();