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