feat(m9/T31): Monaco JSON-schema hover/completion on value-entry surface

This commit is contained in:
Joseph Doherty
2026-06-18 13:15:54 -04:00
parent 95b8caf284
commit 68c0f7ac59
4 changed files with 286 additions and 13 deletions
@@ -1,5 +1,5 @@
// Blazor bridge for Monaco editor.
// Exposes window.MonacoBlazor with createEditor / setValue / getValue / dispose / setMarkers.
// Exposes window.MonacoBlazor with createEditor / setValue / getValue / dispose / setMarkers / setJsonSchema.
// Lazy-loads Monaco's AMD bundle the first time createEditor is called.
(function () {
@@ -285,12 +285,62 @@
}
}
// ---- JSON schema wiring --------------------------------------------------
// Each json editor gets its own model URI so a schema can be scoped to just
// that model via fileMatch — registering it in Monaco's global json defaults
// without leaking onto other json editors on the page.
function modelUriFor(id) {
return monaco.Uri.parse("inmemory://scadabridge/value-" + id + ".json");
}
// Replace (or remove) this editor's entry in the global json schema list,
// then push the merged list back into jsonDefaults so Monaco's built-in JSON
// language provides schema-driven hover/completion/diagnostics. A null/blank
// schema simply drops the entry (the editor falls back to plain json).
function applyJsonSchema(id, schemaJson) {
if (!monaco.languages.json) return;
const uri = modelUriFor(id).toString();
const fileMatch = ["value-" + id + ".json"];
const defaults = monaco.languages.json.jsonDefaults;
const existing = (defaults.diagnosticsOptions && defaults.diagnosticsOptions.schemas) || [];
const others = existing.filter(function (s) { return s.uri !== uri; });
let schemas = others;
if (schemaJson && schemaJson.trim().length > 0) {
let parsed = null;
try { parsed = JSON.parse(schemaJson); } catch (e) { parsed = null; }
if (parsed) {
schemas = others.concat([{ uri: uri, fileMatch: fileMatch, schema: parsed }]);
}
}
defaults.setDiagnosticsOptions({
validate: true,
enableSchemaRequest: false,
schemas: schemas
});
}
async function createEditor(id, host, options, dotNetRef) {
await ensureLoaded();
if (!host) return;
const language = options.language || "csharp";
const isJson = language === "json";
// A json editor with a schema needs an explicitly-URI'd model so the
// schema's fileMatch can target exactly this editor.
let model = null;
if (isJson) {
const uri = modelUriFor(id);
model = monaco.editor.getModel(uri);
if (model) { model.setValue(options.value || ""); }
else { model = monaco.editor.createModel(options.value || "", "json", uri); }
if (options.jsonSchema) { applyJsonSchema(id, options.jsonSchema); }
}
const editor = monaco.editor.create(host, {
value: options.value || "",
language: options.language || "csharp",
value: model ? undefined : (options.value || ""),
model: model || undefined,
language: model ? undefined : language,
theme: "vs",
minimap: { enabled: false },
scrollBeyondLastLine: false,
@@ -321,10 +371,16 @@
dotNetRef.invokeMethodAsync("OnValueChanged", value).catch(function () {});
if (options.language === "csharp") scheduleDiagnostics();
});
editors[id] = { editor: editor, dotNetRef: dotNetRef };
editors[id] = { editor: editor, dotNetRef: dotNetRef, isJson: isJson };
// Run an initial diagnostic pass so existing scripts show their markers.
if (options.language === "csharp") scheduleDiagnostics();
if (language === "csharp") scheduleDiagnostics();
}
function setJsonSchema(id, schemaJson) {
const entry = editors[id];
if (!entry || !entry.isJson) return;
applyJsonSchema(id, schemaJson);
}
function setEditorOption(id, optionName, value) {
@@ -377,6 +433,15 @@
function dispose(id) {
const entry = editors[id];
if (!entry) return;
// Drop this editor's json schema entry and its explicitly-URI'd model so
// a disposed json editor leaves nothing behind in the global defaults.
if (entry.isJson) {
try { applyJsonSchema(id, null); } catch (e) {}
try {
const model = monaco.editor.getModel(modelUriFor(id));
if (model) model.dispose();
} catch (e) {}
}
try { entry.editor.dispose(); } catch (e) {}
delete editors[id];
}
@@ -386,6 +451,7 @@
setValue: setValue,
getValue: getValue,
setMarkers: setMarkers,
setJsonSchema: setJsonSchema,
setEditorOption: setEditorOption,
format: format,
revealLine: revealLine,