const argv = require('yargs').argv; const fs = require('fs'); const path = require("path"); const {servers} = require("./languageServers"); const {spawn} = require('child_process'); const url = require('url'); const WebSocket = require('ws'); const verbose = argv.verbose || false; const { IPCMessageReader, IPCMessageWriter, StreamMessageReader, StreamMessageWriter } = require('vscode-jsonrpc'); const { formatPath, makeClientPath, makeServerPath } = require("./paths-utility"); const _port = 9999; const wss = new WebSocket.Server({port: _port}); console.log("Started websocket server on port: ", _port); wss.on('connection', (ws, req) => { const pathname = url.parse(req.url).pathname; handleLanguageConnection(ws, pathname.substring(1)); }); function handleLanguageConnection(ws, pathname) { const server = servers.find(server => server.endpointName === pathname); setupLanguageServer(ws, server); } function setupLanguageServer(ws, server) { if (!server) return; const { reader, writer } = startLanguageServer(server); server.writer = writer; reader.listen(message => { if (message.error) { console.error(server.nameEndsWith + ":"); console.error(message.error); return; } if (verbose) { console.log(`From server(${server.endpointName}): `); console.log(message); } processMessage(message, ws, server); }); ws.on('message', message => { let parsed = JSON.parse(message); if (verbose) { console.log("From client: "); console.log(parsed); } handleMessage(parsed, server); }); } function startLanguageServer(languageServer) { let env = process.env; const serverProcess = spawn(...languageServer.args, {env, shell: true}); serverProcess.stderr.on('data', data => { console.error(`${serverProcess.spawnfile} error: ${data}`); }); serverProcess.on('exit', code => { fs.readdirSync("temp").forEach(file => { fs.unlinkSync("temp" + path.sep + file); }); console.log(`${serverProcess.spawnfile} exited with code ${code}`); }); serverProcess.on('error', err => { console.error(`Failed to start ${serverProcess.spawnfile}:`, err); }); let reader; let writer; switch (languageServer.connectionType) { case "ipc": reader = new IPCMessageReader(serverProcess); writer = new IPCMessageWriter(serverProcess); break; case "stdio": if (serverProcess.stdin !== null && serverProcess.stdout !== null) { reader = new StreamMessageReader(serverProcess.stdout); writer = new StreamMessageWriter(serverProcess.stdin); } else { throw 'The language server process does not have a valid stdin or stdout'; } break; default: throw 'Unknown connection type...'; } return { reader, writer }; } function processMessage(message, ws, server) { if (message.params) { if (message.params.textDocument && message.params.textDocument.uri) { message.params.textDocument.uri = makeClientPath( message.params.textDocument.uri, server.clientFileNameReplacePattern ); } else if (message.params.uri) { message.params.uri = makeClientPath( message.params.uri, server.clientFileNameReplacePattern ); } } ws.send(JSON.stringify(message)); } function handleMessage(parsed, server) { if (parsed.method) { switch (parsed.method) { case "initialize": let rootUri = formatPath(__dirname + path.sep + "temp"); if (!parsed.params || (!parsed.params.rootUri && !parsed.params.rootPath && !parsed.params.workspaceFolders)) { if (!fs.existsSync("temp")) { fs.mkdirSync("temp"); } parsed.params.rootUri = rootUri; parsed.params.rootPath = __dirname + path.sep + "temp"; } break; } } if (parsed.params && parsed.params.textDocument && parsed.params.textDocument.uri) { parsed.params.textDocument.uri = makeServerPath( parsed.params.textDocument.uri, server.serverFileNameReplacePattern ); if (server && server.relativePath) { parsed.params.textDocument.uri = parsed.params.textDocument.uri.replace(__dirname + path.sep, ""); } } const writer = server?.writer; if (writer) { writer.write(parsed); } }