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/util.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CompletionItem,
|
|
3
|
+
TextEdit,
|
|
4
|
+
InsertReplaceEdit,
|
|
5
|
+
Range,
|
|
6
|
+
Position,
|
|
7
|
+
Diagnostic,
|
|
8
|
+
DiagnosticRelatedInformation,
|
|
9
|
+
Hover,
|
|
10
|
+
MarkupContent,
|
|
11
|
+
MarkedString,
|
|
12
|
+
} from 'vscode-languageserver-protocol'
|
|
13
|
+
import type * as monaco from 'monaco-editor'
|
|
14
|
+
|
|
15
|
+
/** Store original LSP item on Monaco completion item */
|
|
16
|
+
const LSP_ITEM_KEY = Symbol.for('__lsp_completion_item__')
|
|
17
|
+
|
|
18
|
+
/** Convert LSP CompletionItem to Monaco and stash original */
|
|
19
|
+
export const toMonacoCompletionItem = (
|
|
20
|
+
monacoApi: typeof monaco,
|
|
21
|
+
item: CompletionItem,
|
|
22
|
+
): monaco.languages.CompletionItem => {
|
|
23
|
+
const insertText = getInsertText(item)
|
|
24
|
+
const range = toMonacoRange(item.textEdit)
|
|
25
|
+
|
|
26
|
+
// Determine insert text format (snippet vs plain)
|
|
27
|
+
const insertTextFormat = item.insertTextFormat ?? 1 // 1 = PlainText, 2 = Snippet
|
|
28
|
+
const isSnippet = insertTextFormat === 2
|
|
29
|
+
|
|
30
|
+
const result: any = {
|
|
31
|
+
label: item.label,
|
|
32
|
+
insertText,
|
|
33
|
+
kind: item.kind ?? 0,
|
|
34
|
+
detail: item.detail,
|
|
35
|
+
documentation: typeof item.documentation === 'string' ? item.documentation : item.documentation?.value,
|
|
36
|
+
sortText: item.sortText,
|
|
37
|
+
filterText: item.filterText,
|
|
38
|
+
commitCharacters: item.commitCharacters,
|
|
39
|
+
additionalTextEdits: item.additionalTextEdits?.map(toMonacoTextEdit),
|
|
40
|
+
// Insert as snippet when requested
|
|
41
|
+
insertTextRules: isSnippet ? monacoApi.languages.CompletionItemInsertTextRule.InsertAsSnippet : undefined, // 4 = InsertAsSnippet
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Set explicit range only when provided by LSP
|
|
45
|
+
if (range !== undefined) {
|
|
46
|
+
result.range = range
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Keep original for resolve
|
|
50
|
+
result[LSP_ITEM_KEY] = item
|
|
51
|
+
|
|
52
|
+
return result as monaco.languages.CompletionItem
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Convert Monaco CompletionItem to LSP, preferring original */
|
|
56
|
+
export const toLspCompletionItem = (item: monaco.languages.CompletionItem): CompletionItem => {
|
|
57
|
+
// Try to retrieve the original LSP item
|
|
58
|
+
const originalItem = (item as any)[LSP_ITEM_KEY] as CompletionItem | undefined
|
|
59
|
+
|
|
60
|
+
if (originalItem) {
|
|
61
|
+
// Use original to preserve LSP fields
|
|
62
|
+
return originalItem
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fallback: convert Monaco item to LSP format
|
|
66
|
+
const result: CompletionItem = {
|
|
67
|
+
label: typeof item.label === 'string' ? item.label : item.label.label,
|
|
68
|
+
kind: item.kind as any,
|
|
69
|
+
detail: item.detail,
|
|
70
|
+
documentation: typeof item.documentation === 'string' ? item.documentation : item.documentation?.value,
|
|
71
|
+
sortText: item.sortText,
|
|
72
|
+
filterText: item.filterText,
|
|
73
|
+
insertText: typeof item.insertText === 'string' ? item.insertText : undefined,
|
|
74
|
+
commitCharacters: item.commitCharacters,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Merge resolved LSP fields into Monaco item */
|
|
81
|
+
export const mergeResolvedCompletionItem = (
|
|
82
|
+
monacoItem: monaco.languages.CompletionItem,
|
|
83
|
+
resolvedLspItem: CompletionItem,
|
|
84
|
+
): monaco.languages.CompletionItem => {
|
|
85
|
+
// Merge fields that may be resolved
|
|
86
|
+
const merged: monaco.languages.CompletionItem = {
|
|
87
|
+
...monacoItem,
|
|
88
|
+
detail: resolvedLspItem.detail ?? monacoItem.detail,
|
|
89
|
+
documentation:
|
|
90
|
+
resolvedLspItem.documentation !== undefined
|
|
91
|
+
? typeof resolvedLspItem.documentation === 'string'
|
|
92
|
+
? resolvedLspItem.documentation
|
|
93
|
+
: resolvedLspItem.documentation?.value
|
|
94
|
+
: monacoItem.documentation,
|
|
95
|
+
additionalTextEdits: resolvedLspItem.additionalTextEdits?.map(toMonacoTextEdit) ?? monacoItem.additionalTextEdits,
|
|
96
|
+
command: resolvedLspItem.command
|
|
97
|
+
? {
|
|
98
|
+
id: resolvedLspItem.command.command,
|
|
99
|
+
title: resolvedLspItem.command.title,
|
|
100
|
+
arguments: resolvedLspItem.command.arguments,
|
|
101
|
+
}
|
|
102
|
+
: monacoItem.command,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Update insert text/range when textEdit provided
|
|
106
|
+
if (resolvedLspItem.textEdit) {
|
|
107
|
+
const insertText = getInsertText(resolvedLspItem)
|
|
108
|
+
const range = toMonacoRange(resolvedLspItem.textEdit)
|
|
109
|
+
merged.insertText = insertText
|
|
110
|
+
if (range !== undefined) {
|
|
111
|
+
merged.range = range
|
|
112
|
+
}
|
|
113
|
+
} else if (resolvedLspItem.insertText !== undefined) {
|
|
114
|
+
// Handle resolve without textEdit
|
|
115
|
+
merged.insertText = resolvedLspItem.insertText
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Update snippet rule if format changed
|
|
119
|
+
if (resolvedLspItem.insertTextFormat !== undefined) {
|
|
120
|
+
const isSnippet = resolvedLspItem.insertTextFormat === 2
|
|
121
|
+
merged.insertTextRules = isSnippet ? 4 : undefined // 4 = InsertAsSnippet
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Store updated LSP item
|
|
125
|
+
;(merged as any)[LSP_ITEM_KEY] = resolvedLspItem
|
|
126
|
+
|
|
127
|
+
return merged
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Extract insert text: textEdit.newText → insertText → label */
|
|
131
|
+
const getInsertText = (item: CompletionItem): string => {
|
|
132
|
+
if (item.textEdit && 'newText' in item.textEdit) {
|
|
133
|
+
return item.textEdit.newText
|
|
134
|
+
}
|
|
135
|
+
return item.insertText ?? item.label
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Convert LSP TextEdit to Monaco */
|
|
139
|
+
const toMonacoTextEdit = (edit: TextEdit): monaco.languages.TextEdit => {
|
|
140
|
+
return {
|
|
141
|
+
range: toMonacoIRange(edit.range),
|
|
142
|
+
text: edit.newText,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Convert LSP TextEdit/InsertReplaceEdit to Monaco range */
|
|
147
|
+
const toMonacoRange = (
|
|
148
|
+
edit: TextEdit | InsertReplaceEdit | undefined,
|
|
149
|
+
): monaco.IRange | { insert: monaco.IRange; replace: monaco.IRange } | undefined => {
|
|
150
|
+
if (!edit) return undefined
|
|
151
|
+
|
|
152
|
+
if ('range' in edit) {
|
|
153
|
+
return toMonacoIRange(edit.range)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
insert: toMonacoIRange(edit.insert),
|
|
158
|
+
replace: toMonacoIRange(edit.replace),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Convert LSP Range to Monaco IRange (0-based → 1-based) */
|
|
163
|
+
const toMonacoIRange = (range: Range): monaco.IRange => {
|
|
164
|
+
return {
|
|
165
|
+
startLineNumber: range.start.line + 1,
|
|
166
|
+
startColumn: range.start.character + 1,
|
|
167
|
+
endLineNumber: range.end.line + 1,
|
|
168
|
+
endColumn: range.end.character + 1,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Convert Monaco IRange to LSP Range (1-based → 0-based) */
|
|
173
|
+
export const toLspRange = (range: monaco.IRange): Range => {
|
|
174
|
+
return {
|
|
175
|
+
start: {
|
|
176
|
+
line: range.startLineNumber - 1,
|
|
177
|
+
character: range.startColumn - 1,
|
|
178
|
+
},
|
|
179
|
+
end: {
|
|
180
|
+
line: range.endLineNumber - 1,
|
|
181
|
+
character: range.endColumn - 1,
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Convert LSP Position to Monaco IPosition */
|
|
187
|
+
export const toMonacoPosition = (position: Position): monaco.IPosition => {
|
|
188
|
+
return {
|
|
189
|
+
lineNumber: position.line + 1,
|
|
190
|
+
column: position.character + 1,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Convert Monaco IPosition to LSP Position */
|
|
195
|
+
export const toLspPosition = (position: monaco.IPosition): Position => {
|
|
196
|
+
return {
|
|
197
|
+
line: position.lineNumber - 1,
|
|
198
|
+
character: position.column - 1,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Map LSP diagnostics to Monaco markers */
|
|
203
|
+
export const toMonacoMarkers = (monacoApi: typeof monaco, diagnostics: Diagnostic[]): monaco.editor.IMarkerData[] => {
|
|
204
|
+
const toSeverity = (s: number | undefined): monaco.MarkerSeverity => {
|
|
205
|
+
switch (s) {
|
|
206
|
+
case 1:
|
|
207
|
+
return monacoApi.MarkerSeverity.Error
|
|
208
|
+
case 2:
|
|
209
|
+
return monacoApi.MarkerSeverity.Warning
|
|
210
|
+
case 3:
|
|
211
|
+
return monacoApi.MarkerSeverity.Info
|
|
212
|
+
case 4:
|
|
213
|
+
return monacoApi.MarkerSeverity.Hint
|
|
214
|
+
default:
|
|
215
|
+
return monacoApi.MarkerSeverity.Info
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const toTags = (tags: readonly number[] | undefined): monaco.MarkerTag[] | undefined => {
|
|
220
|
+
if (!tags || tags.length === 0) return undefined
|
|
221
|
+
const mapped: monaco.MarkerTag[] = []
|
|
222
|
+
for (const t of tags) {
|
|
223
|
+
if (t === 1) mapped.push(monacoApi.MarkerTag.Unnecessary)
|
|
224
|
+
if (t === 2) mapped.push(monacoApi.MarkerTag.Deprecated)
|
|
225
|
+
}
|
|
226
|
+
return mapped.length ? mapped : undefined
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const toRelated = (
|
|
230
|
+
infos: readonly DiagnosticRelatedInformation[] | undefined,
|
|
231
|
+
): monaco.editor.IRelatedInformation[] | undefined => {
|
|
232
|
+
if (!infos || infos.length === 0) return undefined
|
|
233
|
+
return infos.map((ri) => ({
|
|
234
|
+
resource: monacoApi.Uri.parse(ri.location.uri),
|
|
235
|
+
message: ri.message,
|
|
236
|
+
startLineNumber: ri.location.range.start.line + 1,
|
|
237
|
+
startColumn: ri.location.range.start.character + 1,
|
|
238
|
+
endLineNumber: ri.location.range.end.line + 1,
|
|
239
|
+
endColumn: ri.location.range.end.character + 1,
|
|
240
|
+
}))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return diagnostics.map((d) => ({
|
|
244
|
+
severity: toSeverity(d.severity),
|
|
245
|
+
message: d.message,
|
|
246
|
+
code: typeof d.code === 'string' ? d.code : typeof d.code === 'number' ? String(d.code) : undefined,
|
|
247
|
+
source: d.source,
|
|
248
|
+
tags: toTags(d.tags),
|
|
249
|
+
relatedInformation: toRelated(d.relatedInformation),
|
|
250
|
+
startLineNumber: d.range.start.line + 1,
|
|
251
|
+
startColumn: d.range.start.character + 1,
|
|
252
|
+
endLineNumber: d.range.end.line + 1,
|
|
253
|
+
endColumn: d.range.end.character + 1,
|
|
254
|
+
}))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Map LSP TextEdits to Monaco edits */
|
|
258
|
+
export const toMonacoTextEdits = (edits: TextEdit[]): monaco.languages.TextEdit[] => {
|
|
259
|
+
return edits.map((e) => ({
|
|
260
|
+
range: toMonacoIRange(e.range),
|
|
261
|
+
text: e.newText,
|
|
262
|
+
}))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Convert LSP Hover to Monaco Hover */
|
|
266
|
+
export const lspHoverToMonaco = (hover: Hover): monaco.languages.Hover => {
|
|
267
|
+
const contents: monaco.IMarkdownString[] = []
|
|
268
|
+
|
|
269
|
+
const pushString = (value: string) => contents.push({ value })
|
|
270
|
+
|
|
271
|
+
const pushMarked = (m: MarkedString): void => {
|
|
272
|
+
if (typeof m === 'string') {
|
|
273
|
+
pushString(m)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
// { language, value } → markdown code block
|
|
277
|
+
const fenced = '```' + (m.language || '') + '\n' + m.value + '\n```'
|
|
278
|
+
pushString(fenced)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const pushMarkup = (mc: MarkupContent) => {
|
|
282
|
+
// kind: 'markdown' | 'plaintext'. For plaintext, keep as-is.
|
|
283
|
+
pushString(mc.value)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const c = hover.contents as any
|
|
287
|
+
if (Array.isArray(c)) {
|
|
288
|
+
for (const item of c) pushMarked(item as MarkedString)
|
|
289
|
+
} else if (typeof c === 'string' || (c && typeof c === 'object' && 'language' in c)) {
|
|
290
|
+
pushMarked(c as MarkedString)
|
|
291
|
+
} else if (c && typeof c === 'object' && 'kind' in c) {
|
|
292
|
+
pushMarkup(c as MarkupContent)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const result: monaco.languages.Hover = {
|
|
296
|
+
contents,
|
|
297
|
+
}
|
|
298
|
+
if (hover.range) result.range = toMonacoIRange(hover.range)
|
|
299
|
+
return result
|
|
300
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
/* Language and Environment */
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"lib": ["DOM", "ESNext"],
|
|
6
|
+
|
|
7
|
+
/* Modules */
|
|
8
|
+
"rootDir": "src" /* Specify the root folder within your source files. */,
|
|
9
|
+
"module": "NodeNext" /* Specify what module code is generated. */,
|
|
10
|
+
"moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
|
11
|
+
|
|
12
|
+
/* Emit */
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
|
|
15
|
+
"declarationMap": true /* Create sourcemaps for d.ts files. */,
|
|
16
|
+
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
|
|
17
|
+
"outDir": "lib/esm" /* Specify an output folder for all emitted files. */,
|
|
18
|
+
"declarationDir": "lib/ts",
|
|
19
|
+
|
|
20
|
+
/* Interop Constraints */
|
|
21
|
+
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
|
22
|
+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
|
23
|
+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
|
24
|
+
|
|
25
|
+
/* Type Checking */
|
|
26
|
+
"strict": true /* Enable all strict type-checking options. */,
|
|
27
|
+
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
|
|
28
|
+
"strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */,
|
|
29
|
+
"strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
|
|
30
|
+
"strictBindCallApply": true /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */,
|
|
31
|
+
"strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */,
|
|
32
|
+
"strictBuiltinIteratorReturn": true /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */,
|
|
33
|
+
"noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */,
|
|
34
|
+
"useUnknownInCatchVariables": true /* Default catch clause variables as 'unknown' instead of 'any'. */,
|
|
35
|
+
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
|
|
36
|
+
"noUnusedLocals": true /* Enable error reporting when local variables aren't read. */,
|
|
37
|
+
"noUnusedParameters": true /* Raise an error when a function parameter isn't read. */,
|
|
38
|
+
"exactOptionalPropertyTypes": false /* Interpret optional property types as written, rather than adding 'undefined'. */,
|
|
39
|
+
"noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
|
|
40
|
+
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
|
|
41
|
+
"noUncheckedIndexedAccess": true /* Add 'undefined' to a type when accessed using an index. */,
|
|
42
|
+
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
|
|
43
|
+
"noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type. */,
|
|
44
|
+
|
|
45
|
+
/* Completeness */
|
|
46
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
47
|
+
},
|
|
48
|
+
"include": ["src/**/*"]
|
|
49
|
+
}
|