websocket-text-relay 1.0.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.
Files changed (50) hide show
  1. package/.eslintrc +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +40 -0
  4. package/docs/code-structure.md +77 -0
  5. package/docs/creating-text-editor-plugin.md +10 -0
  6. package/docs/dev-getting-started.md +8 -0
  7. package/index.js +87 -0
  8. package/package.json +45 -0
  9. package/src/language-server/JsonRpcInterface.js +126 -0
  10. package/src/language-server/JsonRpcInterface.test.js +496 -0
  11. package/src/language-server/LspReader.js +119 -0
  12. package/src/language-server/LspReader.test.js +508 -0
  13. package/src/language-server/LspWriter.js +44 -0
  14. package/src/language-server/LspWriter.test.js +179 -0
  15. package/src/language-server/constants.js +23 -0
  16. package/src/ui/css/fonts/Montserrat-Black.ttf +0 -0
  17. package/src/ui/css/fonts/Montserrat-Bold.ttf +0 -0
  18. package/src/ui/css/fonts/Montserrat-ExtraBold.ttf +0 -0
  19. package/src/ui/css/fonts/Montserrat-ExtraLight.ttf +0 -0
  20. package/src/ui/css/fonts/Montserrat-Light.ttf +0 -0
  21. package/src/ui/css/fonts/Montserrat-Medium.ttf +0 -0
  22. package/src/ui/css/fonts/Montserrat-Regular.ttf +0 -0
  23. package/src/ui/css/fonts/Montserrat-SemiBold.ttf +0 -0
  24. package/src/ui/css/fonts/Montserrat-Thin.ttf +0 -0
  25. package/src/ui/css/fonts.css +54 -0
  26. package/src/ui/css/main.css +198 -0
  27. package/src/ui/index.html +36 -0
  28. package/src/ui/js/components/ActivityTimeseriesGraph.js +149 -0
  29. package/src/ui/js/components/HeaderSummary.js +23 -0
  30. package/src/ui/js/components/ServerStatus.js +31 -0
  31. package/src/ui/js/components/SessionLabels.js +171 -0
  32. package/src/ui/js/components/SessionWedges.js +107 -0
  33. package/src/ui/js/components/StatusRing.js +42 -0
  34. package/src/ui/js/components/grids.js +29 -0
  35. package/src/ui/js/index.js +117 -0
  36. package/src/ui/js/main.js +97 -0
  37. package/src/ui/js/util/DependencyManager.js +31 -0
  38. package/src/ui/js/util/EventEmitter.js +40 -0
  39. package/src/ui/js/util/WebsocketClient.js +76 -0
  40. package/src/ui/js/util/constants.js +9 -0
  41. package/src/ui/js/util/drawing.js +128 -0
  42. package/src/websocket-interface/WebsocketClient.js +56 -0
  43. package/src/websocket-interface/WebsocketInterface.js +71 -0
  44. package/src/websocket-interface/WtrSession.js +122 -0
  45. package/src/websocket-interface/httpServer.js +58 -0
  46. package/src/websocket-interface/sessionManager.js +107 -0
  47. package/src/websocket-interface/util.js +12 -0
  48. package/src/websocket-interface/websocketApi.js +116 -0
  49. package/src/websocket-interface/websocketServer.js +18 -0
  50. package/start.js +4 -0
@@ -0,0 +1,496 @@
1
+ import { describe, it, expect, beforeAll } from "vitest"
2
+ import { PassThrough, Writable } from 'node:stream'
3
+ import { JsonRpcInterface } from "./JsonRpcInterface.js"
4
+ import { TESTONLY_resetRequestId } from "./LspWriter.js"
5
+ import { invalidRequestErrorCode, invalidRequestErrorMessage, methodNotFoundErrorCode, methodNotFoundErrorMessage, unexpectedNotificationErrorCode,
6
+ unexpectedNotificationErrorMessage, unexpectedRequestErrorCode, unexpectedRequestErrorMessage } from "./constants.js"
7
+
8
+ describe("JsonRpcInterface", () => {
9
+ describe("onNotification", () => {
10
+ describe("when a we receive a notification message that has a handler", () => {
11
+ let inputStream, outputStream, jsonRpc
12
+ const incomingMessage = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
13
+ const expectedParams = {a: "1"}
14
+ let actualParams = null
15
+
16
+ beforeAll(() => {
17
+ inputStream = new PassThrough()
18
+ outputStream = new PassThrough()
19
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
20
+
21
+ jsonRpc.onNotification("test/test-method", (params) => {
22
+ actualParams= params
23
+ })
24
+
25
+ inputStream.write(incomingMessage)
26
+ })
27
+
28
+ it("should call the handler with the correct params", () => {
29
+ expect(actualParams).to.deep.equal(expectedParams)
30
+ })
31
+ })
32
+
33
+ describe("when attempting to register duplicate onNotification handlers ", () => {
34
+ let inputStream, outputStream, jsonRpc
35
+ let f1 = () => {}
36
+ let f2 = () => {}
37
+
38
+ beforeAll(() => {
39
+ inputStream = new PassThrough()
40
+ outputStream = new PassThrough()
41
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
42
+
43
+ jsonRpc.onNotification("test/test-method", f1)
44
+
45
+ })
46
+
47
+ it("should throw an error if we try to register the same method twice (without first removing the handler)", () => {
48
+ expect(() => jsonRpc.onNotification("test/test-method", f2)).to.toThrowError(/duplicate method handlers/i)
49
+ })
50
+ })
51
+
52
+ describe("when using removeNotificationHandler to change the existing method handler", () => {
53
+ let inputStream, outputStream, jsonRpc
54
+ const incomingMessage = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
55
+ let f1Fired = false
56
+ let f2Fired = false
57
+ let f1 = () => f1Fired = true
58
+ let f2 = () => f2Fired = true
59
+
60
+ beforeAll(() => {
61
+ inputStream = new PassThrough()
62
+ outputStream = new PassThrough()
63
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
64
+
65
+ jsonRpc.onNotification("test/test-method", f1)
66
+ jsonRpc.removeNotificationHandler("test/test-method")
67
+ jsonRpc.onNotification("test/test-method", f2)
68
+
69
+ inputStream.write(incomingMessage)
70
+ })
71
+
72
+ it("should call function f2 instead of f1", () => {
73
+ expect(f1Fired).to.equal(false)
74
+ expect(f2Fired).to.equal(true)
75
+ })
76
+ })
77
+
78
+ describe("when a notification handler throws an error", () => {
79
+ let inputStream, outputStream, jsonRpc
80
+ const incomingMessage = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
81
+ const expectedError = {code: unexpectedNotificationErrorCode, message: unexpectedNotificationErrorMessage, data: {method: "test/test-method", params: {a: "1"}, error: "Test notification error"}}
82
+ let actualError
83
+
84
+ beforeAll(() => {
85
+ inputStream = new PassThrough()
86
+ outputStream = new PassThrough()
87
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
88
+
89
+ jsonRpc.onNotification("test/test-method", () => {
90
+ throw new Error("Test notification error")
91
+ })
92
+
93
+ jsonRpc.events.on('notification-error', (error) => {
94
+ actualError = error
95
+ })
96
+
97
+ inputStream.write(incomingMessage)
98
+ })
99
+
100
+ it("should fire the notification-error event with the correct error data", () => {
101
+ expect(actualError).to.deep.equal(expectedError)
102
+ })
103
+ })
104
+
105
+ describe("when we receive a notification without a handler", () => {
106
+ let inputStream, outputStream, jsonRpc
107
+ const incomingMessage = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
108
+ const expectedError = {code: methodNotFoundErrorCode, message: methodNotFoundErrorMessage, data: {method: "test/test-method"}}
109
+ let actualError
110
+
111
+ beforeAll(() => {
112
+ inputStream = new PassThrough()
113
+ outputStream = new PassThrough()
114
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
115
+
116
+ jsonRpc.events.on('notification-error', (error) => {
117
+ actualError = error
118
+ })
119
+
120
+ inputStream.write(incomingMessage)
121
+ })
122
+
123
+ it("should fire the notification-error event with the correct error data", () => {
124
+ expect(actualError).to.deep.equal(expectedError)
125
+ })
126
+ })
127
+ })
128
+
129
+ describe("onRequest", () => {
130
+ describe("when a we receive a request message that has a handler", () => {
131
+ const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
132
+ const expectedParams = {a: "1"}
133
+ let actualParams = null
134
+ const expectedOutput = `Content-Length: 54\r\n\r\n{"jsonrpc":"2.0","id":"0","result":{"someValue":"33"}}`
135
+ let actualOutput = ""
136
+
137
+ beforeAll(() => {
138
+ const inputStream = new PassThrough()
139
+ const outputStream = new Writable({
140
+ write (data, _enc, next) {
141
+ actualOutput += data.toString()
142
+ next()
143
+ }
144
+ })
145
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
146
+
147
+ jsonRpc.onRequest("test/test-method", (params) => {
148
+ actualParams= params
149
+ return {someValue: "33"}
150
+ })
151
+
152
+ inputStream.write(incomingMessage)
153
+ })
154
+
155
+ it("should call the handler with the correct params", () => {
156
+ expect(actualParams).to.deep.equal(expectedParams)
157
+ })
158
+
159
+ it("should write the response to the output stream", () => {
160
+ expect(actualOutput).toEqual(expectedOutput)
161
+ })
162
+ })
163
+
164
+ describe("should be able to use an async function as a handler", () => {
165
+ const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
166
+ const expectedParams = {a: "1"}
167
+ let actualParams = null
168
+ const expectedOutput = `Content-Length: 54\r\n\r\n{"jsonrpc":"2.0","id":"0","result":{"someValue":"34"}}`
169
+ let actualOutput = ""
170
+
171
+ beforeAll(() => {
172
+ const inputStream = new PassThrough()
173
+ const outputStream = new Writable({
174
+ write (data, _enc, next) {
175
+ actualOutput += data.toString()
176
+ next()
177
+ }
178
+ })
179
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
180
+
181
+ jsonRpc.onRequest("test/test-method", async (params) => {
182
+ actualParams= params
183
+ const someValue = await new Promise((resolve) => {
184
+ setTimeout(() => resolve("34"), 1)
185
+ })
186
+ return {someValue}
187
+ })
188
+
189
+ inputStream.write(incomingMessage)
190
+ })
191
+
192
+ it("should call the handler with the correct params", () => {
193
+ expect(actualParams).to.deep.equal(expectedParams)
194
+ })
195
+
196
+ it("should not write the response until the promise resolves", () => {
197
+ expect(actualOutput).toEqual("")
198
+ })
199
+
200
+ it("should write the response after the async function resolves with the result", async () => {
201
+ await new Promise((resolve) => setTimeout(resolve, 1))
202
+ expect(actualOutput).toEqual(expectedOutput)
203
+ })
204
+ })
205
+
206
+ describe("when attempting to register duplicate onRequest handlers ", () => {
207
+ let inputStream, outputStream, jsonRpc
208
+ let f1 = () => {}
209
+ let f2 = () => {}
210
+
211
+ beforeAll(() => {
212
+ inputStream = new PassThrough()
213
+ outputStream = new PassThrough()
214
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
215
+
216
+ jsonRpc.onRequest("test/test-method", f1)
217
+
218
+ })
219
+
220
+ it("should throw an error if we try to register the same method twice (without first removing the handler)", () => {
221
+ expect(() => jsonRpc.onRequest("test/test-method", f2)).to.toThrowError(/duplicate method handlers/i)
222
+ })
223
+ })
224
+
225
+ describe("when using removeRequestHandler to change the existing method handler", () => {
226
+ const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
227
+ let f1Fired = false
228
+ let f2Fired = false
229
+ let f1 = () => f1Fired = true
230
+ let f2 = () => f2Fired = true
231
+
232
+ beforeAll(() => {
233
+ const inputStream = new PassThrough()
234
+ const outputStream = new PassThrough()
235
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
236
+
237
+ jsonRpc.onRequest("test/test-method", f1)
238
+ jsonRpc.removeRequestHandler("test/test-method")
239
+ jsonRpc.onRequest("test/test-method", f2)
240
+
241
+ inputStream.write(incomingMessage)
242
+ })
243
+
244
+ it("should call function f2 instead of f1", () => {
245
+ expect(f1Fired).to.equal(false)
246
+ expect(f2Fired).to.equal(true)
247
+ })
248
+ })
249
+
250
+ describe("when a request handler throws an error", () => {
251
+ const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
252
+ const expectedError = {id: "0", error: {code: unexpectedRequestErrorCode, message: unexpectedRequestErrorMessage, data: {method: "test/test-method", params: {a: "1"}, error: "Test request error"}}}
253
+ let actualError
254
+ const expectedOutput = `Content-Length: 179\r\n\r\n{"jsonrpc":"2.0","id":"0","error":{"code":-2,"message":"${unexpectedRequestErrorMessage}","data":{"method":"test/test-method","params":{"a":"1"},"error":"Test request error"}}}`
255
+ let actualOutput = ""
256
+
257
+ beforeAll(() => {
258
+ const inputStream = new PassThrough()
259
+ const outputStream = new Writable({
260
+ write (data, _enc, next) {
261
+ actualOutput += data.toString()
262
+ next()
263
+ }
264
+ })
265
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
266
+
267
+ jsonRpc.onRequest("test/test-method", () => {
268
+ throw new Error("Test request error")
269
+ })
270
+
271
+ jsonRpc.events.on('request-error', (error) => {
272
+ actualError = error
273
+ })
274
+
275
+ inputStream.write(incomingMessage)
276
+ })
277
+
278
+ it("should fire the notification-error event with the correct error data", () => {
279
+ expect(actualError).to.deep.equal(expectedError)
280
+ })
281
+
282
+ it("should write the error to the output stream", () => {
283
+ expect(actualOutput).toEqual(expectedOutput)
284
+ })
285
+ })
286
+
287
+ describe("when we receive a request without a handler", () => {
288
+ const incomingMessage = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
289
+ const expectedError = {id: "0", error: {code: methodNotFoundErrorCode, message: methodNotFoundErrorMessage, data: {method: "test/test-method"}}}
290
+ let actualError
291
+ const expectedOutput = `Content-Length: 116\r\n\r\n{"jsonrpc":"2.0","id":"0","error":{"code":-32601,"message":"${methodNotFoundErrorMessage}","data":{"method":"test/test-method"}}}`
292
+ let actualOutput = ""
293
+
294
+ beforeAll(() => {
295
+ const inputStream = new PassThrough()
296
+ const outputStream = new Writable({
297
+ write (data, _enc, next) {
298
+ actualOutput += data.toString()
299
+ next()
300
+ }
301
+ })
302
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
303
+
304
+ jsonRpc.events.on('request-error', (error) => {
305
+ actualError = error
306
+ })
307
+
308
+ inputStream.write(incomingMessage)
309
+ })
310
+
311
+ it("should fire the notification-error event with the correct error data", () => {
312
+ expect(actualError).to.deep.equal(expectedError)
313
+ })
314
+
315
+ it("should write the error to the output stream", () => {
316
+ expect(actualOutput).toEqual(expectedOutput)
317
+ })
318
+ })
319
+ })
320
+
321
+ describe("sendNotification", () => {
322
+ const method = "test/test-method"
323
+ const parameters = {"a": "1"}
324
+ const expectedOutput = `Content-Length: 64\r\n\r\n{"jsonrpc":"2.0","method":"test/test-method","params":{"a":"1"}}`
325
+ let actualOutput = ""
326
+
327
+ beforeAll(() => {
328
+ const inputStream = new PassThrough()
329
+ const outputStream = new Writable({
330
+ write (data, _enc, next) {
331
+ actualOutput += data.toString()
332
+ next()
333
+ }
334
+ })
335
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
336
+ jsonRpc.sendNotification(method, parameters)
337
+ })
338
+
339
+ it("should write a notification message without an ID property on the output stream", () => {
340
+ expect(actualOutput).toEqual(expectedOutput)
341
+ })
342
+ })
343
+
344
+ describe("sendRequest", () => {
345
+ describe("with result response", () => {
346
+ const method = "test/test-method"
347
+ const parameters = {"a": "1"}
348
+ const expectedOutput = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
349
+ let actualOutput = ""
350
+ const clientResponse = `Content-Length: 52\r\n\r\n{"jsonrpc":"2.0","id":"0","result":{"testVal":"39"}}`
351
+ const expectedResult = {testVal: "39"}
352
+ let actualResult = null
353
+ let inputStream, requestPromise
354
+
355
+ beforeAll(() => {
356
+ TESTONLY_resetRequestId()
357
+ inputStream = new PassThrough()
358
+ const outputStream = new Writable({
359
+ write (data, _enc, next) {
360
+ actualOutput += data.toString()
361
+ next()
362
+ }
363
+ })
364
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
365
+ requestPromise = jsonRpc.sendRequest(method, parameters)
366
+ requestPromise.then((result) => {
367
+ actualResult = result
368
+ })
369
+ })
370
+
371
+ it("should write the request message to the output stream", () => {
372
+ expect(actualOutput).toEqual(expectedOutput)
373
+ })
374
+
375
+ it("should return an unresolved promise", () => {
376
+ expect(requestPromise).toBeInstanceOf(Promise)
377
+ expect(actualResult).toBeNull()
378
+ })
379
+
380
+ it("should resolve the promise when the client responds with a result", async () => {
381
+ inputStream.write(clientResponse)
382
+ const result = await requestPromise
383
+ expect(result).to.deep.equal(expectedResult)
384
+ })
385
+ })
386
+
387
+ describe("with error response", () => {
388
+ const method = "test/test-method"
389
+ const parameters = {"a": "1"}
390
+ const expectedOutput = `Content-Length: 73\r\n\r\n{"jsonrpc":"2.0","id":"0","method":"test/test-method","params":{"a":"1"}}`
391
+ let actualOutput = ""
392
+ const clientResponse = `Content-Length: 83\r\n\r\n{"jsonrpc":"2.0","id":"0","error":{"code":1,"message":"test error","data":{"t":5}}}`
393
+ const expectedError = {"code":1,"message":"test error","data":{"t":5}}
394
+ let actualResult = null
395
+ let inputStream, requestPromise
396
+
397
+ beforeAll(() => {
398
+ TESTONLY_resetRequestId()
399
+ inputStream = new PassThrough()
400
+ const outputStream = new Writable({
401
+ write (data, _enc, next) {
402
+ actualOutput += data.toString()
403
+ next()
404
+ }
405
+ })
406
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
407
+ requestPromise = jsonRpc.sendRequest(method, parameters)
408
+ requestPromise.catch((result) => {
409
+ actualResult = result
410
+ })
411
+ })
412
+
413
+ it("should write the request message to the output stream", () => {
414
+ expect(actualOutput).toEqual(expectedOutput)
415
+ })
416
+
417
+ it("should return an unresolved promise", () => {
418
+ expect(requestPromise).toBeInstanceOf(Promise)
419
+ expect(actualResult).toBeNull()
420
+ })
421
+
422
+ it("should reject the promise when the client responds with an error", async () => {
423
+ inputStream.write(clientResponse)
424
+ await requestPromise.catch((error) => {
425
+ expect(error).to.deep.equal(expectedError)
426
+ })
427
+ })
428
+ })
429
+ })
430
+
431
+ describe("when the lspInputStream encounters a parse-error", () => {
432
+ const error = {code: 1, message: "test error", data: {t: 5}}
433
+ const expectedOutput = `Content-Length: 84\r\n\r\n{"jsonrpc":"2.0","id":null,"error":{"code":1,"message":"test error","data":{"t":5}}}`
434
+ let actualOutput = ""
435
+
436
+ beforeAll(() => {
437
+ const inputStream = new PassThrough()
438
+ const outputStream = new Writable({
439
+ write (data, _enc, next) {
440
+ actualOutput += data.toString()
441
+ next()
442
+ }
443
+ })
444
+ const jsonRpc = new JsonRpcInterface({inputStream, outputStream})
445
+ jsonRpc.lspInputStream.emit('parse-error', error)
446
+ })
447
+
448
+ it("should write the error to the output stream", () => {
449
+ expect(actualOutput).toEqual(expectedOutput)
450
+ })
451
+ })
452
+
453
+ describe("_handlRequestResponse: when there is no response handler", () => {
454
+ let jsonRpc
455
+
456
+ beforeAll(() => {
457
+ const inputStream = new PassThrough()
458
+ const outputStream = new PassThrough()
459
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
460
+ })
461
+
462
+ it("should not throw an error when there is not a response handler for that id", () => {
463
+ jsonRpc._handleRequestResponse(33)
464
+ })
465
+ })
466
+
467
+ describe("_handleIncomingLspMessage: when there is no id or method", () => {
468
+ let jsonRpc, actualError
469
+ let actualOutput = ""
470
+ const expectedError = {code: invalidRequestErrorCode, message: invalidRequestErrorMessage}
471
+ const expectedOutput = `Content-Length: 105\r\n\r\n{"jsonrpc":"2.0","id":null,"error":{"code":${invalidRequestErrorCode},"message":"${invalidRequestErrorMessage}"}}`
472
+
473
+ beforeAll(() => {
474
+ const inputStream = new PassThrough()
475
+ const outputStream = new Writable({
476
+ write (data, _enc, next) {
477
+ actualOutput += data.toString()
478
+ next()
479
+ }
480
+ })
481
+ jsonRpc = new JsonRpcInterface({inputStream, outputStream})
482
+ jsonRpc.events.on('rpc-error', (error) => {
483
+ actualError = error
484
+ })
485
+ jsonRpc._handleIncomingLspMessage({id: null, message: null})
486
+ })
487
+
488
+ it("should emit an rpc-error event on the events emitter", () => {
489
+ expect(actualError).to.deep.equal(expectedError)
490
+ })
491
+
492
+ it("should notify the client that it sent an invalid rpc message", () => {
493
+ expect(actualOutput).toEqual(expectedOutput)
494
+ })
495
+ })
496
+ })
@@ -0,0 +1,119 @@
1
+ import { Transform } from 'node:stream'
2
+ import { headerKey, parseErrorCode, parseHeaderErrorMessage, parseJsonErrorMessage } from './constants.js'
3
+
4
+ const defaultInitialBufferSize = 10000
5
+
6
+ const jsonStartCharCode = "{".charCodeAt(0)
7
+
8
+ // as long as there are no '{' characters in the header section, this function will work. (LSP Clients should not be putting any { chars in the header anyhow)
9
+ const findJsonStartIndex = (data, bufferStartIndex, bufferEndIndex) => {
10
+ const bufferedData = data.subarray(bufferStartIndex, bufferEndIndex)
11
+ const index = bufferedData.indexOf(jsonStartCharCode)
12
+ if (index < 0) { return index }
13
+ return index + bufferStartIndex
14
+ }
15
+
16
+ const frameStartRegex = new RegExp(`${headerKey}: ([0-9]+)`, "i")
17
+
18
+ const getJsonLength = (data, bufferStartIndex, jsonStartIndex) => {
19
+ const startStr = data.subarray(bufferStartIndex, jsonStartIndex).toString()
20
+ const match = frameStartRegex.exec(startStr)
21
+ if (!match) { return null }
22
+ return Number.parseInt(match[1])
23
+ }
24
+
25
+ export class LspReader extends Transform {
26
+ constructor (options = {}) {
27
+ super({readableObjectMode: true})
28
+ this.buffer = Buffer.alloc(options.initialBufferSize || defaultInitialBufferSize)
29
+ this._resetBuffer()
30
+ }
31
+
32
+ _transform (data, _enc, next) {
33
+ this._writeToBuffer(data)
34
+ this._handleBufferedData()
35
+ next()
36
+ }
37
+
38
+ _resetBuffer () {
39
+ this.bufferStartIndex = 0
40
+ this.bufferIndex = 0
41
+ this.jsonStartIndex = -1
42
+ this.jsonEndIndex = -1
43
+ this.jsonLength = null
44
+ }
45
+
46
+ _resetBufferWithRemainingData () {
47
+ const remainingData = this.buffer.subarray(this.bufferStartIndex, this.bufferIndex)
48
+ this._resetBuffer()
49
+ this._writeToBuffer(remainingData)
50
+ }
51
+
52
+ _advanceBufferOneMessage () {
53
+ this.bufferStartIndex = this.jsonEndIndex
54
+ this.jsonStartIndex = -1
55
+ this.jsonEndIndex = -1
56
+ this.jsonLength = null
57
+ }
58
+
59
+ _writeToBuffer (data) {
60
+ const spaceLeft = this.buffer.length - this.bufferIndex
61
+ if (data.length > spaceLeft) {
62
+ const newBufferSize = (this.bufferIndex + data.length) * 1.3 // add a padding of 30% to reduce the number of resizes
63
+ const newBuffer = Buffer.alloc(newBufferSize, this.buffer)
64
+ this.buffer = newBuffer
65
+ }
66
+ data.copy(this.buffer, this.bufferIndex)
67
+ this.bufferIndex += data.length
68
+ }
69
+
70
+ _foundJsonLength () {
71
+ if (this.jsonStartIndex < 0) {
72
+ this.jsonStartIndex = findJsonStartIndex(this.buffer, this.bufferStartIndex, this.bufferIndex)
73
+ if (this.jsonStartIndex < 0) { return false }
74
+
75
+ this.jsonLength = getJsonLength(this.buffer, this.bufferStartIndex, this.jsonStartIndex)
76
+
77
+ if (this.jsonLength === null) {
78
+ this.emit("parse-error", {code: parseErrorCode, message: parseHeaderErrorMessage})
79
+ this._resetBuffer()
80
+ return false
81
+ }
82
+
83
+ this.jsonEndIndex = this.jsonStartIndex + this.jsonLength
84
+ }
85
+
86
+ return true
87
+ }
88
+
89
+ _handleBufferedData () {
90
+ // loop as long as we have messages to parse in the buffer (ignore the eslint warning about the constant true value)
91
+ while (true) { // eslint-disable-line
92
+ if (!this._foundJsonLength()) { break } // we don't have a header to read yet, keep buffering
93
+ if (this.jsonEndIndex > this.bufferIndex) { break } // we have a header but not the complete json message, keep buffering
94
+
95
+ const messageStr = this.buffer.subarray(this.jsonStartIndex, this.jsonEndIndex).toString()
96
+
97
+ let messageObj
98
+ try {
99
+ messageObj = JSON.parse(messageStr)
100
+ } catch (e) {
101
+ this.emit("parse-error", {code: parseErrorCode, message: parseJsonErrorMessage})
102
+ this._resetBuffer()
103
+ return
104
+ }
105
+
106
+ this.push(messageObj)
107
+ this._advanceBufferOneMessage()
108
+ }
109
+
110
+ if (this.bufferStartIndex === this.bufferIndex) {
111
+ // we have read all messages in the buffer, reset everything back to empty state
112
+ this._resetBuffer()
113
+ } else if (this.bufferStartIndex > 0) {
114
+ // we have a partial message at the end of this buffer, reset the buffer to contain only this data
115
+ this._resetBufferWithRemainingData()
116
+ }
117
+ // if the bufferStartIndex is still 0, then there is nothing to reset, just keep buffering until we have a complete message
118
+ }
119
+ }