Skip to content

Commit 98ed04f

Browse files
authored
Merge pull request #16 from CodinGame/prepare-coderpad-project-lsp
Prepare coderpad project
2 parents ff015f8 + d9e5ad7 commit 98ed04f

14 files changed

+486
-296
lines changed

package-lock.json

Lines changed: 139 additions & 116 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codingame/monaco-languageclient-wrapper",
3-
"version": "1.8.0",
3+
"version": "2.0.2",
44
"private": false,
55
"description": "Enhanced Monaco editor with TextMate grammars and more",
66
"scripts": {
@@ -21,7 +21,7 @@
2121
],
2222
"types": "dist/index.d.ts",
2323
"dependencies": {
24-
"@codingame/monaco-editor-wrapper": "^1.6.0",
24+
"@codingame/monaco-editor-wrapper": "^1.9.0",
2525
"once": "^1.4.0",
2626
"sweetalert": "^2.1.2"
2727
},
@@ -43,9 +43,9 @@
4343
"@rollup/plugin-typescript": "^8.3.1",
4444
"@types/once": "^1.4.0",
4545
"@types/rollup-plugin-node-builtins": "^2.1.2",
46-
"@types/vscode": "^1.64.0",
47-
"@typescript-eslint/eslint-plugin": "5.13.0",
48-
"@typescript-eslint/parser": "5.13.0",
46+
"@types/vscode": "^1.65.0",
47+
"@typescript-eslint/eslint-plugin": "5.14.0",
48+
"@typescript-eslint/parser": "5.14.0",
4949
"eslint": "8.10.0",
5050
"eslint-config-standard": "16.0.3",
5151
"eslint-config-standard-jsx": "10.0.0",
@@ -54,8 +54,8 @@
5454
"eslint-plugin-promise": "6.0.0",
5555
"eslint-plugin-unused-imports": "2.0.0",
5656
"proxy-polyfill": "^0.3.2",
57-
"rollup": "2.68.0",
58-
"rollup-plugin-dts": "^4.1.0",
57+
"rollup": "2.70.0",
58+
"rollup-plugin-dts": "^4.2.0",
5959
"rollup-plugin-node-builtins": "^2.1.2",
6060
"rollup-plugin-postcss": "4.0.2",
6161
"rollup-plugin-visualizer": "5.6.0",

rollup.types.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as rollup from 'rollup'
22
import dts from 'rollup-plugin-dts'
33
import pkg from './package.json'
4+
import removeVscodeDeclareModule from './rollup/rollup-plugin-remove-vscode-declare-module'
45

56
const externals = [
67
...Object.keys(pkg.dependencies),
@@ -15,7 +16,7 @@ export default rollup.defineConfig({
1516
return false
1617
}
1718
// Do not include types that rollup-plugin-dts fails to parse
18-
if (/^vscode$|proxy-polyfill/.test(source)) {
19+
if (/^proxy-polyfill/.test(source)) {
1920
return true
2021
}
2122
// Force include all types from vscode-* libs (vscode-languageclient, vscode-languageserver-protocol....)
@@ -25,6 +26,7 @@ export default rollup.defineConfig({
2526
return externals.some(external => source.startsWith(external))
2627
},
2728
plugins: [
29+
removeVscodeDeclareModule(),
2830
dts({
2931
respectExternal: true
3032
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PluginImpl } from 'rollup'
2+
3+
const plugin: PluginImpl<{}> = () => {
4+
return {
5+
// Remove "declare module 'vscode'" from vscode type
6+
// Or else dts plugin is unable to extract types from it
7+
name: 'remove-vscode-declare-module',
8+
transform (code, id) {
9+
if (id.endsWith('@types/vscode/index.d.ts')) {
10+
const lines = code.split('\n')
11+
let index = lines.indexOf('declare module \'vscode\' {')
12+
if (index < 0) throw new Error('Module declaration not found')
13+
14+
lines.splice(index, 1)
15+
for (; lines[index] !== '}'; index++) {
16+
// unindent
17+
lines[index] = lines[index].slice(4)
18+
}
19+
lines.splice(index, 1)
20+
21+
return lines.join('\n')
22+
}
23+
return code
24+
}
25+
26+
}
27+
}
28+
29+
export default plugin

src/createLanguageClient.ts

Lines changed: 66 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createWebSocketConnection, ConsoleLogger, toSocket, MessageSignature } from '@codingame/monaco-jsonrpc'
1+
import { MessageSignature, MessageConnection } from '@codingame/monaco-jsonrpc'
22
import { Uri } from 'monaco-editor'
33
import {
44
MonacoLanguageClient,
@@ -7,97 +7,81 @@ import {
77
import once from 'once'
88
import { registerExtensionFeatures } from './extensions'
99
import { LanguageClientId } from './languageClientOptions'
10+
import { Infrastructure } from './infrastructure'
1011

11-
async function openConnection (url: URL | string, errorHandler: ConnectionErrorHandler, closeHandler: () => void): Promise<IConnection> {
12-
return new Promise((resolve, reject) => {
13-
const webSocket = new WebSocket(url)
12+
async function messageConnectionToConnection (messageConnection: MessageConnection, errorHandler: ConnectionErrorHandler, closeHandler: () => void): Promise<IConnection> {
13+
const connection = createConnection(messageConnection, errorHandler, closeHandler)
1414

15-
webSocket.onopen = () => {
16-
const socket = toSocket(webSocket)
17-
const webSocketConnection = createWebSocketConnection(socket, new ConsoleLogger())
18-
webSocketConnection.onDispose(() => {
19-
webSocket.close()
20-
})
21-
22-
const connection = createConnection(webSocketConnection, errorHandler, closeHandler)
23-
24-
const existingRegistrations = new Set<string>()
25-
const fixedConnection: IConnection = {
26-
...connection,
27-
initialize: (params: InitializeParams) => {
28-
// Hack to fix url converted from /toto/tata to \\toto\tata in windows
29-
const rootPath = params.rootPath?.replace(/\\/g, '/')
30-
const fixedParams: InitializeParams = {
31-
...params,
32-
rootPath: rootPath,
33-
rootUri: rootPath != null ? Uri.from({ scheme: 'file', path: rootPath }).toString() : null
34-
}
35-
return connection.initialize(fixedParams)
36-
},
37-
onRequest (...args: Parameters<typeof connection.onRequest>) {
38-
return connection.onRequest(args[0], (...params) => {
39-
// Hack for https://github.com/OmniSharp/omnisharp-roslyn/issues/2119
40-
const method = (args[0] as MessageSignature).method
41-
if (method === RegistrationRequest.type.method) {
42-
const registrationParams = params[0] as unknown as RegistrationParams
43-
registrationParams.registrations = registrationParams.registrations.filter(registration => {
44-
const alreadyExisting = existingRegistrations.has(registration.id)
45-
if (alreadyExisting) {
46-
console.warn('Registration already existing', registration.id, registration.method)
47-
}
48-
return !alreadyExisting
49-
})
50-
registrationParams.registrations.forEach(registration => {
51-
existingRegistrations.add(registration.id)
52-
})
53-
}
54-
if (method === UnregistrationRequest.type.method) {
55-
const unregistrationParams = params[0] as unknown as UnregistrationParams
56-
for (const unregistration of unregistrationParams.unregisterations) {
57-
existingRegistrations.delete(unregistration.id)
58-
}
15+
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: 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 => {
35+
const alreadyExisting = existingRegistrations.has(registration.id)
36+
if (alreadyExisting) {
37+
console.warn('Registration already existing', registration.id, registration.method)
5938
}
60-
return args[1](...params)
39+
return !alreadyExisting
6140
})
62-
},
63-
dispose: () => {
64-
try {
65-
connection.dispose()
66-
} catch (error) {
67-
// The dispose should NEVER fail or the lsp client is not properly cleaned
68-
// see https://github.com/microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L3105
69-
console.warn('[LSP]', 'Error while disposing connection', error)
70-
}
71-
// Hack, when the language client is removed, the connection is disposed but the closeHandler is not always properly called
72-
// The language client is then still active but without a proper connection and errors will occurs
73-
closeHandler()
74-
},
75-
shutdown: async () => {
76-
// The shutdown should NEVER fail or the connection is not closed and the lsp client is not properly cleaned
77-
// see https://github.com/microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L3103
78-
try {
79-
await connection.shutdown()
80-
} catch (error) {
81-
console.warn('[LSP]', 'Error while shutdown lsp', error)
41+
registrationParams.registrations.forEach(registration => {
42+
existingRegistrations.add(registration.id)
43+
})
44+
}
45+
if (method === UnregistrationRequest.type.method) {
46+
const unregistrationParams = params[0] as unknown as UnregistrationParams
47+
for (const unregistration of unregistrationParams.unregisterations) {
48+
existingRegistrations.delete(unregistration.id)
8249
}
8350
}
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)
61+
}
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)
8473
}
85-
resolve(fixedConnection)
86-
}
87-
webSocket.onerror = () => {
88-
reject(new Error('Unable to connect to server'))
8974
}
90-
})
75+
}
76+
77+
return fixedConnection
9178
}
9279

9380
const RETRY_DELAY = 3000
9481
class CGLSPConnectionProvider implements IConnectionProvider {
9582
constructor (
96-
private serverAddress: string,
9783
private id: LanguageClientId,
98-
private sessionId: string | undefined,
99-
private libraryUrls: string[],
100-
private getSecurityToken: () => Promise<string>
84+
private infrastructure: Infrastructure
10185
) {
10286
}
10387

@@ -108,12 +92,9 @@ class CGLSPConnectionProvider implements IConnectionProvider {
10892
}, RETRY_DELAY)
10993
})
11094
try {
111-
const path = this.sessionId != null ? `run/${this.sessionId}/${this.id}` : `run/${this.id}`
112-
const url = new URL(path, this.serverAddress)
113-
this.libraryUrls.forEach(libraryUrl => url.searchParams.append('libraryUrl', libraryUrl))
114-
url.searchParams.append('token', await this.getSecurityToken())
95+
const connection = await this.infrastructure.openConnection(this.id)
11596

116-
return await openConnection(url, errorHandler, onceDelayedCloseHandler)
97+
return await messageConnectionToConnection(connection, errorHandler, onceDelayedCloseHandler)
11798
} catch (err) {
11899
onceDelayedCloseHandler()
119100
throw err
@@ -123,15 +104,12 @@ class CGLSPConnectionProvider implements IConnectionProvider {
123104

124105
function createLanguageClient (
125106
id: LanguageClientId,
126-
sessionId: string | undefined,
107+
infrastructure: Infrastructure,
127108
{
128109
documentSelector,
129110
synchronize,
130111
initializationOptions
131112
}: LanguageClientOptions,
132-
languageServerAddress: string,
133-
getSecurityToken: () => Promise<string>,
134-
libraryUrls: string[],
135113
errorHandler: ErrorHandler,
136114
middleware?: Middleware
137115
): MonacoLanguageClient {
@@ -147,7 +125,7 @@ function createLanguageClient (
147125
synchronize,
148126
initializationOptions
149127
},
150-
connectionProvider: new CGLSPConnectionProvider(languageServerAddress, id, sessionId, libraryUrls, getSecurityToken)
128+
connectionProvider: new CGLSPConnectionProvider(id, infrastructure)
151129
})
152130

153131
client.registerProposedFeatures()

src/customRequests.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,34 @@
1+
import { ProtocolNotificationType, ProtocolRequestType } from 'vscode-languageserver-protocol'
12
import { LanguageClient } from './languageClient'
23

4+
export interface WillShutdownParams {
5+
delay: number
6+
}
7+
export const willShutdownNotificationType = new ProtocolNotificationType<WillShutdownParams, void>('willShutdown')
8+
9+
export interface SaveTextDocumentParams {
10+
textDocument: {
11+
uri: string
12+
text: string
13+
}
14+
}
15+
16+
export const saveTextDocumentRequestType = new ProtocolRequestType<SaveTextDocumentParams, void, never, void, void>('textDocument/save')
17+
18+
export interface GetTextDocumentParams {
19+
textDocument: {
20+
uri: string
21+
}
22+
}
23+
24+
export interface GetTextDocumentResult {
25+
text: string
26+
}
27+
28+
export const getTextDocumentRequestType = new ProtocolRequestType<GetTextDocumentParams, GetTextDocumentResult, never, void, void>('textDocument/get')
29+
330
export function updateFile (uri: string, text: string, languageClient: LanguageClient): Promise<void> {
4-
return languageClient.sendRequest('textDocument/save', {
31+
return languageClient.sendRequest(saveTextDocumentRequestType, {
532
textDocument: {
633
uri,
734
text
@@ -10,7 +37,7 @@ export function updateFile (uri: string, text: string, languageClient: LanguageC
1037
}
1138

1239
export function getFile (uri: string, languageClient: LanguageClient): Promise<{ text: string }> {
13-
return languageClient.sendRequest('textDocument/get', {
40+
return languageClient.sendRequest(getTextDocumentRequestType, {
1441
textDocument: {
1542
uri
1643
}

src/extensions.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import {
22
Disposable,
33
ServerCapabilities, DocumentSelector, MonacoLanguageClient, StaticFeature, Services,
4-
TextDocumentSyncOptions, TextDocument, DidSaveTextDocumentNotification, Emitter, ProtocolNotificationType
4+
TextDocumentSyncOptions, TextDocument, DidSaveTextDocumentNotification, Emitter
55
} from '@codingame/monaco-languageclient'
6-
import { updateFile } from './customRequests'
6+
import { updateFile, willShutdownNotificationType, WillShutdownParams } from './customRequests'
7+
import { LanguageClient } from './languageClient'
78

89
interface ResolvedTextDocumentSyncCapabilities {
910
resolvedTextDocumentSync?: TextDocumentSyncOptions
1011
}
1112

1213
// Initialize the file content into the lsp server for implementations that don't support open/close notifications
13-
class InitializeTextDocumentFeature implements StaticFeature {
14+
export class InitializeTextDocumentFeature implements StaticFeature {
1415
private didOpenTextDocumentDisposable: Disposable | undefined
15-
constructor (private languageClient: MonacoLanguageClient) {}
16+
constructor (private languageClient: LanguageClient) {}
1617

1718
fillClientCapabilities (): void {}
1819

@@ -64,7 +65,7 @@ class CobolResolveSubroutineFeature implements StaticFeature {
6465
initialize (capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {
6566
this.onRequestDisposable = this.languageClient.onRequest('cobol/resolveSubroutine', (routineName: string) => {
6667
const constantRoutinePaths: Partial<Record<string, string>> = {
67-
'assert-equals': 'file:/tmp/project/deps/assert-equals.cbl'
68+
'assert-equals': `${Services.get().workspace.rootUri ?? 'file:/tmp/project'}/deps/assert-equals.cbl`
6869
}
6970
const contantRoutinePath = constantRoutinePaths[routineName.toLowerCase()]
7071
if (contantRoutinePath != null) {
@@ -83,17 +84,11 @@ class CobolResolveSubroutineFeature implements StaticFeature {
8384
}
8485

8586
export function registerExtensionFeatures (client: MonacoLanguageClient, language: string): void {
86-
client.registerFeature(new InitializeTextDocumentFeature(client))
8787
if (language === 'cobol') {
8888
client.registerFeature(new CobolResolveSubroutineFeature(client))
8989
}
9090
}
9191

92-
export interface WillShutdownParams {
93-
delay: number
94-
}
95-
export const willShutdownNotificationType = new ProtocolNotificationType<WillShutdownParams, void>('willShutdown')
96-
9792
export class WillDisposeFeature implements StaticFeature {
9893
constructor (private languageClient: MonacoLanguageClient, private onWillShutdownEmitter: Emitter<WillShutdownParams>) {}
9994
fillClientCapabilities (): void {}

0 commit comments

Comments
 (0)