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/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
+ }