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/LICENSE +21 -0
- package/README.md +61 -0
- package/lib/cjs/error.js +46 -0
- package/lib/cjs/error.js.map +1 -0
- package/lib/cjs/index.js +594 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/protocol.js +21 -0
- package/lib/cjs/protocol.js.map +1 -0
- package/lib/cjs/transport.js +269 -0
- package/lib/cjs/transport.js.map +1 -0
- package/lib/cjs/util.js +264 -0
- package/lib/cjs/util.js.map +1 -0
- package/lib/esm/error.js +40 -0
- package/lib/esm/error.js.map +1 -0
- package/lib/esm/index.js +576 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/protocol.js +15 -0
- package/lib/esm/protocol.js.map +1 -0
- package/lib/esm/transport.js +265 -0
- package/lib/esm/transport.js.map +1 -0
- package/lib/esm/util.js +252 -0
- package/lib/esm/util.js.map +1 -0
- package/lib/ts/error.d.ts +26 -0
- package/lib/ts/error.d.ts.map +1 -0
- package/lib/ts/index.d.ts +78 -0
- package/lib/ts/index.d.ts.map +1 -0
- package/lib/ts/protocol.d.ts +80 -0
- package/lib/ts/protocol.d.ts.map +1 -0
- package/lib/ts/transport.d.ts +74 -0
- package/lib/ts/transport.d.ts.map +1 -0
- package/lib/ts/util.d.ts +21 -0
- package/lib/ts/util.d.ts.map +1 -0
- package/package.json +68 -0
- package/src/error.ts +48 -0
- package/src/index.ts +709 -0
- package/src/protocol.ts +129 -0
- package/src/transport.ts +337 -0
- package/src/util.ts +300 -0
- package/tsconfig.json +49 -0
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
|
+
}
|