feat(playground): integrate language server support with WebSocket connection handling

This commit is contained in:
ngc2207 2025-01-05 03:47:37 +08:00
parent 0bc5758d44
commit 168facb85b
2 changed files with 117 additions and 3 deletions

View File

@ -1,8 +1,10 @@
"use client"; "use client";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { connectToLanguageServer } from "@/lib/lsp";
import { useEffect, useRef, useState } from "react"; 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 { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { COriginal, CplusplusOriginal, JavaOriginal } from "devicons-react"; import { COriginal, CplusplusOriginal, JavaOriginal } from "devicons-react";
@ -39,10 +41,14 @@ int main() {
}, },
}; };
loader.config({ monaco });
export default function PlaygroundPage() { export default function PlaygroundPage() {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null); const [language, setLanguage] = useState<string>("c");
const [language, setLanguage] = useState<keyof typeof files>("c");
const file = files[language]; 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(() => { useEffect(() => {
if (editorRef.current) { if (editorRef.current) {
@ -55,6 +61,15 @@ export default function PlaygroundPage() {
column: lastLineLength + 1, column: lastLineLength + 1,
}); });
editorRef.current.focus(); editorRef.current.focus();
if (webSocketRef.current) {
webSocketRef.current.close();
webSocketRef.current = null;
}
connectToLanguageServer(language, webSocketRef).then(
(languageClient) => {
languageClientRef.current = languageClient;
}
);
} }
} }
}, [language]); }, [language]);
@ -127,6 +142,11 @@ export default function PlaygroundPage() {
column: lastLineLength + 1, column: lastLineLength + 1,
}); });
editorRef.current.focus(); editorRef.current.focus();
connectToLanguageServer(language, webSocketRef).then(
(languageClient) => {
languageClientRef.current = languageClient;
}
);
} }
} }
}} }}

94
src/lib/lsp.ts Normal file
View File

@ -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<WebSocket | null>
): Promise<MonacoLanguageClient> {
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}`));
};
});
}