import type { MonacoSetupReturn } from '@slidev/types' import { createSingletonPromise } from '@antfu/utils' import { shikiToMonaco } from '@shikijs/monaco' 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 configs from '#slidev/configs' import setups from '#slidev/setups/monaco' import { isDark } from '../logic/dark' import { lockShortcuts } from '../state' 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.typescript.typescriptDefaults defaults.setCompilerOptions({ ...defaults.getCompilerOptions(), strict: true, moduleResolution: monaco.typescript.ModuleResolutionKind.NodeJs, module: monaco.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}`) }, }, }) : () => { } const { getEagerHighlighter, languageNames, themeOption } = await (await import('./shiki')).default() 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' }) for (const lang of languageNames) { monaco.languages.register({ id: lang }) } const editorOptions: MonacoSetupReturn['editorOptions'] & object = {} for (const setup of setups) { const result = await setup(monaco) Object.assign(editorOptions, result?.editorOptions) } // Disable shortcuts when focusing Monaco editor. monaco.editor.onDidCreateEditor((editor) => { let release: (() => void) | null = null editor.onDidFocusEditorWidget(() => { release = lockShortcuts() }) editor.onDidBlurEditorWidget(() => { release?.() release = null }) }) // Use Shiki to highlight Monaco const highlighter = await getEagerHighlighter() shikiToMonaco(highlighter, monaco) if (typeof themeOption === 'string') { monaco.editor.setTheme(themeOption) } else { watchEffect(() => { monaco.editor.setTheme(isDark.value ? themeOption.dark || 'vitesse-dark' : themeOption.light || 'vitesse-light') }) } 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.typescript.typescriptDefaults.addExtraLib(code, `file:///${path}`) monaco.editor.createModel(code, 'javascript', uri) } const addFileCache = new Map>() 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