judge/src/app/playground/page.tsx

232 lines
7.1 KiB
TypeScript

"use client";
import { Send } from "lucide-react";
import * as monaco from "monaco-editor";
import { Button } from "@/components/ui/button";
import { runCode } from "@/actions/docker/compile";
import { useEffect, useRef, useState } from "react";
import { Editor, loader } from "@monaco-editor/react";
import { MonacoLanguageClient } from "monaco-languageclient";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { COriginal, CplusplusOriginal, JavaOriginal } from "devicons-react";
import { connectToLanguageServer, LSP_SUPPORTED_LANGUAGES } from "@/lib/lsp";
import { codeToHtml } from "shiki";
const files: {
[key: string]: { name: string; language: string; value: string };
} = {
c: {
name: "main.c",
language: "c",
value: `#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}`,
},
cpp: {
name: "main.cpp",
language: "cpp",
value: `#include <iostream>
int main() {
std::cout << "Hello, World!";
return 0;
}`,
},
java: {
name: "Main.java",
language: "java",
value: `public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}`,
},
};
loader.config({ monaco });
export default function PlaygroundPage() {
const [language, setLanguage] = useState<string>("c");
const [highlightedMessage, setHighlightedMessage] = useState<string>("");
const file = files[language];
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const webSocketRef = useRef<WebSocket | null>(null);
const languageClientRef = useRef<MonacoLanguageClient | null>(null);
useEffect(() => {
const handleLanguageChange = async () => {
if (editorRef.current) {
const model = editorRef.current.getModel();
if (model) {
const lineCount = model.getLineCount();
const lastLineLength = model.getLineLength(lineCount);
editorRef.current.setPosition({
lineNumber: lineCount,
column: lastLineLength + 1,
});
editorRef.current.focus();
if (
languageClientRef.current &&
language in LSP_SUPPORTED_LANGUAGES
) {
await languageClientRef.current.stop();
languageClientRef.current = null;
}
if (
webSocketRef.current &&
webSocketRef.current.readyState !== WebSocket.CLOSED &&
webSocketRef.current.readyState !== WebSocket.CLOSING &&
language in LSP_SUPPORTED_LANGUAGES
) {
webSocketRef.current.close();
webSocketRef.current = null;
}
if (language in LSP_SUPPORTED_LANGUAGES) {
try {
const languageClient = await connectToLanguageServer(
language,
webSocketRef
);
languageClientRef.current = languageClient;
} catch (error) {
console.error("Failed to connect to language server:", error);
}
}
}
}
};
handleLanguageChange();
}, [language]);
const handleSubmit = async () => {
const code = editorRef.current?.getValue();
if (!code) {
console.error("No code to compile");
return;
}
const result = await runCode(code, language);
// 根据编译结果显示不同的信息
const statusMessage = result.success
? "Compilation successful"
: "Compilation failed";
const fullMessage = `${statusMessage}\n\n${result.output}`;
const highlighted = await codeToHtml(fullMessage, {
lang: "log", // 或者根据你的需求选择合适的语言
theme: "one-dark-pro",
});
setHighlightedMessage(highlighted);
};
return (
<div className="h-full flex flex-col">
<div className="flex items-center justify-between">
<Tabs defaultValue={language as string}>
<ScrollArea>
<TabsList className="m-3">
<TabsTrigger
value="c"
onClick={() => setLanguage("c")}
disabled={language === "c"}
>
<COriginal
className="-ms-0.5 me-1.5"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
C
</TabsTrigger>
<TabsTrigger
value="cpp"
className="group"
onClick={() => setLanguage("cpp")}
disabled={language === "cpp"}
>
<CplusplusOriginal
className="-ms-0.5 me-1.5"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
C++
</TabsTrigger>
<TabsTrigger
value="java"
className="group"
onClick={() => setLanguage("java")}
disabled={language === "java"}
>
<JavaOriginal
className="-ms-0.5 me-1.5"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
Java
</TabsTrigger>
</TabsList>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</Tabs>
<div className="m-3">
<Button variant="outline" onClick={handleSubmit}>
Submit
<Send
className="-me-1 ms-2 opacity-60"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
</Button>
</div>
</div>
<div className="flex flex-1 mt-0 m-3 gap-x-1">
<div className="w-2/3">
<Editor
theme="vs-dark"
path={file.name}
defaultLanguage={file.language}
defaultValue={file.value}
options={{ automaticLayout: true }}
onMount={(editor) => {
editorRef.current = editor;
if (editorRef.current) {
const model = editorRef.current.getModel();
if (model) {
const lineCount = model.getLineCount();
const lastLineLength = model.getLineLength(lineCount);
editorRef.current.setPosition({
lineNumber: lineCount,
column: lastLineLength + 1,
});
editorRef.current.focus();
connectToLanguageServer(language, webSocketRef)
.then((languageClient) => {
languageClientRef.current = languageClient;
})
.catch((error) => {
console.error(
"Failed to connect to language server:",
error
);
});
}
}
}}
/>
</div>
<div className="h-full w-1/3">
<div dangerouslySetInnerHTML={{ __html: highlightedMessage }} />
</div>
</div>
</div>
);
}