codemirror-languageserver 1.6.0 → 1.8.1

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/README.md CHANGED
@@ -15,10 +15,20 @@ npm i codemirror-languageserver
15
15
  ``` js
16
16
  import { languageServer } from 'codemirror-languageserver';
17
17
 
18
+ const transport = new WebSocketTransport(serverUri)
19
+
18
20
  var ls = languageServer({
19
- serverUri: serverUri
20
- rootUri: 'file:///'
21
- documentUri: `file:///${filename}`
21
+ // WebSocket server uri and other client options.
22
+ serverUri,
23
+ rootUri: 'file:///',
24
+
25
+ // Alternatively, to share the same client across multiple instances of this plugin.
26
+ client: new LanguageServerClient({
27
+ serverUri,
28
+ rootUri: 'file:///'
29
+ }),
30
+
31
+ documentUri: `file:///${filename}`,
22
32
  languageId: 'cpp' // As defined at https://microsoft.github.io/language-server-protocol/specification#textDocumentItem.
23
33
  });
24
34
 
package/dist/index.d.ts CHANGED
@@ -1,10 +1,89 @@
1
+ import { EditorView, Tooltip, ViewUpdate, PluginValue } from '@codemirror/view';
1
2
  import * as LSP from 'vscode-languageserver-protocol';
2
- interface LanguageServerOptions {
3
- serverUri: `ws://${string}` | `wss://${string}`;
3
+ import { CompletionTriggerKind, PublishDiagnosticsParams } from "vscode-languageserver-protocol";
4
+ import { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
5
+ import { Transport } from '@open-rpc/client-js/build/transports/Transport';
6
+ interface LSPEventMap {
7
+ 'textDocument/publishDiagnostics': LSP.PublishDiagnosticsParams;
8
+ }
9
+ type Notification = {
10
+ [key in keyof LSPEventMap]: {
11
+ jsonrpc: '2.0';
12
+ id?: null | undefined;
13
+ method: key;
14
+ params: LSPEventMap[key];
15
+ };
16
+ }[keyof LSPEventMap];
17
+ declare class LanguageServerClient {
18
+ private rootUri;
19
+ private workspaceFolders;
20
+ private autoClose?;
21
+ private transport;
22
+ private requestManager;
23
+ private client;
24
+ ready: boolean;
25
+ capabilities: LSP.ServerCapabilities<any>;
26
+ private plugins;
27
+ constructor(options: LanguageServerClientOptions);
28
+ initialize(): Promise<void>;
29
+ close(): void;
30
+ textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams): Promise<LSP.DidOpenTextDocumentParams>;
31
+ textDocumentDidChange(params: LSP.DidChangeTextDocumentParams): Promise<LSP.DidChangeTextDocumentParams>;
32
+ textDocumentHover(params: LSP.HoverParams): Promise<LSP.Hover>;
33
+ textDocumentCompletion(params: LSP.CompletionParams): Promise<LSP.CompletionList | LSP.CompletionItem[]>;
34
+ attachPlugin(plugin: LanguageServerPlugin): void;
35
+ detachPlugin(plugin: LanguageServerPlugin): void;
36
+ private request;
37
+ private notify;
38
+ private processNotification;
39
+ }
40
+ declare class LanguageServerPlugin implements PluginValue {
41
+ private view;
42
+ client: LanguageServerClient;
43
+ private documentUri;
44
+ private languageId;
45
+ private documentVersion;
46
+ private changesTimeout;
47
+ constructor(view: EditorView);
48
+ update({ docChanged }: ViewUpdate): void;
49
+ destroy(): void;
50
+ initialize({ documentText }: {
51
+ documentText: string;
52
+ }): Promise<void>;
53
+ sendChange({ documentText }: {
54
+ documentText: string;
55
+ }): Promise<void>;
56
+ requestDiagnostics(view: EditorView): void;
57
+ requestHoverTooltip(view: EditorView, { line, character }: {
58
+ line: number;
59
+ character: number;
60
+ }): Promise<Tooltip | null>;
61
+ requestCompletion(context: CompletionContext, { line, character }: {
62
+ line: number;
63
+ character: number;
64
+ }, { triggerKind, triggerCharacter, }: {
65
+ triggerKind: CompletionTriggerKind;
66
+ triggerCharacter: string | undefined;
67
+ }): Promise<CompletionResult | null>;
68
+ processNotification(notification: Notification): void;
69
+ processDiagnostics(params: PublishDiagnosticsParams): void;
70
+ }
71
+ interface LanguageServerBaseOptions {
4
72
  rootUri: string | null;
5
73
  workspaceFolders: LSP.WorkspaceFolder[] | null;
6
74
  documentUri: string;
7
75
  languageId: string;
8
76
  }
9
- declare function languageServer(options: LanguageServerOptions): import("@codemirror/state").Extension[];
10
- export { languageServer };
77
+ interface LanguageServerClientOptions extends LanguageServerBaseOptions {
78
+ transport: Transport;
79
+ autoClose?: boolean;
80
+ }
81
+ interface LanguageServerOptions extends LanguageServerClientOptions {
82
+ client?: LanguageServerClient;
83
+ }
84
+ interface LanguageServerWebsocketOptions extends LanguageServerBaseOptions {
85
+ serverUri: `ws://${string}` | `wss://${string}`;
86
+ }
87
+ declare function languageServer(options: LanguageServerWebsocketOptions): import("@codemirror/state").Extension[];
88
+ declare function languageServerWithTransport(options: LanguageServerOptions): import("@codemirror/state").Extension[];
89
+ export { LanguageServerClient, languageServer, languageServerWithTransport };
package/dist/index.js CHANGED
@@ -1,65 +1,47 @@
1
1
  import { autocompletion } from '@codemirror/autocomplete';
2
2
  import { setDiagnostics } from '@codemirror/lint';
3
3
  import { Facet } from '@codemirror/state';
4
- import { hoverTooltip } from '@codemirror/tooltip';
5
- import { ViewPlugin, EditorView } from '@codemirror/view';
6
- import { WebSocketTransport, RequestManager, Client } from '@open-rpc/client-js';
4
+ import { ViewPlugin, hoverTooltip, EditorView } from '@codemirror/view';
5
+ import { RequestManager, Client, WebSocketTransport } from '@open-rpc/client-js';
7
6
  import { CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity } from 'vscode-languageserver-protocol';
8
7
 
9
8
  const timeout = 10000;
10
9
  const changesDelay = 500;
11
10
  const CompletionItemKindMap = Object.fromEntries(Object.entries(CompletionItemKind).map(([key, value]) => [value, key]));
12
11
  const useLast = (values) => values.reduce((_, v) => v, '');
13
- const serverUri = Facet.define({ combine: useLast });
14
- const rootUri = Facet.define({ combine: useLast });
15
- const workspaceFolders = Facet.define({ combine: useLast });
12
+ const client = Facet.define({ combine: useLast });
16
13
  const documentUri = Facet.define({ combine: useLast });
17
14
  const languageId = Facet.define({ combine: useLast });
18
- class LanguageServerPlugin {
19
- constructor(view) {
20
- this.view = view;
21
- this.rootUri = this.view.state.facet(rootUri);
22
- this.workspaceFolders = this.view.state.facet(workspaceFolders);
23
- this.documentUri = this.view.state.facet(documentUri);
24
- this.languageId = this.view.state.facet(languageId);
25
- this.documentVersion = 0;
26
- this.changesTimeout = 0;
27
- this.transport = new WebSocketTransport(this.view.state.facet(serverUri));
15
+ class LanguageServerClient {
16
+ constructor(options) {
17
+ this.rootUri = options.rootUri;
18
+ this.workspaceFolders = options.workspaceFolders;
19
+ this.autoClose = options.autoClose;
20
+ this.plugins = [];
21
+ this.transport = options.transport;
28
22
  this.requestManager = new RequestManager([this.transport]);
29
23
  this.client = new Client(this.requestManager);
30
24
  this.client.onNotification((data) => {
31
25
  this.processNotification(data);
32
26
  });
33
- this.initialize({
34
- documentText: this.view.state.doc.toString(),
35
- });
36
- this.transport.connection.addEventListener('message', (message) => {
37
- const data = JSON.parse(message.data);
38
- if (data.method && data.id)
39
- this.processRequest(data);
40
- });
41
- }
42
- update({ docChanged }) {
43
- if (!docChanged)
44
- return;
45
- if (this.changesTimeout)
46
- clearTimeout(this.changesTimeout);
47
- this.changesTimeout = self.setTimeout(() => {
48
- this.sendChange({
49
- documentText: this.view.state.doc.toString(),
27
+ const webSocketTransport = this.transport;
28
+ if (webSocketTransport && webSocketTransport.connection) {
29
+ // XXX(hjr265): Need a better way to do this. Relevant issue:
30
+ // https://github.com/FurqanSoftware/codemirror-languageserver/issues/9
31
+ webSocketTransport.connection.addEventListener('message', (message) => {
32
+ const data = JSON.parse(message.data);
33
+ if (data.method && data.id) {
34
+ webSocketTransport.connection.send(JSON.stringify({
35
+ jsonrpc: '2.0',
36
+ id: data.id,
37
+ result: null
38
+ }));
39
+ }
50
40
  });
51
- }, changesDelay);
52
- }
53
- destroy() {
54
- this.client.close();
55
- }
56
- request(method, params, timeout) {
57
- return this.client.request({ method, params }, timeout);
58
- }
59
- notify(method, params) {
60
- return this.client.notify({ method, params });
41
+ }
42
+ this.initialize();
61
43
  }
62
- async initialize({ documentText }) {
44
+ async initialize() {
63
45
  const { capabilities } = await this.request('initialize', {
64
46
  capabilities: {
65
47
  textDocument: {
@@ -121,21 +103,87 @@ class LanguageServerPlugin {
121
103
  }, timeout * 3);
122
104
  this.capabilities = capabilities;
123
105
  this.notify('initialized', {});
124
- this.notify('textDocument/didOpen', {
106
+ this.ready = true;
107
+ }
108
+ close() {
109
+ this.client.close();
110
+ }
111
+ textDocumentDidOpen(params) {
112
+ return this.notify('textDocument/didOpen', params);
113
+ }
114
+ textDocumentDidChange(params) {
115
+ return this.notify('textDocument/didChange', params);
116
+ }
117
+ async textDocumentHover(params) {
118
+ return await this.request('textDocument/hover', params, timeout);
119
+ }
120
+ async textDocumentCompletion(params) {
121
+ return await this.request('textDocument/completion', params, timeout);
122
+ }
123
+ attachPlugin(plugin) {
124
+ this.plugins.push(plugin);
125
+ }
126
+ detachPlugin(plugin) {
127
+ const i = this.plugins.indexOf(plugin);
128
+ if (i === -1)
129
+ return;
130
+ this.plugins.splice(i, 1);
131
+ if (this.autoClose)
132
+ this.close();
133
+ }
134
+ request(method, params, timeout) {
135
+ return this.client.request({ method, params }, timeout);
136
+ }
137
+ notify(method, params) {
138
+ return this.client.notify({ method, params });
139
+ }
140
+ processNotification(notification) {
141
+ for (const plugin of this.plugins)
142
+ plugin.processNotification(notification);
143
+ }
144
+ }
145
+ class LanguageServerPlugin {
146
+ constructor(view) {
147
+ this.view = view;
148
+ this.client = this.view.state.facet(client);
149
+ this.documentUri = this.view.state.facet(documentUri);
150
+ this.languageId = this.view.state.facet(languageId);
151
+ this.documentVersion = 0;
152
+ this.changesTimeout = 0;
153
+ this.client.attachPlugin(this);
154
+ this.initialize({
155
+ documentText: this.view.state.doc.toString(),
156
+ });
157
+ }
158
+ update({ docChanged }) {
159
+ if (!docChanged)
160
+ return;
161
+ if (this.changesTimeout)
162
+ clearTimeout(this.changesTimeout);
163
+ this.changesTimeout = self.setTimeout(() => {
164
+ this.sendChange({
165
+ documentText: this.view.state.doc.toString(),
166
+ });
167
+ }, changesDelay);
168
+ }
169
+ destroy() {
170
+ this.client.detachPlugin(this);
171
+ }
172
+ async initialize({ documentText }) {
173
+ this.client.textDocumentDidOpen({
125
174
  textDocument: {
126
175
  uri: this.documentUri,
127
176
  languageId: this.languageId,
128
177
  text: documentText,
129
178
  version: this.documentVersion,
130
- },
179
+ }
131
180
  });
132
- this.ready = true;
133
181
  }
134
182
  async sendChange({ documentText }) {
135
- if (!this.ready)
183
+ if (!this.client.ready)
136
184
  return;
137
185
  try {
138
- await this.notify('textDocument/didChange', {
186
+ await this.client.textDocumentDidChange({
139
187
  textDocument: {
140
188
  uri: this.documentUri,
141
189
  version: this.documentVersion++,
@@ -151,13 +199,13 @@ class LanguageServerPlugin {
151
199
  this.sendChange({ documentText: view.state.doc.toString() });
152
200
  }
153
201
  async requestHoverTooltip(view, { line, character }) {
154
- if (!this.ready || !this.capabilities.hoverProvider)
202
+ if (!this.client.ready || !this.client.capabilities.hoverProvider)
155
203
  return null;
156
204
  this.sendChange({ documentText: view.state.doc.toString() });
157
- const result = await this.request('textDocument/hover', {
205
+ const result = await this.client.textDocumentHover({
158
206
  textDocument: { uri: this.documentUri },
159
207
  position: { line, character },
160
- }, timeout);
208
+ });
161
209
  if (!result)
162
210
  return null;
163
211
  const { contents, range } = result;
@@ -175,19 +223,19 @@ class LanguageServerPlugin {
175
223
  return { pos, end, create: (view) => ({ dom }), above: true };
176
224
  }
177
225
  async requestCompletion(context, { line, character }, { triggerKind, triggerCharacter, }) {
178
- if (!this.ready || !this.capabilities.completionProvider)
226
+ if (!this.client.ready || !this.client.capabilities.completionProvider)
179
227
  return null;
180
228
  this.sendChange({
181
229
  documentText: context.state.doc.toString(),
182
230
  });
183
- const result = await this.request('textDocument/completion', {
231
+ const result = await this.client.textDocumentCompletion({
184
232
  textDocument: { uri: this.documentUri },
185
233
  position: { line, character },
186
234
  context: {
187
235
  triggerKind,
188
236
  triggerCharacter,
189
- },
190
- }, timeout);
237
+ }
238
+ });
191
239
  if (!result)
192
240
  return null;
193
241
  const items = 'items' in result ? result.items : result;
@@ -233,13 +281,6 @@ class LanguageServerPlugin {
233
281
  options,
234
282
  };
235
283
  }
236
- processRequest({ id }) {
237
- this.transport.connection.send(JSON.stringify({
238
- jsonrpc: '2.0',
239
- id,
240
- result: null
241
- }));
242
- }
243
284
  processNotification(notification) {
244
285
  try {
245
286
  switch (notification.method) {
@@ -252,6 +293,8 @@ class LanguageServerPlugin {
252
293
  }
253
294
  }
254
295
  processDiagnostics(params) {
296
+ if (params.uri !== this.documentUri)
297
+ return;
255
298
  const diagnostics = params.diagnostics
256
299
  .map(({ range, message, severity }) => ({
257
300
  from: posToOffset(this.view.state.doc, range.start),
@@ -278,11 +321,17 @@ class LanguageServerPlugin {
278
321
  }
279
322
  }
280
323
  function languageServer(options) {
324
+ const serverUri = options.serverUri;
325
+ delete options.serverUri;
326
+ return languageServerWithTransport({
327
+ ...options,
328
+ transport: new WebSocketTransport(serverUri)
329
+ });
330
+ }
331
+ function languageServerWithTransport(options) {
281
332
  let plugin = null;
282
333
  return [
283
- serverUri.of(options.serverUri),
284
- workspaceFolders.of(options.workspaceFolders),
285
- rootUri.of(options.rootUri),
334
+ client.of(options.client || new LanguageServerClient({ ...options, autoClose: true })),
286
335
  documentUri.of(options.documentUri),
287
336
  languageId.of(options.languageId),
288
337
  ViewPlugin.define((view) => (plugin = new LanguageServerPlugin(view))),
@@ -301,7 +350,7 @@ function languageServer(options) {
301
350
  let trigKind = CompletionTriggerKind.Invoked;
302
351
  let trigChar;
303
352
  if (!explicit &&
304
- ((_c = (_b = (_a = plugin.capabilities) === null || _a === void 0 ? void 0 : _a.completionProvider) === null || _b === void 0 ? void 0 : _b.triggerCharacters) === null || _c === void 0 ? void 0 : _c.includes(line.text[pos - line.from - 1]))) {
353
+ ((_c = (_b = (_a = plugin.client.capabilities) === null || _a === void 0 ? void 0 : _a.completionProvider) === null || _b === void 0 ? void 0 : _b.triggerCharacters) === null || _c === void 0 ? void 0 : _c.includes(line.text[pos - line.from - 1]))) {
305
354
  trigKind = CompletionTriggerKind.TriggerCharacter;
306
355
  trigChar = line.text[pos - line.from - 1];
307
356
  }
@@ -381,4 +430,4 @@ const baseTheme = EditorView.baseTheme({
381
430
  },
382
431
  });
383
432
 
384
- export { languageServer };
433
+ export { LanguageServerClient, languageServer, languageServerWithTransport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemirror-languageserver",
3
- "version": "1.6.0",
3
+ "version": "1.8.1",
4
4
  "description": "Language Server Plugin for CodeMirror 6",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -30,11 +30,10 @@
30
30
  "typescript": "^4.2.4"
31
31
  },
32
32
  "dependencies": {
33
- "@codemirror/autocomplete": "^0.19.8",
34
- "@codemirror/lint": "^0.19.3",
35
- "@codemirror/state": "^0.19.8",
36
- "@codemirror/tooltip": "^0.19.14",
37
- "@codemirror/view": "^0.19.43",
33
+ "@codemirror/autocomplete": "^0.20.0",
34
+ "@codemirror/lint": "^0.20.0",
35
+ "@codemirror/state": "^0.20.0",
36
+ "@codemirror/view": "^0.20.0",
38
37
  "@open-rpc/client-js": "^1.7.1",
39
38
  "vscode-languageserver-protocol": "^3.16.0"
40
39
  },