diff --git a/apps/remix-ide-e2e/src/commands/currentSelectedFileIs.ts b/apps/remix-ide-e2e/src/commands/currentSelectedFileIs.ts new file mode 100644 index 0000000000..81164cb97a --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/currentSelectedFileIs.ts @@ -0,0 +1,15 @@ +import { NightwatchBrowser } from 'nightwatch' +import EventEmitter from 'events' + +class CurrentSelectedFileIs extends EventEmitter { + command (this: NightwatchBrowser, value: string): NightwatchBrowser { + this.api + .waitForElementContainsText('*[data-id="tabs-component"] *[data-id="tab-active"]', value) + .perform(() => { + this.emit('complete') + }) + return this + } +} + +module.exports = CurrentSelectedFileIs diff --git a/apps/remix-ide-e2e/src/tests/editor.test.ts b/apps/remix-ide-e2e/src/tests/editor.test.ts index 7b714a432b..59c3211e79 100644 --- a/apps/remix-ide-e2e/src/tests/editor.test.ts +++ b/apps/remix-ide-e2e/src/tests/editor.test.ts @@ -147,6 +147,7 @@ module.exports = { .waitForElementContainsText('.contextview .type', 'uint256') .waitForElementContainsText('.contextview .name', 'number') .click('.contextview [data-action="previous"]') // declaration + .pause(1000) .execute(() => { return (document.getElementById('editorView') as any).getCursorPosition() }, [], (result) => { @@ -154,6 +155,7 @@ module.exports = { browser.assert.equal(result.value, '180') }) .click('.contextview [data-action="next"]') // back to the initial state + .pause(1000) .execute(() => { return (document.getElementById('editorView') as any).getCursorPosition() }, [], (result) => { @@ -161,6 +163,7 @@ module.exports = { browser.assert.equal(result.value, '323') }) .click('.contextview [data-action="next"]') // next reference + .pause(1000) .execute(() => { return (document.getElementById('editorView') as any).getCursorPosition() }, [], (result) => { @@ -168,12 +171,74 @@ module.exports = { browser.assert.equal(result.value, '489') }) .click('.contextview [data-action="gotoref"]') // back to the declaration + .pause(1000) .execute(() => { return (document.getElementById('editorView') as any).getCursorPosition() }, [], (result) => { console.log('result', result) browser.assert.equal(result.value, '180') }) + }, + + 'Should display the context view, loop over "Owner" by switching file #group2': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('solidity') + .click('[for="autoCompile"]') // disable auto compile + .openFile('contracts') + .openFile('contracts/3_Ballot.sol') + .waitForElementVisible('#editorView') + .setEditorValue(BallotWithARefToOwner) + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') // compile + .pause(2000) + .execute(() => { + (document.getElementById('editorView') as any).gotoLine(14, 6) + }, [], () => {}) + .waitForElementVisible('.contextview') + .waitForElementContainsText('.contextview .type', 'ContractDefinition') + .waitForElementContainsText('.contextview .name', 'Owner') + .click('.contextview [data-action="next"]') + .pause(1000) + .execute(() => { + return (document.getElementById('editorView') as any).getCursorPosition() + }, [], (result) => { + console.log('result', result) + browser.assert.equal(result.value, '1061') + }) + .click('.contextview [data-action="next"]') + .pause(1000) + .execute(() => { + return (document.getElementById('editorView') as any).getCursorPosition() + }, [], (result) => { + console.log('result', result) + browser.assert.equal(result.value, '122') + }) + .currentSelectedFileIs('2_Owner.sol') // make sure the current file has been properly changed + .click('.contextview [data-action="next"]') + .pause(1000) + .execute(() => { + return (document.getElementById('editorView') as any).getCursorPosition() + }, [], (result) => { + console.log('result', result) + browser.assert.equal(result.value, '211') + }) + .click('.contextview [data-action="next"]') + .currentSelectedFileIs('3_Ballot.sol') + .pause(1000) + .execute(() => { + return (document.getElementById('editorView') as any).getCursorPosition() + }, [], (result) => { + console.log('result', result) + browser.assert.equal(result.value, '1061') + }) + .click('.contextview [data-action="gotoref"]') // go to the declaration + .pause(1000) + .execute(() => { + return (document.getElementById('editorView') as any).getCursorPosition() + }, [], (result) => { + console.log('result', result) + browser.assert.equal(result.value, '122') + }) .end() } } @@ -281,3 +346,149 @@ contract Storage { return number; } }` + +const BallotWithARefToOwner = ` + + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +import "./2_Owner.sol"; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract Ballot { + Owner c; + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + struct Proposal { + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; + + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + constructor(bytes32[] memory proposalNames) { + c = new Owner(); + chairperson = msg.sender; + voters[chairperson].weight = 1; + + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } +} +` diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index d3df343415..fc09a9c8f1 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -149,6 +149,18 @@ module.exports = { await clickAndCheckLog(browser, 'udapp:getAccounts', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', null, null) }, + 'Should select another provider #group1': async function (browser: NightwatchBrowser) { + await clickAndCheckLog(browser, 'udapp:setEnvironmentMode', null, null, { context: 'vm', fork: 'berlin' }) + await browser + .frameParent() + .useCss() + .clickLaunchIcon('udapp') + .waitForElementContainsText('#selectExEnvOptions option:checked', 'JavaScript VM (Berlin)') + .clickLaunchIcon('localPlugin') + .useXpath() + // @ts-ignore + .frame(0) + }, // context menu item 'Should create context menu item #group1': async function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/url.spec.ts b/apps/remix-ide-e2e/src/tests/url.spec.ts index 9d7e8e323e..13227238f7 100644 --- a/apps/remix-ide-e2e/src/tests/url.spec.ts +++ b/apps/remix-ide-e2e/src/tests/url.spec.ts @@ -77,12 +77,13 @@ module.exports = { 'Should load using URL compiler params': function (browser: NightwatchBrowser) { browser .pause(5000) - .url('http://127.0.0.1:8080/#optimize=true&runs=300&autoCompile=true&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js') + .url('http://127.0.0.1:8080/#optimize=true&runs=300&autoCompile=true&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&language=Yul') .refresh() .pause(5000) .clickLaunchIcon('solidity') .assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770') .assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul') + .assert.containsText('#compilierLanguageSelector option[data-id="selected"]', 'Yul') .verify.elementPresent('#optimize:checked') .verify.elementPresent('#autoCompile:checked') .verify.attributeEquals('#runs', 'value', '300') diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index f9a7c35d63..9abe5c2174 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -61,6 +61,7 @@ declare module 'nightwatch' { acceptAndRemember (this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser clearConsole (this: NightwatchBrowser): NightwatchBrowser clearTransactions (this: NightwatchBrowser): NightwatchBrowser + currentSelectedFileIs (name: string): NightwatchBrowser } export interface NightwatchBrowser { diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 013b1b9853..1bc0a00b82 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -438,7 +438,7 @@ class Editor extends Plugin { if (!filePath) return filePath = await this.call('fileManager', 'getPathFromUrl', filePath) filePath = filePath.file - if (!this.sessions[filePath]) throw new Error('file not found' + filePath) + if (!this.sessions[filePath]) return const path = filePath || this.currentFile const { from } = this.currentRequest diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index 1c4a3e61bf..a3828d68b3 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -470,7 +470,7 @@ class FileManager extends Plugin { } currentFile () { - return this._deps.config.get('currentFile') + return this.editor.current() } async closeAllFiles () { diff --git a/apps/remix-ide/src/app/plugins/remixd-handle.tsx b/apps/remix-ide/src/app/plugins/remixd-handle.tsx index 68367d1df3..9ff3d8339f 100644 --- a/apps/remix-ide/src/app/plugins/remixd-handle.tsx +++ b/apps/remix-ide/src/app/plugins/remixd-handle.tsx @@ -142,11 +142,11 @@ function remixdDialog () {
If you are just looking for the remixd command, here it is: -



${commandText} +



{commandText}
- When connected, a session will be started between ${window.location.origin} and your local file system at ws://127.0.0.1:65520. + When connected, a session will be started between {window.location.origin} and your local file system at ws://127.0.0.1:65520. The shared folder will be in the "File Explorers" workspace named "localhost".
Read more about other Remixd ports usage
@@ -155,7 +155,7 @@ function remixdDialog () {
- Before using, make sure remixd version is latest i.e. ${remixdVersion} + Before using, make sure remixd version is latest i.e. v{remixdVersion}

Read here how to update it
diff --git a/libs/remix-core-plugin/src/lib/editor-context-listener.ts b/libs/remix-core-plugin/src/lib/editor-context-listener.ts index 7bde42de11..9e73f6bc01 100644 --- a/libs/remix-core-plugin/src/lib/editor-context-listener.ts +++ b/libs/remix-core-plugin/src/lib/editor-context-listener.ts @@ -84,11 +84,6 @@ export class EditorContextListener extends Plugin { async _highlightItems (cursorPosition, compilationResult, file) { if (this.currentPosition === cursorPosition) return - if (this.currentFile !== file) { - this.currentFile = file - this.currentPosition = cursorPosition - return - } this._stopHighlighting() this.currentPosition = cursorPosition this.currentFile = file @@ -122,9 +117,13 @@ export class EditorContextListener extends Plugin { async _highlight (node, compilationResult) { if (!node) return const position = sourceMappingDecoder.decode(node.src) + const fileTarget = compilationResult.getSourceName(position.file) + const nodeFound = this._activeHighlights.find((el) => el.fileTarget === fileTarget && el.position.file === position.file && el.position.length === position.length && el.position.start === position.start) + if (nodeFound) return // if the content is already highlighted, do nothing. + await this._highlightInternal(position, node, compilationResult) if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { - this._activeHighlights.push({ position, fileTarget: compilationResult.getSourceName(position.file), nodeId: node.id }) + this._activeHighlights.push({ position, fileTarget, nodeId: node.id }) } } @@ -204,13 +203,16 @@ export class EditorContextListener extends Plugin { } _loadContractInfos (node) { + const path = (this.nodes.length && this.nodes[0].absolutePath) || this.results.source.target for (const i in this.nodes) { if (this.nodes[i].id === node.scope) { const contract = this.nodes[i] - this.contract = this.results.data.contracts[this.results.source.target][contract.name] - this.estimationObj = this.contract.evm.gasEstimates - this.creationCost = this.estimationObj === null ? '-' : this.estimationObj.creation.totalCost - this.codeDepositCost = this.estimationObj === null ? '-' : this.estimationObj.creation.codeDepositCost + this.contract = this.results.data.contracts[path][contract.name] + if (contract) { + this.estimationObj = this.contract.evm.gasEstimates + this.creationCost = this.estimationObj === null ? '-' : this.estimationObj.creation.totalCost + this.codeDepositCost = this.estimationObj === null ? '-' : this.estimationObj.creation.codeDepositCost + } } } } diff --git a/libs/remix-solidity/jest.config.js b/libs/remix-solidity/jest.config.js new file mode 100644 index 0000000000..abfff6a986 --- /dev/null +++ b/libs/remix-solidity/jest.config.js @@ -0,0 +1,24 @@ +module.exports = { + name: 'remix-solidity', + preset: '../../jest.config.js', + verbose: true, + silent: false, // Silent console messages, specially the 'remix-simulator' ones + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + transformIgnorePatterns: ["/node_modules/", "/dist/", "\\.pnp\\.[^\\\/]+$"], + rootDir: "./", + testTimeout: 40000, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'], + // Coverage + collectCoverage: true, + coverageReporters: ['text', 'text-summary'], + collectCoverageFrom: [ + "**/*.ts", + "!**/sol/**", + "!src/types.ts", + "!src/logger.ts" + ], + coverageDirectory: '../../coverage/libs/remix-solidity' + }; + \ No newline at end of file diff --git a/libs/remix-solidity/package.json b/libs/remix-solidity/package.json index 7f90f6ee3c..fdfaca6c02 100644 --- a/libs/remix-solidity/package.json +++ b/libs/remix-solidity/package.json @@ -41,7 +41,6 @@ "@types/node": "^13.1.1", "babel-eslint": "^10.0.0", "babelify": "^10.0.0", - "tape": "^4.6.0", "typescript": "^3.7.4" }, "scripts": { diff --git a/libs/remix-solidity/src/compiler/compiler-input.ts b/libs/remix-solidity/src/compiler/compiler-input.ts index 2baabecc79..8b00c647ee 100644 --- a/libs/remix-solidity/src/compiler/compiler-input.ts +++ b/libs/remix-solidity/src/compiler/compiler-input.ts @@ -1,6 +1,6 @@ 'use strict' -import { CompilerInput, Source, CompilerInputOptions } from './types' +import { CompilerInput, Source, CompilerInputOptions, Language } from './types' export default (sources: Source, opts: CompilerInputOptions): string => { const o: CompilerInput = { @@ -32,3 +32,13 @@ export default (sources: Source, opts: CompilerInputOptions): string => { } return JSON.stringify(o) } + +export const Languages = ['Solidity', 'Yul'] + +export function getValidLanguage (val: string): Language { + if (val !== undefined && val !== null && val) { + const lang = val.slice(0, 1).toUpperCase() + val.slice(1).toLowerCase() + return Languages.indexOf(lang) > -1 ? lang as Language : null + } + return null +} diff --git a/libs/remix-solidity/src/index.ts b/libs/remix-solidity/src/index.ts index 7de163dc97..d5878a0f85 100644 --- a/libs/remix-solidity/src/index.ts +++ b/libs/remix-solidity/src/index.ts @@ -1,6 +1,6 @@ export { Compiler } from './compiler/compiler' export { compile } from './compiler/compiler-helpers' -export { default as CompilerInput } from './compiler/compiler-input' +export { default as CompilerInput, getValidLanguage } from './compiler/compiler-input' export { CompilerAbstract } from './compiler/compiler-abstract' export * from './compiler/types' export { promisedMiniXhr, pathToURL, baseURLBin, baseURLWasm, canUseWorker, urlFromVersion } from './compiler/compiler-utils' diff --git a/libs/remix-solidity/tests/compiler-input.spec.ts b/libs/remix-solidity/tests/compiler-input.spec.ts new file mode 100644 index 0000000000..3dc247624b --- /dev/null +++ b/libs/remix-solidity/tests/compiler-input.spec.ts @@ -0,0 +1,25 @@ +import { getValidLanguage } from '../src/compiler/compiler-input' +import { Language } from '../src/compiler/types' + +describe('compiler-input', () => { + test('getValidLanguage', () => { + const correctYul: Language = 'Yul' + const correctSolidity: Language = 'Solidity' + + const yulUpperCase = 'Yul' + const yulLowerCase = 'yul' + + const solidityUpperCase = 'Solidity' + const solidityLowerCase = 'solidity' + + expect(getValidLanguage(yulLowerCase)).toBe(correctYul) + expect(getValidLanguage(yulUpperCase)).toBe(correctYul) + expect(getValidLanguage(solidityUpperCase)).toBe(correctSolidity) + expect(getValidLanguage(solidityLowerCase)).toBe(correctSolidity) + expect(getValidLanguage(null)).toBe(null) + expect(getValidLanguage(undefined)).toBe(null) + expect(getValidLanguage('')).toBe(null) + expect(getValidLanguage('A')).toBe(null) + expect(getValidLanguage('Something')).toBe(null) + }) +}) diff --git a/libs/remix-solidity/tsconfig.json b/libs/remix-solidity/tsconfig.json index 5ed408bd51..ec3a5d0c25 100644 --- a/libs/remix-solidity/tsconfig.json +++ b/libs/remix-solidity/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "types": ["node"] + "types": ["jest", "node"] }, "include": ["**/*.ts"] } \ No newline at end of file diff --git a/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx b/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx index d2668d57d3..7e25e32592 100644 --- a/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx +++ b/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx @@ -8,15 +8,26 @@ import './remix-ui-editor-context-view.css' export type astNode = { name: string, id: number, - children: Array, + children?: Array, typeDescriptions: any, nodeType: String, - src: any, - nodeId: any, - position: any + src: string // e.g "142:1361:0" +} + +export type nodePositionLight = { + file: number, + length: number, + start: number +} + +export type astNodeLight = { + fileTarget: String, + nodeId: number, + position: nodePositionLight } export type onContextListenerChangedListener = (nodes: Array) => void +export type ononCurrentFileChangedListener = (name: string) => void export type gasEstimationType = { executionCost: string, @@ -30,8 +41,9 @@ export interface RemixUiEditorContextViewProps { offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any, getCurrentFileName: () => String onContextListenerChanged: (listener: onContextListenerChangedListener) => void + onCurrentFileChanged: (listener: ononCurrentFileChangedListener) => void referencesOf: (nodes: astNode) => Array - getActiveHighlights: () => Array + getActiveHighlights: () => Array gasEstimation: (node: astNode) => gasEstimationType declarationOf: (node: astNode) => astNode } @@ -48,49 +60,56 @@ function isDefinition (node: any) { type nullableAstNode = astNode | null export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) { - /* - gotoLineDisableRef is used to temporarily disable the update of the view. - e.g when the user ask the component to "gotoLine" we don't want to rerender the component (but just to put the mouse on the desired line) - */ - const gotoLineDisableRef = useRef(false) + const loopOverReferences = useRef(0) + const currentNodeDeclaration = useRef(null) const [state, setState] = useState<{ nodes: Array, - references: Array, activeHighlights: Array - currentNode: nullableAstNode, gasEstimation: gasEstimationType }>({ nodes: [], - references: [], activeHighlights: [], - currentNode: null, gasEstimation: { executionCost: '', codeDepositCost: '' } }) useEffect(() => { + props.onCurrentFileChanged(() => { + currentNodeDeclaration.current = null + setState(prevState => { + return { ...prevState, nodes: [], activeHighlights: [] } + }) + }) + props.onContextListenerChanged(async (nodes: Array) => { - if (gotoLineDisableRef.current) { - gotoLineDisableRef.current = false - return - } - let currentNode + let nextNodeDeclaration + let nextNode if (!props.hide && nodes && nodes.length) { - currentNode = nodes[nodes.length - 1] - if (!isDefinition(currentNode)) { - currentNode = await props.declarationOf(currentNode) + nextNode = nodes[nodes.length - 1] + if (!isDefinition(nextNode)) { + nextNodeDeclaration = await props.declarationOf(nextNode) + } else { + nextNodeDeclaration = nextNode } } - let references + if (nextNodeDeclaration && currentNodeDeclaration.current && nextNodeDeclaration.id === currentNodeDeclaration.current.id) return + + currentNodeDeclaration.current = nextNodeDeclaration + let gasEstimation - if (currentNode) { - references = await props.referencesOf(currentNode) - if (currentNode.nodeType === 'FunctionDefinition') { - gasEstimation = await props.gasEstimation(currentNode) + if (currentNodeDeclaration.current) { + if (currentNodeDeclaration.current.nodeType === 'FunctionDefinition') { + gasEstimation = await props.gasEstimation(currentNodeDeclaration.current) } } - const activeHighlights = await props.getActiveHighlights() + const activeHighlights: Array = await props.getActiveHighlights() + if (nextNode && activeHighlights && activeHighlights.length) { + loopOverReferences.current = activeHighlights.findIndex((el: astNodeLight) => `${el.position.start}:${el.position.length}:${el.position.file}` === nextNode.src) + loopOverReferences.current = loopOverReferences.current === -1 ? 0 : loopOverReferences.current + } else { + loopOverReferences.current = 0 + } setState(prevState => { - return { ...prevState, nodes, references, activeHighlights, currentNode, gasEstimation } + return { ...prevState, nodes, activeHighlights, gasEstimation } }) }) }, []) @@ -123,8 +142,7 @@ export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) if (fileName !== await props.getCurrentFileName()) { await props.openFile(fileName) } - if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { - gotoLineDisableRef.current = true + if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { props.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) } } @@ -141,14 +159,14 @@ export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) } } - const _render = (node: nullableAstNode) => { + const _render = () => { + const node = currentNodeDeclaration.current if (!node) return (
) - const references = state.references + const references = state.activeHighlights const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType const referencesCount = `${references ? references.length : '0'} reference(s)` - let ref = 0 - const nodes: Array = state.activeHighlights + const nodes: Array = state.activeHighlights const jumpTo = () => { if (node && node.src) { @@ -161,10 +179,10 @@ export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) // JUMP BETWEEN REFERENCES const jump = (e: any) => { - e.target.dataset.action === 'next' ? ref++ : ref-- - if (ref < 0) ref = nodes.length - 1 - if (ref >= nodes.length) ref = 0 - _jumpToInternal(nodes[ref].position) + e.target.dataset.action === 'next' ? loopOverReferences.current++ : loopOverReferences.current-- + if (loopOverReferences.current < 0) loopOverReferences.current = nodes.length - 1 + if (loopOverReferences.current >= nodes.length) loopOverReferences.current = 0 + _jumpToInternal(nodes[loopOverReferences.current].position) } return ( @@ -181,7 +199,7 @@ export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) return ( !props.hide &&
- {_render(state.currentNode)} + {_render()}
) } 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 67df7cd443..82a18ef075 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -398,11 +398,12 @@ export const EditorUI = (props: EditorUIProps) => { props.plugin.call('editor', 'gotoLine', line, column)} - openFile={(file) => props.plugin.call('editor', 'openFile', file)} + openFile={(file) => props.plugin.call('fileManager', 'switchFile', file)} getLastCompilationResult={() => { return props.plugin.call('compilerArtefacts', 'getLastCompilationResult') } } offsetToLineColumn={(position, file, sources, asts) => { return props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) } } getCurrentFileName={() => { return props.plugin.call('fileManager', 'file') } } onContextListenerChanged={(listener) => { props.plugin.on('contextualListener', 'contextChanged', listener) }} + onCurrentFileChanged={(listener) => { props.plugin.on('fileManager', 'currentFileChanged', listener) }} referencesOf={(node: astNode) => { return props.plugin.call('contextualListener', 'referencesOf', node) }} getActiveHighlights={() => { return props.plugin.call('contextualListener', 'getActiveHighlights') }} gasEstimation={(node: astNode) => { return props.plugin.call('contextualListener', 'gasEstimation', node) }} diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx index 8d831c5230..1e0b366b3f 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import './remix-ui-home-tab.css' +import JSZip from 'jszip' import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import PluginButton from './components/pluginButton' // eslint-disable-line @@ -173,9 +174,45 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { _paq.push(['trackEvent', 'pluginManager', 'userActivate', 'sourcify']) } const startPluginManager = async () => { - await plugin.appManager.activatePlugin('pluginManager') plugin.verticalIcons.select('pluginManager') } + const saveAs = (blob, name) => { + const node = document.createElement('a') + node.download = name + node.rel = 'noopener' + node.href = URL.createObjectURL(blob) + setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s + setTimeout(function () { + try { + node.dispatchEvent(new MouseEvent('click')) + } catch (e) { + var evt = document.createEvent('MouseEvents') + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, + 20, false, false, false, false, 0, null) + node.dispatchEvent(evt) + } + }, 0) // 40s + } + const downloadFiles = async () => { + try { + plugin.call('notification', 'toast', 'preparing files for download, please wait..') + const zip = new JSZip() + const browserProvider = fileManager.getProvider('browser') + await browserProvider.copyFolderToJson('/', ({ path, content }) => { + zip.file(path, content) + }) + zip.generateAsync({ type: 'blob' }).then(function (blob) { + var today = new Date() + var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate() + var time = today.getHours() + 'h' + today.getMinutes() + 'min' + saveAs(blob, `remix-backup-at-${time}-${date}.zip`) + }).catch((e) => { + plugin.call('notification', 'toast', e.message) + }) + } catch (e) { + plugin.call('notification', 'toast', e.message) + } + } const showFullMessage = (title: string, loadItem: string, examples: Array) => { setState(prevState => { @@ -229,18 +266,27 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
-
-
- +
+
+
+ +
+
+ playRemi() } alt=""> + +
-
- playRemi() } alt=""> - +
+ + + Scam Alert: Beware of Youtube videos promoting "liquidity front runner bots" asking to paste contract code into Remix IDE. + + Learn more
@@ -280,6 +326,10 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {

+

+ + +

diff --git a/libs/remix-ui/run-tab/src/lib/run-tab.tsx b/libs/remix-ui/run-tab/src/lib/run-tab.tsx index 3dc10d1e52..8cf8615b09 100644 --- a/libs/remix-ui/run-tab/src/lib/run-tab.tsx +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -205,7 +205,7 @@ export function RunTabUI (props: RunTabProps) { {from} is changing your environment to - {env} + {env && env.context}
) diff --git a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx index eabccbd8a8..398250a3dd 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx @@ -7,6 +7,7 @@ import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promi import { compilerReducer, compilerInitialState } from './reducers/compiler' import { resetEditorMode, listenToEvents } from './actions/compiler' import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line +import { getValidLanguage } from '@remix-project/remix-solidity' import './css/style.css' @@ -74,6 +75,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => { const optimize = params.optimize const runs = params.runs as string const evmVersion = params.evmVersion + const language = getValidLanguage(params.language) return { ...prevState, @@ -82,7 +84,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => { includeNightlies: includeNightlies, optimize: optimize, runs: runs, - evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default' + evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default', + language: (language !== null) ? language : 'Solidity' } }) } @@ -537,8 +540,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts index c938cdb633..bb301dc402 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts +++ b/libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts @@ -1,4 +1,5 @@ import { ICompilerApi } from '@remix-project/remix-lib-ts' +import { getValidLanguage } from '@remix-project/remix-solidity' const Compiler = require('@remix-project/remix-solidity').Compiler const EventEmitter = require('events') @@ -15,6 +16,7 @@ export class CompileTabLogic { public optimize public runs public evmVersion: string + public language: string public compilerImport public event @@ -39,6 +41,11 @@ export class CompileTabLogic { } this.api.setCompilerParameters({ evmVersion: this.evmVersion }) this.compiler.set('evmVersion', this.evmVersion) + + this.language = getValidLanguage(this.api.getCompilerParameters().language) + if (this.language != null) { + this.compiler.set('language', this.language) + } } setOptimize (newOptimizeValue) { @@ -68,6 +75,8 @@ export class CompileTabLogic { * @params lang {'Solidity' | 'Yul'} ... */ setLanguage (lang) { + this.language = lang + this.api.setCompilerParameters({ language: lang }) this.compiler.set('language', lang) } 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 3c791d1c1b..45632fc4de 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -36,7 +36,7 @@ export const TabsUI = (props: TabsUIProps) => { const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '') return ( -
{ tabsRef.current[index] = el }} className={classNameTab} title={tab.tooltip}> +
{ tabsRef.current[index] = el }} className={classNameTab} data-id={index === currentIndexRef.current ? 'tab-active' : ''} title={tab.tooltip}> {tab.icon ? () : ()} {tab.title} { props.onClose(index); event.stopPropagation() }}> @@ -74,7 +74,7 @@ export const TabsUI = (props: TabsUIProps) => { }, []) return ( -
+
props.onZoomOut()}> diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx b/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx index efeceed420..94a56bed15 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx +++ b/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx @@ -7,7 +7,7 @@ interface HomeProps { function Home ({ verticalIconPlugin }: HomeProps) { return (
await verticalIconPlugin.activateHome()} {...{ plugin: 'home'}} title="Home" diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx b/libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx index cb5cc56931..a0aa750e73 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx +++ b/libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx @@ -85,7 +85,7 @@ const Icon = ({ return ( <>
{ (verticalIconPlugin as any).toggle(name) }} diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css index c14e3bb47c..5fd6e085ee 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css +++ b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.css @@ -29,6 +29,7 @@ width: 36px; height: 36px; border-radius: 8px; + align-items: center; } .remixui_icon img { width: 28px; @@ -39,15 +40,12 @@ .remixui_icon .selected-dark { filter: invert(1) grayscale(1); - } .remixui_icon .selected-light { filter: invert(0) grayscale(1); } - .remixui_image { - } .remixui_icon svg { width: 28px; height: 28px; @@ -106,9 +104,13 @@ scrollbar-width: none; /* Firefox hide scrollbar */ -ms-overflow-style: none; } + .remixui_requiredSection { + text-align: center; + } .remixui_scrollable-container { flex-basis: 510px; flex-grow: 2; + text-align: center; /* border-bottom: 3px solid #3f4455; */ } .remixui_scrollbar::-webkit-scrollbar { /* Chrome, Safari and other Webkit browsers*/ @@ -119,9 +121,12 @@ } .remixui_default-icons-container { border-bottom: 2px solid #3f4455; + text-align: center; } .remixui_icon-chevron { z-index: 1000; + cursor: pointer; + align-items: center; } .remixui_settings { @@ -132,7 +137,3 @@ list-style: none; margin: 0px; } - - .remixui_icon-chevron { - cursor: pointer; - } \ No newline at end of file diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx index 6d606d3149..3d4413eb4d 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx +++ b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx @@ -90,7 +90,7 @@ const RemixUiVerticalIconsPanel = ({ ) : null } @@ -109,19 +109,19 @@ const RemixUiVerticalIconsPanel = ({ itemContextAction={itemContextAction} />
-
- { scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? () : null } - p.profile.name === 'settings' || p.profile.name === 'pluginManager')} - verticalIconsPlugin={verticalIconsPlugin} - itemContextAction={itemContextAction} - /> -
+
+ { scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? () : null } + p.profile.name === 'settings' || p.profile.name === 'pluginManager')} + verticalIconsPlugin={verticalIconsPlugin} + itemContextAction={itemContextAction} + /> +
) diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index 390a1ff44d..e9bc5d1a62 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -179,7 +179,7 @@ export const createNewFolder = async (path: string, rootDir: string) => { const exists = await fileManager.exists(dirName) if (exists) { - return dispatch(displayNotification('Rename File Failed', `A file or folder ${extractNameFromKey(path)} already exists at this location. Please choose a different name.`, 'Close', null, () => {})) + return dispatch(displayNotification('Failed to create folder', `A folder ${extractNameFromKey(path)} already exists at this location. Please choose a different name.`, 'Close', null, () => {})) } await fileManager.mkdir(dirName) path = path.indexOf(rootDir + '/') === 0 ? path.replace(rootDir + '/', '') : path diff --git a/workspace.json b/workspace.json index 5ff3f1ca9f..8e78b40751 100644 --- a/workspace.json +++ b/workspace.json @@ -309,10 +309,10 @@ } }, "test": { - "builder": "@nrwl/workspace:run-commands", + "builder": "@nrwl/jest:jest", "options": { - "commands": ["./../../node_modules/.bin/npm-run-all test"], - "cwd": "libs/remix-solidity" + "jestConfig": "libs/remix-solidity/jest.config.js", + "tsConfig": "libs/remix-solidity/tsconfig.spec.json" } }, "build": {