mirror of
https://litchi.icu/ngc2207/judge.git
synced 2025-05-19 05:26:33 +00:00
feat: integrate language server support with WebSocket connection for C/C++ languages
This commit is contained in:
parent
104e66d4d0
commit
937330ac3d
Binary file not shown.
@ -18,10 +18,15 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"devicons-react": "^1.4.0",
|
"devicons-react": "^1.4.0",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
|
"monaco-editor": "0.36.1",
|
||||||
|
"monaco-languageclient": "5.0.1",
|
||||||
|
"normalize-url": "~8.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vscode-languageclient": "~8.1.0",
|
||||||
|
"vscode-ws-jsonrpc": "3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
@ -3,19 +3,38 @@ import {
|
|||||||
DEFAULT_EDITOR_THEME,
|
DEFAULT_EDITOR_THEME,
|
||||||
DEFAULT_EDITOR_LANGUAGE,
|
DEFAULT_EDITOR_LANGUAGE,
|
||||||
} from "@/config";
|
} from "@/config";
|
||||||
import { useState } from "react";
|
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { highlighter } from "@/lib/shiki";
|
import { highlighter } from "@/lib/shiki";
|
||||||
import { Bot, CodeXml } from "lucide-react";
|
import { Bot, CodeXml } from "lucide-react";
|
||||||
import { shikiToMonaco } from "@shikijs/monaco";
|
import { shikiToMonaco } from "@shikijs/monaco";
|
||||||
import { DiffEditor, Editor, Monaco } from "@monaco-editor/react";
|
import { connectToLanguageServer } from "@/lib/lsp";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
import { DiffEditor, Editor, Monaco, loader } from "@monaco-editor/react";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
|
loader.config({ monaco });
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [language, setLanguage] = useState(DEFAULT_EDITOR_LANGUAGE);
|
const [language, setLanguage] = useState(DEFAULT_EDITOR_LANGUAGE);
|
||||||
const [theme, setTheme] = useState(DEFAULT_EDITOR_THEME);
|
const [theme, setTheme] = useState(DEFAULT_EDITOR_THEME);
|
||||||
const file = DEFAULT_FILES[language];
|
const file = DEFAULT_FILES[language];
|
||||||
|
const webSocketRef = useRef<WebSocket | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (webSocketRef.current) {
|
||||||
|
webSocketRef.current.close();
|
||||||
|
}
|
||||||
|
connectToLanguageServer(language).then((webSocket) => {
|
||||||
|
webSocketRef.current = webSocket;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (webSocketRef.current) {
|
||||||
|
webSocketRef.current.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-[#282c34] dark overflow-hidden">
|
<div className="h-screen bg-[#282c34] dark overflow-hidden">
|
||||||
@ -71,10 +90,7 @@ function App() {
|
|||||||
beforeMount={async (monaco: Monaco) => {
|
beforeMount={async (monaco: Monaco) => {
|
||||||
shikiToMonaco(await highlighter, monaco);
|
shikiToMonaco(await highlighter, monaco);
|
||||||
}}
|
}}
|
||||||
onMount={(
|
onMount={(editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||||
editor: monaco.editor.IStandaloneCodeEditor,
|
|
||||||
monaco: Monaco
|
|
||||||
) => {
|
|
||||||
const value = editor.getModel()?.getValue();
|
const value = editor.getModel()?.getValue();
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
@ -82,6 +98,9 @@ function App() {
|
|||||||
"*"
|
"*"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
connectToLanguageServer(language).then((webSocket) => {
|
||||||
|
webSocketRef.current = webSocket;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
|
82
code-editor/src/lib/lsp.ts
Normal file
82
code-editor/src/lib/lsp.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {
|
||||||
|
toSocket,
|
||||||
|
WebSocketMessageReader,
|
||||||
|
WebSocketMessageWriter,
|
||||||
|
} from "vscode-ws-jsonrpc";
|
||||||
|
import {
|
||||||
|
CloseAction,
|
||||||
|
ErrorAction,
|
||||||
|
MessageTransports,
|
||||||
|
} from "vscode-languageclient";
|
||||||
|
import normalizeUrl from "normalize-url";
|
||||||
|
import { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
|
|
||||||
|
export const SUPPORTED_LSP_LANGUAGES: {
|
||||||
|
[key: string]: { hostname: string; port: number | null; path: string };
|
||||||
|
} = {
|
||||||
|
c: {
|
||||||
|
hostname: "c.litchi.icu",
|
||||||
|
port: null,
|
||||||
|
path: "/clangd",
|
||||||
|
},
|
||||||
|
cpp: {
|
||||||
|
hostname: "cpp.litchi.icu",
|
||||||
|
port: null,
|
||||||
|
path: "/clangd",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createUrl(
|
||||||
|
hostname: string,
|
||||||
|
port: number | null,
|
||||||
|
path: string
|
||||||
|
): string {
|
||||||
|
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
||||||
|
return port !== null
|
||||||
|
? normalizeUrl(`${protocol}://${hostname}:${port}${path}`)
|
||||||
|
: normalizeUrl(`${protocol}://${hostname}${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLanguageClient(
|
||||||
|
transports: MessageTransports
|
||||||
|
): MonacoLanguageClient {
|
||||||
|
return new MonacoLanguageClient({
|
||||||
|
name: "Judge4c Language Client",
|
||||||
|
clientOptions: {
|
||||||
|
documentSelector: ["c", "cpp"],
|
||||||
|
errorHandler: {
|
||||||
|
error: () => ({ action: ErrorAction.Continue }),
|
||||||
|
closed: () => ({ action: CloseAction.DoNotRestart }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connectionProvider: {
|
||||||
|
get: () => {
|
||||||
|
return Promise.resolve(transports);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWebSocket(url: string) {
|
||||||
|
const webSocket = new WebSocket(url);
|
||||||
|
webSocket.onopen = () => {
|
||||||
|
const socket = toSocket(webSocket);
|
||||||
|
const reader = new WebSocketMessageReader(socket);
|
||||||
|
const writer = new WebSocketMessageWriter(socket);
|
||||||
|
const languageClient = createLanguageClient({
|
||||||
|
reader,
|
||||||
|
writer,
|
||||||
|
});
|
||||||
|
languageClient.start();
|
||||||
|
reader.onClose(() => languageClient.stop());
|
||||||
|
};
|
||||||
|
return webSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function connectToLanguageServer(
|
||||||
|
language: string
|
||||||
|
): Promise<WebSocket> {
|
||||||
|
const { hostname, port, path } = SUPPORTED_LSP_LANGUAGES[language];
|
||||||
|
const url = createUrl(hostname, port, path);
|
||||||
|
return createWebSocket(url);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user