diff --git a/src/lib/components/Widget/CodeEditor/MonacoEditor.svelte b/src/lib/components/Widget/CodeEditor/MonacoEditor.svelte index 2ffdcb7..565afb9 100644 --- a/src/lib/components/Widget/CodeEditor/MonacoEditor.svelte +++ b/src/lib/components/Widget/CodeEditor/MonacoEditor.svelte @@ -2,6 +2,7 @@ import { onMount, onDestroy } from 'svelte'; import type { TabInstance } from '$lib/components/Tabs/types'; import { useProject } from '$lib/client/project'; + import { loadMonacoProject, pathToUri } from '$lib/components/Widget/CodeEditor/project-loader'; interface Props { tab: TabInstance; @@ -20,8 +21,39 @@ const file = await fs.getFile(tab.metadata.path); + monaco.typescript.typescriptDefaults.setCompilerOptions({ + module: monaco.typescript.ModuleKind.ESNext, + moduleResolution: monaco.typescript.ModuleResolutionKind.NodeJs, + baseUrl: 'file:///', + paths: { + '*': ['*'], + }, + allowNonTsExtensions: true, + noEmit: true, + target: monaco.typescript.ScriptTarget.ESNext, + allowImportingTsExtensions: true, + }); + + monaco.typescript.typescriptDefaults.setEagerModelSync(true); + monaco.typescript.typescriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: false, + noSyntaxValidation: false, + diagnosticCodesToIgnore: [2307], // Todo: Delete when lib types import work + }); + + const rootDir = await fs.getDirectory(); + await loadMonacoProject(monaco, rootDir); + + const uri = pathToUri(monaco, tab.metadata.path); + + let model = monaco.editor.getModel(uri); + if (!model) { + const content = (await file.read()) ?? ''; + model = monaco.editor.createModel(content, 'typescript', uri); + } + editor = monaco.editor.create(container, { - value: (await file.read()) || '', + model, language: 'typescript', theme: 'vs-dark', readOnly: false, diff --git a/src/lib/components/Widget/CodeEditor/project-loader.ts b/src/lib/components/Widget/CodeEditor/project-loader.ts new file mode 100644 index 0000000..9526475 --- /dev/null +++ b/src/lib/components/Widget/CodeEditor/project-loader.ts @@ -0,0 +1,50 @@ +import type Monaco from 'monaco-editor'; + +import { type SfsDirectory } from '$lib/client/sync-file-system'; +import type { DirectoryContent } from '$lib/server/file-system/project-directory'; + +export function pathToUri(monaco: typeof Monaco, path: string) { + const clean = normalizePath(path); + return monaco.Uri.parse(`file:///${clean}`); +} + +export function normalizePath(path: string) { + return path.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\/+/, ''); +} + +function joinPath(base: string, name: string) { + return normalizePath(base ? `${base}/${name}` : name); +} + +export async function loadMonacoProject(monaco: typeof Monaco, fsRoot: SfsDirectory) { + const rootContent = await fsRoot.readdir(true); + + async function loadDirectory(dir: SfsDirectory, content: DirectoryContent, basePath: string) { + for (const fileName of content.files) { + const fullPath = joinPath(basePath, fileName); + if (!/\.(ts|js)$/.test(fileName)) continue; + + const file = await dir.getFile(fileName); + const text = (await file.read()) ?? ''; + + const uri = pathToUri(monaco, fullPath); + + let model = monaco.editor.getModel(uri); + + if (!model) { + model = monaco.editor.createModel(text, 'typescript', uri); + } else { + model.setValue(text); + } + } + + for (const [dirName, subContent] of Object.entries(content.directories)) { + if (!subContent) continue; + + const subDir = await dir.getDirectory(dirName); + await loadDirectory(subDir, subContent, joinPath(basePath, dirName)); + } + } + + await loadDirectory(fsRoot, rootContent, ''); +}