diff --git a/src/app/globals.css b/src/app/globals.css
index 9b4f79b..efe06b6 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -314,5 +314,5 @@
}
.cm-editor {
- height: 100%;;
-}
\ No newline at end of file
+ height: 100%;
+}
diff --git a/src/app/run/components/Editor.tsx b/src/app/run/components/Editor.tsx
index 5924d5e..ce7b7a3 100644
--- a/src/app/run/components/Editor.tsx
+++ b/src/app/run/components/Editor.tsx
@@ -1,13 +1,14 @@
"use client";
-import CodeMirror, { EditorState } from "@uiw/react-codemirror";
-import { devRun } from "./theme/theme";
+import CodeMirror, { EditorState, EditorView } from "@uiw/react-codemirror";
+import { devRun } from "./theme";
import { javascript } from "@codemirror/lang-javascript";
import { rust } from "@codemirror/lang-rust";
import { go } from "@codemirror/lang-go";
import { json } from "@codemirror/lang-json";
import { python } from "@codemirror/lang-python";
import { java } from "@codemirror/lang-java";
+import { useEffect, useState } from "react";
type Language =
| "javascript"
@@ -37,12 +38,17 @@ function getLanguageExtension(lang: Language) {
}
}
-export default function Editor() {
- return (
- ); `}
+root.render();`.repeat(3);
+
+// Helper to split text into chunks of ~4 chars
+function chunkText(text: string, size = 4) {
+ const chunks = [];
+ for (let i = 0; i < text.length; i += size) {
+ chunks.push(text.slice(i, i + size));
+ }
+ return chunks;
+}
+
+const chunks = chunkText(code, 4);
+
+const moves: Move[] = chunks.map((chunk, index) => ({
+ latency: 1, // 100ms per chunk
+ cursor: index * 4, // approximate cursor position
+ changes: {
+ from: index * 4,
+ to: index * 4,
+ insert: chunk,
+ },
+}));
+
+export default function Editor() {
+ const [editorView, setEditorView] = useState();
+
+ useEffect(() => {
+ if (!editorView) return;
+
+ let cancelled = false;
+
+ const executeMove = async (move: Move) => {
+ return new Promise((resolve) => {
+ const timeout = setTimeout(() => {
+ if (!editorView || cancelled) return resolve();
+
+ editorView.dispatch({
+ selection: { anchor: move.cursor },
+ scrollIntoView: true,
+ changes: move.changes,
+ });
+
+ resolve();
+ }, move.latency);
+
+ return () => clearTimeout(timeout);
+ });
+ };
+
+ (async () => {
+ for (const move of moves) {
+ if (cancelled) break;
+ await executeMove(move);
+ }
+ })();
+
+ return () => {
+ cancelled = true; // stop future moves
+ };
+ }, [editorView]);
+
+ return (
+ {
@@ -72,10 +143,7 @@ root.render(); `}
theme={devRun}
onCreateEditor={(view) => {
view.focus();
- view.dispatch({
- selection: { anchor: 1 },
- scrollIntoView: true,
- });
+ setEditorView(view);
}}
/>
);
diff --git a/src/app/run/components/theme.ts b/src/app/run/components/theme.ts
new file mode 100644
index 0000000..745b9d4
--- /dev/null
+++ b/src/app/run/components/theme.ts
@@ -0,0 +1,119 @@
+import { EditorView } from "@codemirror/view";
+import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
+import { tags as t } from "@lezer/highlight";
+
+/**
+ * devRun — direct CodeMirror 6 theme
+ * Based on thememirror (dracula), app-integrated
+ */
+export const devRun = [
+ EditorView.theme(
+ {
+ "&": {
+ backgroundColor: "var(--card)",
+ color: "#e6e6eb",
+ },
+
+ ".cm-content": {
+ caretColor: "var(--primary)",
+ },
+
+ ".cm-cursor, .cm-dropCursor": {
+ borderLeftColor: "var(--primary)",
+ },
+
+ "&.cm-focused .cm-selectionBackground, .cm-content ::selection": {
+ backgroundColor: "rgba(120, 120, 150, 0.25)",
+ },
+
+ ".cm-activeLine": {
+ backgroundColor: "rgba(255, 255, 255, 0.03)",
+ },
+
+ ".cm-gutters": {
+ backgroundColor: "var(--card)",
+ color: "rgb(120, 122, 128)",
+ border: "none",
+ },
+
+ ".cm-activeLineGutter": {
+ backgroundColor: "rgba(255, 255, 255, 0.03)",
+ },
+
+ // - Scrollbar -
+ ".cm-scroller": {
+ scrollbarWidth: "thin",
+ scrollbarColor: "rgba(201, 133, 255, 0.3) transparent",
+ },
+ },
+ { dark: true },
+ ),
+
+ syntaxHighlighting(
+ HighlightStyle.define([
+ // Comments — low priority, low contrast
+ {
+ tag: t.comment,
+ color: "#5f6a8a",
+ fontStyle: "italic",
+ },
+
+ // Strings & literals — data, not logic
+ {
+ tag: [t.string, t.special(t.brace)],
+ color: "#f4e58c",
+ },
+
+ // Numbers / booleans — benchmarks & truth
+ {
+ tag: [t.number, t.bool, t.null],
+ color: "#c792ea",
+ fontWeight: "500",
+ },
+
+ // Keywords & operators — decisions & flow
+ {
+ tag: [t.keyword, t.operator],
+ color: "#ff5fa2",
+ fontWeight: "600",
+ },
+
+ // Types & definitions — structure
+ {
+ tag: [t.definitionKeyword, t.typeName],
+ color: "#6be6ff",
+ },
+
+ // Type definitions — neutral, readable
+ {
+ tag: t.definition(t.typeName),
+ color: "#e6e6eb",
+ },
+
+ // Functions, classes, props — execution paths
+ {
+ tag: [
+ t.className,
+ t.definition(t.propertyName),
+ t.function(t.variableName),
+ t.attributeName,
+ ],
+ color: "#4dff9f",
+ fontWeight: "500",
+ },
+
+ // Variables — slightly muted, context-aware
+ {
+ tag: t.variableName,
+ color: "#cfd3dc",
+ },
+
+ // Invalid / errors — instant attention
+ {
+ tag: t.invalid,
+ color: "#ff5555",
+ fontWeight: "700",
+ },
+ ]),
+ ),
+];
diff --git a/src/app/run/components/theme/create-theme.ts b/src/app/run/components/theme/create-theme.ts
deleted file mode 100644
index 661a704..0000000
--- a/src/app/run/components/theme/create-theme.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { EditorView } from "@codemirror/view";
-import { Extension } from "@codemirror/state";
-import {
- HighlightStyle,
- TagStyle,
- syntaxHighlighting,
-} from "@codemirror/language";
-
-interface Options {
- /**
- * Theme variant. Determines which styles CodeMirror will apply by default.
- */
- variant: Variant;
-
- /**
- * Settings to customize the look of the editor, like background, gutter, selection and others.
- */
- settings: Settings;
-
- /**
- * Syntax highlighting styles.
- */
- styles: TagStyle[];
-}
-
-type Variant = "light" | "dark";
-
-interface Settings {
- /**
- * Editor background.
- */
- background: string;
-
- /**
- * Default text color.
- */
- foreground: string;
-
- /**
- * Caret color.
- */
- caret: string;
-
- /**
- * Selection background.
- */
- selection: string;
-
- /**
- * Background of highlighted lines.
- */
- lineHighlight: string;
-
- /**
- * Gutter background.
- */
- gutterBackground: string;
-
- /**
- * Text color inside gutter.
- */
- gutterForeground: string;
-}
-
-const createTheme = ({ variant, settings, styles }: Options): Extension => {
- const theme = EditorView.theme(
- {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- "&": {
- backgroundColor: settings.background,
- color: settings.foreground,
- },
- ".cm-content": {
- caretColor: settings.caret,
- },
- ".cm-cursor, .cm-dropCursor": {
- borderLeftColor: settings.caret,
- },
- "&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection":
- {
- backgroundColor: settings.selection,
- },
- ".cm-activeLine": {
- backgroundColor: settings.lineHighlight,
- },
- ".cm-gutters": {
- backgroundColor: settings.gutterBackground,
- color: settings.gutterForeground,
- },
- ".cm-activeLineGutter": {
- backgroundColor: settings.lineHighlight,
- },
- },
- {
- dark: variant === "dark",
- },
- );
-
- const highlightStyle = HighlightStyle.define(styles);
- const extension = [theme, syntaxHighlighting(highlightStyle)];
-
- return extension;
-};
-
-export default createTheme;
diff --git a/src/app/run/components/theme/theme.ts b/src/app/run/components/theme/theme.ts
deleted file mode 100644
index cf47f3c..0000000
--- a/src/app/run/components/theme/theme.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { tags as t } from "@lezer/highlight";
-import createTheme from "./create-theme";
-
-// Original theme: thememirror (dracula)
-export const devRun = createTheme({
- variant: "dark",
- settings: {
- background: "var(--card)", // stays app-integrated
- foreground: "#e6e6eb",
- caret: "var(--primary)",
- selection: "rgba(120, 120, 150, 0.25)",
- gutterBackground: "var(--card)",
- gutterForeground: "rgb(120, 122, 128)",
- lineHighlight: "rgba(255, 255, 255, 0.03)",
- },
- styles: [
- // Comments — low priority, low contrast
- {
- tag: t.comment,
- color: "#5f6a8a",
- fontStyle: "italic",
- },
-
- // Strings & literals — data, not logic
- {
- tag: [t.string, t.special(t.brace)],
- color: "#f4e58c",
- },
-
- // Numbers / booleans — benchmarks & truth
- {
- tag: [t.number, t.bool, t.null],
- color: "#c792ea",
- fontWeight: "500",
- },
-
- // Keywords & operators — decisions & flow
- {
- tag: [t.keyword, t.operator],
- color: "#ff5fa2",
- fontWeight: "600",
- },
-
- // Types & definitions — structure
- {
- tag: [t.definitionKeyword, t.typeName],
- color: "#6be6ff",
- },
-
- // Type definitions — neutral, readable
- {
- tag: t.definition(t.typeName),
- color: "#e6e6eb",
- },
-
- // Functions, classes, props — execution paths
- {
- tag: [
- t.className,
- t.definition(t.propertyName),
- t.function(t.variableName),
- t.attributeName,
- ],
- color: "#4dff9f",
- fontWeight: "500",
- },
-
- // Variables — slightly muted, context-aware
- {
- tag: t.variableName,
- color: "#cfd3dc",
- },
-
- // Invalid / errors — instant attention
- {
- tag: t.invalid,
- color: "#ff5555",
- fontWeight: "700",
- },
- ],
-});
diff --git a/src/app/run/page.tsx b/src/app/run/page.tsx
index 2a0ea25..850d17a 100644
--- a/src/app/run/page.tsx
+++ b/src/app/run/page.tsx
@@ -1,13 +1,10 @@
"use client";
-import CodeMirror, { EditorState } from "@uiw/react-codemirror";
import { Card, CardContent } from "@/components/ui/card";
import RunTimer, { RunInfo } from "./components/RunTimer";
import RunChat, { ChatMessage } from "./components/RunChat";
import { FileTree } from "./components/FileTree";
import { File } from "lucide-react";
-import { devRun } from "./components/theme/theme";
-import { javascript } from "@codemirror/lang-javascript";
import Editor from "./components/Editor";
export default function Run() {
@@ -53,7 +50,7 @@ export default function Run() {
-