@mdx-js/language-service
Version:
MDX support for Volar
259 lines (230 loc) • 6.73 kB
JavaScript
/**
* @import {DataTransferItem, LanguageServicePlugin, WorkspaceEdit} from '@volar/language-service'
*/
import path from 'node:path/posix'
import {toMarkdown} from 'mdast-util-to-markdown'
import {fromPlace} from 'unist-util-lsp'
import {URI, Utils} from 'vscode-uri'
import {toggleSyntax} from './commands.js'
import {VirtualMdxCode} from './virtual-code.js'
/**
* @callback ApplyEdit
* @param {WorkspaceEdit} edit
* The workspace edit to apply.
* @returns {PromiseLike<unknown>}
*/
/**
* @typedef createMdxServicePlugin.Options
* @property {ApplyEdit} applyEdit
* A function to apply workspace edits.
*/
// https://github.com/microsoft/vscode/blob/1.83.1/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts#L29-L41
const imageExtensions = new Set([
'.bmp',
'.gif',
'.ico',
'.jpe',
'.jpeg',
'.jpg',
'.png',
'.psd',
'.svg',
'.tga',
'.tif',
'.tiff',
'.webp'
])
/**
* Create an Volar service plugin for MDX files.
*
* The service supports:
*
* - Reporting diagnostics for parsing errors.
* - Document drop support for images.
* - Custom commands for toggling `delete`, `emphasis`, `inlineCode`, and
* `strong` text.
*
* @param {createMdxServicePlugin.Options} options
* Options to configure the MDX language service.
* @returns {LanguageServicePlugin}
* The Volar service plugin for MDX files.
*/
export function createMdxServicePlugin(options) {
return {
name: 'mdx',
capabilities: {
diagnosticProvider: {
interFileDependencies: false,
workspaceDiagnostics: false
},
executeCommandProvider: {
commands: [
'mdx.toggleDelete',
'mdx.toggleEmphasis',
'mdx.toggleInlineCode',
'mdx.toggleStrong'
]
},
documentDropEditsProvider: true
},
create(context) {
return {
executeCommand(command, args) {
switch (command) {
case 'mdx.toggleDelete': {
return toggleSyntax(
context,
options,
'delete',
'~~',
args[0],
args[1]
)
}
case 'mdx.toggleEmphasis': {
return toggleSyntax(
context,
options,
'emphasis',
'*',
args[0],
args[1]
)
}
case 'mdx.toggleInlineCode': {
return toggleSyntax(
context,
options,
'inlineCode',
'`',
args[0],
args[1]
)
}
case 'mdx.toggleStrong': {
return toggleSyntax(
context,
options,
'strong',
'**',
args[0],
args[1]
)
}
default: {
throw new Error('Unknown command: ' + command)
}
}
},
async provideDocumentDropEdits(document, position, dataTransfer) {
const documentUri = URI.parse(document.uri)
/** @type {DataTransferItem | undefined} */
let textItem
/** @type {DataTransferItem | undefined} */
let uriListItem
for (const [mime, item] of dataTransfer) {
if (mime === 'text/plain') {
textItem = item
continue
}
if (mime === 'text/uri-list') {
uriListItem = item
continue
}
if (!mime.startsWith('image/')) {
continue
}
const file = item.asFile()
if (!file) {
continue
}
return {
insertText: toMarkdown({type: 'image', url: file.name}).trim(),
insertTextFormat: 1,
createDataTransferFile: [
{
kind: 'create',
uri: String(Utils.joinPath(documentUri, '..', file.name)),
contentsMimeType: mime,
options: {
ignoreIfExists: true
}
}
]
}
}
if (uriListItem) {
const value = await uriListItem.asString()
const uris = value.split(/\r?\n/)
/** @type {string[]} */
const content = []
for (const line of uris) {
try {
const uri = URI.parse(line, true)
const value =
uri.scheme === documentUri.scheme
? path.relative(path.dirname(documentUri.path), uri.path)
: line
content.push(
toMarkdown(
imageExtensions.has(path.extname(uri.path))
? {type: 'image', url: value}
: {type: 'text', value}
).trim()
)
} catch {
continue
}
}
return {
insertText: content.join(' '),
insertTextFormat: 1
}
}
if (textItem) {
const string = await textItem.asString()
return {
insertText: string,
insertTextFormat: 1
}
}
},
provideDiagnostics(document) {
const decoded = context.decodeEmbeddedDocumentUri(
URI.parse(document.uri)
)
const sourceScript =
decoded && context.language.scripts.get(decoded[0])
const virtualCode =
decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1])
if (!(virtualCode instanceof VirtualMdxCode)) {
return
}
const {error} = virtualCode
if (error) {
return [
{
message: error.message,
code: error.source
? error.source + ':' + error.ruleId
: error.ruleId,
codeDescription: {
href:
error.url || 'https://mdxjs.com/docs/troubleshooting-mdx/'
},
range: error.place
? fromPlace(error.place)
: {
start: {line: 0, character: 0},
end: {line: 0, character: 0}
},
severity: 1,
source: 'MDX'
}
]
}
}
}
}
}
}