Improved editor
This commit is contained in:
+2
-2
@@ -314,5 +314,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
height: 100%;;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import CodeMirror, { EditorState } from "@uiw/react-codemirror";
|
import CodeMirror, { EditorState, EditorView } from "@uiw/react-codemirror";
|
||||||
import { devRun } from "./theme/theme";
|
import { devRun } from "./theme";
|
||||||
import { javascript } from "@codemirror/lang-javascript";
|
import { javascript } from "@codemirror/lang-javascript";
|
||||||
import { rust } from "@codemirror/lang-rust";
|
import { rust } from "@codemirror/lang-rust";
|
||||||
import { go } from "@codemirror/lang-go";
|
import { go } from "@codemirror/lang-go";
|
||||||
import { json } from "@codemirror/lang-json";
|
import { json } from "@codemirror/lang-json";
|
||||||
import { python } from "@codemirror/lang-python";
|
import { python } from "@codemirror/lang-python";
|
||||||
import { java } from "@codemirror/lang-java";
|
import { java } from "@codemirror/lang-java";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
type Language =
|
type Language =
|
||||||
| "javascript"
|
| "javascript"
|
||||||
@@ -37,12 +38,17 @@ function getLanguageExtension(lang: Language) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Editor() {
|
type Move = {
|
||||||
return (
|
latency: number;
|
||||||
<CodeMirror
|
cursor: number;
|
||||||
readOnly
|
changes: {
|
||||||
className="h-full w-full"
|
from: number;
|
||||||
value={`// Import the necessary React library features
|
to: number;
|
||||||
|
insert: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const code = `// Import the necessary React library features
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
// Define a functional component named "App"
|
// Define a functional component named "App"
|
||||||
@@ -59,7 +65,72 @@ const domNode = document.getElementById('root');
|
|||||||
|
|
||||||
// Create a root and render your component into the DOM
|
// Create a root and render your component into the DOM
|
||||||
const root = createRoot(domNode);
|
const root = createRoot(domNode);
|
||||||
root.render(<App />); `}
|
root.render(<App />);`.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<EditorView>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editorView) return;
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const executeMove = async (move: Move) => {
|
||||||
|
return new Promise<void>((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 (
|
||||||
|
<CodeMirror
|
||||||
|
readOnly
|
||||||
|
className="h-full w-full"
|
||||||
|
value={``}
|
||||||
extensions={[
|
extensions={[
|
||||||
getLanguageExtension("javascript"),
|
getLanguageExtension("javascript"),
|
||||||
EditorState.transactionFilter.of((tr) => {
|
EditorState.transactionFilter.of((tr) => {
|
||||||
@@ -72,10 +143,7 @@ root.render(<App />); `}
|
|||||||
theme={devRun}
|
theme={devRun}
|
||||||
onCreateEditor={(view) => {
|
onCreateEditor={(view) => {
|
||||||
view.focus();
|
view.focus();
|
||||||
view.dispatch({
|
setEditorView(view);
|
||||||
selection: { anchor: 1 },
|
|
||||||
scrollIntoView: true,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
];
|
||||||
@@ -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;
|
|
||||||
@@ -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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import CodeMirror, { EditorState } from "@uiw/react-codemirror";
|
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import RunTimer, { RunInfo } from "./components/RunTimer";
|
import RunTimer, { RunInfo } from "./components/RunTimer";
|
||||||
import RunChat, { ChatMessage } from "./components/RunChat";
|
import RunChat, { ChatMessage } from "./components/RunChat";
|
||||||
import { FileTree } from "./components/FileTree";
|
import { FileTree } from "./components/FileTree";
|
||||||
import { File } from "lucide-react";
|
import { File } from "lucide-react";
|
||||||
import { devRun } from "./components/theme/theme";
|
|
||||||
import { javascript } from "@codemirror/lang-javascript";
|
|
||||||
import Editor from "./components/Editor";
|
import Editor from "./components/Editor";
|
||||||
|
|
||||||
export default function Run() {
|
export default function Run() {
|
||||||
@@ -53,7 +50,7 @@ export default function Run() {
|
|||||||
<header className="text-sm font-medium text-muted-foreground flex items-center gap-1">
|
<header className="text-sm font-medium text-muted-foreground flex items-center gap-1">
|
||||||
<File size={16} /> myfile.js
|
<File size={16} /> myfile.js
|
||||||
</header>
|
</header>
|
||||||
<div className="flex-1 pt-3 pb-5">
|
<div className="flex-1 pt-3 pb-5 h-full">
|
||||||
<Editor />
|
<Editor />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user