From 5e412f1cd9dd6cc1321e6d29585ccaa9bdba0c2a Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 6 Feb 2026 18:20:41 +0100 Subject: [PATCH] devrun Theme --- package-lock.json | 36 ++++++- package.json | 4 +- src/app/run/components/theme/create-theme.ts | 105 +++++++++++++++++++ src/app/run/components/theme/theme.ts | 81 ++++++++++++++ src/app/run/page.tsx | 27 ++++- 5 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 src/app/run/components/theme/create-theme.ts create mode 100644 src/app/run/components/theme/theme.ts diff --git a/package-lock.json b/package-lock.json index ea2cd87..07b64a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "dev-run", "version": "0.1.0", "dependencies": { + "@codemirror/lang-javascript": "^6.2.4", + "@lezer/highlight": "^1.2.3", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -56,7 +58,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.30", + "@types/node": "^20.19.32", "@types/react": "^19", "@types/react-dom": "^19", "tailwindcss": "^4", @@ -110,6 +112,21 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.12.1", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", @@ -764,6 +781,17 @@ "@lezer/common": "^1.3.0" } }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/lr": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", @@ -3182,9 +3210,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", - "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "version": "20.19.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", + "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index e721d45..20cbe7e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "start": "next start" }, "dependencies": { + "@codemirror/lang-javascript": "^6.2.4", + "@lezer/highlight": "^1.2.3", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -56,7 +58,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.30", + "@types/node": "^20.19.32", "@types/react": "^19", "@types/react-dom": "^19", "tailwindcss": "^4", diff --git a/src/app/run/components/theme/create-theme.ts b/src/app/run/components/theme/create-theme.ts new file mode 100644 index 0000000..661a704 --- /dev/null +++ b/src/app/run/components/theme/create-theme.ts @@ -0,0 +1,105 @@ +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 new file mode 100644 index 0000000..cf47f3c --- /dev/null +++ b/src/app/run/components/theme/theme.ts @@ -0,0 +1,81 @@ +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 2e86e67..2e97a8e 100644 --- a/src/app/run/page.tsx +++ b/src/app/run/page.tsx @@ -1,14 +1,13 @@ "use client"; import CodeMirror, { EditorState } from "@uiw/react-codemirror"; -import { vscodeDark } from "@uiw/codemirror-theme-vscode"; - import { Card, CardContent } from "@/components/ui/card"; import RunTimer, { RunInfo } from "./components/RunTimer"; import RunChat, { ChatMessage } from "./components/RunChat"; -import { useState } from "react"; import { FileTree } from "./components/FileTree"; import { File } from "lucide-react"; +import { devRun } from "./components/theme/theme"; +import { javascript } from "@codemirror/lang-javascript"; export default function Run() { const runInfo: RunInfo = { @@ -58,8 +57,26 @@ export default function Run() { Hello, world! + ); +} + +// Target a DOM element in your HTML (e.g.,
) +const domNode = document.getElementById('root'); + +// Create a root and render your component into the DOM +const root = createRoot(domNode); +root.render(); `} extensions={[ + javascript(), EditorState.transactionFilter.of((tr) => { if (tr.selection && tr.isUserEvent("select")) { return []; @@ -67,7 +84,7 @@ export default function Run() { return tr; }), ]} - theme={vscodeDark} + theme={devRun} onCreateEditor={(view) => { view.focus(); view.dispatch({