diff --git a/.circleci/config.yml b/.circleci/config.yml index e308cf5191..3e19a05dbc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -493,10 +493,10 @@ jobs: working_directory: ~/remix-project steps: + - checkout - node/install: install-yarn: true node-version: "v14.17.6" - - checkout - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production @@ -526,6 +526,9 @@ jobs: steps: - checkout + - node/install: + install-yarn: true + node-version: "v14.17.6" - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production diff --git a/apps/remix-ide-e2e/src/commands/addFile.ts b/apps/remix-ide-e2e/src/commands/addFile.ts index f9536bb259..c64fb830f6 100644 --- a/apps/remix-ide-e2e/src/commands/addFile.ts +++ b/apps/remix-ide-e2e/src/commands/addFile.ts @@ -2,7 +2,7 @@ import { NightwatchBrowser, NightwatchContractContent } from 'nightwatch' import EventEmitter from 'events' class AddFile extends EventEmitter { - command (this: NightwatchBrowser, name: string, content: NightwatchContractContent): NightwatchBrowser { + command(this: NightwatchBrowser, name: string, content: NightwatchContractContent): NightwatchBrowser { this.api.perform((done) => { addFile(this.api, name, content, () => { done() @@ -14,8 +14,21 @@ class AddFile extends EventEmitter { } function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) { - browser.clickLaunchIcon('udapp') - .clickLaunchIcon('filePanel') + browser + .saveScreenshot('./reports/screenshots/addFile.png') + .isVisible({ + selector: "//*[@data-id='sidePanelSwapitTitle' and contains(.,'File explorer')]", + locateStrategy: 'xpath', + suppressNotFoundErrors: true, + timeout: 1000 + }, (okVisible) => { + if (!okVisible.value) { + browser.clickLaunchIcon('filePanel') + .saveScreenshot('./reports/screenshots/addFile2.png') + } + }) + .scrollInto('li[data-id="treeViewLitreeViewItemREADME.txt"]') + .saveScreenshot('./reports/screenshots/addFile3.png') .waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]') .click('li[data-id="treeViewLitreeViewItemREADME.txt"]').pause(1000) // focus on root directory .elements('css selector', `li[data-id="treeViewLitreeViewItem${name}"]`, (res) => { diff --git a/apps/remix-ide-e2e/src/helpers/init.ts b/apps/remix-ide-e2e/src/helpers/init.ts index ddf8c13917..0a64b64710 100644 --- a/apps/remix-ide-e2e/src/helpers/init.ts +++ b/apps/remix-ide-e2e/src/helpers/init.ts @@ -24,8 +24,8 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url .pause(6000) .perform(done()) }) - .maximizeWindow() - .fullscreenWindow(() => { + //.maximizeWindow() + .perform(() => { if (preloadPlugins) { initModules(browser, () => { browser.pause(2000).clickLaunchIcon('solidity') diff --git a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts index 9ca0bb89ba..ef6213ea23 100644 --- a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts +++ b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts @@ -163,7 +163,7 @@ const remixIdeThemes = { }, flatly: { primary: '#2C3E50', - secondary: '#95a5a6', + secondary: '#dadfe0', success: '#18BC9C', info: '#3498DB', warning: '#F39C12', @@ -171,7 +171,7 @@ const remixIdeThemes = { }, spacelab: { primary: '#446E9B', - secondary: '#999', + secondary: '#dadfe0', success: '#3CB521', info: '#3399F3', warning: '#D47500', @@ -179,7 +179,7 @@ const remixIdeThemes = { }, cyborg: { primary: '#2A9FD6', - secondary: '#555', + secondary: '#3c3939', success: '#77B300', info: '#93C', warning: '#F80', diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index 477ed83b7b..139c50d77b 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -66,7 +66,7 @@ module.exports = { }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') - .pause(2000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {`) !== -1, 'Incorrect content') @@ -129,28 +129,28 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') // check js and ts files are not transformed .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') - .pause(5000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {`) !== -1, 'Incorrect content') @@ -177,28 +177,76 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') // check js and ts files are not transformed .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') - .pause(4000) + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/MyToken_test.sol"]') + }, + + 'Should create ERC1155 workspace with files #group1': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspaceCreate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') + // eslint-disable-next-line dot-notation + .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' }) + .click('select[id="wstemplate"]') + .click('select[id="wstemplate"] option[value=ozerc1155]') + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) + .pause(100) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + // check js and ts files are not transformed + .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + .pause(1000) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') - .pause(4000) + .pause(100) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {`) !== -1, 'Incorrect content') @@ -207,6 +255,64 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/MyToken_test.sol"]') }, + 'Should create ERC1155 workspace with template customizations #group1': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspaceCreate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') + .click('select[id="wstemplate"]') + .click('select[id="wstemplate"] option[value=ozerc1155]') + .waitForElementPresent('*[data-id="ozCustomization"]') + .click('*[data-id="featureTypeMintable"]') + .click('*[data-id="featureTypeBurnable"]') + .click('*[data-id="featureTypePausable"]') + .click('*[data-id="upgradeTypeUups"]') + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) + .pause(100) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .pause(1000) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + // check js and ts files are not transformed + .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`import { deploy } from './web3-lib'`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {`) !== -1, + 'Incorrect content') + browser.assert.ok(content.indexOf(`gas: gas || 3600000`) !== -1, + 'Incorrect gas cost') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') + .pause(100) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {`) !== -1, + 'Incorrect content') + }) + // No test file is added in upgradeable contract template + }, + // WORKSPACE TEMPLATES E2E END 'Should create two workspace and switch to the first one #group1': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx index 9dc8fa2bca..b8a10b742f 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -6,6 +6,7 @@ import { CompilationResult } from '@remix-project/remix-solidity' import CodeParserGasService from './services/code-parser-gas-service' import CodeParserCompiler from './services/code-parser-compiler' import CodeParserAntlrService from './services/code-parser-antlr-service' +import CodeParserImports, { CodeParserImportsData } from './services/code-parser-imports' import React from 'react' import { Profile } from '@remixproject/plugin-utils' import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from 'dist/libs/remix-analyzer/src/types' @@ -15,7 +16,7 @@ import { ParseResult } from './types/antlr-types' const profile: Profile = { name: 'codeParser', - methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], + methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates', 'getImports'], events: [], version: '0.0.1' } @@ -68,12 +69,15 @@ export class CodeParser extends Plugin { gasService: CodeParserGasService compilerService: CodeParserCompiler antlrService: CodeParserAntlrService + importService: CodeParserImports parseSolidity: (text: string) => Promise getLastNodeInLine: (ast: string) => Promise listAstNodes: () => Promise getANTLRBlockAtPosition: (position: any, text?: string) => Promise setCurrentFileAST: (text?: string) => Promise + getImports: () => Promise + constructor(astWalker: any) { super(profile) @@ -104,13 +108,14 @@ export class CodeParser extends Plugin { this.gasService = new CodeParserGasService(this) this.compilerService = new CodeParserCompiler(this) this.antlrService = new CodeParserAntlrService(this) + this.importService = new CodeParserImports(this) this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService) this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService) this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService) this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService) this.setCurrentFileAST = this.antlrService.setCurrentFileAST.bind(this.antlrService) - + this.getImports = this.importService.getImports.bind(this.importService) this.on('editor', 'didChangeFile', async (file) => { await this.call('editor', 'discardLineTexts') @@ -119,7 +124,24 @@ export class CodeParser extends Plugin { this.on('filePanel', 'setWorkspace', async () => { await this.call('fileDecorator', 'clearFileDecorators') + await this.importService.setFileTree() + }) + + this.on('fileManager', 'fileAdded', async () => { + await this.importService.setFileTree() + }) + this.on('fileManager', 'fileRemoved', async () => { + await this.importService.setFileTree() + }) + + + this.on('fileManager', 'fileAdded', async () => { + await this.importService.setFileTree() + }) + this.on('fileManager', 'fileRemoved', async () => { + await this.importService.setFileTree() }) + this.on('fileManager', 'currentFileChanged', async () => { @@ -139,8 +161,6 @@ export class CodeParser extends Plugin { } - - /** * * @returns @@ -149,10 +169,6 @@ export class CodeParser extends Plugin { return this.compilerAbstract } - - - - getSubNodes(node: T): number[] { return node.nodeType == "ContractDefinition" && node.contractDependencies; } diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts index a743738ae9..7b289faecb 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts @@ -8,22 +8,23 @@ import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' import { sourceMappingDecoder } from '@remix-project/remix-debug' import { CompilerRetriggerMode } from '@remix-project/remix-solidity-ts'; import { MarkerSeverity } from 'monaco-editor'; +import { findLinesInStringWithMatch, SearchResultLine } from '@remix-ui/search' type errorMarker = { message: string severity: MarkerSeverity position: { - start: { - line: number - column: number - }, - end: { - line: number - column: number - } + start: { + line: number + column: number + }, + end: { + line: number + column: number + } }, file: string - } +} export default class CodeParserCompiler { plugin: CodeParser compiler: any // used to compile the current file seperately from the main compiler @@ -43,36 +44,47 @@ export default class CodeParserCompiler { this.errorState = true const result = new CompilerAbstract('soljson', data, source, input) let allErrors: errorMarker[] = [] - if (data.errors) { - const sources = result.getSourceCode().sources - for (const error of data.errors) { - - const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content) - const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({ - start: error.sourceLocation.start, - length: error.sourceLocation.end - error.sourceLocation.start - }, lineBreaks) - - const filePath = error.sourceLocation.file - - allErrors = [...allErrors, { - message: error.formattedMessage, - severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, - position: { - start: { - line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, - column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 - }, - end: { - line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, - column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 + if (data.errors || data.error) { + const file = await this.plugin.call('fileManager', 'getCurrentFile') + const currentFileContent = await this.plugin.call('fileManager', 'readFile', file) + const sources = result.getSourceCode().sources || [] + if (data.error) { + if (data.error.formattedMessage) { + // mark this file as error + const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } }) + allErrors = [...allErrors, errorMarker] + } + } else { + for (const error of data.errors) { + if (!error.sourceLocation) { + // mark this file as error + const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } }) + allErrors = [...allErrors, errorMarker] + } else { + const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content) + const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({ + start: error.sourceLocation.start, + length: error.sourceLocation.end - error.sourceLocation.start + }, lineBreaks) + + + const filePath = error.sourceLocation.file + const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath) + + const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent) + for (const importFilePosition of importFilePositions) { + for (const line of importFilePosition.lines) { + allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)] + } } + + allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)] } - , file: filePath - }] + } } + const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') - if(displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors) + if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors) this.addDecorators(allErrors, sources) } else { await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) @@ -143,14 +155,14 @@ export default class CodeParserCompiler { this.compiler.compile(sources, this.plugin.currentFile) } } catch (e) { - // do nothing + // do nothing } } async addDecorators(allErrors: errorMarker[], sources: any) { const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') - if(!displayErrors) return - const errorsPerFiles: {[fileName: string]: errorMarker[]} = {} + if (!displayErrors) return + const errorsPerFiles: { [fileName: string]: errorMarker[] } = {} for (const error of allErrors) { if (!errorsPerFiles[error.file]) { errorsPerFiles[error.file] = [] @@ -164,7 +176,7 @@ export default class CodeParserCompiler { } // sort errorPerFiles by error priority - const sortedErrorsPerFiles: {[fileName: string]: errorMarker[]} = {} + const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {} for (const fileName in errorsPerFiles) { const errors = errorsPerFiles[fileName] errors.sort((a, b) => { @@ -182,7 +194,7 @@ export default class CodeParserCompiler { const decorator: fileDecoration = { path: fileTarget.file, isDirectory: false, - fileStateType: errors[0].severity == MarkerSeverity.Error? fileDecorationType.Error : fileDecorationType.Warning, + fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning, fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning', fileStateIconClass: '', fileStateIcon: '', @@ -213,8 +225,27 @@ export default class CodeParserCompiler { } + async createErrorMarker(error: any, filePath: string, lineColumn): Promise { + return { + message: error.formattedMessage, + severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, + position: { + start: { + line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, + column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 + }, + end: { + line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, + column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 + } + } + , file: filePath + } + } + async clearDecorators(sources: any) { const decorators: fileDecoration[] = [] + if (!sources) return for (const fileName of Object.keys(sources)) { const decorator: fileDecoration = { path: fileName, @@ -234,4 +265,13 @@ export default class CodeParserCompiler { await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) } + async getPositionForImportErrors(importedFileName: string, text: string) { + const re = new RegExp(importedFileName, 'gi') + const result: SearchResultLine[] = findLinesInStringWithMatch( + text, + re + ) + return result + } + } \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts new file mode 100644 index 0000000000..9eaef92dd9 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts @@ -0,0 +1,82 @@ +'use strict' +import { CodeParser } from "../code-parser"; + +export type CodeParserImportsData= { + files?: string[], + modules?: string[], + packages?: string[], +} + +export default class CodeParserImports { + plugin: CodeParser + + data: CodeParserImportsData = {} + constructor(plugin: CodeParser) { + this.plugin = plugin + this.init() + } + + async getImports(){ + return this.data + } + + async init() { + // @ts-ignore + const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt') + this.data.modules = txt.default.split('\n') + .filter(x => x !== '') + .map(x => x.replace('./node_modules/', '')) + .filter(x => { + if(x.includes('@openzeppelin')) { + return !x.includes('mock') + }else{ + return true + } + }) + + // get unique first words of the values in the array + this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))] + } + + setFileTree = async () => { + this.data.files = await this.getDirectory('/') + this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git')) + } + + getDirectory = async (dir: string) => { + let result = [] + const files = await this.plugin.call('fileManager', 'readdir', dir) + const fileArray = this.normalize(files) + for (const fi of fileArray) { + if (fi) { + const type = fi.data.isDirectory + if (type === true) { + result = [...result, ...(await this.getDirectory(`${fi.filename}`))] + } else { + result = [...result, fi.filename] + } + } + } + return result + } + + normalize = filesList => { + const folders = [] + const files = [] + Object.keys(filesList || {}).forEach(key => { + if (filesList[key].isDirectory) { + folders.push({ + filename: key, + data: filesList[key] + }) + } else { + files.push({ + filename: key, + data: filesList[key] + }) + } + }) + return [...folders, ...files] + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css index 30026bb48b..ced466267d 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css @@ -25,7 +25,7 @@ --gray:#555; --gray-dark:#222; --primary:#2a9fd6; - --secondary:#555; + --secondary:#3c3939; --success:#77b300; --info:#93c; --warning:#f80; @@ -5595,7 +5595,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#2180ac!important } .bg-secondary { - background-color:#555!important + background-color:#3c3939!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:#3c3c3c!important @@ -8358,7 +8358,7 @@ a.text-warning:focus,a.text-warning:hover { color:#b35f00!important } .text-danger { - color:#c00!important + color:#ff4242!important } a.text-danger:focus,a.text-danger:hover { color:maroon!important @@ -8594,7 +8594,7 @@ legend { color:#555 } .nav-pills .nav-link.active,.nav-tabs .nav-link.active { - background-color:#2a9fd6 + background-color:#034767 } .breadcrumb a { color:#fff diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css index c62ab28e0f..5940867b25 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css @@ -24,7 +24,7 @@ --gray:#95a5a6; --gray-dark:#343a40; --primary:#2c3e50; - --secondary:#95a5a6; + --secondary:#dadfe0; --success:#18bc9c; --info:#3498db; --warning:#f39c12; @@ -4460,7 +4460,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#1a252f!important } .bg-secondary { - background-color:#95a5a6!important + background-color:#dadfe0!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:#798d8f!important diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css index 4639bcdad8..b8e90b0107 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css @@ -25,7 +25,7 @@ --gray:#777; --gray-dark:#333; --primary:#446e9b; - --secondary:#999; + --secondary:#dadfe0; --success:#3cb521; --info:#3399f3; --warning:#d47500; @@ -5596,7 +5596,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#345578!important } .bg-secondary { - background-color:#999!important + background-color:#dadfe0!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:gray!important diff --git a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css index e039facfec..28503c8562 100644 --- a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css +++ b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css @@ -3641,7 +3641,7 @@ input[type="submit"].btn-block { .nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { color: #fff; - background-color: var(--body-bg); + background-color: #2a2c3f; border-color: #3f4455; } .nav-tabs .dropdown-menu { diff --git a/apps/remix-ide/webpack.config.js b/apps/remix-ide/webpack.config.js index 529fe58d39..57fd709495 100644 --- a/apps/remix-ide/webpack.config.js +++ b/apps/remix-ide/webpack.config.js @@ -16,6 +16,13 @@ module.exports = config => { const nxWebpackConfig = nxWebpack(config) const webpackConfig = { ...nxWebpackConfig, + resolve: { + ...nxWebpackConfig.resolve, + alias: { + path: require.resolve("path-browserify"), + } + }, + node: { fs: 'empty', tls: 'empty', @@ -38,7 +45,7 @@ module.exports = config => { }) ] } - + webpackConfig.output.chunkLoadTimeout = 600000 if (process.env.NODE_ENV === 'production') { diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index f1302f88b9..08efe15f14 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -281,7 +281,7 @@ export class Compiler { } switch (data.cmd) { case 'versionLoaded': - if (data.data && data.license) this.onCompilerLoaded(data.data, data.license) + if (data.data) this.onCompilerLoaded(data.data, data.license) break case 'compiled': { diff --git a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts index a3dd0f05e8..4d69093b1c 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -1,5 +1,12 @@ import { IRange } from "monaco-editor"; import monaco from "../../../types/monaco"; +import path from "path"; + +type CodeParserImportsData = { + files?: string[], + modules?: string[], + packages?: string[], +} export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ @@ -176,13 +183,6 @@ export function getCompletionSnippets(range: IRange, monaco): monaco.languages.C insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, range }, - { - label: 'import', - kind: monaco.languages.CompletionItemKind.Snippet, - insertText: 'import "${1:library}";', - insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, - range - }, { label: 'SPDX-License-Identifier', kind: monaco.languages.CompletionItemKind.Snippet, @@ -330,10 +330,10 @@ function CreateCompletionItem(label: string, kind: monaco.languages.CompletionIt export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] { const completionItems = []; const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for', - 'after', 'promise', 'alias', 'apply','auto', 'copyof', 'default', 'define', 'final', 'implements', + 'after', 'promise', 'alias', 'apply', 'auto', 'copyof', 'default', 'define', 'final', 'implements', 'inline', 'let', 'macro', 'match', 'mutable', 'null', 'of', 'partial', 'reference', 'relocatable', 'sealed', 'sizeof', 'static', 'supports', 'switch', 'typedef', - 'if', 'new', 'return', 'returns', 'while', 'using', 'emit', 'anonymous', 'indexed', + 'if', 'new', 'return', 'returns', 'while', 'using', 'emit', 'anonymous', 'indexed', 'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally', 'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override']; keywords.forEach(unit => { @@ -390,6 +390,89 @@ export function GeCompletionUnits(range: IRange, monaco): monaco.languages.Compl return completionItems; } +export function GetImports(range: IRange + , monaco, data: CodeParserImportsData + , word: string +): monaco.languages.CompletionItem[] { + let list = [] + if (!word.startsWith('@')) { + word = word.replace('"', ''); + const nextPaths = [...new Set(data.files + .filter((item) => item.startsWith(word)) + .map((item) => item.replace(word, '').split('/')[0]))] + + list = [...list, ...nextPaths + .filter((item) => !item.endsWith('.sol')) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Folder, + range: range, + label: `${item}`, + insertText: `${item}`, + } + })] + + + list = [...list, + ...data.files + .filter((item) => item.startsWith(word)) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.File, + range: range, + label: `${item}`, + insertText: `${item.replace(word, '')}`, + } + })] + } + if (word === '@' || word === '') { + list = [...list, ...data.packages.map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Module, + range: range, + label: `${item}`, + insertText: word === '@' ? `${item.replace('@', '')}` : `${item}`, + } + })] + } + if (word.startsWith('@') && word.length > 1) { + const nextPaths = [...new Set(data.modules + .filter((item) => item.startsWith(word)) + .map((item) => item.replace(word, '').split('/')[0]))] + + list = [...list, ...nextPaths + .filter((item) => !item.endsWith('.sol')) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Folder, + range: range, + label: `${item}`, + insertText: `${item}`, + } + })] + + list = [...list + , ...data.modules + .filter((item) => item.startsWith(word)) + .map((item) => { + // remove the first part if it starts with @ + let label = item; + if (label.startsWith('@')) { + label = label.substring(label.indexOf('/') + 1); + } + const filename = path.basename(label) + return { + kind: monaco.languages.CompletionItemKind.Reference, + range: range, + label: `${filename}: ${label}`, + insertText: `${item.replace(word, '')}`, + } + }) + ] + } + return list; +}; + export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ { @@ -668,7 +751,7 @@ export function getAddressCompletionItems(range: IRange, monaco): monaco.languag ] } - + export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { if (word === 'ArrayTypeName') { return getArrayCompletionItems(range, monaco); @@ -680,4 +763,4 @@ export function getContextualAutoCompleteBTypeName(word: string, range: IRange, return getAddressCompletionItems(range, monaco); } return []; -} \ No newline at end of file +} diff --git a/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt b/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt new file mode 100644 index 0000000000..94615103e0 --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt @@ -0,0 +1,198 @@ +./node_modules/@openzeppelin/contracts/access/AccessControl.sol +./node_modules/@openzeppelin/contracts/access/AccessControlCrossChain.sol +./node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol +./node_modules/@openzeppelin/contracts/access/IAccessControl.sol +./node_modules/@openzeppelin/contracts/access/IAccessControlEnumerable.sol +./node_modules/@openzeppelin/contracts/access/Ownable.sol +./node_modules/@openzeppelin/contracts/crosschain/amb/CrossChainEnabledAMB.sol +./node_modules/@openzeppelin/contracts/crosschain/amb/LibAMB.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL1.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL2.sol +./node_modules/@openzeppelin/contracts/crosschain/CrossChainEnabled.sol +./node_modules/@openzeppelin/contracts/crosschain/errors.sol +./node_modules/@openzeppelin/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol +./node_modules/@openzeppelin/contracts/crosschain/optimism/LibOptimism.sol +./node_modules/@openzeppelin/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +./node_modules/@openzeppelin/contracts/finance/PaymentSplitter.sol +./node_modules/@openzeppelin/contracts/finance/VestingWallet.sol +./node_modules/@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +./node_modules/@openzeppelin/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorProposalThreshold.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorSettings.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotes.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotesComp.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +./node_modules/@openzeppelin/contracts/governance/extensions/IGovernorTimelock.sol +./node_modules/@openzeppelin/contracts/governance/Governor.sol +./node_modules/@openzeppelin/contracts/governance/IGovernor.sol +./node_modules/@openzeppelin/contracts/governance/TimelockController.sol +./node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol +./node_modules/@openzeppelin/contracts/governance/utils/Votes.sol +./node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol +./node_modules/@openzeppelin/contracts/interfaces/draft-IERC2612.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155MetadataURI.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1271.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363Spender.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC165.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1820Registry.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC20.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC20Metadata.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC2981.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC4626.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Metadata.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777Recipient.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777Sender.sol +./node_modules/@openzeppelin/contracts/metatx/ERC2771Context.sol +./node_modules/@openzeppelin/contracts/metatx/MinimalForwarder.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol +./node_modules/@openzeppelin/contracts/proxy/Clones.sol +./node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol +./node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol +./node_modules/@openzeppelin/contracts/proxy/Proxy.sol +./node_modules/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +./node_modules/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +./node_modules/@openzeppelin/contracts/proxy/utils/Initializable.sol +./node_modules/@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol +./node_modules/@openzeppelin/contracts/security/Pausable.sol +./node_modules/@openzeppelin/contracts/security/PullPayment.sol +./node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol +./node_modules/@openzeppelin/contracts/token/common/ERC2981.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/ERC1155.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/IERC1155.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesComp.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol +./node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol +./node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +./node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/utils/TokenTimelock.sol +./node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol +./node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol +./node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +./node_modules/@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol +./node_modules/@openzeppelin/contracts/token/ERC777/ERC777.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777Sender.sol +./node_modules/@openzeppelin/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol +./node_modules/@openzeppelin/contracts/utils/Address.sol +./node_modules/@openzeppelin/contracts/utils/Arrays.sol +./node_modules/@openzeppelin/contracts/utils/Base64.sol +./node_modules/@openzeppelin/contracts/utils/Checkpoints.sol +./node_modules/@openzeppelin/contracts/utils/Context.sol +./node_modules/@openzeppelin/contracts/utils/Counters.sol +./node_modules/@openzeppelin/contracts/utils/Create2.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol +./node_modules/@openzeppelin/contracts/utils/escrow/ConditionalEscrow.sol +./node_modules/@openzeppelin/contracts/utils/escrow/Escrow.sol +./node_modules/@openzeppelin/contracts/utils/escrow/RefundEscrow.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165Checker.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165Storage.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol +./node_modules/@openzeppelin/contracts/utils/math/Math.sol +./node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol +./node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol +./node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol +./node_modules/@openzeppelin/contracts/utils/math/SignedSafeMath.sol +./node_modules/@openzeppelin/contracts/utils/Multicall.sol +./node_modules/@openzeppelin/contracts/utils/StorageSlot.sol +./node_modules/@openzeppelin/contracts/utils/Strings.sol +./node_modules/@openzeppelin/contracts/utils/structs/BitMaps.sol +./node_modules/@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol +./node_modules/@openzeppelin/contracts/utils/structs/EnumerableMap.sol +./node_modules/@openzeppelin/contracts/utils/structs/EnumerableSet.sol +./node_modules/@openzeppelin/contracts/utils/Timers.sol +./node_modules/@openzeppelin/contracts/vendor/amb/IAMB.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IArbSys.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IBridge.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IInbox.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IMessageProvider.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IOutbox.sol +./node_modules/@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol +./node_modules/@openzeppelin/contracts/vendor/optimism/ICrossDomainMessenger.sol +./node_modules/@openzeppelin/contracts/vendor/polygon/IFxMessageProcessor.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3PoolDeployer.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolActions.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolEvents.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolImmutables.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol +./node_modules/@uniswap/v3-core/contracts/libraries/BitMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FixedPoint128.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FixedPoint96.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FullMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/LiquidityMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Oracle.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Position.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SafeCast.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SqrtPriceMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SwapMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Tick.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TickBitmap.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TickMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TransferHelper.sol +./node_modules/@uniswap/v3-core/contracts/libraries/UnsafeMath.sol diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 70f5e9dd86..39107f941d 100644 --- a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts @@ -3,7 +3,7 @@ import { isArray } from "lodash" import { editor, languages, Position } from "monaco-editor" import monaco from "../../types/monaco" import { EditorUIProps } from "../remix-ui-editor" -import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals" +import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, GetImports } from "./completion/completionGlobals" export class RemixCompletionProvider implements languages.CompletionItemProvider { @@ -16,12 +16,15 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider this.monaco = monaco } - triggerCharacters = ['.', ''] + triggerCharacters = ['.', '', '"', '@', '/'] async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise { const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion') if (!completionSettings) return +<<<<<<< HEAD +======= +>>>>>>> a82c40d86150cabc4d774c312ed442613d04e8bc const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, @@ -34,13 +37,10 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider let nodes: AstNode[] = [] let suggestions: monaco.languages.CompletionItem[] = [] - if (context.triggerCharacter === '.') { - const lineTextBeforeCursor: string = line.substring(0, position.column - 1) - const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) - const expressionElements = lineTextBeforeCursor.split('.') - let dotCompleted = false + if (context.triggerCharacter === '"' || context.triggerCharacter === '@' || context.triggerCharacter === '/') { +<<<<<<< HEAD // handles completion from for builtin types if (lastNodeInExpression.memberName === 'sender') { // exception for this member lastNodeInExpression.name = 'sender' @@ -62,9 +62,28 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider const dotCompletions = await this.getDotCompletions(nameOfLastTypedExpression, range) nodes = [...nodes, ...dotCompletions.nodes] suggestions = [...suggestions, ...dotCompletions.suggestions] +======= + const lastpart = line.substring(0, position.column - 1).split(';').pop() + + if (lastpart.startsWith('import')) { + const imports = await this.props.plugin.call('codeParser', 'getImports') + if (context.triggerCharacter === '"' || context.triggerCharacter === '@') { + suggestions = [...suggestions, + ...GetImports(range, this.monaco, imports, context.triggerCharacter), + ] + } else if (context.triggerCharacter === '/') { + const word = line.split('"')[1] + suggestions = [...suggestions, + ...GetImports(range, this.monaco, imports, word), + ] + } else { + return + } +>>>>>>> a82c40d86150cabc4d774c312ed442613d04e8bc } - } else { + } else +<<<<<<< HEAD // handles contract completions and other suggestions suggestions = [...suggestions, ...GetGlobalVariable(range, this.monaco), @@ -76,18 +95,71 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider ] let contractCompletions = await this.getContractCompletions() - - // we can't have external nodes without using this. - contractCompletions = contractCompletions.filter(node => { - if (node.visibility && node.visibility === 'external') { - return false +======= + if (context.triggerCharacter === '.') { + const lineTextBeforeCursor: string = line.substring(0, position.column - 1) + const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) + const expressionElements = lineTextBeforeCursor.split('.') + + let dotCompleted = false +>>>>>>> a82c40d86150cabc4d774c312ed442613d04e8bc + + // handles completion from for builtin types + if (lastNodeInExpression.memberName === 'sender') { // exception for this member + lastNodeInExpression.name = 'sender' } +<<<<<<< HEAD return true }) nodes = [...nodes, ...contractCompletions] +======= + const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) + if (globalCompletion) { + dotCompleted = true + suggestions = [...suggestions, ...globalCompletion] + setTimeout(() => { + // eslint-disable-next-line no-debugger + // debugger + }, 2000) + } + // handle completion for global THIS. + if (lastNodeInExpression.name === 'this') { + dotCompleted = true + nodes = [...nodes, ...await this.getThisCompletions(position)] + } + // handle completion for other dot completions + if (expressionElements.length > 1 && !dotCompleted) { +>>>>>>> a82c40d86150cabc4d774c312ed442613d04e8bc + + const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName + const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) + nodes = [...nodes, ...dotCompletions.nodes] + suggestions = [...suggestions, ...dotCompletions.suggestions] + } + } else { + + // handles contract completions and other suggestions + suggestions = [...suggestions, + ...GetGlobalVariable(range, this.monaco), + ...getCompletionSnippets(range, this.monaco), + ...GetCompletionTypes(range, this.monaco), + ...GetCompletionKeywords(range, this.monaco), + ...GetGlobalFunctions(range, this.monaco), + ...GeCompletionUnits(range, this.monaco), + ] + let contractCompletions = await this.getContractCompletions(position) + + // we can't have external nodes without using this. + contractCompletions = contractCompletions.filter(node => { + if (node.visibility && node.visibility === 'external') { + return false + } + return true + }) + nodes = [...nodes, ...contractCompletions] - } + } // remove duplicates const nodeIds = {}; diff --git a/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts index 3b563c6a15..9d6daee730 100644 --- a/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts @@ -13,8 +13,22 @@ export class RemixDefinitionProvider implements monaco.languages.DefinitionProvi // eslint-disable-next-line @typescript-eslint/no-unused-vars async provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise { const cursorPosition = this.props.editorAPI.getCursorPosition() - const jumpLocation = await this.jumpToDefinition(cursorPosition) - if(!jumpLocation || !jumpLocation.fileName) return [] + let jumpLocation = await this.jumpToDefinition(cursorPosition) + if (!jumpLocation || !jumpLocation.fileName) { + const line = model.getLineContent(position.lineNumber) + const lastpart = line.substring(0, position.column - 1).split(';').pop() + if (lastpart.startsWith('import')) { + const importPath = line.substring(lastpart.indexOf('"') + 1) + const importPath2 = importPath.substring(0, importPath.indexOf('"')) + jumpLocation = { + startLineNumber: 1, + startColumn: 1, + endColumn: 1, + endLineNumber: 1, + fileName: importPath2 + } + } + } return [{ uri: this.monaco.Uri.parse(jumpLocation.fileName), range: { 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 e1ff7ceb9f..ef99b07edc 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -158,6 +158,7 @@ export const EditorUI = (props: EditorUIProps) => { const infoColor = formatColor('--info') const darkColor = formatColor('--dark') const secondaryColor = formatColor('--secondary') + const primaryColor = formatColor('--primary') const textColor = formatColor('--text') || darkColor const textbackground = formatColor('--text-background') || lightColor @@ -266,7 +267,8 @@ export const EditorUI = (props: EditorUIProps) => { 'editorSuggestWidget.background': lightColor, 'editorSuggestWidget.selectedBackground': secondaryColor, 'editorSuggestWidget.selectedForeground': textColor, - 'editorSuggestWidget.highlightForeground': infoColor, + 'editorSuggestWidget.highlightForeground': primaryColor, + 'editorSuggestWidget.focusHighlightForeground': infoColor, 'editor.lineHighlightBorder': secondaryColor, 'editor.lineHighlightBackground': textbackground === darkColor ? lightColor : secondaryColor, 'editorGutter.background': lightColor, diff --git a/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx index 75c3698f1f..37fa018c92 100644 --- a/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx +++ b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx @@ -15,7 +15,7 @@ export function RemixPluginPanel (props: RemixPanelProps) { <> {props.header}
-
+
{Object.values(props.plugins).map((pluginRecord) => { return })} diff --git a/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx b/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx index b15d0e52ed..793d2ea076 100644 --- a/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx +++ b/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx @@ -52,7 +52,7 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
{to.displayName} :

{to.description || No description Provided}

{pluginMessage()} - { sensitiveCall ?

You are going to process a sensitive call. Please make sure you trust this plugin.

: '' } + { sensitiveCall ?

Make sure you trust this plugin before processing this call.

: '' }
{ !sensitiveCall &&
diff --git a/libs/remix-ui/search/src/index.ts b/libs/remix-ui/search/src/index.ts index 8617e2ddff..72588397c4 100644 --- a/libs/remix-ui/search/src/index.ts +++ b/libs/remix-ui/search/src/index.ts @@ -1 +1,3 @@ -export { SearchTab } from './lib/components/Search'; \ No newline at end of file +export { SearchTab } from './lib/components/Search'; +export * from './lib/components/results/SearchHelper'; +export * from './lib/types'; \ No newline at end of file diff --git a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx index 84aab5e5d0..e0379d1b11 100644 --- a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx +++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx @@ -80,9 +80,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version) return tmp ? tmp[1] : version } - if (version != '' && !semver.gt(truncateVersion(version), '0.4.12')) { + if (version && version != '' && !semver.gt(truncateVersion(version), '0.4.12')) { setIsSupportedVersion(false) - setRunButtonTitle('Sselect Solidity compiler version greater than 0.4.12.') + setRunButtonTitle('Select Solidity compiler version greater than 0.4.12.') } else { setIsSupportedVersion(true) setRunButtonTitle('Run static analysis') diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index afe4c54897..4bca319f0e 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -186,7 +186,7 @@ export const TabsUI = (props: TabsUIProps) => { }} > - {props.tabs.map((tab, i) => {renderTab(tab, i)})} + {props.tabs.map((tab, i) => {renderTab(tab, i)})} {props.tabs.map((tab) => )} diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index 74b767cf73..7a9ed8a416 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -13,7 +13,7 @@ export const listenOnPluginEvents = (filePanelPlugin) => { plugin = filePanelPlugin plugin.on('filePanel', 'createWorkspaceReducerEvent', (name: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void) => { - createWorkspace(name, workspaceTemplateName, isEmpty, cb) + createWorkspace(name, workspaceTemplateName, null, isEmpty, cb) }) plugin.on('filePanel', 'renameWorkspaceReducerEvent', (oldName: string, workspaceName: string, cb: (err: Error, result?: string | number | boolean | Record) => void) => { diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 3ef203644e..ab455ee24c 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -43,17 +43,19 @@ export const addInputField = async (type: 'file' | 'folder', path: string, cb?: return promise } -export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean = false) => { +export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, opts = null, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean = false) => { await plugin.fileManager.closeAllFiles() const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName) - dispatch(createWorkspaceRequest(promise)) promise.then(async () => { dispatch(createWorkspaceSuccess({ name: workspaceName, isGitRepo })) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspaces(await getWorkspaces()) await plugin.workspaceCreated(workspaceName) - if (!isEmpty) await loadWorkspacePreset(workspaceTemplateName) + + if (isGitRepo) await plugin.call('dGitProvider', 'init') + if (!isEmpty) await loadWorkspacePreset(workspaceTemplateName, opts) + cb && cb(null, workspaceName) }).catch((error) => { dispatch(createWorkspaceError({ error })) @@ -80,7 +82,7 @@ export type UrlParametersType = { language: string } -export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault') => { +export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault', opts?) => { const workspaceProvider = plugin.fileProviders.workspace const params = queryParams.get() as UrlParametersType @@ -159,7 +161,7 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe if (!templateList.includes(template)) break _paq.push(['trackEvent', 'workspace', 'template', template]) // @ts-ignore - const files = await templateWithContent[template]() + const files = await templateWithContent[template](opts) for (const file in files) { try { await workspaceProvider.set(file, files[file]) @@ -339,7 +341,7 @@ export const cloneRepository = async (url: string) => { try { const repoName = await getRepositoryTitle(url) - await createWorkspace(repoName, 'blank', true, null, true) + await createWorkspace(repoName, 'blank', null, true, null, true) const promise = plugin.call('dGitProvider', 'clone', repoConfig, repoName, true) dispatch(cloneRepositoryRequest()) diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index 1724ca7c29..2fd6a71b46 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -9,7 +9,7 @@ export const FileSystemContext = createContext<{ dispatchFetchDirectory:(path: string) => Promise, dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise, dispatchRemoveInputField:(path: string) => Promise, - dispatchCreateWorkspace: (workspaceName: string, workspaceTemplateName: string) => Promise, + dispatchCreateWorkspace: (workspaceName: string, workspaceTemplateName: string, opts?, initGitRepo?: boolean) => Promise, toast: (toasterMsg: string) => void, dispatchFetchWorkspaceDirectory: (path: string) => Promise, dispatchSwitchToWorkspace: (name: string) => Promise, diff --git a/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css b/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css index db1165b494..44e67f30fd 100644 --- a/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css +++ b/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css @@ -7,7 +7,7 @@ } .remixui_fileexplorer { position : relative; - overflow-y : auto; + overflow-y : hidden; } .remixui_fileExplorerTree { cursor : default; diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 2d4010ca31..2b1409fe86 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -45,8 +45,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await removeInputField(path) } - const dispatchCreateWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate) => { - await createWorkspace(workspaceName, workspaceTemplateName) + const dispatchCreateWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, opts?, initGitRepo?: boolean) => { + await createWorkspace(workspaceName, workspaceTemplateName, opts, null, null, initGitRepo) } const dispatchFetchWorkspaceDirectory = async (path: string) => { diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 512fd2ec65..7aef284f10 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -15,11 +15,15 @@ export function Workspace () { const [currentWorkspace, setCurrentWorkspace] = useState(NO_WORKSPACE) const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null) const [showDropdown, setShowDropdown] = useState(false) + const displayOzCustomRef = useRef() + const ozFeatures = useRef({mintable: false, burnable: false, pausable: false}) + const upgradeable = useRef() const global = useContext(FileSystemContext) const workspaceRenameInput = useRef() const workspaceCreateInput = useRef() const workspaceCreateTemplateInput = useRef() const cloneUrlRef = useRef() + const initGitRepoRef = useRef() useEffect(() => { setCurrentWorkspace(localStorage.getItem('currentWorkspace') ? localStorage.getItem('currentWorkspace') : '') @@ -104,9 +108,15 @@ export function Workspace () { const workspaceName = workspaceCreateInput.current.value // @ts-ignore: Object is possibly 'null'. const workspaceTemplateName = workspaceCreateTemplateInput.current.value || 'remixDefault' + const initGitRepo = initGitRepoRef.current.checked + const features = ozFeatures.current + const opts = { + upgradeable: upgradeable.current, + features + } try { - await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName) + await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName, opts, initGitRepo) } catch (e) { global.modal('Create Workspace', e.message, 'OK', () => {}, '') console.error(e) @@ -138,6 +148,13 @@ export function Workspace () { } const updateWsName = () => { + // @ts-ignore + if (workspaceCreateTemplateInput.current.value.startsWith('oz') && displayOzCustomRef && displayOzCustomRef.current) { + displayOzCustomRef.current.style.display = 'block' + upgradeable.current = undefined + ozFeatures.current = {mintable: false, burnable: false, pausable: false} + } else displayOzCustomRef.current.style.display = 'none' + // @ts-ignore workspaceCreateInput.current.value = `${workspaceCreateTemplateInput.current.value || 'remixDefault'}_${Date.now()}` } @@ -156,19 +173,92 @@ export function Workspace () { setShowDropdown(isOpen) } + const handleUpgradeability = (e) => { + // @ts-ignore + upgradeable.current = e.target.value + // @ts-ignore + workspaceCreateInput.current.value = `${workspaceCreateTemplateInput.current.value + '_upgradeable'}_${Date.now()}` + } + + const handleFeatures = (e) => { + // @ts-ignore + ozFeatures.current[e.target.value] = e.target.checked + } + const createModalMessage = () => { return ( <> - -
- - + + + + + + + + + + + + + +
+ + + +
handleFeatures(e)}> +
+ + +
+
+ + +
+
+ + +
+
+ + +
handleUpgradeability(e)}> +
+ + +
+
+ + +
+
+ +
+ + + + +
+ {}} + /> + +
+ ) } @@ -279,9 +369,9 @@ export function Workspace () { { - createWorkspace() - }} + onClick={() => { + createWorkspace() + }} > { - create a new workspace - diff --git a/libs/remix-ws-templates/src/index.ts b/libs/remix-ws-templates/src/index.ts index 9e12a868c8..f00b25f961 100644 --- a/libs/remix-ws-templates/src/index.ts +++ b/libs/remix-ws-templates/src/index.ts @@ -1,5 +1,7 @@ export { default as remixDefault } from './templates/remixDefault' export { default as blank } from './templates/blank' export { default as ozerc20 } from './templates/ozerc20' -export { default as zeroxErc20 } from './templates/zeroxErc20' export { default as ozerc721 } from './templates/ozerc721' +export { default as ozerc1155 } from './templates/ozerc1155' +export { default as zeroxErc20 } from './templates/zeroxErc20' + diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/index.ts b/libs/remix-ws-templates/src/templates/ozerc1155/index.ts new file mode 100644 index 0000000000..4164d91e76 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/index.ts @@ -0,0 +1,26 @@ +import { erc1155 } from '@openzeppelin/wizard'; + +export default async (opts) => { + if (opts.features) { + erc1155.defaults.mintable = opts.features.mintable + erc1155.defaults.burnable = opts.features.burnable + erc1155.defaults.pausable = opts.features.pausable + } + + const filesObj = { + 'contracts/MyToken.sol': erc1155.print({ ...erc1155.defaults, upgradeable: opts.upgradeable}), + // @ts-ignore + 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default, + // @ts-ignore + 'scripts/deploy_with_web3.ts': (await import('!!raw-loader!./scripts/deploy_with_web3.ts')).default, + // @ts-ignore + 'scripts/ethers-lib.ts': (await import('!!raw-loader!./scripts/ethers-lib.ts')).default, + // @ts-ignore + 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default + } + // If no options is selected, opts.upgradeable will be undefined + // We do not show test file for upgradeable contract + // @ts-ignore + if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default + return filesObj +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts new file mode 100644 index 0000000000..a6c8cf30e5 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts @@ -0,0 +1,10 @@ +import { deploy } from './ethers-lib' + +(async () => { + try { + const result = await deploy('MyToken', []) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } + })() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_web3.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_web3.ts new file mode 100644 index 0000000000..b22b119246 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_web3.ts @@ -0,0 +1,10 @@ +import { deploy } from './web3-lib' + +(async () => { + try { + const result = await deploy('MyToken', []) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts new file mode 100644 index 0000000000..e875db9a43 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts @@ -0,0 +1,29 @@ +import { ethers } from 'ethers' + +/** + * Deploy the given contract + * @param {string} contractName name of the contract to deploy + * @param {Array} args list of constructor' parameters + * @param {Number} accountIndex account index from the exposed account + * @return {Contract} deployed contract + */ +export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => { + + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + + const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + + const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) + + const contract = await factory.deploy(...args) + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.deployed() + return contract +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/web3-lib.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/web3-lib.ts new file mode 100644 index 0000000000..e6d9e09f35 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/web3-lib.ts @@ -0,0 +1,36 @@ +import Web3 from 'web3' +import { Contract, ContractSendMethod, Options } from 'web3-eth-contract' + +/** + * Deploy the given contract + * @param {string} contractName name of the contract to deploy + * @param {Array} args list of constructor' parameters + * @param {string} from account used to send the transaction + * @param {number} gas gas limit + * @return {Options} deployed contract + */ +export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => { + + const web3 = new Web3(web3Provider) + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + + const accounts = await web3.eth.getAccounts() + + const contract: Contract = new web3.eth.Contract(metadata.abi) + + const contractSend: ContractSendMethod = contract.deploy({ + data: metadata.data.bytecode.object, + arguments: args + }) + + const newContractInstance = await contractSend.send({ + from: from || accounts[0], + gas: gas || 3600000 + }) + return newContractInstance.options +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/tests/MyToken_test.sol b/libs/remix-ws-templates/src/templates/ozerc1155/tests/MyToken_test.sol new file mode 100644 index 0000000000..73a77e7094 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/ozerc1155/tests/MyToken_test.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; +import "remix_tests.sol"; +import "../contracts/MyToken.sol"; + +contract MyTokenTest { + + MyToken s; + function beforeAll () public { + s = new MyToken(); + } + + function testSetURI () public { + string memory uri = "https://testuri.io/token"; + s.setURI(uri); + Assert.equal(s.uri(1), uri, "uri did not match"); + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc20/index.ts b/libs/remix-ws-templates/src/templates/ozerc20/index.ts index f44f812bb2..5eca9475f8 100644 --- a/libs/remix-ws-templates/src/templates/ozerc20/index.ts +++ b/libs/remix-ws-templates/src/templates/ozerc20/index.ts @@ -1,8 +1,14 @@ import { erc20 } from '@openzeppelin/wizard'; -export default async () => { - return { - 'contracts/MyToken.sol': erc20.print(), +export default async (opts) => { + if (opts.features) { + erc20.defaults.mintable = opts.features.mintable + erc20.defaults.burnable = opts.features.burnable + erc20.defaults.pausable = opts.features.pausable + } + + const filesObj = { + 'contracts/MyToken.sol': erc20.print({ ...erc20.defaults, upgradeable: opts.upgradeable}), // @ts-ignore 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default, // @ts-ignore @@ -10,8 +16,12 @@ export default async () => { // @ts-ignore 'scripts/ethers-lib.ts': (await import('!!raw-loader!./scripts/ethers-lib.ts')).default, // @ts-ignore - 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default, - // @ts-ignore - 'tests/MyToken_test.sol': (await import('raw-loader!./tests/MyToken_test.sol')).default + 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default } + + // If no options is selected, opts.upgradeable will be undefined + // We do not show test file for upgradeable contract + // @ts-ignore + if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default + return filesObj } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc721/index.ts b/libs/remix-ws-templates/src/templates/ozerc721/index.ts index 26a3acd59b..5c7f01293d 100644 --- a/libs/remix-ws-templates/src/templates/ozerc721/index.ts +++ b/libs/remix-ws-templates/src/templates/ozerc721/index.ts @@ -1,8 +1,14 @@ import { erc721 } from '@openzeppelin/wizard'; -export default async () => { - return { - 'contracts/MyToken.sol': erc721.print(), +export default async (opts) => { + if (opts.features) { + erc721.defaults.mintable = opts.features.mintable + erc721.defaults.burnable = opts.features.burnable + erc721.defaults.pausable = opts.features.pausable + } + + const filesObj = { + 'contracts/MyToken.sol': erc721.print({ ...erc721.defaults, upgradeable: opts.upgradeable}), // @ts-ignore 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default, // @ts-ignore @@ -10,8 +16,12 @@ export default async () => { // @ts-ignore 'scripts/ethers-lib.ts': (await import('!!raw-loader!./scripts/ethers-lib.ts')).default, // @ts-ignore - 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default, - // @ts-ignore - 'tests/MyToken_test.sol': (await import('raw-loader!./tests/MyToken_test.sol')).default + 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default } + + // If no options is selected, opts.upgradeable will be undefined + // We do not show test file for upgradeable contract + // @ts-ignore + if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default + return filesObj } \ No newline at end of file diff --git a/package.json b/package.json index cb6a863b2b..4a8164df48 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "test-browser": "npm-run-all -lpr selenium make-mock-compiler serve browsertest", "watch": "watchify apps/remix-ide/src/index.js -dv -p browserify-reload -o apps/remix-ide/build/app.js --exclude solc", "reinstall": "rm ./node-modules/ -rf && rm yarn.lock && rm ./build/ -rf && yarn install & yarn run build", - "ganache-cli": "npx ganache-cli" + "ganache-cli": "npx ganache-cli", + "build-contracts": "find ./node_modules/@openzeppelin/contracts | grep -i '.sol' > libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt && find ./node_modules/@uniswap/v3-core/contracts | grep -i '.sol' >> libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt" }, "browserify": { "transform": [ @@ -197,6 +198,7 @@ "merge": "^2.1.1", "monaco-editor": "^0.30.1", "npm-install-version": "^6.0.2", + "path-browserify": "^1.0.1", "prettier": "^2.7.1", "prettier-plugin-solidity": "^1.0.0-beta.24", "raw-loader": "^4.0.2", @@ -263,6 +265,8 @@ "@types/ws": "^7.2.4", "@typescript-eslint/eslint-plugin": "^4.32.0", "@typescript-eslint/parser": "^4.32.0", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v3-core": "^1.0.1", "ace-mode-lexon": "^1.*.*", "ace-mode-move": "0.0.1", "ace-mode-solidity": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index 498e79f2a1..62de31106f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4813,6 +4813,16 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@uniswap/v2-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -18513,6 +18523,11 @@ path-browserify@0.0.1, path-browserify@~0.0.0: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"