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.
@@ -0,0 +1,653 @@
1
+ function getDefaultExportFromCjs (x) {
2
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3
+ }
4
+
5
+ var lspWorker$1 = {};
6
+
7
+ /**
8
+ * Minimal LSP server for JavaScript, running in a Web Worker.
9
+ *
10
+ * Supports: completion, hover, diagnostics, document highlight, formatting.
11
+ */
12
+
13
+ var hasRequiredLspWorker;
14
+
15
+ function requireLspWorker () {
16
+ if (hasRequiredLspWorker) return lspWorker$1;
17
+ hasRequiredLspWorker = 1;
18
+ const documents = new Map();
19
+
20
+ // --- Known globals and their members/docs ---
21
+
22
+ const globalDocs = {
23
+ console: {
24
+ doc: 'The console object provides access to the browser debugging console.',
25
+ members: {
26
+ log: {
27
+ detail: 'console.log(...data: any[]): void',
28
+ doc: 'Outputs a message to the console.',
29
+ },
30
+ error: {
31
+ detail: 'console.error(...data: any[]): void',
32
+ doc: 'Outputs an error message to the console.',
33
+ },
34
+ warn: {
35
+ detail: 'console.warn(...data: any[]): void',
36
+ doc: 'Outputs a warning message to the console.',
37
+ },
38
+ info: {
39
+ detail: 'console.info(...data: any[]): void',
40
+ doc: 'Outputs an informational message to the console.',
41
+ },
42
+ debug: {
43
+ detail: 'console.debug(...data: any[]): void',
44
+ doc: 'Outputs a debug message to the console.',
45
+ },
46
+ table: {
47
+ detail: 'console.table(data: any, columns?: string[]): void',
48
+ doc: 'Displays tabular data as a table.',
49
+ },
50
+ clear: {
51
+ detail: 'console.clear(): void',
52
+ doc: 'Clears the console.',
53
+ },
54
+ time: {
55
+ detail: 'console.time(label?: string): void',
56
+ doc: 'Starts a timer you can use to track how long an operation takes.',
57
+ },
58
+ timeEnd: {
59
+ detail: 'console.timeEnd(label?: string): void',
60
+ doc: 'Stops a timer and logs the elapsed time.',
61
+ },
62
+ },
63
+ },
64
+ Math: {
65
+ doc: 'The Math object provides mathematical constants and functions.',
66
+ members: {
67
+ floor: {
68
+ detail: 'Math.floor(x: number): number',
69
+ doc: 'Returns the largest integer less than or equal to x.',
70
+ },
71
+ ceil: {
72
+ detail: 'Math.ceil(x: number): number',
73
+ doc: 'Returns the smallest integer greater than or equal to x.',
74
+ },
75
+ round: {
76
+ detail: 'Math.round(x: number): number',
77
+ doc: 'Returns the value of x rounded to the nearest integer.',
78
+ },
79
+ random: {
80
+ detail: 'Math.random(): number',
81
+ doc: 'Returns a pseudo-random number between 0 and 1.',
82
+ },
83
+ max: {
84
+ detail: 'Math.max(...values: number[]): number',
85
+ doc: 'Returns the largest of zero or more numbers.',
86
+ },
87
+ min: {
88
+ detail: 'Math.min(...values: number[]): number',
89
+ doc: 'Returns the smallest of zero or more numbers.',
90
+ },
91
+ abs: {
92
+ detail: 'Math.abs(x: number): number',
93
+ doc: 'Returns the absolute value of x.',
94
+ },
95
+ sqrt: {
96
+ detail: 'Math.sqrt(x: number): number',
97
+ doc: 'Returns the square root of x.',
98
+ },
99
+ pow: {
100
+ detail: 'Math.pow(base: number, exponent: number): number',
101
+ doc: 'Returns base raised to the power of exponent.',
102
+ },
103
+ PI: {
104
+ detail: 'Math.PI: number',
105
+ doc: 'The ratio of the circumference of a circle to its diameter (~3.14159).',
106
+ },
107
+ },
108
+ },
109
+ document: {
110
+ doc: 'The Document interface represents the web page loaded in the browser.',
111
+ members: {
112
+ getElementById: {
113
+ detail:
114
+ 'document.getElementById(id: string): HTMLElement | null',
115
+ doc: 'Returns the element with the specified ID.',
116
+ },
117
+ querySelector: {
118
+ detail:
119
+ 'document.querySelector(selectors: string): Element | null',
120
+ doc: 'Returns the first element matching the specified CSS selector.',
121
+ },
122
+ querySelectorAll: {
123
+ detail:
124
+ 'document.querySelectorAll(selectors: string): NodeList',
125
+ doc: 'Returns all elements matching the specified CSS selector.',
126
+ },
127
+ createElement: {
128
+ detail:
129
+ 'document.createElement(tagName: string): HTMLElement',
130
+ doc: 'Creates an HTML element specified by tagName.',
131
+ },
132
+ addEventListener: {
133
+ detail:
134
+ 'document.addEventListener(type: string, listener: Function): void',
135
+ doc: 'Registers an event handler on the document.',
136
+ },
137
+ body: {
138
+ detail: 'document.body: HTMLBodyElement',
139
+ doc: 'Returns the <body> element of the document.',
140
+ },
141
+ title: {
142
+ detail: 'document.title: string',
143
+ doc: 'Gets or sets the title of the document.',
144
+ },
145
+ },
146
+ },
147
+ JSON: {
148
+ doc: 'The JSON object contains methods for parsing and stringifying JSON.',
149
+ members: {
150
+ stringify: {
151
+ detail:
152
+ 'JSON.stringify(value: any, replacer?: Function, space?: number): string',
153
+ doc: 'Converts a JavaScript value to a JSON string.',
154
+ },
155
+ parse: {
156
+ detail: 'JSON.parse(text: string, reviver?: Function): any',
157
+ doc: 'Parses a JSON string and returns the resulting value.',
158
+ },
159
+ },
160
+ },
161
+ Array: {
162
+ doc: 'The Array object is used to store multiple values in a single variable.',
163
+ members: {
164
+ isArray: {
165
+ detail: 'Array.isArray(value: any): boolean',
166
+ doc: 'Determines whether the passed value is an Array.',
167
+ },
168
+ from: {
169
+ detail: 'Array.from(iterable: Iterable): Array',
170
+ doc: 'Creates a new Array instance from an iterable object.',
171
+ },
172
+ of: {
173
+ detail: 'Array.of(...items: any[]): Array',
174
+ doc: 'Creates a new Array instance with the given arguments as elements.',
175
+ },
176
+ },
177
+ },
178
+ Object: {
179
+ doc: 'The Object class represents one of JavaScript\'s data types.',
180
+ members: {
181
+ keys: {
182
+ detail: 'Object.keys(obj: object): string[]',
183
+ doc: 'Returns an array of a given object\'s own enumerable property names.',
184
+ },
185
+ values: {
186
+ detail: 'Object.values(obj: object): any[]',
187
+ doc: 'Returns an array of a given object\'s own enumerable property values.',
188
+ },
189
+ entries: {
190
+ detail: 'Object.entries(obj: object): [string, any][]',
191
+ doc: 'Returns an array of a given object\'s own enumerable string-keyed property [key, value] pairs.',
192
+ },
193
+ assign: {
194
+ detail:
195
+ 'Object.assign(target: object, ...sources: object[]): object',
196
+ doc: 'Copies all enumerable own properties from one or more source objects to a target object.',
197
+ },
198
+ freeze: {
199
+ detail: 'Object.freeze(obj: object): object',
200
+ doc: 'Freezes an object, preventing new properties from being added.',
201
+ },
202
+ },
203
+ },
204
+ Promise: {
205
+ doc: 'The Promise object represents the eventual completion or failure of an asynchronous operation.',
206
+ members: {
207
+ all: {
208
+ detail: 'Promise.all(promises: Promise[]): Promise',
209
+ doc: 'Returns a promise that resolves when all of the promises resolve.',
210
+ },
211
+ race: {
212
+ detail: 'Promise.race(promises: Promise[]): Promise',
213
+ doc: 'Returns a promise that resolves or rejects as soon as one of the promises resolves or rejects.',
214
+ },
215
+ resolve: {
216
+ detail: 'Promise.resolve(value: any): Promise',
217
+ doc: 'Returns a Promise that is resolved with the given value.',
218
+ },
219
+ reject: {
220
+ detail: 'Promise.reject(reason: any): Promise',
221
+ doc: 'Returns a Promise that is rejected with the given reason.',
222
+ },
223
+ },
224
+ },
225
+ };
226
+
227
+ const globalCompletions = [
228
+ {
229
+ label: 'setTimeout',
230
+ kind: 3,
231
+ detail: 'setTimeout(callback: Function, ms: number): number',
232
+ documentation: 'Calls a function after a specified number of milliseconds.',
233
+ },
234
+ {
235
+ label: 'setInterval',
236
+ kind: 3,
237
+ detail: 'setInterval(callback: Function, ms: number): number',
238
+ documentation: 'Calls a function repeatedly at specified intervals.',
239
+ },
240
+ {
241
+ label: 'clearTimeout',
242
+ kind: 3,
243
+ detail: 'clearTimeout(id: number): void',
244
+ documentation: 'Cancels a timeout previously set with setTimeout.',
245
+ },
246
+ {
247
+ label: 'clearInterval',
248
+ kind: 3,
249
+ detail: 'clearInterval(id: number): void',
250
+ documentation: 'Cancels an interval previously set with setInterval.',
251
+ },
252
+ {
253
+ label: 'parseInt',
254
+ kind: 3,
255
+ detail: 'parseInt(string: string, radix?: number): number',
256
+ documentation: 'Parses a string argument and returns an integer.',
257
+ },
258
+ {
259
+ label: 'parseFloat',
260
+ kind: 3,
261
+ detail: 'parseFloat(string: string): number',
262
+ documentation: 'Parses a string argument and returns a floating point number.',
263
+ },
264
+ {
265
+ label: 'isNaN',
266
+ kind: 3,
267
+ detail: 'isNaN(value: any): boolean',
268
+ documentation: 'Determines whether a value is NaN.',
269
+ },
270
+ {
271
+ label: 'fetch',
272
+ kind: 3,
273
+ detail: 'fetch(input: string, init?: RequestInit): Promise<Response>',
274
+ documentation: 'Starts the process of fetching a resource from the network.',
275
+ },
276
+ ...Object.keys(globalDocs).map((name) => ({
277
+ label: name,
278
+ kind: 6,
279
+ detail: name,
280
+ documentation: globalDocs[name].doc,
281
+ })),
282
+ ];
283
+
284
+ // --- Helpers ---
285
+
286
+ function send(msg) {
287
+ self.postMessage(JSON.stringify(msg));
288
+ }
289
+
290
+ function sendResponse(id, result) {
291
+ send({ jsonrpc: '2.0', id, result });
292
+ }
293
+
294
+ function sendNotification(method, params) {
295
+ send({ jsonrpc: '2.0', method, params });
296
+ }
297
+
298
+ function getWordAt(text, line, character) {
299
+ const lines = text.split('\n');
300
+ if (line >= lines.length) return null;
301
+ const lineText = lines[line];
302
+ let start = character;
303
+ let end = character;
304
+ while (start > 0 && /\w/.test(lineText[start - 1])) start--;
305
+ while (end < lineText.length && /\w/.test(lineText[end])) end++;
306
+ if (start === end) return null;
307
+ return {
308
+ word: lineText.slice(start, end),
309
+ range: {
310
+ start: { line, character: start },
311
+ end: { line, character: end },
312
+ },
313
+ };
314
+ }
315
+
316
+ function getObjectAccess(text, line, character) {
317
+ const lines = text.split('\n');
318
+ if (line >= lines.length) return null;
319
+ const lineText = lines[line];
320
+
321
+ let end = character;
322
+ while (end < lineText.length && /\w/.test(lineText[end])) end++;
323
+
324
+ let memberStart = character;
325
+ while (memberStart > 0 && /\w/.test(lineText[memberStart - 1]))
326
+ memberStart--;
327
+
328
+ if (memberStart === 0 || lineText[memberStart - 1] !== '.') return null;
329
+
330
+ let objEnd = memberStart - 1;
331
+ let objStart = objEnd;
332
+ while (objStart > 0 && /\w/.test(lineText[objStart - 1])) objStart--;
333
+
334
+ const obj = lineText.slice(objStart, objEnd);
335
+ const member = lineText.slice(memberStart, end);
336
+ return { obj, member };
337
+ }
338
+
339
+ // --- Diagnostics ---
340
+
341
+ function publishDiagnostics(uri, text) {
342
+ const diagnostics = [];
343
+ const lines = text.split('\n');
344
+
345
+ for (let i = 0; i < lines.length; i++) {
346
+ const line = lines[i];
347
+
348
+ // Flag `var` usage
349
+ const varMatch = line.match(/\bvar\s/);
350
+ if (varMatch) {
351
+ const col = varMatch.index;
352
+ diagnostics.push({
353
+ range: {
354
+ start: { line: i, character: col },
355
+ end: { line: i, character: col + 3 },
356
+ },
357
+ severity: 2,
358
+ source: 'demo-lsp',
359
+ message:
360
+ "'var' is discouraged. Use 'let' or 'const' instead.",
361
+ });
362
+ }
363
+
364
+ // Flag unclosed strings
365
+ const singleQuotes = (line.match(/'/g) || []).length;
366
+ const doubleQuotes = (line.match(/"/g) || []).length;
367
+ const backticks = (line.match(/`/g) || []).length;
368
+ if (singleQuotes % 2 !== 0) {
369
+ diagnostics.push({
370
+ range: {
371
+ start: { line: i, character: 0 },
372
+ end: { line: i, character: line.length },
373
+ },
374
+ severity: 1,
375
+ source: 'demo-lsp',
376
+ message: 'Unclosed string literal (single quote).',
377
+ });
378
+ }
379
+ if (doubleQuotes % 2 !== 0) {
380
+ diagnostics.push({
381
+ range: {
382
+ start: { line: i, character: 0 },
383
+ end: { line: i, character: line.length },
384
+ },
385
+ severity: 1,
386
+ source: 'demo-lsp',
387
+ message: 'Unclosed string literal (double quote).',
388
+ });
389
+ }
390
+ if (backticks % 2 !== 0) {
391
+ diagnostics.push({
392
+ range: {
393
+ start: { line: i, character: 0 },
394
+ end: { line: i, character: line.length },
395
+ },
396
+ severity: 1,
397
+ source: 'demo-lsp',
398
+ message: 'Unclosed template literal.',
399
+ });
400
+ }
401
+ }
402
+
403
+ sendNotification('textDocument/publishDiagnostics', { uri, diagnostics });
404
+ }
405
+
406
+ // --- Request handlers ---
407
+
408
+ function handleInitialize(id) {
409
+ sendResponse(id, {
410
+ capabilities: {
411
+ textDocumentSync: 1,
412
+ completionProvider: {
413
+ triggerCharacters: ['.'],
414
+ resolveProvider: false,
415
+ },
416
+ hoverProvider: true,
417
+ documentHighlightProvider: true,
418
+ documentFormattingProvider: true,
419
+ },
420
+ });
421
+ }
422
+
423
+ function handleCompletion(id, params) {
424
+ const { textDocument, position } = params;
425
+ const doc = documents.get(textDocument.uri);
426
+ if (!doc) {
427
+ sendResponse(id, []);
428
+ return;
429
+ }
430
+
431
+ const lines = doc.split('\n');
432
+ const lineText = lines[position.line] || '';
433
+ const textBefore = lineText.slice(0, position.character);
434
+
435
+ // Check for object member access (e.g. "console.")
436
+ const dotMatch = textBefore.match(/(\w+)\.\w*$/);
437
+ if (dotMatch) {
438
+ const objName = dotMatch[1];
439
+ const info = globalDocs[objName];
440
+ if (info && info.members) {
441
+ const items = Object.entries(info.members).map(
442
+ ([name, member]) => ({
443
+ label: name,
444
+ kind: name[0] === name[0].toUpperCase() ? 6 : 3,
445
+ detail: member.detail,
446
+ documentation: { kind: 'markdown', value: member.doc },
447
+ }),
448
+ );
449
+ sendResponse(id, items);
450
+ return;
451
+ }
452
+ }
453
+
454
+ // Global completions
455
+ sendResponse(id, globalCompletions);
456
+ }
457
+
458
+ function handleHover(id, params) {
459
+ const { textDocument, position } = params;
460
+ const doc = documents.get(textDocument.uri);
461
+ if (!doc) {
462
+ sendResponse(id, null);
463
+ return;
464
+ }
465
+
466
+ // Check for object.member hover
467
+ const access = getObjectAccess(doc, position.line, position.character);
468
+ if (access) {
469
+ const info = globalDocs[access.obj];
470
+ if (info && info.members && info.members[access.member]) {
471
+ const member = info.members[access.member];
472
+ sendResponse(id, {
473
+ contents: {
474
+ kind: 'markdown',
475
+ value: `**${member.detail}**\n\n${member.doc}`,
476
+ },
477
+ });
478
+ return;
479
+ }
480
+ }
481
+
482
+ // Check for global hover
483
+ const wordInfo = getWordAt(doc, position.line, position.character);
484
+ if (wordInfo) {
485
+ const info = globalDocs[wordInfo.word];
486
+ if (info) {
487
+ sendResponse(id, {
488
+ contents: {
489
+ kind: 'markdown',
490
+ value: `**${wordInfo.word}**\n\n${info.doc}`,
491
+ },
492
+ range: wordInfo.range,
493
+ });
494
+ return;
495
+ }
496
+ }
497
+
498
+ sendResponse(id, null);
499
+ }
500
+
501
+ function handleDocumentHighlight(id, params) {
502
+ const { textDocument, position } = params;
503
+ const doc = documents.get(textDocument.uri);
504
+ if (!doc) {
505
+ sendResponse(id, []);
506
+ return;
507
+ }
508
+
509
+ const wordInfo = getWordAt(doc, position.line, position.character);
510
+ if (!wordInfo) {
511
+ sendResponse(id, []);
512
+ return;
513
+ }
514
+
515
+ const highlights = [];
516
+ const lines = doc.split('\n');
517
+ const re = new RegExp(`\\b${wordInfo.word}\\b`, 'g');
518
+
519
+ for (let i = 0; i < lines.length; i++) {
520
+ let match;
521
+ while ((match = re.exec(lines[i])) !== null) {
522
+ highlights.push({
523
+ range: {
524
+ start: { line: i, character: match.index },
525
+ end: {
526
+ line: i,
527
+ character: match.index + wordInfo.word.length,
528
+ },
529
+ },
530
+ kind: 1,
531
+ });
532
+ }
533
+ }
534
+
535
+ sendResponse(id, highlights);
536
+ }
537
+
538
+ function handleFormatting(id, params) {
539
+ const { textDocument } = params;
540
+ const doc = documents.get(textDocument.uri);
541
+ if (!doc) {
542
+ sendResponse(id, []);
543
+ return;
544
+ }
545
+
546
+ const lines = doc.split('\n');
547
+ const formatted = [];
548
+ let indent = 0;
549
+
550
+ for (const line of lines) {
551
+ const trimmed = line.trim();
552
+ if (!trimmed) {
553
+ formatted.push('');
554
+ continue;
555
+ }
556
+
557
+ if (/^[}\])]/.test(trimmed)) indent = Math.max(0, indent - 1);
558
+
559
+ // Continuation lines (e.g. .then(), .catch()) get extra indent
560
+ const isContinuation = /^[.]/.test(trimmed);
561
+ const extraIndent = isContinuation ? 1 : 0;
562
+
563
+ formatted.push(' '.repeat(indent + extraIndent) + trimmed);
564
+
565
+ const opens = (trimmed.match(/[{[(]/g) || []).length;
566
+ const closes = (trimmed.match(/[}\])]/g) || []).length;
567
+ indent = Math.max(0, indent + opens - closes);
568
+ }
569
+
570
+ const newText = formatted.join('\n');
571
+ sendResponse(id, [
572
+ {
573
+ range: {
574
+ start: { line: 0, character: 0 },
575
+ end: {
576
+ line: lines.length - 1,
577
+ character: lines[lines.length - 1].length,
578
+ },
579
+ },
580
+ newText,
581
+ },
582
+ ]);
583
+ }
584
+
585
+ // --- Message dispatch ---
586
+
587
+ self.onmessage = function (event) {
588
+ const raw = typeof event.data === 'string' ? event.data : '';
589
+ let msg;
590
+ try {
591
+ msg = JSON.parse(raw);
592
+ } catch {
593
+ return;
594
+ }
595
+
596
+ const { id, method, params } = msg;
597
+
598
+ switch (method) {
599
+ case 'initialize':
600
+ handleInitialize(id);
601
+ break;
602
+
603
+ case 'initialized':
604
+ break;
605
+
606
+ case 'textDocument/didOpen': {
607
+ const { textDocument } = params;
608
+ documents.set(textDocument.uri, textDocument.text);
609
+ publishDiagnostics(textDocument.uri, textDocument.text);
610
+ break;
611
+ }
612
+
613
+ case 'textDocument/didChange': {
614
+ const { textDocument, contentChanges } = params;
615
+ if (contentChanges.length > 0) {
616
+ const text =
617
+ contentChanges[contentChanges.length - 1].text;
618
+ documents.set(textDocument.uri, text);
619
+ publishDiagnostics(textDocument.uri, text);
620
+ }
621
+ break;
622
+ }
623
+
624
+ case 'textDocument/completion':
625
+ handleCompletion(id, params);
626
+ break;
627
+
628
+ case 'textDocument/hover':
629
+ handleHover(id, params);
630
+ break;
631
+
632
+ case 'textDocument/documentHighlight':
633
+ handleDocumentHighlight(id, params);
634
+ break;
635
+
636
+ case 'textDocument/formatting':
637
+ handleFormatting(id, params);
638
+ break;
639
+
640
+ default:
641
+ if (id != null) {
642
+ sendResponse(id, null);
643
+ }
644
+ break;
645
+ }
646
+ };
647
+ return lspWorker$1;
648
+ }
649
+
650
+ var lspWorkerExports = requireLspWorker();
651
+ var lspWorker = /*@__PURE__*/getDefaultExportFromCjs(lspWorkerExports);
652
+
653
+ export { lspWorker as default };