2026-05-31 13:54:31 +02:00

142 lines
5.1 KiB
TypeScript

import type { MonacoSetupReturn } from '@slidev/types'
import configs from '#slidev/configs'
import setups from '#slidev/setups/monaco'
import { createSingletonPromise } from '@antfu/utils'
import { setupTypeAcquisition } from '@typescript/ata'
import * as monaco from 'monaco-editor'
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
// @ts-expect-error missing types
import { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
// @ts-expect-error missing types
import { ContextViewService } from 'monaco-editor/esm/vs/platform/contextview/browser/contextViewService'
// @ts-expect-error missing types
import { SyncDescriptor } from 'monaco-editor/esm/vs/platform/instantiation/common/descriptors'
import ts from 'typescript'
import { watchEffect } from 'vue'
import { isDark } from '../logic/dark'
window.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json')
return new JsonWorker()
if (label === 'css' || label === 'scss' || label === 'less')
return new CssWorker()
if (label === 'html' || label === 'handlebars' || label === 'razor')
return new HtmlWorker()
if (label === 'typescript' || label === 'javascript')
return new TsWorker()
return new EditorWorker()
},
}
class ContextViewService2 extends ContextViewService {
showContextView(...args: any) {
super.showContextView(...args)
// @ts-expect-error missing types
const contextView = this.contextView.view as HTMLElement
contextView.style.left = `calc(${contextView.style.left} / var(--slidev-slide-scale))`
contextView.style.top = `calc(${contextView.style.top} / var(--slidev-slide-scale))`
// Reset the scale to 1. Otherwise, the sub-menu will be in the wrong position.
contextView.style.transform = `scale(calc(1 / var(--slidev-slide-scale)))`
contextView.style.transformOrigin = '0 0'
}
}
// Initialize services first, otherwise we can't override them.
StandaloneServices.initialize({
contextViewService: new SyncDescriptor(ContextViewService2, [], true),
})
const setup = createSingletonPromise(async () => {
const defaults = monaco.languages.typescript.typescriptDefaults
defaults.setCompilerOptions({
...defaults.getCompilerOptions(),
strict: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.ESNext,
})
const ata = configs.monacoTypesSource === 'cdn'
? setupTypeAcquisition({
projectName: 'TypeScript Playground',
typescript: ts as any, // Version mismatch. No problem found so far.
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
defaults.addExtraLib(code, `file://${path}`)
const uri = monaco.Uri.file(path)
if (monaco.editor.getModel(uri) === null)
monaco.editor.createModel(code, 'javascript', uri)
},
progress: (downloaded: number, total: number) => {
// eslint-disable-next-line no-console
console.debug(`[Typescript ATA] ${downloaded} / ${total}`)
},
},
})
: () => { }
monaco.languages.register({ id: 'vue' })
monaco.languages.register({ id: 'html' })
monaco.languages.register({ id: 'css' })
monaco.languages.register({ id: 'typescript' })
monaco.languages.register({ id: 'javascript' })
const { shiki, languages, themes, shikiToMonaco } = await import('#slidev/shiki')
const highlighter = await shiki
const editorOptions: MonacoSetupReturn['editorOptions'] & object = {}
for (const setup of setups) {
const result = await setup(monaco)
Object.assign(editorOptions, result?.editorOptions)
}
// Use Shiki to highlight Monaco
shikiToMonaco(highlighter, monaco)
if (typeof themes === 'string') {
monaco.editor.setTheme(themes)
}
else {
watchEffect(() => {
monaco.editor.setTheme(isDark.value
? themes.dark || 'vitesse-dark'
: themes.light || 'vitesse-light')
})
}
// Register all languages, otherwise Monaco will not highlight them
for (const lang of languages) {
monaco.languages.register({ id: lang })
}
return {
monaco,
ata,
editorOptions,
}
})
async function _addFile(raw: () => Promise<{ default: string }>, path: string) {
const uri = monaco.Uri.file(path)
const code = (await raw()).default
monaco.languages.typescript.typescriptDefaults.addExtraLib(code, `file:///${path}`)
monaco.editor.createModel(code, 'javascript', uri)
}
const addFileCache = new Map<string, Promise<void>>()
export async function addFile(raw: () => Promise<{ default: string }>, path: string) {
if (addFileCache.has(path))
return addFileCache.get(path)
const promise = _addFile(raw, path)
addFileCache.set(path, promise)
return promise
}
export default setup