// 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); }