sim/ui/js/editor.js

86 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

// Thin glue around Monaco (+ monaco-vim), both loaded lazily from a CDN.
const MONACO = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min';
const VIM = 'https://cdn.jsdelivr.net/npm/monaco-vim@0.4.2/dist/monaco-vim';
let monacoReady = null;
let editor = null;
let vimMode = null;
let vimLib = null;
let statusEl = null;
let applyCb = null;
function loadMonaco() {
monacoReady ??= new Promise((resolve, reject) => {
window.MonacoEnvironment = {
getWorkerUrl: () => URL.createObjectURL(new Blob(
[`self.MonacoEnvironment={baseUrl:'${MONACO}/'};importScripts('${MONACO}/vs/base/worker/workerMain.js');`],
{ type: 'text/javascript' })),
};
const s = document.createElement('script');
s.src = MONACO + '/vs/loader.js';
s.onload = () => {
require.config({ paths: { vs: MONACO + '/vs' } });
require(['vs/editor/editor.main'], () => resolve(window.monaco), reject);
};
s.onerror = () => { monacoReady = null; reject(new Error('could not load the editor from the CDN')); };
document.head.appendChild(s);
});
return monacoReady;
}
export async function editor_mount(container, status, src, vim, onApply) {
await loadMonaco();
if (editor) return;
statusEl = status;
applyCb = onApply;
editor = monaco.editor.create(container, {
value: src, language: 'rust', theme: 'vs-dark',
automaticLayout: true, minimap: { enabled: false }, fontSize: 13, tabSize: 4,
});
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => applyCb());
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => applyCb());
if (vim) await editor_set_vim(true);
}
export function editor_focus() { editor?.focus(); }
export function editor_value() { return editor ? editor.getValue() : ''; }
export async function editor_set_vim(on) {
if (!editor) return;
if (!on) {
vimMode?.dispose();
vimMode = null;
statusEl.textContent = '';
return;
}
vimLib ??= await new Promise((resolve, reject) => {
require.config({ paths: { 'monaco-vim': VIM } });
require(['monaco-vim'], resolve, reject);
});
if (vimMode) return;
vimMode = vimLib.initVimMode(editor, statusEl);
vimLib.VimMode.Vim.defineEx('write', 'w', () => applyCb());
editor.focus();
}
// Turns Rune diagnostics ("behavior:LINE:COL") into editor markers; empty text clears them.
export function editor_show_errors(text) {
if (!editor) return;
const model = editor.getModel();
const markers = [];
for (const block of text.split(/\n(?=error:|warning:)/)) {
const m = /behavior:(\d+):(\d+)/.exec(block);
if (!m) continue;
const line = +m[1], col = +m[2];
const word = model.getWordAtPosition({ lineNumber: line, column: col });
markers.push({
severity: block.startsWith('warning') ? monaco.MarkerSeverity.Warning : monaco.MarkerSeverity.Error,
message: block.split('\n')[0].replace(/^(error|warning): /, ''),
startLineNumber: line, startColumn: col,
endLineNumber: line, endColumn: word ? word.endColumn : col + 1,
});
}
monaco.editor.setModelMarkers(model, 'rune', markers);
}