This commit is contained in:
2025-10-29 21:02:56 -07:00
parent d90a9b5826
commit 667624d0ca
12 changed files with 681 additions and 27 deletions

View File

@@ -2,19 +2,51 @@
import { useEffect, useRef, forwardRef, useImperativeHandle, useState } from "react";
import styles from "@/styles/Editor.module.css";
import { loadRhaiWasm, initRhaiMode } from "@/utils/wasmLoader";
// Dynamic import for CodeMirror to avoid SSR issues
let CodeMirror: any = null;
let isCodeMirrorReady = false;
if (typeof window !== "undefined") {
import("codemirror")
.then(async (cm) => {
CodeMirror = cm.default;
await import("codemirror/mode/javascript/javascript");
await import("codemirror/addon/edit/matchbrackets");
await import("codemirror/addon/edit/closebrackets");
await import("codemirror/addon/selection/active-line");
await import("codemirror/lib/codemirror.css");
await import("codemirror/theme/monokai.css");
await import("codemirror/addon/comment/comment");
// @ts-ignore - CodeMirror addon type issues
await import("codemirror/addon/fold/brace-fold");
// @ts-ignore - CodeMirror addon type issues
await import("codemirror/addon/fold/foldgutter");
// @ts-ignore - CodeMirror addon type issues
await import("codemirror/addon/search/match-highlighter");
require("codemirror/lib/codemirror.css");
require("codemirror/theme/monokai.css");
require("codemirror/addon/fold/foldgutter.css");
try {
await loadRhaiWasm();
initRhaiMode(CodeMirror);
console.log('✅ WASM-based Rhai mode initialized successfully');
} catch (error) {
console.warn('⚠️ Failed to load WASM Rhai mode, falling back to JavaScript mode:', error);
// Fallback to JavaScript mode if WASM fails
CodeMirror.defineMode("rhai", (config: any) => {
const jsMode = CodeMirror.getMode(config, "javascript");
return {
...jsMode,
name: "rhai",
helperType: "rhai"
};
});
}
isCodeMirrorReady = true;
})
.catch(error => {
console.error('Failed to load CodeMirror:', error);
});
}
@@ -26,7 +58,7 @@ interface EditorProps {
}
export const Editor = forwardRef<any, EditorProps>(function Editor(
{ initialValue = "", onChange, onRequestRun, onReady },
{ initialValue = "", onChange, onReady },
ref,
) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -37,19 +69,30 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
// Initialize editor only once
useEffect(() => {
if (!CodeMirror || !textareaRef.current || editorRef.current) return;
if (!isCodeMirrorReady || !CodeMirror || !textareaRef.current || editorRef.current) return;
const editor = CodeMirror.fromTextArea(textareaRef.current, {
lineNumbers: true,
mode: "javascript", // Placeholder mode, will be 'rhai' when WASM is integrated
mode: "rhai",
theme: "monokai",
indentUnit: 4,
matchBrackets: true,
autoCloseBrackets: true,
foldGutter: {
rangeFinder: CodeMirror.fold.brace,
},
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
styleActiveLine: true,
extraKeys: {
"Ctrl-Enter": () => {
onRequestRun?.();
},
highlightSelectionMatches: {
minChars: 3,
showToken: true,
annotateScrollbar: true,
},
rulers: [],
autoCloseBrackets: {
pairs: `()[]{}''""`,
closeBefore: `)]}'":;,`,
triples: "",
explode: "()[]{}",
},
});
@@ -70,25 +113,14 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
editorRef.current = null;
}
};
}, []); // Empty dependency array - only initialize once
// Update keyboard shortcut when onRequestRun changes
useEffect(() => {
if (editorRef.current && onRequestRun) {
editorRef.current.setOption("extraKeys", {
"Ctrl-Enter": () => {
onRequestRun();
},
});
}
}, [onRequestRun]);
}, []); // Only run once
return (
<div className={styles.editorContainer}>
<textarea
ref={textareaRef}
defaultValue={initialValue}
placeholder="Enter your Rhai code here..."
placeholder="Code goes here"
/>
</div>
);

View File

@@ -210,7 +210,7 @@ export default function Playground() {
className={styles.result}
readOnly
autoComplete="off"
placeholder="Script output will appear here..."
placeholder="Use print() to produce output"
/>
</div>
</div>

View File

@@ -66,7 +66,7 @@ export const Terminal = forwardRef<TerminalRef, {}>(function Terminal(_props, re
});
term.open(terminalRef.current);
term.write('Terminal ready...\r\n');
term.write('Terminal ready.\r\n');
xtermRef.current = term;
} catch (error) {

View File

@@ -15,3 +15,24 @@
resize: none;
outline: none;
}
/* Enhanced CodeMirror styles from playground */
.editorContainer :global(.CodeMirror) {
border: 1px solid #ccc;
height: 100% !important;
box-sizing: border-box;
font-size: 0.95em;
line-height: initial;
}
.editorContainer :global(.rhai-error) {
text-decoration: underline wavy red;
}
.editorContainer :global(.cm-matchhighlight) {
background-color: rgba(0, 0, 0, 0.1);
}
.editorContainer :global(.CodeMirror-selection-highlight-scrollbar) {
background-color: rgba(0, 0, 0, 0.1);
}

View File

@@ -0,0 +1,38 @@
// WASM loader for Rhai CodeMirror mode
import init, { RhaiMode, init_codemirror_pass } from '@/wasm/rhai-codemirror/rhai_codemirror.js';
let wasmInitialized = false;
let wasmModule: any = null;
export const loadRhaiWasm = async () => {
if (wasmInitialized) {
return wasmModule;
}
try {
// Initialize the WASM module
wasmModule = await init();
wasmInitialized = true;
return wasmModule;
} catch (error) {
console.error('Failed to load Rhai WASM module:', error);
throw error;
}
};
export const initRhaiMode = (CodeMirror: any) => {
if (!wasmInitialized || !wasmModule) {
throw new Error('WASM module not loaded. Call loadRhaiWasm() first.');
}
// Initialize CodeMirror Pass for the WASM module
init_codemirror_pass(CodeMirror.Pass);
// Define the Rhai mode using the WASM-based RhaiMode
CodeMirror.defineMode("rhai", (config: any) => {
return new RhaiMode(config.indentUnit || 4);
});
};
export { RhaiMode };