diff --git a/frontend/src/components/editor/CodeEditor.tsx b/frontend/src/components/editor/CodeEditor.tsx index 6efaa17b..faa99a92 100644 --- a/frontend/src/components/editor/CodeEditor.tsx +++ b/frontend/src/components/editor/CodeEditor.tsx @@ -2,6 +2,7 @@ import Editor from '@monaco-editor/react'; import { useEditorStore } from '../../store/useEditorStore'; import { useSimulatorStore } from '../../store/useSimulatorStore'; import { registerRetroAsm, LANGUAGE_ID as RETRO_ASM_ID } from './retroAsmLanguage'; +import { attachIntellisenseMonaco } from '../../lib/intellisenseRegistry'; function getLanguage(filename: string): string { const ext = filename.split('.').pop()?.toLowerCase() ?? ''; @@ -62,6 +63,10 @@ export const CodeEditor = () => { // Register the 8080/Z80 assembly language once so Monaco knows how // to tokenize .s / .asm files when they're opened. registerRetroAsm(monaco); + // Hand the monaco instance to the intellisense seam. Inert in OSS; + // with the pro overlay loaded it registers the completion engine + // (idempotent per monaco instance, so per-file remounts are fine). + attachIntellisenseMonaco(monaco); }} onChange={(value) => { if (activeFileId) setFileContent(activeFileId, value || ''); diff --git a/frontend/src/lib/intellisenseRegistry.ts b/frontend/src/lib/intellisenseRegistry.ts new file mode 100644 index 00000000..5649916d --- /dev/null +++ b/frontend/src/lib/intellisenseRegistry.ts @@ -0,0 +1,45 @@ +/** + * Intellisense registry. + * + * Seam between the OSS editor and the pro overlay's code-completion engine + * (the private `velxio-intellisense` package). OSS ships no completion + * engine of its own, so without an overlay this registry is inert and the + * editor behaves exactly as before. + * + * Two-sided handshake, because arrival order is not guaranteed: + * + * - CodeEditor's `beforeMount` calls `attachIntellisenseMonaco(monaco)` + * every time an editor instance is created (which happens on every + * file switch — the editor is keyed by file id). + * - The overlay calls `installIntellisenseImpl(impl)` from `mountPro()`, + * which lands via an async `import('@pro/index')` and may resolve + * before or after the first editor mounted. + * + * Whichever side arrives last triggers the install. The impl itself is + * responsible for being idempotent per monaco instance (the package guards + * with a WeakSet), so replaying it on every mount is safe and cheap. + */ + +type IntellisenseImpl = (monaco: unknown) => void; + +let _impl: IntellisenseImpl | null = null; +let _monaco: unknown = null; + +export function installIntellisenseImpl(impl: IntellisenseImpl | null): void { + _impl = impl; + if (_impl && _monaco) run(_impl, _monaco); +} + +export function attachIntellisenseMonaco(monaco: unknown): void { + _monaco = monaco; + if (_impl) run(_impl, monaco); +} + +function run(impl: IntellisenseImpl, monaco: unknown): void { + try { + impl(monaco); + } catch (err) { + // eslint-disable-next-line no-console + console.warn('[oss] intellisense impl threw:', err); + } +}