codemirror-languageserver 1.5.1 → 1.8.0

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,8 +1,89 @@
1
- interface LanguageServerOptions {
2
- serverUri: `ws://${string}` | `wss://${string}`;
3
- rootUri: string;
1
+ import { EditorView, Tooltip, ViewUpdate, PluginValue } from '@codemirror/view';
2
+ import * as LSP from 'vscode-languageserver-protocol';
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 {
72
+ rootUri: string | null;
73
+ workspaceFolders: LSP.WorkspaceFolder[] | null;
4
74
  documentUri: string;
5
75
  languageId: string;
6
76
  }
7
- declare function languageServer(options: LanguageServerOptions): import("@codemirror/state").Extension[];
8
- 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,58 +1,32 @@
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 });
12
+ const client = Facet.define({ combine: useLast });
15
13
  const documentUri = Facet.define({ combine: useLast });
16
14
  const languageId = Facet.define({ combine: useLast });
17
- class LanguageServerPlugin {
18
- constructor(view) {
19
- this.view = view;
20
- this.rootUri = this.view.state.facet(rootUri);
21
- this.documentUri = this.view.state.facet(documentUri);
22
- this.languageId = this.view.state.facet(languageId);
23
- this.documentVersion = 0;
24
- this.changesTimeout = 0;
25
- 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;
26
22
  this.requestManager = new RequestManager([this.transport]);
27
23
  this.client = new Client(this.requestManager);
28
24
  this.client.onNotification((data) => {
29
25
  this.processNotification(data);
30
26
  });
31
- this.initialize({
32
- documentText: this.view.state.doc.toString(),
33
- });
34
- }
35
- update({ docChanged }) {
36
- if (!docChanged)
37
- return;
38
- if (this.changesTimeout)
39
- clearTimeout(this.changesTimeout);
40
- this.changesTimeout = self.setTimeout(() => {
41
- this.sendChange({
42
- documentText: this.view.state.doc.toString(),
43
- });
44
- }, changesDelay);
45
- }
46
- destroy() {
47
- this.client.close();
48
- }
49
- request(method, params, timeout) {
50
- return this.client.request({ method, params }, timeout);
27
+ this.initialize();
51
28
  }
52
- notify(method, params) {
53
- return this.client.notify({ method, params });
54
- }
55
- async initialize({ documentText }) {
29
+ async initialize() {
56
30
  const { capabilities } = await this.request('initialize', {
57
31
  capabilities: {
58
32
  textDocument: {
@@ -110,30 +84,91 @@ class LanguageServerPlugin {
110
84
  initializationOptions: null,
111
85
  processId: null,
112
86
  rootUri: this.rootUri,
113
- workspaceFolders: [
114
- {
115
- name: 'root',
116
- uri: this.rootUri,
117
- },
118
- ],
87
+ workspaceFolders: this.workspaceFolders,
119
88
  }, timeout * 3);
120
89
  this.capabilities = capabilities;
121
90
  this.notify('initialized', {});
122
- this.notify('textDocument/didOpen', {
91
+ this.ready = true;
92
+ }
93
+ close() {
94
+ this.client.close();
95
+ }
96
+ textDocumentDidOpen(params) {
97
+ return this.notify('textDocument/didOpen', params);
98
+ }
99
+ textDocumentDidChange(params) {
100
+ return this.notify('textDocument/didChange', params);
101
+ }
102
+ async textDocumentHover(params) {
103
+ return await this.request('textDocument/hover', params, timeout);
104
+ }
105
+ async textDocumentCompletion(params) {
106
+ return await this.request('textDocument/completion', params, timeout);
107
+ }
108
+ attachPlugin(plugin) {
109
+ this.plugins.push(plugin);
110
+ }
111
+ detachPlugin(plugin) {
112
+ const i = this.plugins.indexOf(plugin);
113
+ if (i === -1)
114
+ return;
115
+ this.plugins.splice(i, 1);
116
+ if (this.autoClose)
117
+ this.close();
118
+ }
119
+ request(method, params, timeout) {
120
+ return this.client.request({ method, params }, timeout);
121
+ }
122
+ notify(method, params) {
123
+ return this.client.notify({ method, params });
124
+ }
125
+ processNotification(notification) {
126
+ for (const plugin of this.plugins)
127
+ plugin.processNotification(notification);
128
+ }
129
+ }
130
+ class LanguageServerPlugin {
131
+ constructor(view) {
132
+ this.view = view;
133
+ this.client = this.view.state.facet(client);
134
+ this.documentUri = this.view.state.facet(documentUri);
135
+ this.languageId = this.view.state.facet(languageId);
136
+ this.documentVersion = 0;
137
+ this.changesTimeout = 0;
138
+ this.client.attachPlugin(this);
139
+ this.initialize({
140
+ documentText: this.view.state.doc.toString(),
141
+ });
142
+ }
143
+ update({ docChanged }) {
144
+ if (!docChanged)
145
+ return;
146
+ if (this.changesTimeout)
147
+ clearTimeout(this.changesTimeout);
148
+ this.changesTimeout = self.setTimeout(() => {
149
+ this.sendChange({
150
+ documentText: this.view.state.doc.toString(),
151
+ });
152
+ }, changesDelay);
153
+ }
154
+ destroy() {
155
+ this.client.detachPlugin(this);
156
+ }
157
+ async initialize({ documentText }) {
158
+ this.client.textDocumentDidOpen({
123
159
  textDocument: {
124
160
  uri: this.documentUri,
125
161
  languageId: this.languageId,
126
162
  text: documentText,
127
163
  version: this.documentVersion,
128
- },
164
+ }
129
165
  });
130
- this.ready = true;
131
166
  }
132
167
  async sendChange({ documentText }) {
133
- if (!this.ready)
168
+ if (!this.client.ready)
134
169
  return;
135
170
  try {
136
- await this.notify('textDocument/didChange', {
171
+ await this.client.textDocumentDidChange({
137
172
  textDocument: {
138
173
  uri: this.documentUri,
139
174
  version: this.documentVersion++,
@@ -149,13 +184,13 @@ class LanguageServerPlugin {
149
184
  this.sendChange({ documentText: view.state.doc.toString() });
150
185
  }
151
186
  async requestHoverTooltip(view, { line, character }) {
152
- if (!this.ready || !this.capabilities.hoverProvider)
187
+ if (!this.client.ready || !this.client.capabilities.hoverProvider)
153
188
  return null;
154
189
  this.sendChange({ documentText: view.state.doc.toString() });
155
- const result = await this.request('textDocument/hover', {
190
+ const result = await this.client.textDocumentHover({
156
191
  textDocument: { uri: this.documentUri },
157
192
  position: { line, character },
158
- }, timeout);
193
+ });
159
194
  if (!result)
160
195
  return null;
161
196
  const { contents, range } = result;
@@ -173,19 +208,19 @@ class LanguageServerPlugin {
173
208
  return { pos, end, create: (view) => ({ dom }), above: true };
174
209
  }
175
210
  async requestCompletion(context, { line, character }, { triggerKind, triggerCharacter, }) {
176
- if (!this.ready || !this.capabilities.completionProvider)
211
+ if (!this.client.ready || !this.client.capabilities.completionProvider)
177
212
  return null;
178
213
  this.sendChange({
179
214
  documentText: context.state.doc.toString(),
180
215
  });
181
- const result = await this.request('textDocument/completion', {
216
+ const result = await this.client.textDocumentCompletion({
182
217
  textDocument: { uri: this.documentUri },
183
218
  position: { line, character },
184
219
  context: {
185
220
  triggerKind,
186
221
  triggerCharacter,
187
- },
188
- }, timeout);
222
+ }
223
+ });
189
224
  if (!result)
190
225
  return null;
191
226
  const items = 'items' in result ? result.items : result;
@@ -243,6 +278,8 @@ class LanguageServerPlugin {
243
278
  }
244
279
  }
245
280
  processDiagnostics(params) {
281
+ if (params.uri !== this.documentUri)
282
+ return;
246
283
  const diagnostics = params.diagnostics
247
284
  .map(({ range, message, severity }) => ({
248
285
  from: posToOffset(this.view.state.doc, range.start),
@@ -269,10 +306,17 @@ class LanguageServerPlugin {
269
306
  }
270
307
  }
271
308
  function languageServer(options) {
309
+ const serverUri = options.serverUri;
310
+ delete options.serverUri;
311
+ return languageServerWithTransport({
312
+ ...options,
313
+ transport: new WebSocketTransport(serverUri)
314
+ });
315
+ }
316
+ function languageServerWithTransport(options) {
272
317
  let plugin = null;
273
318
  return [
274
- serverUri.of(options.serverUri),
275
- rootUri.of(options.rootUri),
319
+ client.of(options.client || new LanguageServerClient({ ...options, autoClose: true })),
276
320
  documentUri.of(options.documentUri),
277
321
  languageId.of(options.languageId),
278
322
  ViewPlugin.define((view) => (plugin = new LanguageServerPlugin(view))),
@@ -291,7 +335,7 @@ function languageServer(options) {
291
335
  let trigKind = CompletionTriggerKind.Invoked;
292
336
  let trigChar;
293
337
  if (!explicit &&
294
- ((_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]))) {
338
+ ((_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]))) {
295
339
  trigKind = CompletionTriggerKind.TriggerCharacter;
296
340
  trigChar = line.text[pos - line.from - 1];
297
341
  }
@@ -371,4 +415,4 @@ const baseTheme = EditorView.baseTheme({
371
415
  },
372
416
  });
373
417
 
374
- export { languageServer };
418
+ export { LanguageServerClient, languageServer, languageServerWithTransport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemirror-languageserver",
3
- "version": "1.5.1",
3
+ "version": "1.8.0",
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
  },
@@ -1,10 +0,0 @@
1
- interface LanguageServerOptions {
2
- serverUri: `ws://${string}` | `wss://${string}`;
3
- rootUri: string;
4
- documentUri: string;
5
- languageId: string;
6
- onConnect(): void;
7
- onDisconnect(): void;
8
- }
9
- declare function languageServer(options: LanguageServerOptions): import("@codemirror/state").Extension[];
10
- export { languageServer };