monaco-lsp-bridge 0.0.1

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/src/index.ts ADDED
@@ -0,0 +1,709 @@
1
+ import type {
2
+ InitializeResult,
3
+ CompletionOptions,
4
+ ClientCapabilities,
5
+ InitializeParams,
6
+ TextEdit,
7
+ } from 'vscode-languageserver-protocol'
8
+
9
+ import type * as monaco from 'monaco-editor'
10
+
11
+ import {
12
+ toMonacoCompletionItem,
13
+ toLspPosition,
14
+ toLspRange,
15
+ toLspCompletionItem,
16
+ mergeResolvedCompletionItem,
17
+ toMonacoMarkers,
18
+ toMonacoTextEdits,
19
+ lspHoverToMonaco,
20
+ } from './util.js'
21
+ import { LspTransport, Transport } from './transport.js'
22
+ import type { MaybeCancelled } from './transport.js'
23
+ import { ProgressPayload } from './protocol.js'
24
+ import type { LspError } from './error.js'
25
+
26
+ export type * from './protocol.js'
27
+ export * from './transport.js'
28
+
29
+ type Monaco = typeof monaco
30
+
31
+ // Event types
32
+ export type ClientEvent =
33
+ | { type: 'initialized'; capabilities: InitializeResult['capabilities'] }
34
+ | { type: 'error'; error: LspError }
35
+ | { type: 'workDoneProgress'; params: ProgressPayload }
36
+ | { type: 'logMessage'; method: string; params: any }
37
+ | { type: 'shutdownError'; error: unknown }
38
+
39
+ export type ClientEventHandler = (event: ClientEvent) => void
40
+
41
+ export type MonacoLspClientOptions = {
42
+ languageSelector: monaco.languages.LanguageSelector
43
+ /** Send shutdown/exit on dispose when server is dedicated */
44
+ dedicatedServer?: boolean
45
+ initialParams?: InitializeParams
46
+ }
47
+
48
+ export class LspMonacoClient {
49
+ static CLIENT_NAME = 'monaco-lsp-bridge'
50
+ static CLIENT_VERSION = '0.0.1'
51
+
52
+ private editor: monaco.editor.ICodeEditor | null = null
53
+ private monaco: Monaco | null = null
54
+ private binding: LspTransport
55
+ private endpoint: Transport
56
+ private openDocuments = new Set<string>() // Track opened document URIs
57
+ private initialized = false
58
+ private eventHandlers = new Set<ClientEventHandler>()
59
+ private syncKind: number = 0 // 0=None, 1=Full, 2=Incremental
60
+ private readonly MARKER_OWNER = 'lsp-diagnostics'
61
+
62
+ /** Construct a Monaco LSP client bound to an endpoint */
63
+ constructor(
64
+ endpoint: Transport,
65
+ private options: MonacoLspClientOptions,
66
+ ) {
67
+ this.endpoint = endpoint
68
+ this.binding = LspTransport.infer(endpoint)
69
+ }
70
+
71
+ /** Register an event handler for client events */
72
+ onEvent(handler: ClientEventHandler): () => void {
73
+ this.eventHandlers.add(handler)
74
+ return () => this.eventHandlers.delete(handler)
75
+ }
76
+
77
+ /** Emit an event to all registered handlers */
78
+ private emit(event: ClientEvent): void {
79
+ for (const handler of this.eventHandlers) {
80
+ try {
81
+ handler(event)
82
+ } catch (error) {
83
+ // Prevent handler errors from breaking the client
84
+ console.error('Event handler error:', error)
85
+ }
86
+ }
87
+ }
88
+
89
+ /** Register Monaco editor instance and initialize LSP wiring */
90
+ connect(monaco: Monaco, editor: monaco.editor.ICodeEditor) {
91
+ if (this.editor === editor) return
92
+
93
+ this.dispose()
94
+
95
+ // Recreate binding after dispose
96
+ this.binding = LspTransport.infer(this.endpoint)
97
+ this.editor = editor
98
+ this.monaco = monaco
99
+
100
+ // Wire error/progress events
101
+ this.binding
102
+ .onError((error) => this.emit({ type: 'error', error }))
103
+ .onProgressNotification((params) => this.emit({ type: 'workDoneProgress', params }))
104
+ .onServerNotification('window/logMessage', (params) =>
105
+ this.emit({ type: 'logMessage', method: 'window/logMessage', params }),
106
+ )
107
+
108
+ // Initialize server
109
+ this.initialize()
110
+ }
111
+
112
+ /** Initialize the LSP server connection and capabilities */
113
+ private async initialize() {
114
+ try {
115
+ const init = await this.binding.sendRequest(
116
+ 'initialize',
117
+ this.options.initialParams ?? {
118
+ // @ts-ignore
119
+ processId: typeof process !== 'undefined' ? process.pid : null,
120
+ rootUri: null,
121
+ clientInfo: { name: LspMonacoClient.CLIENT_NAME, version: LspMonacoClient.CLIENT_VERSION },
122
+ locale: typeof navigator !== 'undefined' ? navigator.language : 'en-US',
123
+ capabilities: LspMonacoClient.CAPABILITIES,
124
+ // Omit workspaceFolders (we advertise false)
125
+ },
126
+ )
127
+
128
+ if (!init.cancelled && init.result?.capabilities) {
129
+ this.initialized = true
130
+ this.emit({ type: 'initialized', capabilities: init.result.capabilities })
131
+ this.applyServerCapabilities(init.result.capabilities)
132
+ }
133
+ } catch (error) {
134
+ // Error already emitted; keep uninitialized
135
+ this.initialized = false
136
+ }
137
+ }
138
+
139
+ /** Dispose the client, closing docs and optionally shutting down server */
140
+ async dispose() {
141
+ // Close all open documents
142
+ for (const uri of this.openDocuments) {
143
+ this.binding.sendNotification('textDocument/didClose', { textDocument: { uri } })
144
+ // Clear diagnostics markers for each open document
145
+ const monacoApi = this.monaco
146
+ if (monacoApi) {
147
+ const model = monacoApi.editor.getModel(monacoApi.Uri.parse(uri))
148
+ if (model) monacoApi.editor.setModelMarkers(model, this.MARKER_OWNER, [])
149
+ }
150
+ }
151
+ this.openDocuments.clear()
152
+
153
+ // Graceful shutdown for dedicated servers
154
+ if (this.options.dedicatedServer && this.initialized) {
155
+ try {
156
+ // Request shutdown with timeout
157
+ await this.binding.sendRequest(
158
+ 'shutdown',
159
+ null,
160
+ { timeoutMs: 5000 }, // 5 second timeout
161
+ )
162
+ } catch (error) {
163
+ this.emit({ type: 'shutdownError', error })
164
+ // Continue with exit anyway
165
+ } finally {
166
+ // Always send exit (fire-and-forget)
167
+ this.binding.sendNotification('exit', null)
168
+ }
169
+ }
170
+
171
+ // Tear down binding
172
+ this.binding.dispose()
173
+ this.editor = null
174
+ this.initialized = false
175
+ }
176
+
177
+ /** Register Monaco features based on server-reported capabilities */
178
+ private applyServerCapabilities(caps: InitializeResult['capabilities']) {
179
+ if (!this.editor) return
180
+ const sync = caps.textDocumentSync
181
+
182
+ // TextDocumentSyncKind: 0=None, 1=Full, 2=Incremental
183
+ if (typeof sync === 'number') {
184
+ this.syncKind = sync
185
+ } else if (typeof sync === 'object' && sync !== null) {
186
+ this.syncKind = sync.change ?? 0
187
+ } else {
188
+ this.syncKind = 0
189
+ }
190
+
191
+ // Open/close supported
192
+ const enableOpenClose = typeof sync === 'object' ? !!sync.openClose : sync !== undefined && sync !== 0
193
+
194
+ // Change supported for Full/Incremental
195
+ const enableChange = this.syncKind === 1 || this.syncKind === 2
196
+
197
+ if (enableOpenClose) {
198
+ this.handleModelChange({ oldModelUrl: null, newModelUrl: this.editor.getModel()?.uri ?? null } as any)
199
+ this.binding.addDisposable(this.editor.onDidChangeModel((e) => this.handleModelChange(e)))
200
+ }
201
+ if (enableChange) {
202
+ this.binding.addDisposable(this.editor.onDidChangeModelContent((e) => this.handleContentChange(e)))
203
+ }
204
+ if (caps.completionProvider) this.registerCompletionProvider(caps.completionProvider)
205
+ if (caps.hoverProvider) this.registerHoverProvider()
206
+ if (caps.documentFormattingProvider) this.registerFormattingProvider()
207
+ if (caps.documentRangeFormattingProvider) this.registerRangeFormattingProvider()
208
+ if (caps.documentOnTypeFormattingProvider)
209
+ this.registerOnTypeFormattingProvider(caps.documentOnTypeFormattingProvider)
210
+ // Listen for diagnostics
211
+ this.wireDiagnostics()
212
+ }
213
+
214
+ /** Register hover provider backed by the LSP server */
215
+ private registerHoverProvider() {
216
+ const monaco = this.monaco
217
+ if (!monaco) return
218
+ this.binding.addDisposable(
219
+ monaco.languages.registerHoverProvider(this.options.languageSelector, {
220
+ provideHover: (model, position, token) =>
221
+ withCancellation<any | null, null>(
222
+ token,
223
+ (p) =>
224
+ this.binding.sendRequest(
225
+ 'textDocument/hover',
226
+ {
227
+ textDocument: { uri: model.uri.toString() },
228
+ position: toLspPosition(position),
229
+ },
230
+ p,
231
+ ),
232
+ null,
233
+ ).then((h) => (h ? lspHoverToMonaco(h) : null)),
234
+ }),
235
+ )
236
+ }
237
+
238
+ /** Register Monaco completion provider backed by the LSP server */
239
+ private registerCompletionProvider(opts: CompletionOptions) {
240
+ const monaco = this.monaco
241
+ if (!monaco) return
242
+
243
+ const resolveCompletionItem = opts.resolveProvider
244
+ ? async (item: monaco.languages.CompletionItem, token: monaco.CancellationToken) =>
245
+ withCancellation(token, async (p) =>
246
+ this.binding.sendRequest('completionItem/resolve', toLspCompletionItem(item), p),
247
+ ).then((r) => (r ? mergeResolvedCompletionItem(item, r) : item))
248
+ : undefined
249
+
250
+ const provideCompletionItems = (
251
+ model: monaco.editor.ITextModel,
252
+ position: monaco.Position,
253
+ context: monaco.languages.CompletionContext,
254
+ token: monaco.CancellationToken,
255
+ ) =>
256
+ withCancellation(token, async (p) => {
257
+ const lspContext: any = {
258
+ triggerKind: context.triggerKind + 1, // Monaco is 0-based, LSP is 1-based
259
+ }
260
+ if (context.triggerCharacter) {
261
+ lspContext.triggerCharacter = context.triggerCharacter
262
+ }
263
+ return this.binding.sendRequest(
264
+ 'textDocument/completion',
265
+ {
266
+ textDocument: { uri: model.uri.toString() },
267
+ position: toLspPosition(position),
268
+ context: lspContext,
269
+ },
270
+ p,
271
+ )
272
+ }).then((r) => {
273
+ if (!r) return { suggestions: [] }
274
+ const items = Array.isArray(r) ? r : (r?.items ?? [])
275
+ return { suggestions: items.map((x: any) => toMonacoCompletionItem(monaco, x)) }
276
+ })
277
+
278
+ this.binding.addDisposable(
279
+ monaco.languages.registerCompletionItemProvider(this.options.languageSelector, {
280
+ triggerCharacters: opts.triggerCharacters,
281
+ provideCompletionItems,
282
+ resolveCompletionItem,
283
+ }),
284
+ )
285
+ }
286
+
287
+ /** Register document formatting provider backed by the LSP server */
288
+ private registerFormattingProvider() {
289
+ const monaco = this.monaco
290
+ if (!monaco) return
291
+ this.binding.addDisposable(
292
+ monaco.languages.registerDocumentFormattingEditProvider(this.options.languageSelector, {
293
+ provideDocumentFormattingEdits: (model, options, token) =>
294
+ withCancellation<TextEdit[] | null, []>(
295
+ token,
296
+ (p) =>
297
+ this.binding.sendRequest(
298
+ 'textDocument/formatting',
299
+ {
300
+ textDocument: { uri: model.uri.toString() },
301
+ options: { tabSize: options.tabSize, insertSpaces: options.insertSpaces },
302
+ },
303
+ p,
304
+ ),
305
+ [],
306
+ ).then((r) => (r ? toMonacoTextEdits(r) : [])),
307
+ }),
308
+ )
309
+ }
310
+
311
+ /** Register range formatting provider backed by the LSP server */
312
+ private registerRangeFormattingProvider() {
313
+ const monaco = this.monaco
314
+ if (!monaco) return
315
+ this.binding.addDisposable(
316
+ monaco.languages.registerDocumentRangeFormattingEditProvider(this.options.languageSelector, {
317
+ provideDocumentRangeFormattingEdits: (model, range, options, token) =>
318
+ withCancellation<TextEdit[] | null, []>(
319
+ token,
320
+ (p) =>
321
+ this.binding.sendRequest(
322
+ 'textDocument/rangeFormatting',
323
+ {
324
+ textDocument: { uri: model.uri.toString() },
325
+ range: toLspRange(range),
326
+ options: { tabSize: options.tabSize, insertSpaces: options.insertSpaces },
327
+ },
328
+ p,
329
+ ),
330
+ [],
331
+ ).then((r) => (r ? toMonacoTextEdits(r) : [])),
332
+ }),
333
+ )
334
+ }
335
+
336
+ /** Register on-type formatting provider backed by the LSP server */
337
+ private registerOnTypeFormattingProvider(opts: { firstTriggerCharacter: string; moreTriggerCharacter?: string[] }) {
338
+ const monaco = this.monaco
339
+ if (!monaco) return
340
+ const triggers = [opts.firstTriggerCharacter, ...(opts.moreTriggerCharacter ?? [])]
341
+ this.binding.addDisposable(
342
+ monaco.languages.registerOnTypeFormattingEditProvider(this.options.languageSelector, {
343
+ autoFormatTriggerCharacters: triggers,
344
+ provideOnTypeFormattingEdits: (model, position, ch, options, token) =>
345
+ withCancellation<TextEdit[] | null, []>(
346
+ token,
347
+ (p) =>
348
+ this.binding.sendRequest(
349
+ 'textDocument/onTypeFormatting',
350
+ {
351
+ textDocument: { uri: model.uri.toString() },
352
+ position: toLspPosition(position),
353
+ ch,
354
+ options: { tabSize: options.tabSize, insertSpaces: options.insertSpaces },
355
+ },
356
+ p,
357
+ ),
358
+ [],
359
+ ).then((r) => (r ? toMonacoTextEdits(r) : [])),
360
+ }),
361
+ )
362
+ }
363
+
364
+ // ---------------- Editor event handlers ----------------
365
+ /** Send textDocument/didChange according to server sync kind */
366
+ private handleContentChange(e: monaco.editor.IModelContentChangedEvent) {
367
+ const model = this.editor?.getModel()
368
+ if (!model) return
369
+ const uri = model.uri.toString()
370
+ const version = model.getVersionId()
371
+
372
+ // Respect TextDocumentSyncKind
373
+ let contentChanges: Array<{ text: string; range?: any }>
374
+
375
+ if (this.syncKind === 1) {
376
+ // Full: send entire text
377
+ contentChanges = [{ text: model.getValue() }]
378
+ } else if (this.syncKind === 2) {
379
+ // Incremental: send ranged changes
380
+ contentChanges = e.changes.map((c) => ({ range: toLspRange(c.range), text: c.text }))
381
+ } else {
382
+ // None/unknown: no-op
383
+ return
384
+ }
385
+
386
+ this.binding.sendNotification('textDocument/didChange', {
387
+ textDocument: { uri, version },
388
+ contentChanges,
389
+ })
390
+ }
391
+
392
+ /** Handle model open/close and send didOpen/didClose notifications */
393
+ private handleModelChange(event: monaco.editor.IModelChangedEvent) {
394
+ // Close old model if open
395
+ if (event.oldModelUrl) {
396
+ const oldUri = event.oldModelUrl.toString()
397
+ if (this.openDocuments.has(oldUri)) {
398
+ this.binding.sendNotification('textDocument/didClose', { textDocument: { uri: oldUri } })
399
+ this.openDocuments.delete(oldUri)
400
+ // Clear old model markers
401
+ const monacoApi = this.monaco
402
+ if (monacoApi) {
403
+ const oldModel = monacoApi.editor.getModel(event.oldModelUrl)
404
+ if (oldModel) monacoApi.editor.setModelMarkers(oldModel, this.MARKER_OWNER, [])
405
+ }
406
+ }
407
+ }
408
+
409
+ // Open new model
410
+ const newModel = this.editor?.getModel()
411
+ if (!newModel) return
412
+
413
+ const newUri = newModel.uri.toString()
414
+
415
+ // Avoid reopening same URI
416
+ if (this.openDocuments.has(newUri)) return
417
+
418
+ // Cleanup on model dispose
419
+ this.binding.addDisposable(
420
+ newModel.onWillDispose(() => {
421
+ const uri = newModel.uri.toString()
422
+ if (this.openDocuments.has(uri)) {
423
+ this.binding.sendNotification('textDocument/didClose', { textDocument: { uri } })
424
+ this.openDocuments.delete(uri)
425
+ }
426
+ // Clear markers on dispose
427
+ const monacoApi = this.monaco
428
+ if (monacoApi) monacoApi.editor.setModelMarkers(newModel, this.MARKER_OWNER, [])
429
+ }),
430
+ )
431
+
432
+ // Send didOpen and track
433
+ this.binding.sendNotification('textDocument/didOpen', {
434
+ textDocument: {
435
+ uri: newUri,
436
+ languageId: newModel.getLanguageId(),
437
+ version: newModel.getVersionId(),
438
+ text: newModel.getValue(),
439
+ },
440
+ })
441
+ this.openDocuments.add(newUri)
442
+ }
443
+
444
+ /** Listen for publishDiagnostics and update Monaco markers */
445
+ private wireDiagnostics() {
446
+ const monacoApi = this.monaco
447
+ if (!monacoApi) return
448
+
449
+ this.binding.onServerNotification('textDocument/publishDiagnostics', (params: any) => {
450
+ try {
451
+ const { uri, diagnostics, version } = params as {
452
+ uri: string
453
+ diagnostics: any[]
454
+ version?: number
455
+ }
456
+ const model = monacoApi.editor.getModel(monacoApi.Uri.parse(uri))
457
+ if (!model) return
458
+ if (typeof version === 'number' && model.getVersionId() !== version) return
459
+ const markers = toMonacoMarkers(monacoApi, diagnostics)
460
+ monacoApi.editor.setModelMarkers(model, this.MARKER_OWNER, markers)
461
+ } catch (e) {
462
+ // Swallow diagnostics errors
463
+ }
464
+ })
465
+ }
466
+
467
+ private static CAPABILITIES: ClientCapabilities = {
468
+ textDocument: {
469
+ synchronization: {
470
+ dynamicRegistration: false,
471
+ willSave: false,
472
+ willSaveWaitUntil: false,
473
+ didSave: false,
474
+ },
475
+ completion: {
476
+ dynamicRegistration: false,
477
+ completionItem: {
478
+ snippetSupport: true,
479
+ commitCharactersSupport: true,
480
+ documentationFormat: ['markdown', 'plaintext'],
481
+ deprecatedSupport: true,
482
+ preselectSupport: true,
483
+ tagSupport: {
484
+ valueSet: [1], // 1 = Deprecated
485
+ },
486
+ insertReplaceSupport: true,
487
+ resolveSupport: {
488
+ properties: ['documentation', 'detail', 'additionalTextEdits'],
489
+ },
490
+ insertTextModeSupport: {
491
+ valueSet: [1, 2], // 1 = asIs, 2 = adjustIndentation
492
+ },
493
+ labelDetailsSupport: true,
494
+ },
495
+ completionItemKind: {
496
+ valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
497
+ },
498
+ contextSupport: true,
499
+ },
500
+ hover: {
501
+ dynamicRegistration: false,
502
+ contentFormat: ['markdown', 'plaintext'],
503
+ },
504
+ signatureHelp: {
505
+ dynamicRegistration: false,
506
+ signatureInformation: {
507
+ documentationFormat: ['markdown', 'plaintext'],
508
+ parameterInformation: {
509
+ labelOffsetSupport: true,
510
+ },
511
+ activeParameterSupport: true,
512
+ },
513
+ contextSupport: true,
514
+ },
515
+ declaration: {
516
+ dynamicRegistration: false,
517
+ linkSupport: true,
518
+ },
519
+ definition: {
520
+ dynamicRegistration: false,
521
+ linkSupport: true,
522
+ },
523
+ typeDefinition: {
524
+ dynamicRegistration: false,
525
+ linkSupport: true,
526
+ },
527
+ implementation: {
528
+ dynamicRegistration: false,
529
+ linkSupport: true,
530
+ },
531
+ references: {
532
+ dynamicRegistration: false,
533
+ },
534
+ documentHighlight: {
535
+ dynamicRegistration: false,
536
+ },
537
+ documentSymbol: {
538
+ dynamicRegistration: false,
539
+ symbolKind: {
540
+ valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
541
+ },
542
+ hierarchicalDocumentSymbolSupport: true,
543
+ tagSupport: {
544
+ valueSet: [1], // 1 = Deprecated
545
+ },
546
+ labelSupport: true,
547
+ },
548
+ codeAction: {
549
+ dynamicRegistration: false,
550
+ codeActionLiteralSupport: {
551
+ codeActionKind: {
552
+ valueSet: [
553
+ '',
554
+ 'quickfix',
555
+ 'refactor',
556
+ 'refactor.extract',
557
+ 'refactor.inline',
558
+ 'refactor.rewrite',
559
+ 'source',
560
+ 'source.organizeImports',
561
+ ],
562
+ },
563
+ },
564
+ isPreferredSupport: true,
565
+ disabledSupport: true,
566
+ dataSupport: true,
567
+ resolveSupport: {
568
+ properties: ['edit'],
569
+ },
570
+ honorsChangeAnnotations: false,
571
+ },
572
+ codeLens: {
573
+ dynamicRegistration: false,
574
+ },
575
+ formatting: {
576
+ dynamicRegistration: false,
577
+ },
578
+ rangeFormatting: {
579
+ dynamicRegistration: false,
580
+ },
581
+ onTypeFormatting: {
582
+ dynamicRegistration: false,
583
+ },
584
+ rename: {
585
+ dynamicRegistration: false,
586
+ prepareSupport: true,
587
+ prepareSupportDefaultBehavior: 1,
588
+ honorsChangeAnnotations: false,
589
+ },
590
+ documentLink: {
591
+ dynamicRegistration: false,
592
+ tooltipSupport: true,
593
+ },
594
+ foldingRange: {
595
+ dynamicRegistration: false,
596
+ rangeLimit: 5000,
597
+ lineFoldingOnly: true,
598
+ },
599
+ selectionRange: {
600
+ dynamicRegistration: false,
601
+ },
602
+ publishDiagnostics: {
603
+ relatedInformation: true,
604
+ tagSupport: {
605
+ valueSet: [1, 2], // 1 = Unnecessary, 2 = Deprecated
606
+ },
607
+ versionSupport: true,
608
+ codeDescriptionSupport: true,
609
+ dataSupport: true,
610
+ },
611
+ },
612
+ workspace: {
613
+ applyEdit: false,
614
+ workspaceEdit: {
615
+ documentChanges: false,
616
+ resourceOperations: [],
617
+ failureHandling: 'abort',
618
+ normalizesLineEndings: false,
619
+ changeAnnotationSupport: {
620
+ groupsOnLabel: false,
621
+ },
622
+ },
623
+ didChangeConfiguration: {
624
+ dynamicRegistration: false,
625
+ },
626
+ didChangeWatchedFiles: {
627
+ dynamicRegistration: false,
628
+ },
629
+ symbol: {
630
+ dynamicRegistration: false,
631
+ symbolKind: {
632
+ valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
633
+ },
634
+ tagSupport: {
635
+ valueSet: [1], // 1 = Deprecated
636
+ },
637
+ },
638
+ executeCommand: {
639
+ dynamicRegistration: false,
640
+ },
641
+ workspaceFolders: false,
642
+ configuration: false,
643
+ semanticTokens: {
644
+ refreshSupport: false,
645
+ },
646
+ codeLens: {
647
+ refreshSupport: false,
648
+ },
649
+ fileOperations: {
650
+ dynamicRegistration: false,
651
+ didCreate: false,
652
+ didRename: false,
653
+ didDelete: false,
654
+ willCreate: false,
655
+ willRename: false,
656
+ willDelete: false,
657
+ },
658
+ inlineValue: {
659
+ refreshSupport: false,
660
+ },
661
+ inlayHint: {
662
+ refreshSupport: false,
663
+ },
664
+ diagnostics: {
665
+ refreshSupport: false,
666
+ },
667
+ },
668
+ window: {
669
+ workDoneProgress: true,
670
+ showMessage: {
671
+ messageActionItem: {
672
+ additionalPropertiesSupport: true,
673
+ },
674
+ },
675
+ showDocument: {
676
+ support: false,
677
+ },
678
+ },
679
+ general: {
680
+ regularExpressions: {
681
+ engine: 'ECMAScript',
682
+ version: 'ES2020',
683
+ },
684
+ markdown: {
685
+ parser: 'marked',
686
+ version: '1.0.0',
687
+ },
688
+ },
689
+ }
690
+ }
691
+
692
+ const withCancellation = async <R, E = null>(
693
+ token: monaco.CancellationToken,
694
+ f: (p: { signal: AbortSignal }) => Promise<MaybeCancelled<R>>,
695
+ cancelled: E = null as E,
696
+ ) => {
697
+ if (token.isCancellationRequested) return cancelled
698
+ const controller = new AbortController()
699
+ const off = token.onCancellationRequested(() => controller.abort())
700
+ try {
701
+ const result = await f({ signal: controller.signal })
702
+ if (result.cancelled) return cancelled
703
+ return result.result
704
+ } catch (e) {
705
+ return cancelled
706
+ } finally {
707
+ off.dispose()
708
+ }
709
+ }