blob: b6530d77dd60abeb4f4859feb28af792fb7a1123 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ServerOptions, WebSocketServer } from "ws";
import { IncomingMessage, Server } from "node:http";
import { fileURLToPath, URL } from "node:url";
import { Socket } from "node:net";
import { dirname } from "node:path";
import { IWebSocket, WebSocketMessageReader, WebSocketMessageWriter } from "vscode-ws-jsonrpc";
import { createConnection, createServerProcess, forward } from "vscode-ws-jsonrpc/server";
import { InitializeParams, InitializeRequest, Message } from "vscode-languageserver";
import * as cp from "child_process";
export enum LanguageName {
/** https://nodejs.org/api/cli.html */
node = "node",
/** https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html */
java = "java"
}
export interface LanguageServerRunConfig {
serverName: string;
pathName: string;
serverPort: number;
runCommand: LanguageName | string;
runCommandArgs: string[];
wsServerOptions: ServerOptions,
spawnOptions?: cp.SpawnOptions;
}
/**
* start the language server inside the current process
*/
export const launchLanguageServer = (runconfig: LanguageServerRunConfig, socket: IWebSocket) => {
const { serverName, runCommand, runCommandArgs, spawnOptions } = runconfig;
// start the language server as an external process
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
const socketConnection = createConnection(reader, writer, () => socket.dispose());
const serverConnection = createServerProcess(serverName, runCommand, runCommandArgs, spawnOptions);
if (serverConnection) {
forward(socketConnection, serverConnection, message => {
if (Message.isRequest(message)) {
console.log(`${serverName} Server received:`);
console.log(message);
if (message.method === InitializeRequest.type.method) {
const initializeParams = message.params as InitializeParams;
initializeParams.processId = process.pid;
}
}
if (Message.isResponse(message)) {
console.log(`${serverName} Server sent:`);
console.log(message);
}
return message;
});
}
};
export const upgradeWsServer = (runconfig: LanguageServerRunConfig,
config: {
server: Server,
wss: WebSocketServer
}) => {
config.server.on("upgrade", (request: IncomingMessage, socket: Socket, head: Buffer) => {
const baseURL = `http://${request.headers.host}/`;
const pathName = request.url !== undefined ? new URL(request.url, baseURL).pathname : undefined;
if (pathName === runconfig.pathName) {
config.wss.handleUpgrade(request, socket, head, webSocket => {
const socket: IWebSocket = {
send: content => webSocket.send(content, error => {
if (error) {
throw error;
}
}),
onMessage: cb => webSocket.on("message", (data) => {
console.log(data.toString());
cb(data);
}),
onError: cb => webSocket.on("error", cb),
onClose: cb => webSocket.on("close", cb),
dispose: () => webSocket.close(),
};
// launch the server when the web socket is opened
if (webSocket.readyState === webSocket.OPEN) {
launchLanguageServer(runconfig, socket);
} else {
webSocket.on("open", () => {
launchLanguageServer(runconfig, socket);
});
}
});
}
});
};
/**
* Solves: __dirname is not defined in ES module scope
*/
export const getLocalDirectory = (referenceUrl: string | URL) => {
const __filename = fileURLToPath(referenceUrl);
return dirname(__filename);
};