From 168facb85b5f36e33ed778abed654c892b85af95 Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Sun, 5 Jan 2025 03:47:37 +0800 Subject: [PATCH] feat(playground): integrate language server support with WebSocket connection handling --- src/app/playground/page.tsx | 26 ++++++++-- src/lib/lsp.ts | 94 +++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/lib/lsp.ts diff --git a/src/app/playground/page.tsx b/src/app/playground/page.tsx index ff002fe..05df209 100644 --- a/src/app/playground/page.tsx +++ b/src/app/playground/page.tsx @@ -1,8 +1,10 @@ "use client"; import * as monaco from "monaco-editor"; +import { connectToLanguageServer } from "@/lib/lsp"; import { useEffect, useRef, useState } from "react"; -import { Editor } from "@monaco-editor/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"; @@ -39,10 +41,14 @@ int main() { }, }; +loader.config({ monaco }); + export default function PlaygroundPage() { - const editorRef = useRef(null); - const [language, setLanguage] = useState("c"); + const [language, setLanguage] = useState("c"); const file = files[language]; + const editorRef = useRef(null); + const webSocketRef = useRef(null); + const languageClientRef = useRef(null); useEffect(() => { if (editorRef.current) { @@ -55,6 +61,15 @@ export default function PlaygroundPage() { column: lastLineLength + 1, }); editorRef.current.focus(); + if (webSocketRef.current) { + webSocketRef.current.close(); + webSocketRef.current = null; + } + connectToLanguageServer(language, webSocketRef).then( + (languageClient) => { + languageClientRef.current = languageClient; + } + ); } } }, [language]); @@ -127,6 +142,11 @@ export default function PlaygroundPage() { column: lastLineLength + 1, }); editorRef.current.focus(); + connectToLanguageServer(language, webSocketRef).then( + (languageClient) => { + languageClientRef.current = languageClient; + } + ); } } }} diff --git a/src/lib/lsp.ts b/src/lib/lsp.ts new file mode 100644 index 0000000..f2f2a90 --- /dev/null +++ b/src/lib/lsp.ts @@ -0,0 +1,94 @@ +import { + toSocket, + WebSocketMessageReader, + WebSocketMessageWriter, +} from "vscode-ws-jsonrpc"; +import { + CloseAction, + ErrorAction, + MessageTransports, +} from "vscode-languageclient"; +import { RefObject } from "react"; +import normalizeUrl from "normalize-url"; +import { MonacoLanguageClient } from "monaco-languageclient"; + +const LSP_SUPPORTED_LANGUAGES: { + [key: string]: { hostname: string; port: number; path: string }; +} = { + c: { + hostname: "localhost", + port: 30001, + path: "/clangd", + }, + cpp: { + hostname: "localhost", + port: 30002, + path: "/clangd", + }, +}; + +function createUrl(hostname: string, port: number, path: string): string { + const protocol = location.protocol === "https:" ? "wss" : "ws"; + return normalizeUrl(`${protocol}://${hostname}:${port}${path}`); +} + +const languageClientConfig = { + name: "Language Client", + clientOptions: { + documentSelector: ["c", "cpp"], + errorHandler: { + error: () => ({ action: ErrorAction.Continue }), + closed: () => ({ action: CloseAction.DoNotRestart }), + }, + }, +}; + +function createLanguageClient( + transports: MessageTransports +): MonacoLanguageClient { + return new MonacoLanguageClient({ + ...languageClientConfig, + connectionProvider: { + get: () => Promise.resolve(transports), + }, + }); +} + +export async function connectToLanguageServer( + language: string, + webSocketRef: RefObject +): Promise { + return new Promise((resolve, reject) => { + let url: string; + if (language in LSP_SUPPORTED_LANGUAGES) { + const { hostname, port, path } = LSP_SUPPORTED_LANGUAGES[language]; + url = createUrl(hostname, port, path); + } else { + reject(new Error("Unsupported language")); + return; + } + + const webSocket = new WebSocket(url); + webSocketRef.current = webSocket; + + webSocket.onopen = () => { + console.log("WebSocket connection opened successfully."); + const socket = toSocket(webSocket); + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + const languageClient = createLanguageClient({ reader, writer }); + languageClient.start(); + resolve(languageClient); + }; + + webSocket.onclose = () => { + console.log("WebSocket connection closed."); + reject(new Error("WebSocket connection closed unexpectedly.")); + }; + + webSocket.onerror = (error) => { + console.error("WebSocket connection error:", error); + reject(new Error(`WebSocket connection error: ${error}`)); + }; + }); +}