diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index c89fd8a1d07..842894399e2 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -6,6 +6,8 @@ import { Plugin } from '@remixproject/engine' import * as packageJson from '../../../../../package.json' import { PluginViewWrapper } from '@remix-ui/helper' +import { startTypeLoadingProcess } from './type-fetcher' + const EventManager = require('../../lib/events') const profile = { @@ -71,12 +73,26 @@ export default class Editor extends Plugin { this.api = {} this.dispatch = null this.ref = null + + this.monaco = null + this.typeLoaderDebounce = null + + this.tsModuleMappings = {} + this.processedPackages = new Set() + + this.typesLoadingCount = 0 + this.shimDisposers = new Map() + this.pendingPackagesBatch = new Set() } setDispatch (dispatch) { this.dispatch = dispatch } + setMonaco (monaco) { + this.monaco = monaco + } + updateComponent(state) { return this.setMonaco(monaco)} /> } @@ -128,6 +145,26 @@ export default class Editor extends Plugin { async onActivation () { this.activated = true + this.on('editor', 'editorMounted', () => { + if (!this.monaco) return + const ts = this.monaco.languages.typescript + const tsDefaults = ts.typescriptDefaults + + tsDefaults.setCompilerOptions({ + moduleResolution: ts.ModuleResolutionKind.NodeNext, + module: ts.ModuleKind.NodeNext, + target: ts.ScriptTarget.ES2022, + lib: ['es2022', 'dom', 'dom.iterable'], + allowNonTsExtensions: true, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + baseUrl: 'file:///node_modules/', + paths: this.tsModuleMappings, + }) + tsDefaults.setDiagnosticsOptions({ noSemanticValidation: false, noSyntaxValidation: false }) + ts.typescriptDefaults.setEagerModelSync(true) + console.log('[DIAGNOSE-SETUP] CompilerOptions set to NodeNext and diagnostics enabled') + }) this.on('sidePanel', 'focusChanged', (name) => { this.keepDecorationsFor(name, 'sourceAnnotationsPerFile') this.keepDecorationsFor(name, 'markerPerFile') @@ -156,27 +193,196 @@ export default class Editor extends Plugin { this.off('sidePanel', 'pluginDisabled') } - async _onChange (file) { - this.triggerEvent('didChangeFile', [file]) - const currentFile = await this.call('fileManager', 'file') - if (!currentFile) { - return + updateTsCompilerOptions() { + if (!this.monaco) return + console.log('[DIAGNOSE-PATHS] Updating TS compiler options...') + console.log('[DIAGNOSE-PATHS] Current path mappings:', JSON.stringify(this.tsModuleMappings, null, 2)) + + const tsDefaults = this.monaco.languages.typescript.typescriptDefaults + const currentOptions = tsDefaults.getCompilerOptions() + + tsDefaults.setCompilerOptions({ + ...currentOptions, + paths: { ...currentOptions.paths, ...this.tsModuleMappings } + }) + console.log('[DIAGNOSE-PATHS] TS compiler options updated.') + } + + toggleTsDiagnostics(enable) { + if (!this.monaco) return + const ts = this.monaco.languages.typescript + ts.typescriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: !enable, + noSyntaxValidation: false + }) + console.log(`[DIAGNOSE-DIAG] Semantic diagnostics ${enable ? 'enabled' : 'disabled'}`) + } + + addShimForPackage(pkg) { + if (!this.monaco) return + const tsDefaults = this.monaco.languages.typescript.typescriptDefaults + + const shimMainPath = `file:///__shims__/${pkg}.d.ts` + const shimWildPath = `file:///__shims__/${pkg}__wildcard.d.ts` + + if (!this.shimDisposers.has(shimMainPath)) { + const d1 = tsDefaults.addExtraLib(`declare module '${pkg}' { const _default: any\nexport = _default }`, shimMainPath) + this.shimDisposers.set(shimMainPath, d1) } - if (currentFile !== file) { - return + + if (!this.shimDisposers.has(shimWildPath)) { + const d2 = tsDefaults.addExtraLib(`declare module '${pkg}/*' { const _default: any\nexport = _default }`, shimWildPath) + this.shimDisposers.set(shimWildPath, d2) } - const input = this.get(currentFile) - if (!input) { - return + + this.tsModuleMappings[pkg] = [shimMainPath.replace('file:///', '')] + this.tsModuleMappings[`${pkg}/*`] = [`${pkg}/*`] + } + + removeShimsForPackage(pkg) { + const keys = [`file:///__shims__/${pkg}.d.ts`, `file:///__shims__/${pkg}__wildcard.d.ts`] + for (const k of keys) { + const disp = this.shimDisposers.get(k) + if (disp && typeof disp.dispose === 'function') { + disp.dispose() + this.shimDisposers.delete(k) + } } - // if there's no change, don't do anything - if (input === this.previousInput) { - return + } + + beginTypesBatch() { + if (this.typesLoadingCount === 0) { + this.toggleTsDiagnostics(false) + this.triggerEvent('typesLoading', ['start']) + console.log('[DIAGNOSE-BATCH] Types batch started') + } + this.typesLoadingCount++ + } + + endTypesBatch() { + this.typesLoadingCount = Math.max(0, this.typesLoadingCount - 1) + if (this.typesLoadingCount === 0) { + this.updateTsCompilerOptions() + this.toggleTsDiagnostics(true) + this.triggerEvent('typesLoading', ['end']) + console.log('[DIAGNOSE-BATCH] Types batch ended') } + } + + addExtraLibs(libs) { + if (!this.monaco || !libs || libs.length === 0) return + console.log(`[DIAGNOSE-LIBS] Adding ${libs.length} new files to Monaco...`) + + const tsDefaults = this.monaco.languages.typescript.typescriptDefaults + + libs.forEach(lib => { + if (!tsDefaults.getExtraLibs()[lib.filePath]) { + tsDefaults.addExtraLib(lib.content, lib.filePath) + } + }) + console.log(`[DIAGNOSE-LIBS] Files added. Total extra libs now: ${Object.keys(tsDefaults.getExtraLibs()).length}.`) + } + + // Called on every editor content change to parse import statements and trigger type loading. + async _onChange (file) { + this.triggerEvent('didChangeFile', [file]) + + if (this.monaco && (file.endsWith('.ts') || file.endsWith('.js'))) { + clearTimeout(this.typeLoaderDebounce) + this.typeLoaderDebounce = setTimeout(async () => { + if (!this.monaco) return + const model = this.monaco.editor.getModel(this.monaco.Uri.parse(file)) + if (!model) return + const code = model.getValue() + + try { + const IMPORT_ANY_RE = + /(?:import|export)\s+[^'"]*?from\s*['"]([^'"]+)['"]|import\s*['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]\s*\)/g + + const rawImports = [...code.matchAll(IMPORT_ANY_RE)] + .map(m => (m[1] || m[2] || m[3] || '').trim()) + .filter(p => p && !p.startsWith('.') && !p.startsWith('file://')) + + const uniqueImports = [...new Set(rawImports)] + const getBasePackage = (p) => p.startsWith('@') ? p.split('/').slice(0, 2).join('/') : p.split('/')[0] + + const newBasePackages = [...new Set(uniqueImports.map(getBasePackage))] + .filter(p => !this.processedPackages.has(p)) + + if (newBasePackages.length === 0) return + + console.log('[DIAGNOSE] New base packages for analysis:', newBasePackages) + + // Temporarily disable type checking during type loading to prevent error flickering. + this.beginTypesBatch() + + // [Phase 1: Fast Feedback] + // Add temporary type definitions (shims) first to immediately remove red underlines on import statements. + uniqueImports.forEach(pkgImport => { + this.addShimForPackage(pkgImport) + }) + + this.updateTsCompilerOptions() + console.log('[DIAGNOSE] Shims added. Red lines should disappear.') + + // [Phase 2: Deep Analysis] + // In the background, fetch the actual type files to enable autocompletion. + await Promise.all(newBasePackages.map(async (basePackage) => { + this.processedPackages.add(basePackage) + + console.log(`[DIAGNOSE-DEEP-PASS] Starting deep pass for "${basePackage}"`) + try { + const result = await startTypeLoadingProcess(basePackage) + if (result && result.libs && result.libs.length > 0) { + console.log(`[DIAGNOSE-DEEP-PASS] "${basePackage}" deep pass complete. Adding ${result.libs.length} files.`) + // Add all fetched type files to Monaco. + this.addExtraLibs(result.libs) + + // Update path mappings so TypeScript can find the types. + if (result.subpathMap) { + for (const [subpath, virtualPath] of Object.entries(result.subpathMap)) { + this.tsModuleMappings[subpath] = [virtualPath] + } + } + if (result.mainVirtualPath) { + this.tsModuleMappings[basePackage] = [result.mainVirtualPath.replace('file:///node_modules/', '')] + } + this.tsModuleMappings[`${basePackage}/*`] = [`${basePackage}/*`] + + // Remove the temporary shims now that the real types are loaded. + uniqueImports + .filter(p => getBasePackage(p) === basePackage) + .forEach(p => this.removeShimsForPackage(p)) + + } else { + // Shim will remain if no types are found. + console.warn(`[DIAGNOSE-DEEP-PASS] No types found for "${basePackage}". Shim will remain.`) + } + } catch (e) { + // Crawler can fail, but we don't want to crash the whole process. + console.error(`[DIAGNOSE-DEEP-PASS] Crawler failed for "${basePackage}":`, e) + } + })) + + console.log('[DIAGNOSE] All processes finished.') + // After all type loading is complete, re-enable type checking and apply the final state. + this.endTypesBatch() + + } catch (error) { + console.error('[DIAGNOSE-ONCHANGE] Critical error during type loading process:', error) + this.endTypesBatch() + } + }, 1500) + } + + const currentFile = await this.call('fileManager', 'file') + if (!currentFile || currentFile !== file) return + + const input = this.get(currentFile) + if (!input || input === this.previousInput) return + this.previousInput = input - // fire storage update - // NOTE: save at most once per 5 seconds if (this.saveTimeout) { window.clearTimeout(this.saveTimeout) } @@ -232,7 +438,7 @@ export default class Editor extends Plugin { this.emit('addModel', contentDep, 'typescript', pathDep, this.readOnlySessions[path]) } } else { - console.log("The file ", pathDep, " can't be found.") + // console.log("The file ", pathDep, " can't be found.") } } catch (e) { console.log(e) diff --git a/apps/remix-ide/src/app/editor/type-fetcher.ts b/apps/remix-ide/src/app/editor/type-fetcher.ts new file mode 100644 index 00000000000..058a5360870 --- /dev/null +++ b/apps/remix-ide/src/app/editor/type-fetcher.ts @@ -0,0 +1,187 @@ +import { Monaco } from '@monaco-editor/react' + +type Library = { filePath: string; content: string } +type PackageJson = { + name?: string + version?: string + types?: string + typings?: string + exports?: string | Record +} +type ResolveResult = { finalUrl: string; content: string } + +const CDN_BASE = 'https://cdn.jsdelivr.net/npm/' +const VIRTUAL_BASE = 'file:///node_modules/' +const IMPORT_ANY_RE = /(?:import|export)\s+[^'"]*?from\s*['"]([^'"]+)['"]|import\s*['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]\s*\)/g +const TRIPLE_SLASH_REF_RE = /\/\/\/\s*/g + +function isRelative(p: string): boolean { + return p.startsWith('./') || p.startsWith('../') || p.startsWith('/') +} + +function normalizeBareSpecifier(p: string): string { + if (!p) return p + if (p.startsWith('@')) return p.split('/').slice(0, 2).join('/') + return p.split('/')[0] +} + +// Function to generate @types package names, includes logic to prevent infinite recursion. +function toTypesScopedName(pkg: string): string { + if (pkg.startsWith('@types/')) return pkg + if (pkg.startsWith('@')) return '@types/' + pkg.slice(1).replace('/', '__') + return '@types/' + pkg +} + +function toVirtual(url: string): string { + return url.replace(CDN_BASE, VIRTUAL_BASE) +} + +function stripJsLike(url: string): string { + return url.replace(/\.d\.[mc]?ts$/, '').replace(/\.[mc]?ts$/, '').replace(/\.[mc]?js$/, '') +} + +async function fetchJson(url: string): Promise { + const res = await fetch(url) + if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`) + return res.json() +} + +// Guesses potential type definition file (.d.ts) paths from a JS file path. +function guessDtsFromJs(jsPath: string): string[] { + const base = stripJsLike(jsPath) + return [`${base}.d.ts`, `${base}.ts`, `${base}/index.d.ts`, `${base}/index.ts`] +} + +// Analyzes the 'exports' field of package.json to create a map of subpaths to their type file URLs. +function buildExportTypeMap(pkgName: string, pkgJson: PackageJson): Record { + const map: Record = {} + const base = `${CDN_BASE}${pkgName}/` + const push = (subpath: string, relPath: string | undefined) => { + if (!relPath) return + map[subpath] = guessDtsFromJs(relPath).map(a => new URL(a, base).href) + } + + if (pkgJson.exports && typeof pkgJson.exports === 'object') { + for (const [k, v] of Object.entries(pkgJson.exports)) { + if (typeof v === 'string') push(k, v) + else if (v && typeof v === 'object') { + if (v.types) push(k, v.types) + else if (v.import) push(k, v.import) + else if (v.default) push(k, v.default) + } + } + } + // Fallback to 'types'/'typings' field if 'exports' is missing or has no main '.' path defined. + if (!map['.'] && (pkgJson.types || pkgJson.typings)) { + push('.', pkgJson.types || pkgJson.typings) + } + return map +} + +async function tryFetchOne(urls: string[]): Promise { + for (const u of [...new Set(urls)]) { + try { + const r = await fetch(u); if (r.ok) return { finalUrl: u, content: await r.text() } + } catch (e) {} + } + return null +} + +// A crawler that recursively follows imports/exports within a type definition file (.d.ts). +async function crawl(entryUrl: string, pkgName: string, visited: Set, enqueuePackage: (name: string) => void): Promise { + if (visited.has(entryUrl)) return [] + visited.add(entryUrl) + const out: Library[] = [] + try { + const res = await tryFetchOne(guessDtsFromJs(entryUrl)) + if(!res) return [] + + const { finalUrl, content } = res + out.push({ filePath: toVirtual(finalUrl), content }) + const subPromises: Promise[] = [] + const crawlNext = (nextUrl: string) => { + if (!visited.has(nextUrl)) subPromises.push(crawl(nextUrl, pkgName, visited, enqueuePackage)) + } + // Handles triple-slash directives like '/// '. + for (const m of content.matchAll(TRIPLE_SLASH_REF_RE)) crawlNext(new URL(m[1], finalUrl).href) + for (const m of content.matchAll(IMPORT_ANY_RE)) { + const spec = (m[1] || m[2] || m[3] || '').trim() + if (!spec) continue + // Continues crawling for relative path imports, and queues up external package imports. + if (isRelative(spec)) crawlNext(new URL(spec, finalUrl).href) + else { + const bare = normalizeBareSpecifier(spec) + // Ignores Node.js built-in modules that use the 'node:' protocol. + if (bare && !bare.startsWith('node:')) enqueuePackage(bare) + } + } + const results = await Promise.all(subPromises) + results.forEach(arr => out.push(...arr)) + } catch (e) {} + return out +} + +// Main function for the type loading process. Fetches type files for a package and all its dependencies. +export async function startTypeLoadingProcess(packageName: string): Promise<{ mainVirtualPath: string; libs: Library[]; subpathMap: Record } | void> { + const visitedPackages = new Set() + const collected: Library[] = [] + const subpathMap: Record = {} + + // An inner function that recursively loads packages. + async function loadPackage(pkgNameToLoad: string) { + if (visitedPackages.has(pkgNameToLoad)) return + visitedPackages.add(pkgNameToLoad) + + let pkgJson: PackageJson + try { + const pkgJsonUrl = new URL('package.json', `${CDN_BASE}${pkgNameToLoad}/`).href + pkgJson = await fetchJson(pkgJsonUrl) + } catch (e) { + console.log(`- Package '${pkgNameToLoad}' not found. Attempting @types fallback.`) + // If the package is not found, attempt to find its @types equivalent. + try { await loadPackage(toTypesScopedName(pkgNameToLoad)) } catch (ee) {} + return + } + + const exportMap = buildExportTypeMap(pkgNameToLoad, pkgJson) + + // If the package is found but contains no type information, attempt the @types fallback. + if (Object.keys(exportMap).length === 0) { + console.log(`- No type declarations in '${pkgNameToLoad}'. Attempting @types fallback.`) + try { await loadPackage(toTypesScopedName(pkgNameToLoad)) } catch (ee) {} + return + } + + console.log(`[LOG 1] Starting full analysis for package: '${pkgNameToLoad}'`) + const pendingDependencies = new Set() + const enqueuePackage = (p: string) => { if (!visitedPackages.has(p)) pendingDependencies.add(p) } + + const crawlPromises: Promise[] = [] + // Crawl all entry points of the package to gather complete type information. + for (const [subpath, urls] of Object.entries(exportMap)) { + const entryPointUrl = urls[0] + if (entryPointUrl) { + const virtualPathKey = subpath === '.' ? pkgNameToLoad : `${pkgNameToLoad}/${subpath.replace('./', '')}` + subpathMap[virtualPathKey] = entryPointUrl.replace(CDN_BASE, '') + crawlPromises.push(crawl(entryPointUrl, pkgNameToLoad, new Set(), enqueuePackage)) + } + } + + const libsArrays = await Promise.all(crawlPromises) + libsArrays.forEach(libs => collected.push(...libs)) + + // Recursively load any discovered dependency packages. + if (pendingDependencies.size > 0) { + console.log(`- Found dependencies for '${pkgNameToLoad}': ${Array.from(pendingDependencies).join(', ')}`) + await Promise.all(Array.from(pendingDependencies).map(loadPackage)) + } + } + + await loadPackage(packageName) + + const mainVirtualPath = subpathMap[packageName] ? `${VIRTUAL_BASE}${subpathMap[packageName]}` : '' + const finalPackages = [...new Set(collected.map(lib => normalizeBareSpecifier(lib.filePath.replace(VIRTUAL_BASE, ''))))] + console.log(`[LOG 2] Full analysis complete. Total files: ${collected.length}. Packages loaded: ${finalPackages.join(', ')}`) + + return { mainVirtualPath, libs: collected, subpathMap } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/script-runner-bridge.tsx b/apps/remix-ide/src/app/plugins/script-runner-bridge.tsx index 961afb16fc8..361bf5533f5 100644 --- a/apps/remix-ide/src/app/plugins/script-runner-bridge.tsx +++ b/apps/remix-ide/src/app/plugins/script-runner-bridge.tsx @@ -28,6 +28,25 @@ const configFileName = 'remix.config.json' let baseUrl = 'https://remix-project-org.github.io/script-runner-generator' const customBuildUrl = 'http://localhost:4000/build' // this will be used when the server is ready +function transformScriptForRuntime(scriptContent: string): string { + const dynamicImportHelper = `const dynamicImport = (p) => new Function(\`return import('https://cdn.jsdelivr.net/npm/\${p}/+esm')\`)();\n` + + let transformed = scriptContent.replace( + /import\s+({[\s\S]*?})\s+from\s+['"]([^'"]+)['"]/g, + 'const $1 = await dynamicImport("$2");' + ) + transformed = transformed.replace( + /import\s+([\w\d_$]+)\s+from\s+['"]([^'"]+)['"]/g, + 'const $1 = (await dynamicImport("$2")).default;' + ) + transformed = transformed.replace( + /import\s+\*\s+as\s+([\w\d_$]+)\s+from\s+['"]([^'"]+)['"]/g, + 'const $1 = await dynamicImport("$2");' + ) + + return `${dynamicImportHelper}\n(async () => {\n try {\n${transformed}\n } catch (e) { console.error('Error executing script:', e); }\n})();` +} + export class ScriptRunnerBridgePlugin extends Plugin { engine: Engine dispatch: React.Dispatch = () => {} @@ -197,7 +216,15 @@ export class ScriptRunnerBridgePlugin extends Plugin { } try { this.setIsLoading(this.activeConfig.name, true) - await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute', script, filePath) + const transformedScript = transformScriptForRuntime(script) + + console.log('--- [ScriptRunner] Original Script ---') + console.log(script) + console.log('--- [ScriptRunner] Transformed Script for Runtime ---') + console.log(transformedScript) + + await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute',transformedScript, filePath) + } catch (e) { console.error('Error executing script', e) } diff --git a/libs/remix-ui/editor/src/lib/providers/tsCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/tsCompletionProvider.ts new file mode 100644 index 00000000000..0e879da5df6 --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/tsCompletionProvider.ts @@ -0,0 +1,85 @@ +import { monacoTypes } from '@remix-ui/editor' + +interface TsCompletionInfo { + entries: { + name: string + kind: string + }[] +} + +export class RemixTSCompletionProvider implements monacoTypes.languages.CompletionItemProvider { + monaco: any + + constructor(monaco: any) { + this.monaco = monaco + } + + triggerCharacters = ['.', '"', "'", '/', '@'] + + async provideCompletionItems(model: monacoTypes.editor.ITextModel, position: monacoTypes.Position, context: monacoTypes.languages.CompletionContext): Promise { + const word = model.getWordUntilPosition(position) + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + } + + try { + const worker = await this.monaco.languages.typescript.getTypeScriptWorker() + const client = await worker(model.uri) + const completions: TsCompletionInfo = await client.getCompletionsAtPosition( + model.uri.toString(), + model.getOffsetAt(position) + ) + + if (!completions || !completions.entries) { + return { suggestions: []} + } + + const suggestions = completions.entries.map(entry => { + return { + label: entry.name, + kind: this.mapTsCompletionKindToMonaco(entry.kind), + insertText: entry.name, + range: range + } + }) + + return { suggestions } + } catch (error) { + console.error('[TSCompletionProvider] Error fetching completions:', error) + return { suggestions: []} + } + } + + private mapTsCompletionKindToMonaco(kind: string): monacoTypes.languages.CompletionItemKind { + const { CompletionItemKind } = this.monaco.languages + switch (kind) { + case 'method': + case 'memberFunction': + return CompletionItemKind.Method + case 'function': + return CompletionItemKind.Function + case 'property': + case 'memberVariable': + return CompletionItemKind.Property + case 'class': + return CompletionItemKind.Class + case 'interface': + return CompletionItemKind.Interface + case 'keyword': + return CompletionItemKind.Keyword + case 'variable': + return CompletionItemKind.Variable + case 'constructor': + return CompletionItemKind.Constructor + case 'enum': + return CompletionItemKind.Enum + case 'module': + return CompletionItemKind.Module + default: + return CompletionItemKind.Text + } + } +} \ No newline at end of file diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index aa913e823d4..036c037325e 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -28,6 +28,7 @@ import { noirLanguageConfig, noirTokensProvider } from './syntaxes/noir' import { IPosition, IRange } from 'monaco-editor' import { GenerationParams } from '@remix/remix-ai-core'; import { RemixInLineCompletionProvider } from './providers/inlineCompletionProvider' +import { RemixTSCompletionProvider } from './providers/tsCompletionProvider' const _paq = (window._paq = window._paq || []) // Key for localStorage @@ -154,6 +155,7 @@ export interface EditorUIProps { } plugin: PluginType editorAPI: EditorAPIType + setMonaco: (monaco: Monaco) => void } const contextMenuEvent = new EventManager() export const EditorUI = (props: EditorUIProps) => { @@ -1152,6 +1154,7 @@ export const EditorUI = (props: EditorUIProps) => { function handleEditorWillMount(monaco) { monacoRef.current = monaco + props.setMonaco(monaco) // Register a new language monacoRef.current.languages.register({ id: 'remix-solidity' }) monacoRef.current.languages.register({ id: 'remix-cairo' }) @@ -1164,9 +1167,11 @@ export const EditorUI = (props: EditorUIProps) => { // Allow JSON schema requests monacoRef.current.languages.json.jsonDefaults.setDiagnosticsOptions({ enableSchemaRequest: true }) + monacoRef.current.languages.registerCompletionItemProvider('typescript', new RemixTSCompletionProvider(monaco)) + monacoRef.current.languages.registerCompletionItemProvider('javascript', new RemixTSCompletionProvider(monaco)) + // hide the module resolution error. We have to remove this when we know how to properly resolve imports. monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ diagnosticCodesToIgnore: [2792]}) - // Register a tokens provider for the language monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any) monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig as any)