@platformos/codemirror-language-client 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +91 -0
- package/dist/esm/CodeMirrorLanguageClient.d.ts +60 -0
- package/dist/esm/CodeMirrorLanguageClient.js +80 -0
- package/dist/esm/CodeMirrorLanguageClient.js.map +1 -0
- package/dist/esm/LanguageClient.d.ts +112 -0
- package/dist/esm/LanguageClient.js +187 -0
- package/dist/esm/LanguageClient.js.map +1 -0
- package/dist/esm/extensions/client.d.ts +6 -0
- package/dist/esm/extensions/client.js +12 -0
- package/dist/esm/extensions/client.js.map +1 -0
- package/dist/esm/extensions/complete.d.ts +17 -0
- package/dist/esm/extensions/complete.js +202 -0
- package/dist/esm/extensions/complete.js.map +1 -0
- package/dist/esm/extensions/complete.spec.d.ts +1 -0
- package/dist/esm/extensions/complete.spec.js +189 -0
- package/dist/esm/extensions/complete.spec.js.map +1 -0
- package/dist/esm/extensions/documentHighlights.d.ts +14 -0
- package/dist/esm/extensions/documentHighlights.js +78 -0
- package/dist/esm/extensions/documentHighlights.js.map +1 -0
- package/dist/esm/extensions/documentHighlights.spec.d.ts +1 -0
- package/dist/esm/extensions/documentHighlights.spec.js +99 -0
- package/dist/esm/extensions/documentHighlights.spec.js.map +1 -0
- package/dist/esm/extensions/hover.d.ts +16 -0
- package/dist/esm/extensions/hover.js +49 -0
- package/dist/esm/extensions/hover.js.map +1 -0
- package/dist/esm/extensions/hover.spec.d.ts +1 -0
- package/dist/esm/extensions/hover.spec.js +59 -0
- package/dist/esm/extensions/hover.spec.js.map +1 -0
- package/dist/esm/extensions/index.d.ts +6 -0
- package/dist/esm/extensions/index.js +7 -0
- package/dist/esm/extensions/index.js.map +1 -0
- package/dist/esm/extensions/lspLinter.d.ts +23 -0
- package/dist/esm/extensions/lspLinter.js +104 -0
- package/dist/esm/extensions/lspLinter.js.map +1 -0
- package/dist/esm/extensions/lspLinter.spec.d.ts +1 -0
- package/dist/esm/extensions/lspLinter.spec.js +141 -0
- package/dist/esm/extensions/lspLinter.spec.js.map +1 -0
- package/dist/esm/extensions/snippet.d.ts +19 -0
- package/dist/esm/extensions/snippet.js +25 -0
- package/dist/esm/extensions/snippet.js.map +1 -0
- package/dist/esm/extensions/snippet.spec.d.ts +1 -0
- package/dist/esm/extensions/snippet.spec.js +23 -0
- package/dist/esm/extensions/snippet.spec.js.map +1 -0
- package/dist/esm/extensions/textDocumentSync.d.ts +4 -0
- package/dist/esm/extensions/textDocumentSync.js +109 -0
- package/dist/esm/extensions/textDocumentSync.js.map +1 -0
- package/dist/esm/extensions/textDocumentSync.spec.d.ts +1 -0
- package/dist/esm/extensions/textDocumentSync.spec.js +163 -0
- package/dist/esm/extensions/textDocumentSync.spec.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/test/MockClient.d.ts +18 -0
- package/dist/esm/test/MockClient.js +63 -0
- package/dist/esm/test/MockClient.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildInfo +1 -0
- package/dist/esm/utils/simpleStateField.d.ts +3 -0
- package/dist/esm/utils/simpleStateField.js +17 -0
- package/dist/esm/utils/simpleStateField.js.map +1 -0
- package/dist/umd/CodeMirrorLanguageClient.d.ts +60 -0
- package/dist/umd/CodeMirrorLanguageClient.js +94 -0
- package/dist/umd/CodeMirrorLanguageClient.js.map +1 -0
- package/dist/umd/LanguageClient.d.ts +112 -0
- package/dist/umd/LanguageClient.js +202 -0
- package/dist/umd/LanguageClient.js.map +1 -0
- package/dist/umd/extensions/client.d.ts +6 -0
- package/dist/umd/extensions/client.js +25 -0
- package/dist/umd/extensions/client.js.map +1 -0
- package/dist/umd/extensions/complete.d.ts +17 -0
- package/dist/umd/extensions/complete.js +217 -0
- package/dist/umd/extensions/complete.js.map +1 -0
- package/dist/umd/extensions/documentHighlights.d.ts +14 -0
- package/dist/umd/extensions/documentHighlights.js +93 -0
- package/dist/umd/extensions/documentHighlights.js.map +1 -0
- package/dist/umd/extensions/hover.d.ts +16 -0
- package/dist/umd/extensions/hover.js +64 -0
- package/dist/umd/extensions/hover.js.map +1 -0
- package/dist/umd/extensions/index.d.ts +6 -0
- package/dist/umd/extensions/index.js +36 -0
- package/dist/umd/extensions/index.js.map +1 -0
- package/dist/umd/extensions/lspLinter.d.ts +23 -0
- package/dist/umd/extensions/lspLinter.js +119 -0
- package/dist/umd/extensions/lspLinter.js.map +1 -0
- package/dist/umd/extensions/snippet.d.ts +19 -0
- package/dist/umd/extensions/snippet.js +38 -0
- package/dist/umd/extensions/snippet.js.map +1 -0
- package/dist/umd/extensions/textDocumentSync.d.ts +4 -0
- package/dist/umd/extensions/textDocumentSync.js +122 -0
- package/dist/umd/extensions/textDocumentSync.js.map +1 -0
- package/dist/umd/index.d.ts +2 -0
- package/dist/umd/index.js +29 -0
- package/dist/umd/index.js.map +1 -0
- package/dist/umd/test/MockClient.d.ts +18 -0
- package/dist/umd/test/MockClient.js +77 -0
- package/dist/umd/test/MockClient.js.map +1 -0
- package/dist/umd/tsconfig.tsbuildInfo +1 -0
- package/dist/umd/utils/simpleStateField.d.ts +3 -0
- package/dist/umd/utils/simpleStateField.js +30 -0
- package/dist/umd/utils/simpleStateField.js.map +1 -0
- package/package.json +67 -0
- package/playground/src/index.html +10 -0
- package/playground/src/language-server-worker.ts +82 -0
- package/playground/src/playground.ts +251 -0
- package/playground/tsconfig.json +26 -0
- package/playground/webpack.config.js +85 -0
- package/src/CodeMirrorLanguageClient.ts +179 -0
- package/src/LanguageClient.ts +329 -0
- package/src/extensions/client.ts +17 -0
- package/src/extensions/complete.spec.ts +200 -0
- package/src/extensions/complete.ts +274 -0
- package/src/extensions/documentHighlights.spec.ts +111 -0
- package/src/extensions/documentHighlights.ts +91 -0
- package/src/extensions/hover.spec.ts +68 -0
- package/src/extensions/hover.ts +66 -0
- package/src/extensions/index.ts +19 -0
- package/src/extensions/lspLinter.spec.ts +156 -0
- package/src/extensions/lspLinter.ts +154 -0
- package/src/extensions/snippet.spec.ts +31 -0
- package/src/extensions/snippet.ts +42 -0
- package/src/extensions/textDocumentSync.spec.ts +188 -0
- package/src/extensions/textDocumentSync.ts +138 -0
- package/src/index.ts +2 -0
- package/src/test/MockClient.ts +96 -0
- package/src/utils/simpleStateField.ts +22 -0
- package/tsconfig.json +18 -0
- package/tsconfig.umd.json +9 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { ClientCapabilities, MarkupKind } from 'vscode-languageserver-protocol';
|
|
3
|
+
|
|
4
|
+
import { Dependencies, LanguageClient } from './LanguageClient';
|
|
5
|
+
import {
|
|
6
|
+
AutocompleteOptions,
|
|
7
|
+
DiagnosticRenderer,
|
|
8
|
+
HoverOptions,
|
|
9
|
+
HoverRenderer,
|
|
10
|
+
InfoRenderer,
|
|
11
|
+
LinterOptions,
|
|
12
|
+
clientFacet,
|
|
13
|
+
diagnosticRendererFacet,
|
|
14
|
+
fileUriFacet,
|
|
15
|
+
infoRendererFacet,
|
|
16
|
+
lspComplete,
|
|
17
|
+
lspDocumentHighlights,
|
|
18
|
+
lspHover,
|
|
19
|
+
lspLinter,
|
|
20
|
+
serverCapabilitiesFacet,
|
|
21
|
+
textDocumentSync,
|
|
22
|
+
} from './extensions';
|
|
23
|
+
import { hoverRendererFacet } from './extensions/hover';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The client capabilities are how we tell the language server what
|
|
27
|
+
* features we support so that they have the opportunity to skip doing
|
|
28
|
+
* things that the client does not support. Everything is false by default,
|
|
29
|
+
* but we migth need to change that and this is where we'll do it.
|
|
30
|
+
*/
|
|
31
|
+
const clientCapabilities: ClientCapabilities = {
|
|
32
|
+
textDocument: {
|
|
33
|
+
documentHighlight: {
|
|
34
|
+
dynamicRegistration: false,
|
|
35
|
+
},
|
|
36
|
+
completion: {
|
|
37
|
+
// We send the completion context to the server
|
|
38
|
+
contextSupport: true,
|
|
39
|
+
completionItem: {
|
|
40
|
+
snippetSupport: true,
|
|
41
|
+
insertReplaceSupport: true,
|
|
42
|
+
documentationFormat: [MarkupKind.PlainText, MarkupKind.Markdown],
|
|
43
|
+
commitCharactersSupport: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const defaultLogger = console.log.bind(console);
|
|
50
|
+
|
|
51
|
+
export { Dependencies };
|
|
52
|
+
|
|
53
|
+
export interface FeatureFlags {
|
|
54
|
+
shouldComplete: boolean;
|
|
55
|
+
shouldLint: boolean;
|
|
56
|
+
shouldHover: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ClientDependencies = Partial<Dependencies>;
|
|
60
|
+
|
|
61
|
+
export interface CodeMirrorDependencies {
|
|
62
|
+
/**
|
|
63
|
+
* The infoRenderer is a function that returns a DOM node that contains the documentation
|
|
64
|
+
* for a completion item. Presumably does markdown conversions to DOM nodes.
|
|
65
|
+
*
|
|
66
|
+
* A function that takes a completion object and returns a DOM node.
|
|
67
|
+
*/
|
|
68
|
+
infoRenderer?: InfoRenderer;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Say you wanted to change the settings of the `autocomplete` extension,
|
|
72
|
+
* you'd do it with that.
|
|
73
|
+
*/
|
|
74
|
+
autocompleteOptions?: AutocompleteOptions;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Say you wanted to change the settings of the `linter` extension,
|
|
78
|
+
* you'd do it with that.
|
|
79
|
+
*/
|
|
80
|
+
linterOptions?: LinterOptions;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The diagnosticRenderer is a function that returns a DOM node that
|
|
84
|
+
* contains the content of a diagnostic. It overrides the default
|
|
85
|
+
* rendering logic for diagnostics.
|
|
86
|
+
*/
|
|
87
|
+
diagnosticRenderer?: DiagnosticRenderer;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The hoverRenderer is a function that returns a DOM node that contains the documentation
|
|
91
|
+
* for the item under the cursor. The documentation is provided by the Language Server.
|
|
92
|
+
*/
|
|
93
|
+
hoverRenderer?: HoverRenderer;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Say you wanted to change the settings of the `hoverTooltip` extension,
|
|
97
|
+
* you'd do it with that.
|
|
98
|
+
*/
|
|
99
|
+
hoverOptions?: HoverOptions;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// There is one LanguageClient
|
|
103
|
+
// There is one LanguageServer
|
|
104
|
+
// There are many CodeMirror instances
|
|
105
|
+
export class CodeMirrorLanguageClient {
|
|
106
|
+
readonly client: LanguageClient;
|
|
107
|
+
private readonly infoRenderer: InfoRenderer | undefined;
|
|
108
|
+
private readonly autocompleteExtension: Extension;
|
|
109
|
+
private readonly diagnosticRenderer: DiagnosticRenderer | undefined;
|
|
110
|
+
private readonly linterExtension: Extension;
|
|
111
|
+
private readonly hoverRenderer: HoverRenderer | undefined;
|
|
112
|
+
private readonly hoverExtension: Extension;
|
|
113
|
+
private readonly documentHighlightsExtension: Extension;
|
|
114
|
+
|
|
115
|
+
constructor(
|
|
116
|
+
private readonly worker: Worker,
|
|
117
|
+
{ log = defaultLogger, initializationOptions }: ClientDependencies = {},
|
|
118
|
+
{
|
|
119
|
+
infoRenderer,
|
|
120
|
+
autocompleteOptions,
|
|
121
|
+
diagnosticRenderer,
|
|
122
|
+
linterOptions,
|
|
123
|
+
hoverRenderer,
|
|
124
|
+
hoverOptions,
|
|
125
|
+
}: CodeMirrorDependencies = {},
|
|
126
|
+
) {
|
|
127
|
+
this.client = new LanguageClient(worker, {
|
|
128
|
+
clientCapabilities,
|
|
129
|
+
initializationOptions,
|
|
130
|
+
log,
|
|
131
|
+
});
|
|
132
|
+
this.worker = worker;
|
|
133
|
+
this.infoRenderer = infoRenderer;
|
|
134
|
+
this.autocompleteExtension = lspComplete(autocompleteOptions);
|
|
135
|
+
|
|
136
|
+
this.diagnosticRenderer = diagnosticRenderer;
|
|
137
|
+
this.linterExtension = lspLinter(linterOptions);
|
|
138
|
+
|
|
139
|
+
this.hoverRenderer = hoverRenderer;
|
|
140
|
+
this.hoverExtension = lspHover(hoverOptions);
|
|
141
|
+
|
|
142
|
+
this.documentHighlightsExtension = lspDocumentHighlights();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async start() {
|
|
146
|
+
await this.client.start();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public async stop() {
|
|
150
|
+
try {
|
|
151
|
+
await this.client.stop();
|
|
152
|
+
} finally {
|
|
153
|
+
this.worker.terminate();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public extension(
|
|
158
|
+
fileUri: string,
|
|
159
|
+
{ shouldLint, shouldComplete, shouldHover }: FeatureFlags = {
|
|
160
|
+
shouldLint: true,
|
|
161
|
+
shouldComplete: true,
|
|
162
|
+
shouldHover: true,
|
|
163
|
+
},
|
|
164
|
+
): Extension[] {
|
|
165
|
+
return [
|
|
166
|
+
clientFacet.of(this.client),
|
|
167
|
+
serverCapabilitiesFacet.of(this.client.serverCapabilities),
|
|
168
|
+
fileUriFacet.of(fileUri),
|
|
169
|
+
textDocumentSync,
|
|
170
|
+
infoRendererFacet.of(this.infoRenderer),
|
|
171
|
+
diagnosticRendererFacet.of(this.diagnosticRenderer),
|
|
172
|
+
hoverRendererFacet.of(this.hoverRenderer),
|
|
173
|
+
this.documentHighlightsExtension,
|
|
174
|
+
]
|
|
175
|
+
.concat(shouldLint ? this.linterExtension : [])
|
|
176
|
+
.concat(shouldComplete ? this.autocompleteExtension : [])
|
|
177
|
+
.concat(shouldHover ? this.hoverExtension : []);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/* eslint-disable lines-between-class-members */
|
|
2
|
+
import {
|
|
3
|
+
CancellationToken,
|
|
4
|
+
CancellationTokenSource,
|
|
5
|
+
Disposable,
|
|
6
|
+
ExitNotification,
|
|
7
|
+
InitializeRequest,
|
|
8
|
+
InitializedNotification,
|
|
9
|
+
Message,
|
|
10
|
+
NotificationHandler,
|
|
11
|
+
NotificationMessage,
|
|
12
|
+
ProtocolNotificationType,
|
|
13
|
+
ProtocolRequestType,
|
|
14
|
+
ProtocolRequestType0,
|
|
15
|
+
RequestHandler,
|
|
16
|
+
RequestMessage,
|
|
17
|
+
ResponseError,
|
|
18
|
+
ResponseMessage,
|
|
19
|
+
ShutdownRequest,
|
|
20
|
+
MessageSignature,
|
|
21
|
+
ProtocolNotificationType0,
|
|
22
|
+
ServerCapabilities,
|
|
23
|
+
ClientCapabilities,
|
|
24
|
+
} from 'vscode-languageserver-protocol';
|
|
25
|
+
|
|
26
|
+
export interface PromiseCompletion {
|
|
27
|
+
resolve(value: unknown): void;
|
|
28
|
+
reject(error: unknown): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Dependencies {
|
|
32
|
+
clientCapabilities: ClientCapabilities;
|
|
33
|
+
initializationOptions?: any;
|
|
34
|
+
log(...args: any[]): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
|
+
|
|
39
|
+
// Useful for mocking/stubbing the client in our tests. aka our "public" API.
|
|
40
|
+
export interface AbstractLanguageClient {
|
|
41
|
+
clientCapabilities: ClientCapabilities;
|
|
42
|
+
serverCapabilities: ServerCapabilities | null;
|
|
43
|
+
serverInfo: any;
|
|
44
|
+
onRequest: LanguageClient['onRequest'];
|
|
45
|
+
onNotification: LanguageClient['onNotification'];
|
|
46
|
+
sendRequest: LanguageClient['sendRequest'];
|
|
47
|
+
sendNotification: LanguageClient['sendNotification'];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class LanguageClient extends EventTarget implements AbstractLanguageClient {
|
|
51
|
+
public readonly clientCapabilities: ClientCapabilities;
|
|
52
|
+
public readonly initializationOptions: any;
|
|
53
|
+
public serverCapabilities: ServerCapabilities | null;
|
|
54
|
+
public serverInfo: any;
|
|
55
|
+
|
|
56
|
+
private requestId: number;
|
|
57
|
+
private requests: Map<string, PromiseCompletion>;
|
|
58
|
+
private dispose: () => void;
|
|
59
|
+
private disposables: Disposable[];
|
|
60
|
+
private log: Dependencies['log'];
|
|
61
|
+
|
|
62
|
+
constructor(public readonly worker: Worker, dependencies: Dependencies) {
|
|
63
|
+
super();
|
|
64
|
+
this.requests = new Map();
|
|
65
|
+
this.requestId = 0;
|
|
66
|
+
this.dispose = () => {};
|
|
67
|
+
this.disposables = [];
|
|
68
|
+
this.clientCapabilities = dependencies.clientCapabilities;
|
|
69
|
+
this.initializationOptions = dependencies.initializationOptions;
|
|
70
|
+
this.log = dependencies.log;
|
|
71
|
+
this.serverCapabilities = null;
|
|
72
|
+
this.serverInfo = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Lifecycle method to start the server
|
|
77
|
+
*/
|
|
78
|
+
async start() {
|
|
79
|
+
/**
|
|
80
|
+
* Here we setup the web worker event handler.
|
|
81
|
+
* We route the message sent via the worker's postMessage to our event handler.
|
|
82
|
+
*/
|
|
83
|
+
const handler = (ev: MessageEvent<Message>) => this.handleMessage(ev.data);
|
|
84
|
+
this.worker.addEventListener('message', handler);
|
|
85
|
+
this.dispose = () => {
|
|
86
|
+
this.worker.removeEventListener('message', handler);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* We send the `initialize` request and obtain the capabilities supported by
|
|
91
|
+
* the language server.
|
|
92
|
+
*
|
|
93
|
+
* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
|
|
94
|
+
*/
|
|
95
|
+
const response = await this.sendRequest(InitializeRequest.type, {
|
|
96
|
+
capabilities: this.clientCapabilities,
|
|
97
|
+
initializationOptions: this.initializationOptions,
|
|
98
|
+
processId: 0,
|
|
99
|
+
rootUri: 'browser:///',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* We unpack the response from the server and remember the capabilities.
|
|
104
|
+
* Those will be useful.
|
|
105
|
+
*/
|
|
106
|
+
this.serverCapabilities = response.capabilities;
|
|
107
|
+
this.serverInfo = response.serverInfo;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Once we're ready, we send the `initialized` notification to tell the
|
|
111
|
+
* server that we're ready to roll!
|
|
112
|
+
*
|
|
113
|
+
* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialized
|
|
114
|
+
*/
|
|
115
|
+
this.sendNotification(InitializedNotification.type, {});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Lifecycle method to stop the server
|
|
120
|
+
*/
|
|
121
|
+
async stop() {
|
|
122
|
+
await Promise.race([this.sendRequest(ShutdownRequest.type), sleep(1000)]);
|
|
123
|
+
this.disposables.forEach((disposable) => disposable.dispose());
|
|
124
|
+
this.disposables = [];
|
|
125
|
+
this.dispose();
|
|
126
|
+
this.sendNotification(ExitNotification.type);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* For when you want to handle requests sent from the language server.
|
|
131
|
+
*
|
|
132
|
+
* @param {MessageSignature} type this is an export from vscode-languageserver-protocol
|
|
133
|
+
* @param {RequestHandler} handler a type-inferred event handler whose return value matches the MessageSignature
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* import {ApplyWorkspaceEditRequest, ApplyWorkspaceEditResponse} from 'vscode-languageserver-protocol';
|
|
137
|
+
* client.onRequest(ApplyWorkspaceEditRequest.type, (params, cancellationToken) => {
|
|
138
|
+
* // `params` and `cancellationToken` are typed inferred
|
|
139
|
+
* // The return value must match the MessageSignature (ApplyWorkspaceEditResponse)
|
|
140
|
+
* return result as ApplyWorkspaceEditResponse;
|
|
141
|
+
* })
|
|
142
|
+
*/
|
|
143
|
+
onRequest<P, R, PR, E, RO>(
|
|
144
|
+
type: ProtocolRequestType<P, R, PR, E, RO>,
|
|
145
|
+
handler: RequestHandler<P, R, E>,
|
|
146
|
+
): Disposable;
|
|
147
|
+
onRequest<P>(type: string | MessageSignature, handler: RequestHandler<P, any, any>): Disposable {
|
|
148
|
+
const method = typeof type === 'string' ? type : type.method;
|
|
149
|
+
const callback = async (event: Event) => {
|
|
150
|
+
const { params, id } = (event as CustomEvent<RequestMessage>).detail;
|
|
151
|
+
const cancellationToken: CancellationToken = new CancellationTokenSource().token;
|
|
152
|
+
try {
|
|
153
|
+
const response = await handler(params as any as P, cancellationToken);
|
|
154
|
+
if (response && typeof response === 'object' && 'code' in response) {
|
|
155
|
+
this.sendResponse(id!, undefined, response as any as ResponseError);
|
|
156
|
+
} else {
|
|
157
|
+
this.sendResponse(id!, response, undefined);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.sendResponse(
|
|
161
|
+
id!,
|
|
162
|
+
undefined,
|
|
163
|
+
new ResponseError(1, error instanceof Error ? error.message : (error as string)),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
this.addEventListener(method, callback);
|
|
169
|
+
return this.disposable(() => this.removeEventListener(method, callback));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* For when you want to handle notifications sent from the language server.
|
|
174
|
+
*
|
|
175
|
+
* @param {MessageSignature} type this is an export from vscode-languageserver-protocol
|
|
176
|
+
* @param {NotificationHandler} handler this is a type-inferred event handler
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* import {PublishDiagnosticsNotification} from 'vscode-languageserver-protocol';
|
|
180
|
+
* client.onNotification(PublishDiagnosticsNotification.type, (params) => {
|
|
181
|
+
* // the type of `params` is inferred from the first argument
|
|
182
|
+
* });
|
|
183
|
+
*/
|
|
184
|
+
onNotification<P, RO>(
|
|
185
|
+
type: ProtocolNotificationType<P, RO>,
|
|
186
|
+
handler: NotificationHandler<P>,
|
|
187
|
+
): Disposable;
|
|
188
|
+
onNotification<P>(type: string | MessageSignature, handler: NotificationHandler<P>): Disposable {
|
|
189
|
+
const method = typeof type === 'string' ? type : type.method;
|
|
190
|
+
const callback = (event: Event) => {
|
|
191
|
+
const { params } = (event as CustomEvent<NotificationMessage>).detail;
|
|
192
|
+
handler(params as any as P);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
this.addEventListener(method, callback);
|
|
196
|
+
return this.disposable(() => this.removeEventListener(method, callback));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Send a request to the language server and await a response.
|
|
201
|
+
*
|
|
202
|
+
* @param {MessageSignature} type this is an export from vscode-languageserver-protocol
|
|
203
|
+
* @param [params] its type is inferred from the first argument
|
|
204
|
+
* @returns {any} response, its type is inferred from the first argument
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
*
|
|
208
|
+
* import {CompletionRequest} from 'vscode-languageserver-protocol';
|
|
209
|
+
* const completions = await client.sendRequest(
|
|
210
|
+
* CompletionRequest.type,
|
|
211
|
+
* params
|
|
212
|
+
* );
|
|
213
|
+
*/
|
|
214
|
+
public sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>): Promise<R>;
|
|
215
|
+
public sendRequest<P, R, PR, E, RO>(
|
|
216
|
+
type: ProtocolRequestType<P, R, PR, E, RO>,
|
|
217
|
+
params?: P,
|
|
218
|
+
): Promise<R>;
|
|
219
|
+
public async sendRequest<R>(type: string | MessageSignature, params?: any): Promise<R> {
|
|
220
|
+
this.requestId += 1;
|
|
221
|
+
const requestId = this.requestId;
|
|
222
|
+
const method = typeof type === 'string' ? type : type.method;
|
|
223
|
+
const request: RequestMessage = {
|
|
224
|
+
jsonrpc: '2.0',
|
|
225
|
+
id: requestId,
|
|
226
|
+
method,
|
|
227
|
+
};
|
|
228
|
+
if (params) request.params = params;
|
|
229
|
+
|
|
230
|
+
this.log(`Client->Server ${request.method} request [${request.id}]`, request);
|
|
231
|
+
this.sendMessage(request);
|
|
232
|
+
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
this.requests.set(requestId.toString(), { resolve, reject });
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Send a notification to the language server. Notifications are fire and forget.
|
|
240
|
+
*
|
|
241
|
+
* @param {MessageSignature} type this is an export from vscode-languageserver-protocol
|
|
242
|
+
* @param [params] its type is inferred from the first argument
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
*
|
|
246
|
+
* import {DidChangeTextDocumentNotification} from 'vscode-languageserver-protocol';
|
|
247
|
+
* client.sendNotification(
|
|
248
|
+
* DidChangeTextDocumentNotification.type,
|
|
249
|
+
* params
|
|
250
|
+
* );
|
|
251
|
+
*/
|
|
252
|
+
public sendNotification<RO>(type: ProtocolNotificationType0<RO>): void;
|
|
253
|
+
public sendNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params?: P): void;
|
|
254
|
+
public sendNotification(type: string | MessageSignature, params?: any): void {
|
|
255
|
+
const method = typeof type === 'string' ? type : type.method;
|
|
256
|
+
const notification: NotificationMessage = {
|
|
257
|
+
jsonrpc: '2.0',
|
|
258
|
+
method,
|
|
259
|
+
};
|
|
260
|
+
if (params) notification.params = params;
|
|
261
|
+
this.log(`Client->Server ${method} notification`, notification);
|
|
262
|
+
this.sendMessage(notification);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private sendResponse(id: number | string, result: any, error?: ResponseError) {
|
|
266
|
+
const response: ResponseMessage = { jsonrpc: '2.0', id };
|
|
267
|
+
if (result !== undefined) response.result = result;
|
|
268
|
+
if (error !== undefined) response.error = error.toJson();
|
|
269
|
+
this.log(`Client->Server response [${id}]`, response);
|
|
270
|
+
this.sendMessage(response);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private sendMessage(message: Message) {
|
|
274
|
+
this.worker.postMessage(message);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Map messages to their correct destition.
|
|
279
|
+
* - Responses -> resolve or reject the corresponding Request
|
|
280
|
+
* - Notification -> emit(message.method, message)
|
|
281
|
+
* - Request -> emit(message.method, message)
|
|
282
|
+
*/
|
|
283
|
+
private handleMessage(message: any) {
|
|
284
|
+
if (isResponse(message)) {
|
|
285
|
+
this.log(`Server->Client response [${message.id}]`, message);
|
|
286
|
+
const id = message.id!.toString();
|
|
287
|
+
if ('result' in message) {
|
|
288
|
+
this.requests.get(id)!.resolve(message.result!);
|
|
289
|
+
} else {
|
|
290
|
+
this.requests.get(id)!.reject(message.error);
|
|
291
|
+
}
|
|
292
|
+
this.requests.delete(id);
|
|
293
|
+
} else if (isRequest(message)) {
|
|
294
|
+
this.log(`Server->Client ${message.method} request [${message.id}]`, message);
|
|
295
|
+
this.dispatchEvent(new CustomEvent<RequestMessage>(message.method, { detail: message }));
|
|
296
|
+
} else if (isNotification(message)) {
|
|
297
|
+
this.log(`Server->Client ${message.method} notification`, message);
|
|
298
|
+
this.dispatchEvent(
|
|
299
|
+
new CustomEvent(message.method, {
|
|
300
|
+
detail: message,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
this.log('what?', message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private disposable(dispose: () => void) {
|
|
309
|
+
const disp = disposable(dispose);
|
|
310
|
+
this.disposables.push(disp);
|
|
311
|
+
return disp;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function disposable(dispose: () => void): Disposable {
|
|
316
|
+
return { dispose };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isResponse(message: any): message is ResponseMessage {
|
|
320
|
+
return 'id' in message && ('error' in message || 'result' in message);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isRequest(message: any): message is RequestMessage {
|
|
324
|
+
return 'id' in message && 'method' in message;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isNotification(message: any): message is NotificationMessage {
|
|
328
|
+
return !('id' in message) && 'method' in message;
|
|
329
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Facet } from '@codemirror/state';
|
|
2
|
+
|
|
3
|
+
import { AbstractLanguageClient } from '../LanguageClient';
|
|
4
|
+
import { ServerCapabilities } from 'vscode-languageserver-protocol';
|
|
5
|
+
|
|
6
|
+
export const clientFacet = Facet.define<AbstractLanguageClient, AbstractLanguageClient>({
|
|
7
|
+
combine: (values) => values[0],
|
|
8
|
+
static: true,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const fileUriFacet = Facet.define<string, string>({
|
|
12
|
+
combine: (values) => values[0],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const serverCapabilitiesFacet = Facet.define<ServerCapabilities | null, ServerCapabilities>({
|
|
16
|
+
combine: (values) => values[0] ?? {},
|
|
17
|
+
});
|