Skip to content

Commit 5e48d2a

Browse files
committed
fix: Adapt to api, make it so IConnectionProvider returns a MessageTransports
1 parent d27cd10 commit 5e48d2a

File tree

2 files changed

+108
-86
lines changed

2 files changed

+108
-86
lines changed

src/createLanguageClient.ts

Lines changed: 91 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,73 @@
1-
import { MessageSignature, MessageConnection } from '@codingame/monaco-jsonrpc'
1+
import { MessageReader, MessageWriter, Message, Event, DataCallback, Disposable, PartialMessageInfo } from 'vscode-jsonrpc'
22
import { Uri } from 'monaco-editor'
33
import {
4-
MonacoLanguageClient,
5-
createConnection, ConnectionErrorHandler, ConnectionCloseHandler, IConnection, Middleware, ErrorHandler, IConnectionProvider, InitializeParams, RegistrationRequest, RegistrationParams, UnregistrationRequest, UnregistrationParams, LanguageClientOptions
4+
MonacoLanguageClient, Middleware, ErrorHandler, IConnectionProvider, InitializeParams, RegistrationRequest, RegistrationParams, UnregistrationRequest, UnregistrationParams, LanguageClientOptions, MessageTransports, InitializeRequest
65
} from 'monaco-languageclient'
7-
import once from 'once'
86
import { registerExtensionFeatures } from './extensions'
97
import { LanguageClientId } from './languageClientOptions'
108
import { Infrastructure } from './infrastructure'
119

12-
async function messageConnectionToConnection (messageConnection: MessageConnection, errorHandler: ConnectionErrorHandler, closeHandler: () => void): Promise<IConnection> {
13-
const connection = createConnection(messageConnection, errorHandler, closeHandler)
10+
interface MessageMiddleware {
11+
(message: Message): Message
12+
}
13+
14+
class MiddlewareMessageWriter implements MessageWriter {
15+
constructor (private delegate: MessageWriter, private middleware: MessageMiddleware) {}
16+
17+
onError: Event<[Error, Message | undefined, number | undefined]> = (cb) => {
18+
return this.delegate.onError(cb)
19+
}
20+
21+
onClose: Event<void> = (cb) => {
22+
return this.delegate.onClose(cb)
23+
}
24+
25+
dispose (): void {
26+
this.delegate.dispose()
27+
}
28+
29+
write (msg: Message): Promise<void> {
30+
return this.delegate.write(this.middleware(msg))
31+
}
32+
33+
end (): void {
34+
return this.delegate.end()
35+
}
36+
}
37+
class MiddlewareMessageReader implements MessageReader {
38+
constructor (private delegate: MessageReader, private middleware: MessageMiddleware) {}
39+
40+
onError: Event<Error> = (cb) => {
41+
return this.delegate.onError(cb)
42+
}
43+
44+
onClose: Event<void> = (cb) => {
45+
return this.delegate.onClose(cb)
46+
}
47+
48+
onPartialMessage: Event<PartialMessageInfo> = (cb) => {
49+
return this.delegate.onPartialMessage(cb)
50+
}
1451

52+
listen (callback: DataCallback): Disposable {
53+
return this.delegate.listen(message => {
54+
callback(this.middleware(message))
55+
})
56+
}
57+
58+
dispose (): void {
59+
this.delegate.dispose()
60+
}
61+
}
62+
63+
function hackTransports (transports: MessageTransports): MessageTransports {
1564
const existingRegistrations = new Set<string>()
16-
const fixedConnection: IConnection = {
17-
...connection,
18-
initialize: (params: InitializeParams) => {
19-
// Hack to fix url converted from /toto/tata to \\toto\tata in windows
20-
const rootPath = params.rootPath?.replace(/\\/g, '/')
21-
const fixedParams: InitializeParams = {
22-
...params,
23-
rootPath,
24-
rootUri: rootPath != null ? Uri.from({ scheme: 'file', path: rootPath }).toString() : null
25-
}
26-
return connection.initialize(fixedParams)
27-
},
28-
onRequest (...args: Parameters<typeof connection.onRequest>) {
29-
return connection.onRequest(args[0], (...params) => {
30-
// Hack for https://github.com/OmniSharp/omnisharp-roslyn/issues/2119
31-
const method = (args[0] as MessageSignature).method
32-
if (method === RegistrationRequest.type.method) {
33-
const registrationParams = params[0] as unknown as RegistrationParams
34-
registrationParams.registrations = registrationParams.registrations.filter(registration => {
65+
return {
66+
reader: new MiddlewareMessageReader(transports.reader, message => {
67+
if (Message.isRequest(message)) {
68+
if (message.method === RegistrationRequest.type.method) {
69+
const registrationParams = message.params as RegistrationParams
70+
const filteredRegistrations = registrationParams.registrations.filter(registration => {
3571
const alreadyExisting = existingRegistrations.has(registration.id)
3672
if (alreadyExisting) {
3773
console.warn('Registration already existing', registration.id, registration.method)
@@ -41,64 +77,53 @@ async function messageConnectionToConnection (messageConnection: MessageConnecti
4177
registrationParams.registrations.forEach(registration => {
4278
existingRegistrations.add(registration.id)
4379
})
80+
const fixedParams: RegistrationParams = {
81+
...registrationParams,
82+
registrations: filteredRegistrations
83+
}
84+
return {
85+
...message,
86+
params: fixedParams
87+
}
4488
}
45-
if (method === UnregistrationRequest.type.method) {
46-
const unregistrationParams = params[0] as unknown as UnregistrationParams
89+
if (message.method === UnregistrationRequest.type.method) {
90+
const unregistrationParams = message.params as UnregistrationParams
4791
for (const unregistration of unregistrationParams.unregisterations) {
4892
existingRegistrations.delete(unregistration.id)
4993
}
5094
}
51-
return args[1](...params)
52-
})
53-
},
54-
dispose: () => {
55-
try {
56-
connection.dispose()
57-
} catch (error) {
58-
// The dispose should NEVER fail or the lsp client is not properly cleaned
59-
// see https://github.com/microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L3105
60-
console.warn('[LSP]', 'Error while disposing connection', error)
6195
}
62-
// Hack, when the language client is removed, the connection is disposed but the closeHandler is not always properly called
63-
// The language client is then still active but without a proper connection and errors will occurs
64-
closeHandler()
65-
},
66-
shutdown: async () => {
67-
// The shutdown should NEVER fail or the connection is not closed and the lsp client is not properly cleaned
68-
// see https://github.com/microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L3103
69-
try {
70-
await connection.shutdown()
71-
} catch (error) {
72-
console.warn('[LSP]', 'Error while shutdown lsp', error)
96+
return message
97+
}),
98+
writer: new MiddlewareMessageWriter(transports.writer, message => {
99+
if (Message.isRequest(message) && message.method === InitializeRequest.type.method) {
100+
const params = message.params as InitializeParams
101+
// Hack to fix url converted from /toto/tata to \\toto\tata in windows
102+
const rootPath = params.rootPath?.replace(/\\/g, '/')
103+
const fixedParams: InitializeParams = {
104+
...params,
105+
rootPath,
106+
rootUri: rootPath != null ? Uri.from({ scheme: 'file', path: rootPath }).toString() : null
107+
}
108+
return {
109+
...message,
110+
params: fixedParams
111+
}
73112
}
74-
}
113+
return message
114+
})
75115
}
76-
77-
return fixedConnection
78116
}
79117

80-
const RETRY_DELAY = 3000
81118
class CGLSPConnectionProvider implements IConnectionProvider {
82119
constructor (
83120
private id: LanguageClientId,
84121
private infrastructure: Infrastructure
85122
) {
86123
}
87124

88-
async get (errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler) {
89-
const onceDelayedCloseHandler = once(() => {
90-
setTimeout(() => {
91-
closeHandler()
92-
}, RETRY_DELAY)
93-
})
94-
try {
95-
const connection = await this.infrastructure.openConnection(this.id)
96-
97-
return await messageConnectionToConnection(connection, errorHandler, onceDelayedCloseHandler)
98-
} catch (err) {
99-
onceDelayedCloseHandler()
100-
throw err
101-
}
125+
async get () {
126+
return hackTransports(await this.infrastructure.openConnection(this.id))
102127
}
103128
}
104129

src/infrastructure.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { ConsoleLogger, createWebSocketConnection, toSocket } from '@codingame/monaco-jsonrpc'
2-
import { MessageConnection } from 'vscode-languageserver-protocol'
3-
import { TextDocument, TextDocumentSaveReason } from 'monaco-languageclient'
1+
import { IWebSocket, WebSocketMessageReader, WebSocketMessageWriter, toSocket } from '@codingame/monaco-jsonrpc'
2+
import { MessageTransports, TextDocument, TextDocumentSaveReason } from 'monaco-languageclient'
43
import * as monaco from 'monaco-editor'
54
import type * as vscode from 'vscode'
65
import { getFile, updateFile } from './customRequests'
@@ -46,27 +45,25 @@ export interface Infrastructure {
4645
* Open a connection to the language server
4746
* @param id The language server id
4847
*/
49-
openConnection (id: LanguageClientId): Promise<MessageConnection>
48+
openConnection (id: LanguageClientId): Promise<MessageTransports>
5049
}
5150

52-
function openWebsocketConnection (url: URL | string): Promise<MessageConnection> {
53-
return new Promise<MessageConnection>((resolve, reject) => {
54-
const webSocket = new WebSocket(url)
51+
async function openWebsocketConnection (url: URL | string): Promise<MessageTransports> {
52+
const webSocket = new WebSocket(url)
53+
const socket: IWebSocket = toSocket(webSocket)
5554

56-
webSocket.onopen = () => {
57-
const socket = toSocket(webSocket)
58-
const webSocketConnection = createWebSocketConnection(socket, new ConsoleLogger())
59-
webSocketConnection.onDispose(() => {
60-
webSocket.close()
61-
})
55+
const reader = new WebSocketMessageReader(socket)
56+
const writer = new WebSocketMessageWriter(socket)
6257

63-
resolve(webSocketConnection)
64-
}
65-
66-
webSocket.onerror = () => {
67-
reject(new Error('Unable to connect to server'))
68-
}
58+
await new Promise((resolve, reject) => {
59+
webSocket.addEventListener('open', resolve)
60+
webSocket.addEventListener('error', reject)
6961
})
62+
63+
return {
64+
reader,
65+
writer
66+
}
7067
}
7168

7269
export abstract class CodinGameInfrastructure implements Infrastructure {
@@ -120,7 +117,7 @@ export abstract class CodinGameInfrastructure implements Infrastructure {
120117
*/
121118
protected abstract getSecurityToken(): Promise<string>
122119

123-
public async openConnection (id: LanguageClientId): Promise<MessageConnection> {
120+
public async openConnection (id: LanguageClientId): Promise<MessageTransports> {
124121
const url = new URL(this.sessionId != null ? `run/${this.sessionId}/${id}` : `run/${id}`, this.serverAddress)
125122
this.libraryUrls?.forEach(libraryUrl => url.searchParams.append('libraryUrl', libraryUrl))
126123
url.searchParams.append('token', await this.getSecurityToken())

0 commit comments

Comments
 (0)