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/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/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-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
+ + +