From af23dd328926fb0db11fc4ef26b5b8cf07f89ddb Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Tue, 4 Mar 2025 20:58:06 +0800 Subject: [PATCH] feat(editor): add language server connection via WebSocket --- src/lib/language-server.ts | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/lib/language-server.ts diff --git a/src/lib/language-server.ts b/src/lib/language-server.ts new file mode 100644 index 0000000..e874134 --- /dev/null +++ b/src/lib/language-server.ts @@ -0,0 +1,70 @@ +import normalizeUrl from "normalize-url"; +import type { MessageTransports } from "vscode-languageclient"; +import { EditorLanguageMetadata } from "@/types/editor-language"; +import type { MonacoLanguageClient } from "monaco-languageclient"; +import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from "vscode-ws-jsonrpc"; + +// Create the WebSocket URL based on the protocol and port +function createUrl(protocol: string, hostname: string, port: number | null, path: string | null): string { + const formattedPort = port !== null ? `:${port}` : ""; + const formattedPath = path ? (path.startsWith("/") ? path : `/${path}`) : ""; + return normalizeUrl(`${protocol}://${hostname}${formattedPort}${formattedPath}`); +} + +// Create the language client with the given transports +async function createLanguageClient(transports: MessageTransports, lang: EditorLanguageMetadata): Promise { + const { MonacoLanguageClient } = await import("monaco-languageclient"); + const { CloseAction, ErrorAction } = await import("vscode-languageclient"); + + return new MonacoLanguageClient({ + name: `${lang.label} Language Client`, + clientOptions: { + // use a language id as a document selector + documentSelector: [lang.id], + // disable the default error handler + errorHandler: { + error: () => ({ action: ErrorAction.Continue }), + closed: () => ({ action: CloseAction.DoNotRestart }), + }, + }, + // create a language client connection from the JSON RPC connection on demand + connectionProvider: { + get: () => { + return Promise.resolve(transports); + } + } + }); +} + +// Connect to the WebSocket and create the language client +export function connectToLanguageServer(protocol: string, hostname: string, port: number | null, path: string | null, lang: EditorLanguageMetadata): Promise { + const url = createUrl(protocol, hostname, port, path); + const webSocket = new WebSocket(url); + + return new Promise((resolve, reject) => { + // Handle the WebSocket opening event + webSocket.onopen = async () => { + const socket = toSocket(webSocket); + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + + try { + const languageClient = await createLanguageClient({ reader, writer }, lang); + // Start the language client + languageClient.start(); + + // Stop the language client when the reader closes + reader.onClose(() => languageClient.stop()); + + resolve(languageClient); + } catch (error) { + reject(error); + } + }; + + // Handle WebSocket errors + webSocket.onerror = (error) => { + reject(error); + }; + }); +}