diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index f5750ee..ba10bc8 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -35,6 +35,8 @@ import { PreviewProvider, usePreview } from "@/context/PreviewContext" import { useSocket } from "@/context/SocketContext" import { Button } from "../ui/button" import React from "react" +import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig" +import { deepMerge } from "@/lib/utils" export default function CodeEditor({ userData, @@ -154,9 +156,78 @@ export default function CodeEditor({ } // Post-mount editor keybindings and actions - const handleEditorMount: OnMount = (editor, monaco) => { + const handleEditorMount: OnMount = async (editor, monaco) => { setEditorRef(editor) monacoRef.current = monaco + /** + * Sync all the models to the worker eagerly. + * This enables intelliSense for all files without needing an `addExtraLib` call. + */ + monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true) + monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true) + + monaco.languages.typescript.typescriptDefaults.setCompilerOptions( + defaultCompilerOptions + ) + monaco.languages.typescript.javascriptDefaults.setCompilerOptions( + defaultCompilerOptions + ) + const fetchFileContent = (fileId: string): Promise => { + return new Promise((resolve) => { + socket?.emit("getFile", fileId, (content: string) => { + resolve(content) + }) + }) + } + const loadTSConfig = async (files: (TFolder | TFile)[]) => { + const tsconfigFiles = files.filter((file) => + file.name.endsWith("tsconfig.json") + ) + let mergedConfig: any = { compilerOptions: {} } + + for (const file of tsconfigFiles) { + const containerId = file.id.split("/").slice(0, 2).join("/") + const content = await fetchFileContent(file.id) + + try { + let tsConfig = JSON.parse(content) + + // Handle references + if (tsConfig.references) { + for (const ref of tsConfig.references) { + const path = ref.path.replace("./", "") + const fileId = `${containerId}/${path}` + const refContent = await fetchFileContent(fileId) + const referenceTsConfig = JSON.parse(refContent) + + // Merge configurations + mergedConfig = deepMerge(mergedConfig, referenceTsConfig) + } + } + + // Merge current file's config + mergedConfig = deepMerge(mergedConfig, tsConfig) + } catch (error) { + console.error("Error parsing TSConfig:", error) + } + } + // Apply merged compiler options + if (mergedConfig.compilerOptions) { + const updatedOptions = parseTSConfigToMonacoOptions({ + ...defaultCompilerOptions, + ...mergedConfig.compilerOptions, + }) + monaco.languages.typescript.typescriptDefaults.setCompilerOptions( + updatedOptions + ) + monaco.languages.typescript.javascriptDefaults.setCompilerOptions( + updatedOptions + ) + } + } + + // Call the function with your file structure + await loadTSConfig(files) editor.onDidChangeCursorPosition((e) => { setIsSelected(false) @@ -784,11 +855,11 @@ export default function CodeEditor({ const afterLineNumber = isAbove ? line - 1 : line id = changeAccessor.addZone({ afterLineNumber, - heightInLines: isAbove?11: 12, + heightInLines: isAbove ? 11 : 12, domNode: generateRef.current, }) - const contentWidget= generate.widget - if (contentWidget){ + const contentWidget = generate.widget + if (contentWidget) { editorRef?.layoutContentWidget(contentWidget) } } else { @@ -984,3 +1055,18 @@ export default function CodeEditor({ ) } + +/** + * Configure the typescript compiler to detect JSX and load type definitions + */ +const defaultCompilerOptions: monaco.languages.typescript.CompilerOptions = { + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true, + resolveJsonModule: true, + + jsx: monaco.languages.typescript.JsxEmit.ReactJSX, + module: monaco.languages.typescript.ModuleKind.ESNext, + moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, + target: monaco.languages.typescript.ScriptTarget.ESNext, +} diff --git a/frontend/lib/tsconfig.ts b/frontend/lib/tsconfig.ts new file mode 100644 index 0000000..12744d5 --- /dev/null +++ b/frontend/lib/tsconfig.ts @@ -0,0 +1,99 @@ +import * as monaco from "monaco-editor" + +export function parseTSConfigToMonacoOptions( + tsconfig: any +): monaco.languages.typescript.CompilerOptions { + const compilerOptions: monaco.languages.typescript.CompilerOptions = {} + + // Map tsconfig options to Monaco CompilerOptions + if (tsconfig.strict) compilerOptions.strict = tsconfig.strict + if (tsconfig.target) compilerOptions.target = mapScriptTarget(tsconfig.target) + if (tsconfig.module) compilerOptions.module = mapModule(tsconfig.module) + if (tsconfig.lib) compilerOptions.lib = tsconfig.lib + if (tsconfig.allowJs) compilerOptions.allowJs = tsconfig.allowJs + if (tsconfig.checkJs) compilerOptions.checkJs = tsconfig.checkJs + if (tsconfig.jsx) compilerOptions.jsx = mapJSX(tsconfig.jsx) + if (tsconfig.declaration) compilerOptions.declaration = tsconfig.declaration + if (tsconfig.declarationMap) + compilerOptions.declarationMap = tsconfig.declarationMap + if (tsconfig.sourceMap) compilerOptions.sourceMap = tsconfig.sourceMap + if (tsconfig.outFile) compilerOptions.outFile = tsconfig.outFile + if (tsconfig.outDir) compilerOptions.outDir = tsconfig.outDir + if (tsconfig.removeComments) + compilerOptions.removeComments = tsconfig.removeComments + if (tsconfig.noEmit) compilerOptions.noEmit = tsconfig.noEmit + if (tsconfig.noEmitOnError) + compilerOptions.noEmitOnError = tsconfig.noEmitOnError + + return compilerOptions +} + +function mapScriptTarget( + target: string +): monaco.languages.typescript.ScriptTarget { + const targetMap: { [key: string]: monaco.languages.typescript.ScriptTarget } = + { + es3: monaco.languages.typescript.ScriptTarget.ES3, + es5: monaco.languages.typescript.ScriptTarget.ES5, + es6: monaco.languages.typescript.ScriptTarget.ES2015, + es2015: monaco.languages.typescript.ScriptTarget.ES2015, + es2016: monaco.languages.typescript.ScriptTarget.ES2016, + es2017: monaco.languages.typescript.ScriptTarget.ES2017, + es2018: monaco.languages.typescript.ScriptTarget.ES2018, + es2019: monaco.languages.typescript.ScriptTarget.ES2019, + es2020: monaco.languages.typescript.ScriptTarget.ES2020, + esnext: monaco.languages.typescript.ScriptTarget.ESNext, + } + if (typeof target !== "string") { + return monaco.languages.typescript.ScriptTarget.Latest + } + return ( + targetMap[target?.toLowerCase()] || + monaco.languages.typescript.ScriptTarget.Latest + ) +} + +function mapModule(module: string): monaco.languages.typescript.ModuleKind { + const moduleMap: { [key: string]: monaco.languages.typescript.ModuleKind } = { + none: monaco.languages.typescript.ModuleKind.None, + commonjs: monaco.languages.typescript.ModuleKind.CommonJS, + amd: monaco.languages.typescript.ModuleKind.AMD, + umd: monaco.languages.typescript.ModuleKind.UMD, + system: monaco.languages.typescript.ModuleKind.System, + es6: monaco.languages.typescript.ModuleKind.ES2015, + es2015: monaco.languages.typescript.ModuleKind.ES2015, + esnext: monaco.languages.typescript.ModuleKind.ESNext, + } + if (typeof module !== "string") { + return monaco.languages.typescript.ModuleKind.ESNext + } + return ( + moduleMap[module.toLowerCase()] || + monaco.languages.typescript.ModuleKind.ESNext + ) +} + +function mapJSX(jsx: string): monaco.languages.typescript.JsxEmit { + const jsxMap: { [key: string]: monaco.languages.typescript.JsxEmit } = { + preserve: monaco.languages.typescript.JsxEmit.Preserve, + react: monaco.languages.typescript.JsxEmit.React, + "react-native": monaco.languages.typescript.JsxEmit.ReactNative, + } + return jsxMap[jsx.toLowerCase()] || monaco.languages.typescript.JsxEmit.React +} + +// Example usage: +const tsconfigJSON = { + compilerOptions: { + strict: true, + target: "ES2020", + module: "ESNext", + lib: ["DOM", "ES2020"], + jsx: "react", + sourceMap: true, + outDir: "./dist", + }, +} + +const monacoOptions = parseTSConfigToMonacoOptions(tsconfigJSON.compilerOptions) +console.log(monacoOptions) diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts index 63b3764..8fd506f 100644 --- a/frontend/lib/utils.ts +++ b/frontend/lib/utils.ts @@ -75,3 +75,26 @@ export function debounce void>( timeout = setTimeout(() => func(...args), wait) } as T } + +// Deep merge utility function +export const deepMerge = (target: any, source: any) => { + const output = { ...target } + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach((key) => { + if (isObject(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }) + } else { + output[key] = deepMerge(target[key], source[key]) + } + } else { + Object.assign(output, { [key]: source[key] }) + } + }) + } + return output +} + +const isObject = (item: any) => { + return item && typeof item === "object" && !Array.isArray(item) +}