forked from CrossBreezeNL/crossmodel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlaunch.ts
90 lines (82 loc) · 3.55 KB
/
launch.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/********************************************************************************
* Copyright (c) 2023 CrossBreeze.
********************************************************************************/
import { MODELSERVER_PORT_COMMAND } from '@crossbreeze/protocol';
import console from 'console';
import * as net from 'net';
import * as rpc from 'vscode-jsonrpc/node.js';
import { URI } from 'vscode-uri';
import { CrossModelLSPServices } from '../integration.js';
import { ModelServer } from './model-server.js';
const currentConnections: rpc.MessageConnection[] = [];
/**
* Creates a socket-based RPC model server that acts as a facade to the Langium-based semantic model index (documents).
*
* @param services language services
* @returns a promise that is resolved as soon as the server is shut down or rejects if an error occurs
*/
export function startModelServer(services: CrossModelLSPServices, workspaceFolder: URI): Promise<void> {
const netServer = net.createServer(socket => createClientConnection(socket, services));
netServer.listen(0);
netServer.on('listening', () => {
const addressInfo = netServer.address();
if (!addressInfo) {
console.error('[ModelServer] Could not resolve address info. Shutting down.');
close(netServer);
return;
} else if (typeof addressInfo === 'string') {
console.error(`[ModelServer] Unexpectedly listening to pipe or domain socket "${addressInfo}". Shutting down.`);
close(netServer);
return;
}
console.log(`[ModelServer] Ready to accept new client requests on port: ${addressInfo.port}`);
services.shared.lsp.Connection?.onRequest(MODELSERVER_PORT_COMMAND, () => addressInfo.port);
});
netServer.on('error', err => {
console.error('[ModelServer] Error: ', err);
close(netServer);
});
return new Promise((resolve, reject) => {
netServer.on('close', () => resolve(undefined));
netServer.on('error', error => reject(error));
});
}
/**
* Create a new connection for an incoming client on the given socket. Each client gets their own connection and model server instance.
*
* @param socket socket connection
* @param services language services
* @returns a promise that is resolved as soon as the connection is closed or rejects if an error occurs
*/
async function createClientConnection(socket: net.Socket, services: CrossModelLSPServices): Promise<void> {
console.info(`[ModelServer] Starting model server connection for client: '${socket.localAddress}'`);
const connection = createConnection(socket);
currentConnections.push(connection);
const modelServer = new ModelServer(connection, services.shared.model.ModelService);
connection.onDispose(() => modelServer.dispose());
socket.on('close', () => modelServer.dispose());
connection.listen();
console.info(`[ModelServer] Connecting to client: '${socket.localAddress}'`);
return new Promise((resolve, rejects) => {
connection.onClose(() => resolve(undefined));
connection.onError(error => rejects(error));
});
}
/**
* Creates an RPC-message connection for the given socket.
*
* @param socket socket
* @returns message connection
*/
function createConnection(socket: net.Socket): rpc.MessageConnection {
return rpc.createMessageConnection(new rpc.SocketMessageReader(socket), new rpc.SocketMessageWriter(socket), console);
}
/**
* Closes the server.
*
* @param netServer server to be closed
*/
function close(netServer: net.Server): void {
currentConnections.forEach(connection => connection.dispose());
netServer.close();
}