From 048f2390362c8d38b252f2e34408f17ef3750df2 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Tue, 25 Jul 2023 15:42:32 +0200 Subject: [PATCH] Make Monaco integration functional --- demo/package.json | 2 + demo/src/index.js | 6 +- demo/webpack.config.js | 12 +- packages/language-service/lib/error.js | 59 ---- packages/language-service/lib/markdown.js | 51 ---- packages/language-service/lib/outline.js | 88 ------ packages/monaco/index.js | 79 +----- packages/monaco/lib/convert.js | 315 ---------------------- packages/monaco/lib/language-features.js | 282 ------------------- packages/monaco/mdx.worker.js | 62 ++--- packages/monaco/package.json | 3 +- 11 files changed, 58 insertions(+), 901 deletions(-) delete mode 100644 packages/language-service/lib/error.js delete mode 100644 packages/language-service/lib/markdown.js delete mode 100644 packages/language-service/lib/outline.js delete mode 100644 packages/monaco/lib/convert.js delete mode 100644 packages/monaco/lib/language-features.js diff --git a/demo/package.json b/demo/package.json index e210f4c1..2c3bbb26 100644 --- a/demo/package.json +++ b/demo/package.json @@ -9,11 +9,13 @@ }, "dependencies": { "@mdx-js/monaco": "*", + "@types/node": "^18.0.0", "@types/webpack-env": "^1.0.0", "css-loader": "^6.0.0", "html-webpack-plugin": "^5.0.0", "mini-css-extract-plugin": "^2.0.0", "monaco-editor": "^0.34.0", + "path-browserify": "^1.0.0", "remark-frontmatter": "^4.0.0", "remark-gfm": "^3.0.0", "webpack": "^5.0.0", diff --git a/demo/src/index.js b/demo/src/index.js index fd144306..454f2140 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -35,7 +35,9 @@ window.MonacoEnvironment = { } case 'mdx': { - return new Worker(new URL('mdx.worker.js', import.meta.url)) + return new Worker( + new URL('@mdx-js/monaco/mdx.worker.js', import.meta.url) + ) } default: { @@ -60,7 +62,7 @@ monaco.languages.register({ }) // This is where we actually configure the MDX integration. -initializeMonacoMdx(monaco, { +await initializeMonacoMdx(monaco, { createData: { compilerOptions: { checkJs: true diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 6da7bb4a..0ac7c0e9 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -1,5 +1,9 @@ +import {createRequire} from 'node:module' import HtmlWebPackPlugin from 'html-webpack-plugin' import MiniCssExtractPlugin from 'mini-css-extract-plugin' +import webpack from 'webpack' + +const require = createRequire(import.meta.url) /** * @type {import('webpack').Configuration} @@ -8,7 +12,10 @@ const config = { devtool: 'source-map', entry: './src/index.js', resolve: { - conditionNames: ['worker'] + conditionNames: ['worker'], + alias: { + path: require.resolve('path-browserify') + } }, module: { exprContextRegExp: /$^/, @@ -34,7 +41,8 @@ const config = { }, plugins: [ new HtmlWebPackPlugin(), - new MiniCssExtractPlugin({filename: '[contenthash].css'}) + new MiniCssExtractPlugin({filename: '[contenthash].css'}), + new webpack.IgnorePlugin({resourceRegExp: /perf_hooks/}) ] } diff --git a/packages/language-service/lib/error.js b/packages/language-service/lib/error.js deleted file mode 100644 index cb91d5b4..00000000 --- a/packages/language-service/lib/error.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @typedef {import('typescript/lib/tsserverlibrary.js').DiagnosticWithLocation} DiagnosticWithLocation - * @typedef {import('vfile-message').VFileMessage} VFileMessage - */ - -/** - * Check whether or not an object is a vfile message. - * - * @param {unknown} object - * The object to check. - * @returns {object is VFileMessage} - * Whether or not the object is a vfile message. - */ -function isVFileMessage(object) { - if (typeof object !== 'object') { - return false - } - - if (!object) { - return false - } - - const message = /** @type {VFileMessage | Record} */ (object) - return typeof message.message === 'string' -} - -/** - * Represent an error as a TypeScript diagnostic. - * - * @param {import('typescript/lib/tsserverlibrary.js')} ts - * The TypeScript module to use. - * @param {unknown} error - * The error to represent. - * @returns {[DiagnosticWithLocation]} - * The error as a TypeScript diagnostic. - */ -export function toDiagnostic(ts, error) { - let start = 0 - let length = 1 - let messageText = 'An unexpecter parsing error occurred' - if (isVFileMessage(error)) { - start = error.position?.start?.offset ?? 0 - length = (error.position?.end?.offset ?? start) - start - messageText = error.reason - } - - return [ - { - category: ts.DiagnosticCategory.Error, - // @ts-expect-error A number is expected, but it’s only used for display purposes. - code: 'MDX', - messageText, - // @ts-expect-error We don’t use file. - file: undefined, - start, - length - } - ] -} diff --git a/packages/language-service/lib/markdown.js b/packages/language-service/lib/markdown.js deleted file mode 100644 index 0af4c105..00000000 --- a/packages/language-service/lib/markdown.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @typedef {import('mdast').Definition} Definition - * @typedef {import('mdast').LinkReference} LinkReference - * @typedef {import('mdast').Root} Root - * @typedef {import('unist').Position} Position - */ - -import {visit} from 'unist-util-visit' - -/** - * Get the definition link of a markdown AST at a given position. - * - * @param {Root} ast - * The markdown AST. - * @param {number} position - * The position to get the definition for. - * @returns {Definition | undefined} - * The position at which the definition can be found. - */ -export function getMarkdownDefinitionAtPosition(ast, position) { - /** @type {LinkReference | undefined} */ - let reference - /** @type {Map} */ - const definitions = new Map() - - visit(ast, (node) => { - const start = node.position?.start.offset - const end = node.position?.end.offset - - if (start === undefined || end === undefined) { - return - } - - if (node.type === 'linkReference') { - if (position >= start && position <= end) { - reference = node - } - } else if ( - node.type === 'definition' && - !definitions.has(node.identifier) - ) { - definitions.set(node.identifier, node) - } - }) - - if (!reference) { - return - } - - return definitions.get(reference.identifier) -} diff --git a/packages/language-service/lib/outline.js b/packages/language-service/lib/outline.js deleted file mode 100644 index 118d1b11..00000000 --- a/packages/language-service/lib/outline.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @typedef {import('mdast').Content} Content - * @typedef {import('mdast').Parent} Parent - * @typedef {import('mdast').Root} Root - * @typedef {import('typescript/lib/tsserverlibrary.js').OutliningSpan} OutliningSpan - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Position} Position - */ - -import {visit} from 'unist-util-visit' -import {unistPositionToTextSpan} from './utils.js' - -/** - * Create outline spans based on a markdown AST. - * - * @param {typeof import('typescript/lib/tsserverlibrary.js')} ts - * The TypeScript module to use. - * @param {Root} ast - * The markdown AST to get outline spans for. - * @returns {OutliningSpan[]} - * The outline spans that represent Markdown sections - */ -export function getFoldingRegions(ts, ast) { - /** @type {OutliningSpan[]} */ - const sections = [] - - visit(ast, (node) => { - if (node.position && (node.type === 'code' || node.type === 'blockquote')) { - sections.push({ - textSpan: unistPositionToTextSpan(node.position), - hintSpan: unistPositionToTextSpan(node.position), - bannerText: node.type, - autoCollapse: false, - kind: ts.OutliningSpanKind.Region - }) - } - - if (!('children' in node)) { - return - } - - /** @type {(OutliningSpan | undefined)[]} */ - const scope = [] - - for (const child of node.children) { - const end = child.position?.end?.offset - - if (end === undefined) { - continue - } - - if (child.type === 'heading') { - const index = child.depth - 1 - for (const done of scope.splice(index)) { - if (done) { - sections.push(done) - } - } - - scope[index] = { - textSpan: unistPositionToTextSpan( - /** @type {Position} */ (child.position) - ), - hintSpan: unistPositionToTextSpan( - /** @type {Position} */ (child.position) - ), - bannerText: 'Heading ' + child.depth, - autoCollapse: false, - kind: ts.OutliningSpanKind.Region - } - } - - for (const section of scope) { - if (section) { - section.textSpan.length = end - section.textSpan.start - } - } - } - - for (const section of scope) { - if (section) { - sections.push(section) - } - } - }) - - return sections -} diff --git a/packages/monaco/index.js b/packages/monaco/index.js index 12bc8e4f..f625d702 100644 --- a/packages/monaco/index.js +++ b/packages/monaco/index.js @@ -12,14 +12,7 @@ * Options to pass to the MDX worker. */ -import {registerMarkerDataProvider} from 'monaco-marker-data-provider' -import { - createCompletionItemProvider, - createDefinitionProvider, - createHoverProvider, - createMarkerDataProvider, - createReferenceProvider -} from './lib/language-features.js' +import * as volar from '@volar/monaco' /** * Initialize MDX IntelliSense for MDX. @@ -28,73 +21,21 @@ import { * The Monaco editor module. * @param {InitializeMonacoMdxOptions} [options] * Additional options for MDX IntelliSense. - * @returns {IDisposable} + * @returns {Promise} * A disposable. */ -export function initializeMonacoMdx(monaco, options) { - const worker = /** @type {MonacoWebWorker} */ ( - monaco.editor.createWebWorker({ - moduleId: '@mdx-js/monaco', - label: 'mdx', - keepIdleModels: true, - createData: /** @type {CreateData} */ ({ - compilerOptions: options?.createData?.compilerOptions || {}, - extraLibs: options?.createData?.extraLibs || {}, - inlayHintsOptions: options?.createData?.inlayHintsOptions || {} - }) - }) - ) - - /** - * @param {Uri[]} resources - */ - const getProxy = (...resources) => worker.withSyncedResources(resources) - - /** - * Synchronize all MDX, JavaScript, and TypeScript files with the web worker. - * - * @param {ITextModel} model - */ - const synchronize = (model) => { - const languageId = model.getLanguageId() - if ( - languageId === 'mdx' || - languageId === 'javascript' || - languageId === 'javascriptreact' || - languageId === 'typescript' || - languageId === 'typescriptreact' - ) { - getProxy(model.uri) - } - } - - monaco.editor.onDidChangeModelLanguage(({model}) => { - synchronize(model) +export async function initializeMonacoMdx(monaco, options) { + const worker = monaco.editor.createWebWorker({ + moduleId: '@mdx-js/monaco/mdx.worker.js', + label: 'mdx' }) - const disposables = [ worker, - monaco.editor.onDidCreateModel(synchronize), - monaco.languages.registerCompletionItemProvider( - 'mdx', - createCompletionItemProvider(monaco, getProxy) - ), - monaco.languages.registerDefinitionProvider( - 'mdx', - createDefinitionProvider(monaco, getProxy) - ), - monaco.languages.registerHoverProvider( - 'mdx', - createHoverProvider(monaco, getProxy) - ), - monaco.languages.registerReferenceProvider( - 'mdx', - createReferenceProvider(monaco, getProxy) - ), - registerMarkerDataProvider( - monaco, + await volar.languages.registerProvides( + worker, 'mdx', - createMarkerDataProvider(monaco, getProxy) + () => monaco.editor.getModels().map((model) => model.uri), + monaco.languages ) ] diff --git a/packages/monaco/lib/convert.js b/packages/monaco/lib/convert.js deleted file mode 100644 index 9ce29ff1..00000000 --- a/packages/monaco/lib/convert.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @typedef {import('monaco-editor')} Monaco - * @typedef {import('monaco-editor').editor.IMarkerData} IMarkerData - * @typedef {import('monaco-editor').editor.IRelatedInformation} IRelatedInformation - * @typedef {import('monaco-editor').editor.ITextModel} ITextModel - * @typedef {import('monaco-editor').languages.typescript.Diagnostic} Diagnostic - * @typedef {import('monaco-editor').languages.typescript.DiagnosticRelatedInformation} DiagnosticRelatedInformation - * @typedef {import('monaco-editor').languages.CompletionItemKind} CompletionItemKind - * @typedef {import('monaco-editor').IRange} IRange - * @typedef {import('monaco-editor').MarkerSeverity} MarkerSeverity - * @typedef {import('monaco-editor').MarkerTag} MarkerTag - * @typedef {import('typescript').CompletionEntryDetails} CompletionEntryDetails - * @typedef {import('typescript').DiagnosticCategory} DiagnosticCategory - * @typedef {import('typescript').DiagnosticMessageChain} DiagnosticMessageChain - * @typedef {import('typescript').JSDocTagInfo} JSDocTagInfo - * @typedef {import('typescript').ScriptElementKind} ScriptElementKind - * @typedef {import('typescript').SymbolDisplayPart} SymbolDisplayPart - * @typedef {import('typescript').TextSpan} TextSpan - */ - -/** - * Convert a TypeScript script element kind to a Monaco completion item kind. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {ScriptElementKind} kind - * The TypeScript script element kind tp convert. - * @returns {CompletionItemKind} - * The matching Monaco completion item kind. - */ -export function convertScriptElementKind(monaco, kind) { - switch (kind) { - case 'primitive type': - case 'keyword': { - return monaco.languages.CompletionItemKind.Keyword - } - - case 'var': - case 'local var': { - return monaco.languages.CompletionItemKind.Variable - } - - case 'property': - case 'getter': - case 'setter': { - return monaco.languages.CompletionItemKind.Field - } - - case 'function': - case 'method': - case 'construct': - case 'call': - case 'index': { - return monaco.languages.CompletionItemKind.Function - } - - case 'enum': { - return monaco.languages.CompletionItemKind.Enum - } - - case 'module': { - return monaco.languages.CompletionItemKind.Module - } - - case 'class': { - return monaco.languages.CompletionItemKind.Class - } - - case 'interface': { - return monaco.languages.CompletionItemKind.Interface - } - - case 'warning': { - return monaco.languages.CompletionItemKind.File - } - - default: { - return monaco.languages.CompletionItemKind.Property - } - } -} - -/** - * Convert TypeScript symbol display parts to a string. - * - * @param {SymbolDisplayPart[] | undefined} displayParts - * The display parts to convert. - * @returns {string} - * A string representation of the symbol display parts. - */ -export function displayPartsToString(displayParts) { - if (displayParts) { - return displayParts.map((displayPart) => displayPart.text).join('') - } - - return '' -} - -/** - * Create a markdown documentation string - * - * @param {CompletionEntryDetails} details - * The details to represent. - * @returns {string} - * The details represented as a markdown string. - */ -export function createDocumentationString(details) { - let documentationString = displayPartsToString(details.documentation) - if (details.tags) { - for (const tag of details.tags) { - documentationString += `\n\n${tagToString(tag)}` - } - } - - return documentationString -} - -/** - * Represent a TypeScript JSDoc tag as a string. - * - * @param {JSDocTagInfo} tag - * The JSDoc tag to represent. - * @returns {string} - * A representation of the JSDoc tag. - */ -export function tagToString(tag) { - let tagLabel = `*@${tag.name}*` - if (tag.name === 'param' && tag.text) { - const [parameterName, ...rest] = tag.text - tagLabel += `\`${parameterName.text}\`` - if (rest.length > 0) tagLabel += ` — ${rest.map((r) => r.text).join(' ')}` - } else if (Array.isArray(tag.text)) { - tagLabel += ` — ${tag.text.map((r) => r.text).join(' ')}` - } else if (tag.text) { - tagLabel += ` — ${tag.text}` - } - - return tagLabel -} - -/** - * Convert a text span to a Monaco range that matches the given model. - * - * @param {ITextModel} model - * The Monaco model to which the text span applies. - * @param {TextSpan} span - * The TypeScript text span to convert. - * @returns {IRange} - * The text span as a Monaco range. - */ -export function textSpanToRange(model, span) { - const p1 = model.getPositionAt(span.start) - const p2 = model.getPositionAt(span.start + span.length) - const {lineNumber: startLineNumber, column: startColumn} = p1 - const {lineNumber: endLineNumber, column: endColumn} = p2 - return {startLineNumber, startColumn, endLineNumber, endColumn} -} - -/** - * Flatten a TypeScript diagnostic message chain into a string representation. - * @param {string | DiagnosticMessageChain | undefined} diag - * The diagnostic to represent. - * @param {number} [indent] - * The indentation to use. - * @returns {string} - * A flattened diagnostic text. - */ -function flattenDiagnosticMessageText(diag, indent = 0) { - if (typeof diag === 'string') { - return diag - } - - if (diag === undefined) { - return '' - } - - let result = '' - if (indent) { - result += `\n${' '.repeat(indent)}` - } - - result += diag.messageText - indent++ - if (diag.next) { - for (const kid of diag.next) { - result += flattenDiagnosticMessageText(kid, indent) - } - } - - return result -} - -/** - * Convert TypeScript diagnostic related information to Monaco related - * information. - * - * @param {ITextModel} model - * The Monaco model the information is related to. - * @param {DiagnosticRelatedInformation[]} [relatedInformation] - * The TypeScript related information to convert. - * @returns {IRelatedInformation[]} - * TypeScript diagnostic related information as Monaco related information. - */ -function convertRelatedInformation(model, relatedInformation) { - if (!relatedInformation) { - return [] - } - - /** @type {IRelatedInformation[]} */ - const result = [] - for (const info of relatedInformation) { - const relatedResource = model - - if (!relatedResource) { - continue - } - - const infoStart = info.start || 0 - // eslint-disable-next-line unicorn/explicit-length-check - const infoLength = info.length || 1 - const {lineNumber: startLineNumber, column: startColumn} = - relatedResource.getPositionAt(infoStart) - const {lineNumber: endLineNumber, column: endColumn} = - relatedResource.getPositionAt(infoStart + infoLength) - - result.push({ - resource: relatedResource.uri, - startLineNumber, - startColumn, - endLineNumber, - endColumn, - message: flattenDiagnosticMessageText(info.messageText) - }) - } - - return result -} - -/** - * Convert a TypeScript diagnostic category to a Monaco diagnostic severity. - * - * @param {Monaco} monaco - * The Monaco editor module. - * @param {DiagnosticCategory} category - * The TypeScript diagnostic category to convert. - * @returns {MarkerSeverity} - * TypeScript diagnostic severity as Monaco marker severity. - */ -function tsDiagnosticCategoryToMarkerSeverity(monaco, category) { - switch (category) { - case 0: { - return monaco.MarkerSeverity.Warning - } - - case 1: { - return monaco.MarkerSeverity.Error - } - - case 2: { - return monaco.MarkerSeverity.Hint - } - - default: { - return monaco.MarkerSeverity.Info - } - } -} - -/** - * Convert a TypeScript dignostic to a Monaco editor diagnostic. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {ITextModel} model - * The Monaco editor model to which the diagnostic applies. - * @param {Diagnostic} diag - * The TypeScript diagnostic to convert. - * @returns {IMarkerData} - * The TypeScript diagnostic converted to Monaco marker data. - */ -export function convertDiagnostics(monaco, model, diag) { - const diagStart = diag.start || 0 - // eslint-disable-next-line unicorn/explicit-length-check - const diagLength = diag.length || 1 - const {lineNumber: startLineNumber, column: startColumn} = - model.getPositionAt(diagStart) - const {lineNumber: endLineNumber, column: endColumn} = model.getPositionAt( - diagStart + diagLength - ) - - /** @type {MarkerTag[]} */ - const tags = [] - if (diag.reportsUnnecessary) { - tags.push(monaco.MarkerTag.Unnecessary) - } - - if (diag.reportsDeprecated) { - tags.push(monaco.MarkerTag.Deprecated) - } - - return { - severity: tsDiagnosticCategoryToMarkerSeverity(monaco, diag.category), - startLineNumber, - startColumn, - endLineNumber, - endColumn, - message: flattenDiagnosticMessageText(diag.messageText), - code: diag.code.toString(), - tags, - relatedInformation: convertRelatedInformation( - model, - diag.relatedInformation - ) - } -} diff --git a/packages/monaco/lib/language-features.js b/packages/monaco/lib/language-features.js deleted file mode 100644 index 7a05f8e8..00000000 --- a/packages/monaco/lib/language-features.js +++ /dev/null @@ -1,282 +0,0 @@ -/** - * @typedef {import('monaco-editor')} Monaco - * @typedef {import('monaco-editor').languages.CompletionItemProvider} CompletionItemProvider - * @typedef {import('monaco-editor').languages.DefinitionProvider} DefinitionProvider - * @typedef {import('monaco-editor').languages.HoverProvider} HoverProvider - * @typedef {import('monaco-editor').languages.Location} Location - * @typedef {import('monaco-editor').languages.ReferenceProvider} ReferenceProvider - * @typedef {import('monaco-editor').languages.typescript.TypeScriptWorker} TypeScriptWorker - * @typedef {import('monaco-editor').Uri} Uri - * @typedef {import('monaco-marker-data-provider').MarkerDataProvider} MarkerDataProvider - * @typedef {import('typescript').CompletionEntryDetails} CompletionEntryDetails - * @typedef {import('typescript').CompletionInfo} CompletionInfo - * @typedef {import('typescript').QuickInfo} QuickInfo - * @typedef {import('typescript').ReferenceEntry} ReferenceEntry - * - * @typedef {(...resources: Uri[]) => Promise} GetWorker - */ - -import { - convertDiagnostics, - convertScriptElementKind, - createDocumentationString, - displayPartsToString, - tagToString, - textSpanToRange -} from './convert.js' - -/** - * Create a completion item provider for MDX documents. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {GetWorker} getWorker - * A function to get the MDX web worker. - * @returns {CompletionItemProvider} - * A completion item provider for MDX documents. - */ -export function createCompletionItemProvider(monaco, getWorker) { - return { - async provideCompletionItems(model, position) { - const worker = await getWorker(model.uri) - const offset = model.getOffsetAt(position) - const wordInfo = model.getWordUntilPosition(position) - const wordRange = new monaco.Range( - position.lineNumber, - wordInfo.startColumn, - position.lineNumber, - wordInfo.endColumn - ) - - if (model.isDisposed()) { - return - } - - const info = /** @type {CompletionInfo | undefined} */ ( - await worker.getCompletionsAtPosition(String(model.uri), offset) - ) - - if (!info || model.isDisposed()) { - return - } - - const suggestions = info.entries.map((entry) => { - const range = entry.replacementSpan - ? textSpanToRange(model, entry.replacementSpan) - : wordRange - - const tags = entry.kindModifiers?.includes('deprecated') - ? [monaco.languages.CompletionItemTag.Deprecated] - : [] - - return { - uri: model.uri, - position, - offset, - range, - label: entry.name, - insertText: entry.name, - sortText: entry.sortText, - kind: convertScriptElementKind(monaco, entry.kind), - tags - } - }) - - return { - suggestions - } - }, - - async resolveCompletionItem(item) { - const {label, offset, uri} = /** @type {any} */ (item) - - const worker = await getWorker(uri) - - const details = /** @type {CompletionEntryDetails | undefined} */ ( - await worker.getCompletionEntryDetails(String(uri), offset, label) - ) - - if (!details) { - return item - } - - return { - ...item, - label: details.name, - kind: convertScriptElementKind(monaco, details.kind), - detail: displayPartsToString(details.displayParts), - documentation: { - value: createDocumentationString(details) - } - } - } - } -} - -/** - * Create a hover provider for MDX documents. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {GetWorker} getWorker - * A function to get the MDX web worker. - * @returns {HoverProvider} - * A hover provider for MDX documents. - */ -export function createHoverProvider(monaco, getWorker) { - return { - async provideHover(model, position) { - const worker = await getWorker(model.uri) - - /** @type {QuickInfo | undefined} */ - const info = await worker.getQuickInfoAtPosition( - String(model.uri), - model.getOffsetAt(position) - ) - - if (!info) { - return - } - - const documentation = displayPartsToString(info.documentation) - const tags = info.tags - ? info.tags.map((tag) => tagToString(tag)).join(' \n\n') - : '' - const contents = displayPartsToString(info.displayParts) - - return { - range: textSpanToRange(model, info.textSpan), - contents: [ - { - value: '```typescript\n' + contents + '\n```\n' - }, - { - value: documentation + (tags ? '\n\n' + tags : '') - } - ] - } - } - } -} - -/** - * Create a link provider for MDX documents. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {GetWorker} getWorker - * A function to get the MDX web worker. - * @returns {DefinitionProvider} - * A link provider for MDX documents. - */ -export function createDefinitionProvider(monaco, getWorker) { - return { - async provideDefinition(model, position) { - const worker = await getWorker(model.uri) - - const offset = model.getOffsetAt(position) - const entries = /** @type {ReferenceEntry[] | undefined} */ ( - await worker.getDefinitionAtPosition(String(model.uri), offset) - ) - if (!entries?.length) { - return - } - - /** @type {Location[]} */ - const result = [] - for (const entry of entries) { - const uri = monaco.Uri.parse(entry.fileName) - const refModel = monaco.editor.getModel(uri) - if (refModel) { - result.push({ - uri, - range: textSpanToRange(model, entry.textSpan) - }) - } - } - - return result - } - } -} - -/** - * Create a marker data provider for MDX documents. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {GetWorker} getWorker - * A function to get the MDX web worker. - * @returns {MarkerDataProvider} - * A marker data provider for MDX documents. - */ -export function createMarkerDataProvider(monaco, getWorker) { - return { - owner: 'mdx', - - async provideMarkerData(model) { - const worker = await getWorker(model.uri) - const uri = String(model.uri) - const diagnostics = await Promise.all([ - worker.getSemanticDiagnostics(uri), - worker.getSuggestionDiagnostics(uri), - worker.getSyntacticDiagnostics(uri) - ]) - - if (model.isDisposed()) { - return - } - - return diagnostics - .flat() - .map((diagnostic) => convertDiagnostics(monaco, model, diagnostic)) - } - } -} - -/** - * Create a reference provider for MDX documents. - * - * @param {Monaco} monaco - * The Monaco editor module to use. - * @param {GetWorker} getWorker - * A function to get the MDX web worker. - * @returns {ReferenceProvider} - * A reference provider for MDX documents. - */ -export function createReferenceProvider(monaco, getWorker) { - return { - async provideReferences(model, position) { - const worker = await getWorker(model.uri) - const resource = model.uri - const offset = model.getOffsetAt(position) - - if (model.isDisposed()) { - return - } - - const entries = /** @type {ReferenceEntry[] | undefined} */ ( - await worker.getReferencesAtPosition(resource.toString(), offset) - ) - - if (!entries || model.isDisposed()) { - return - } - - /** @type {Location[]} */ - const result = [] - for (const entry of entries) { - const uri = monaco.Uri.parse(entry.fileName) - const refModel = monaco.editor.getModel(uri) - if (refModel) { - result.push({ - uri: refModel.uri, - range: textSpanToRange(refModel, entry.textSpan) - }) - } - } - - return result - } - } -} diff --git a/packages/monaco/mdx.worker.js b/packages/monaco/mdx.worker.js index 0a451ce0..6f8683ba 100644 --- a/packages/monaco/mdx.worker.js +++ b/packages/monaco/mdx.worker.js @@ -1,4 +1,3 @@ -/* global ts */ /** * @typedef {import('monaco-editor').languages.typescript.CompilerOptions} CompilerOptions * @typedef {import('monaco-editor').languages.typescript.IExtraLibs} IExtraLibs @@ -25,45 +24,46 @@ * example `remark-frontmatter`, but not `remark-mdx-frontmatter`. */ -import {createMdxLanguageService} from '@mdx-js/language-service' +import {resolveConfig} from '@mdx-js/language-service' +import { + createLanguageService, + createLanguageHost, + createServiceEnvironment +} from '@volar/monaco/worker.js' // @ts-expect-error This module is untyped. -import {initialize as initializeWorker} from 'monaco-editor/esm/vs/editor/editor.worker.js' -// @ts-expect-error This module is untyped. -import {create} from 'monaco-editor/esm/vs/language/typescript/ts.worker.js' +import {initialize} from 'monaco-editor/esm/vs/editor/editor.worker.js' +import typescript from 'typescript/lib/tsserverlibrary.js' /** @type {PluggableList | undefined} */ let plugins -/** - * @param {TypeScriptWorkerClass} TypeScriptWorker - * @returns {TypeScriptWorkerClass} A custom TypeScript worker which knows how to handle MDX. - */ -function worker(TypeScriptWorker) { - return class MDXWorker extends TypeScriptWorker { - _languageService = createMdxLanguageService( - // @ts-expect-error This is globally defined in the worker. - ts, - this, - plugins - ) - } -} - -// @ts-expect-error This is missing in the Monaco type definitions. -self.customTSWorkerFactory = worker - -// Trick the TypeScript worker into using the `customTSWorkerFactory` -self.importScripts = () => {} - // eslint-disable-next-line unicorn/prefer-add-event-listener self.onmessage = () => { - initializeWorker( + initialize( /** - * @param {IWorkerContext} ctx - * @param {CreateData} createData - * @returns {MDXWorker} The MDX TypeScript worker. + * @param {IWorkerContext} workerContext */ - (ctx, createData) => create(ctx, {...createData, customWorkerPath: true}) + (workerContext) => { + const env = createServiceEnvironment() + console.log({env}) + const host = createLanguageHost(workerContext.getMirrorModels, env, '/', { + checkJs: true, + jsx: typescript.JsxEmit.ReactJSX, + moduleResolution: typescript.ModuleResolutionKind.NodeJs + }) + console.log({host}) + const config = resolveConfig({}, typescript, plugins) + console.log({config}) + + const languageService = createLanguageService( + {typescript}, + env, + config, + host + ) + console.log({languageService}) + return languageService + } ) } diff --git a/packages/monaco/package.json b/packages/monaco/package.json index ab181303..1a8c6482 100644 --- a/packages/monaco/package.json +++ b/packages/monaco/package.json @@ -43,8 +43,7 @@ }, "dependencies": { "@mdx-js/language-service": "0.1.0", - "monaco-marker-data-provider": "^1.0.0", - "monaco-worker-manager": "^2.0.0", + "@volar/monaco": "~1.9.0", "unified": "^10.0.0" }, "devDependencies": {