codemirror-languageserver 1.20.0 → 1.22.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  This plugin connects a [CodeMirror 6](https://codemirror.net/) editor with a [Language Server](https://microsoft.github.io/language-server-protocol/) over WebSocket, providing IDE-like features in the browser.
6
6
 
7
- [How It Works](https://hjr265.me/blog/codemirror-lsp/)
7
+ [How It Works](https://hjr265.me/blog/codemirror-lsp/) | [Live Demo](https://furqansoftware.github.io/codemirror-languageserver/)
8
8
 
9
9
  ## Features
10
10
 
@@ -67,6 +67,28 @@ This sets up all built-in features: completion, hover, diagnostics, go-to-defini
67
67
  | `synchronizationMethod` | `SynchronizationMethod` | `'full'` or `'incremental'` document sync (default: `'full'`) |
68
68
  | `client` | `LanguageServerClient` | Share a client across multiple editor instances |
69
69
  | `onCapabilities` | `(capabilities) => void` | Called when server capabilities are available |
70
+ | `onError` | `(error: Error) => void` | Called when the connection encounters an error |
71
+ | `onClose` | `() => void` | Called when the connection is closed |
72
+
73
+ ## Error Handling
74
+
75
+ The plugin does not crash on connection failures. Use `onError` and `onClose` to handle disconnections:
76
+
77
+ ```js
78
+ languageServer({
79
+ serverUri: 'ws://localhost:8080',
80
+ rootUri: 'file:///',
81
+ documentUri: `file:///${filename}`,
82
+ languageId: 'cpp',
83
+ onError(error) {
84
+ console.error('LSP error:', error);
85
+ },
86
+ onClose() {
87
+ console.log('LSP connection closed');
88
+ // Recreate the editor extensions to reconnect
89
+ },
90
+ })
91
+ ```
70
92
 
71
93
  ## Shared Client
72
94
 
package/dist/index.js CHANGED
@@ -35,8 +35,10 @@ class JSONRPCClient {
35
35
  this.nextId = 0;
36
36
  this.pending = new Map();
37
37
  this.notificationHandlers = [];
38
+ this.errorHandlers = [];
39
+ this.closeHandlers = [];
38
40
  this.transport = transport;
39
- this.ready = new Promise((resolve, reject) => {
41
+ this.ready = new Promise((resolve) => {
40
42
  if (transport instanceof WebSocketTransport) {
41
43
  const ws = transport.connection;
42
44
  if (ws.readyState === WebSocket.OPEN) {
@@ -44,13 +46,27 @@ class JSONRPCClient {
44
46
  }
45
47
  else {
46
48
  ws.addEventListener('open', () => resolve());
47
- ws.addEventListener('error', () => reject(new Error('WebSocket connection failed')));
48
49
  }
49
50
  }
50
51
  else {
51
52
  resolve();
52
53
  }
53
54
  });
55
+ transport.onError((error) => {
56
+ for (const handler of this.errorHandlers) {
57
+ handler(error);
58
+ }
59
+ });
60
+ transport.onClose(() => {
61
+ for (const [, pending] of this.pending) {
62
+ clearTimeout(pending.timer);
63
+ pending.reject(new Error('Connection closed'));
64
+ }
65
+ this.pending.clear();
66
+ for (const handler of this.closeHandlers) {
67
+ handler();
68
+ }
69
+ });
54
70
  transport.onMessage((raw) => {
55
71
  let msg;
56
72
  try {
@@ -107,6 +123,12 @@ class JSONRPCClient {
107
123
  onNotification(handler) {
108
124
  this.notificationHandlers.push(handler);
109
125
  }
126
+ onError(handler) {
127
+ this.errorHandlers.push(handler);
128
+ }
129
+ onClose(handler) {
130
+ this.closeHandlers.push(handler);
131
+ }
110
132
  close() {
111
133
  for (const [id, pending] of this.pending) {
112
134
  clearTimeout(pending.timer);
@@ -157,7 +179,6 @@ function changeSetToEvents(doc, changes) {
157
179
  }
158
180
 
159
181
  const timeout = 10000;
160
- const changesDelay = 500;
161
182
  const CompletionItemKindMap = Object.fromEntries(Object.entries(CompletionItemKind).map(([key, value]) => [value, key]));
162
183
  const useLast = (values) => values.reduce((_, v) => v, '');
163
184
  Facet.define({
@@ -192,7 +213,19 @@ class LanguageServerClient {
192
213
  }
193
214
  this.processNotification(data);
194
215
  });
195
- this.initializePromise = this.initialize();
216
+ this.client.onError((error) => {
217
+ var _a, _b;
218
+ (_b = (_a = this.options).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
219
+ });
220
+ this.client.onClose(() => {
221
+ var _a, _b;
222
+ this.ready = false;
223
+ (_b = (_a = this.options).onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
224
+ });
225
+ this.initializePromise = this.initialize().catch((error) => {
226
+ var _a, _b;
227
+ (_b = (_a = this.options).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
228
+ });
196
229
  }
197
230
  getInitializeParams() {
198
231
  var _a;
@@ -359,8 +392,6 @@ var SynchronizationMethod;
359
392
  class LanguageServerPlugin {
360
393
  constructor(view, { client, documentUri, languageId, allowHTMLContent, synchronizationMethod, }) {
361
394
  this.view = view;
362
- this.changesTimeout = null;
363
- this.pendingChanges = null;
364
395
  this.client = client;
365
396
  this.documentUri = documentUri;
366
397
  this.languageId = languageId;
@@ -377,40 +408,20 @@ class LanguageServerPlugin {
377
408
  if (!docChanged) {
378
409
  return;
379
410
  }
380
- if (this.changesTimeout) {
381
- clearTimeout(this.changesTimeout);
382
- }
383
411
  switch (this.synchronizationMethod) {
384
412
  case SynchronizationMethod.Full:
385
- this.pendingChanges = [
413
+ this.sendChanges([
386
414
  {
387
415
  text: this.view.state.doc.toString(),
388
416
  },
389
- ];
417
+ ]);
390
418
  break;
391
419
  case SynchronizationMethod.Incremental:
392
- if (!this.pendingChanges) {
393
- this.pendingChanges = [];
394
- }
395
- this.pendingChanges.push(...changeSetToEvents(startState.doc, changes));
420
+ this.sendChanges(changeSetToEvents(startState.doc, changes));
396
421
  break;
397
422
  }
398
- this.changesTimeout = setTimeout(() => {
399
- this.changesTimeout = null;
400
- if (this.pendingChanges) {
401
- this.sendChanges(this.pendingChanges);
402
- this.pendingChanges = null;
403
- }
404
- }, changesDelay);
405
423
  }
406
424
  destroy() {
407
- if (this.changesTimeout) {
408
- clearTimeout(this.changesTimeout);
409
- if (this.pendingChanges) {
410
- this.sendChanges(this.pendingChanges);
411
- this.pendingChanges = null;
412
- }
413
- }
414
425
  this.client.detachPlugin(this);
415
426
  }
416
427
  async initialize({ documentText }) {
package/dist/jsonrpc.d.ts CHANGED
@@ -27,6 +27,8 @@ export declare class JSONRPCClient {
27
27
  private nextId;
28
28
  private pending;
29
29
  private notificationHandlers;
30
+ private errorHandlers;
31
+ private closeHandlers;
30
32
  private ready;
31
33
  constructor(transport: Transport);
32
34
  request(req: {
@@ -38,5 +40,7 @@ export declare class JSONRPCClient {
38
40
  params: any;
39
41
  }): Promise<void>;
40
42
  onNotification(handler: (data: any) => void): void;
43
+ onError(handler: (error: Error) => void): void;
44
+ onClose(handler: () => void): void;
41
45
  close(): void;
42
46
  }
package/dist/plugin.d.ts CHANGED
@@ -127,8 +127,6 @@ export declare class LanguageServerPlugin implements PluginValue {
127
127
  private documentVersion;
128
128
  private allowHTMLContent;
129
129
  private synchronizationMethod;
130
- private changesTimeout;
131
- private pendingChanges;
132
130
  constructor(view: EditorView, { client, documentUri, languageId, allowHTMLContent, synchronizationMethod, }: {
133
131
  client: LanguageServerClient;
134
132
  documentUri: string;
@@ -215,5 +213,9 @@ export interface LanguageServerClientOptions<InitializationOptions = unknown> ex
215
213
  locale?: string;
216
214
  /** Called once after initialization with the server's capabilities. */
217
215
  onCapabilities?: (capabilities: LSP.ServerCapabilities) => void;
216
+ /** Called when the connection encounters an error. */
217
+ onError?: (error: Error) => void;
218
+ /** Called when the connection is closed. */
219
+ onClose?: () => void;
218
220
  }
219
221
  export {};