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() {
myfile.js
-
+