Merge branch 'master' into theme-hacker-owl

pull/1527/head
Rob 3 years ago committed by GitHub
commit 24572ba75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      apps/debugger/src/app/debugger-api.ts
  2. 4
      apps/debugger/src/app/debugger.ts
  3. 2
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  4. 4
      apps/remix-ide-e2e/src/commands/goToVMTraceStep.ts
  5. 173
      apps/remix-ide-e2e/src/examples/editor-test-contracts.ts
  6. 1
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  7. 257
      apps/remix-ide-e2e/src/tests/editor.test.ts
  8. 517
      apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts
  9. 236
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  10. 196
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  11. 2
      apps/remix-ide-e2e/src/tests/gist.test.ts
  12. 1
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  13. 13
      apps/remix-ide/src/app.js
  14. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  15. 13
      apps/remix-ide/src/app/editor/editor.js
  16. 29
      apps/remix-ide/src/app/files/fileManager.ts
  17. 24
      apps/remix-ide/src/app/panels/layout.ts
  18. 643
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  19. 158
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  20. 235
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  21. 76
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  22. 732
      apps/remix-ide/src/app/plugins/parser/types/antlr-types.ts
  23. 1
      apps/remix-ide/src/app/plugins/parser/types/index.ts
  24. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css
  25. 251
      apps/remix-ide/src/assets/js/parser/Solidity-EZVQ6AE4.tokens
  26. 41374
      apps/remix-ide/src/assets/js/parser/antlr.js
  27. 7
      apps/remix-ide/src/assets/js/parser/antlr.js.map
  28. 1
      apps/remix-ide/src/index.html
  29. 1
      apps/solidity-compiler/src/app/compiler-api.ts
  30. 4
      jest.preset.js
  31. 8
      libs/remix-analyzer/src/types.ts
  32. 1
      libs/remix-core-plugin/src/index.ts
  33. 244
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  34. 8
      libs/remix-core-plugin/tsconfig.lib.json
  35. 11
      libs/remix-debug/src/debugger/stepManager.ts
  36. 1
      libs/remix-solidity/src/compiler/compiler-worker.ts
  37. 57
      libs/remix-solidity/src/compiler/compiler.ts
  38. 8
      libs/remix-solidity/src/compiler/types.ts
  39. 21
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  40. 16
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  41. 3
      libs/remix-ui/app/src/lib/remix-app/style/remix-app.css
  42. 6
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  43. 4
      libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts
  44. 5
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  45. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/calldata-panel.tsx
  46. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/callstack-panel.tsx
  47. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/code-list-view.tsx
  48. 10
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
  49. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/full-storages-changes.tsx
  50. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/function-panel.tsx
  51. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/global-variables.tsx
  52. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx
  53. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-locals.tsx
  54. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx
  55. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/stack-panel.tsx
  56. 6
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/step-detail.tsx
  57. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/storage-panel.tsx
  58. 16
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
  59. 22
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx
  60. 1
      libs/remix-ui/debugger-ui/src/types/index.ts
  61. 0
      libs/remix-ui/drag-n-drop/.babelrc
  62. 2
      libs/remix-ui/drag-n-drop/.eslintrc.json
  63. 7
      libs/remix-ui/drag-n-drop/README.md
  64. 9
      libs/remix-ui/drag-n-drop/jest.config.js
  65. 2
      libs/remix-ui/drag-n-drop/src/index.ts
  66. 112
      libs/remix-ui/drag-n-drop/src/lib/remix-ui-drag-n-drop.tsx
  67. 23
      libs/remix-ui/drag-n-drop/tsconfig.json
  68. 0
      libs/remix-ui/drag-n-drop/tsconfig.lib.json
  69. 15
      libs/remix-ui/drag-n-drop/tsconfig.spec.json
  70. 7
      libs/remix-ui/editor-context-view/README.md
  71. 1
      libs/remix-ui/editor-context-view/src/index.ts
  72. 43
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.css
  73. 207
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx
  74. 16
      libs/remix-ui/editor-context-view/tsconfig.json
  75. 13
      libs/remix-ui/editor/src/lib/actions/editor.ts
  76. 662
      libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts
  77. 457
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  78. 49
      libs/remix-ui/editor/src/lib/providers/definitionProvider.ts
  79. 38
      libs/remix-ui/editor/src/lib/providers/highlightProvider.ts
  80. 166
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  81. 47
      libs/remix-ui/editor/src/lib/providers/referenceProvider.ts
  82. 102
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  83. 1
      libs/remix-ui/editor/src/lib/web-types.ts
  84. 2
      libs/remix-ui/editor/src/types/monaco.ts
  85. 2
      libs/remix-ui/editor/tsconfig.lib.json
  86. 2
      libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx
  87. 2
      libs/remix-ui/search/src/lib/context/context.tsx
  88. 3
      libs/remix-ui/settings/src/lib/constants.ts
  89. 77
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  90. 15
      libs/remix-ui/settings/src/lib/settingsAction.ts
  91. 47
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  92. 1
      libs/remix-ui/tree-view/src/types/index.ts
  93. 10
      libs/remix-ui/workspace/src/lib/actions/index.ts
  94. 12
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  95. 17
      libs/remix-ui/workspace/src/lib/components/file-render.tsx
  96. 17
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  97. 10
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  98. 2
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  99. 1
      libs/remix-ui/workspace/src/lib/types/index.ts
  100. 7
      nx.json
  101. Some files were not shown because too many files have changed in this diff Show More

@ -8,8 +8,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
initialWeb3
initDebuggerApi () {
this.debugHash = null
const self = this
this.web3Provider = {
sendAsync (payload, callback) {
@ -132,7 +130,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
} catch (e) {
console.error(e)
}
this.debugHash = hash
if (web3) this._web3 = web3
else this._web3 = this.initialWeb3
remixDebug.init.extendWeb3(this._web3)
@ -154,6 +151,14 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
showMessage (title: string, message: string) {}
onStartDebugging () {
this.call('layout', 'maximiseSidePanel')
}
onStopDebugging () {
this.call('layout', 'resetSidePanel')
}
}
export class CompilerAbstract implements CompilationOutput { // this is a subset of /remix-ide/src/app/compiler/compiler-abstract.js

@ -12,8 +12,6 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
}
offsetToLineColumnConverter: IDebuggerApi['offsetToLineColumnConverter']
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onBreakpointCleared: (listener: onBreakpointClearedListener) => void
onBreakpointAdded: (listener: onBreakpointAddedListener) => void
@ -26,5 +24,7 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
setFile: (path: string, content: string) => Promise<void>
getDebugWeb3: () => any // returns an instance of web3.js, if applicable (mainet, goerli, ...) it returns a reference to a node from devops (so we are sure debug endpoint is available)
web3: () => any // returns an instance of web3.js
onStartDebugging: () => void // called when debug starts
onStopDebugging: () => void // called when debug stops
}

@ -21,7 +21,7 @@ function checkFilter (browser: NightwatchBrowser, filter: string, test: string,
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {

@ -10,7 +10,9 @@ class GoToVmTraceStep extends EventEmitter {
}
function goToVMtraceStep (browser: NightwatchBrowser, step: number, incr: number, done: VoidFunction) {
browser.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
browser.waitForElementVisible('*[data-id="slider"]')
.waitForElementVisible('#stepdetail')
.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.execute((step) => {
(document.querySelector('*[data-id="slider"]') as any).internal_onmouseup({ target: { value: step }})

@ -0,0 +1,173 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testContract = {
name: 'contracts/test.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/base.sol";
import "contracts/import1.sol";
contract test is base {
string public publicstring;
string private privatestring;
string internal internalstring;
struct TestBookDefinition {
string title;
string author;
uint book_id;
}
TestBookDefinition public mybook;
enum MyEnum{ SMALL, MEDIUM, LARGE }
event MyEvent(uint abc);
importcontract importedcontract;
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
constructor(){
}
function testing() public view {
}
function myprivatefunction() private {
}
function myinternalfunction() internal {
}
function myexternalfunction() external {
}
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseContract = {
name: 'contracts/base.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/baseofbase.sol";
contract base is baseofbase {
event BaseEvent(address indexed _from, uint _value);
enum BaseEnum{ SMALL, MEDIUM, LARGE }
struct Book {
string title;
string author;
uint book_id;
}
Book public book;
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseOfBaseContract = {
name: 'contracts/baseofbase.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract baseofbase {
struct BaseBook {
string title;
string author;
uint book_id;
}
BaseBook public basebook;
string private basestring;
string internal internalbasestring;
function privatebase() private {
}
function internalbasefunction() internal {
}
function publicbasefunction() public {
}
function externalbasefunction() external {
}
}`}
const import1Contract = {
name: 'contracts/import1.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "contracts/importbase.sol";
import "contracts/secondimport.sol";
contract importcontract is importbase {
struct ImportedBook {
string title;
string author;
uint book_id;
}
ImportedBook public importedbook;
string private importprivatestring;
string internal internalimportstring;
string public importpublicstring;
function privateimport() private {
}
function internalimport() internal {
}
function publicimport() public {
}
function externalimport() external {
}
}`}
const importbase = {
name: 'contracts/importbase.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract importbase {
string public importbasestring;
}
`}
const secondimport = {
name: 'contracts/secondimport.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract secondimport {
string public secondimportstring;
}
`}
export default {
testContract,
baseContract,
baseOfBaseContract,
import1Contract,
importbase,
secondimport
}

@ -241,6 +241,7 @@ module.exports = {
.clickInstance(0)
.clickFunction('callA - transact (not payable)')
.debugTransaction(1)
.pause(4000)
.goToVMTraceStep(79)
.waitForElementVisible('*[data-id="debugGoToRevert"]', 60000)
.click('*[data-id="debugGoToRevert"]')

@ -128,119 +128,6 @@ module.exports = {
.waitForElementNotPresent('.highlightLine51', 60000)
},
'Should display the context view #group2': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError)
.pause(2000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(17, 16)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'FunctionDefinition')
.waitForElementContainsText('.contextview .name', 'store')
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(18, 12)
}, [], () => {})
.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) => {
console.log('result', result)
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) => {
console.log('result', result)
browser.assert.equal(result.value, '323')
})
.click('.contextview [data-action="next"]') // next reference
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
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(6000)
.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, '153')
})
.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, '153')
})
.end()
}
}
const aceThemes = {
@ -347,148 +234,4 @@ contract Storage {
}
}`
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;
}
}
`

@ -0,0 +1,517 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/editor-test-contracts'
const autoCompleteLineElement = (name: string) => {
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]`
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should add test and base files #group1': function (browser: NightwatchBrowser) {
browser.addFile(examples.testContract.name, examples.testContract)
.addFile(examples.baseContract.name, examples.baseContract)
.addFile(examples.import1Contract.name, examples.import1Contract)
.addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract)
.addFile(examples.secondimport.name, examples.secondimport)
.addFile(examples.importbase.name, examples.importbase)
.openFile(examples.testContract.name)
},
'Should put cursor in the () of the function #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(36)
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]"
browser.waitForElementVisible('#editorView')
.useXpath()
.click(path).pause(1000)
},
'Should complete variable declaration types in a function definition #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uint25')
})
.waitForElementPresent(autoCompleteLineElement('uint256'))
.click(autoCompleteLineElement('uint256'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' abc')
.sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow
.sendKeys(', testb')
})
.waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"TestBookDefinition"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' memo')
})
.waitForElementPresent(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' btextbook')
.sendKeys(this.Keys.ENTER)
.sendKeys(', BaseB')
})
.waitForElementPresent(autoCompleteLineElement('"BaseBook"'))
.click(autoCompleteLineElement('"BaseBook"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' stor')
})
.waitForElementPresent(autoCompleteLineElement('storage'))
.click(autoCompleteLineElement('storage'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' localbbook')
}).pause(3000)
},
'Should put cursor at the end of function #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]"
browser
.useXpath()
.click(path).pause(1000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
// right arrow key
sendKeys(this.Keys.ARROW_RIGHT).
sendKeys(this.Keys.ARROW_RIGHT)
})
},
'Should autcomplete address types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('addre')
})
.waitForElementPresent(autoCompleteLineElement('address'))
.click(autoCompleteLineElement('address'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' someaddress;')
.sendKeys(this.Keys.ENTER)
}).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('someaddress.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.waitForElementVisible(autoCompleteLineElement('code'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autcomplete array types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uin')
})
.waitForElementPresent(autoCompleteLineElement('uint'))
.click(autoCompleteLineElement('uint'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('[] mem')
})
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' somearray;')
}
).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
.sendKeys('somearray.')
})
.waitForElementVisible(autoCompleteLineElement('push'))
.waitForElementVisible(autoCompleteLineElement('pop'))
.waitForElementVisible(autoCompleteLineElement('length'))
.click(autoCompleteLineElement('length'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete second import because it was imported by the first import #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('secondi')
})
.waitForElementPresent(autoCompleteLineElement('secondimport'))
.click(autoCompleteLineElement('secondimport'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' sec;')
.sendKeys(this.Keys.ENTER)
}).pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('sec.')
})
.waitForElementVisible(autoCompleteLineElement('secondimportstring'))
.click(autoCompleteLineElement('secondimportstring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete imported local class #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('import')
})
.waitForElementPresent(autoCompleteLineElement('importedcontract'))
.click(autoCompleteLineElement('importedcontract'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('externalimport'))
.waitForElementVisible(autoCompleteLineElement('importbasestring'))
.waitForElementVisible(autoCompleteLineElement('importedbook'))
.waitForElementVisible(autoCompleteLineElement('importpublicstring'))
.waitForElementVisible(autoCompleteLineElement('publicimport'))
// no private
.waitForElementNotPresent(autoCompleteLineElement('importprivatestring'))
.waitForElementNotPresent(autoCompleteLineElement('privateimport'))
// no internal
.waitForElementNotPresent(autoCompleteLineElement('importinternalstring'))
.waitForElementNotPresent(autoCompleteLineElement('internalimport'))
.click(autoCompleteLineElement('importbasestring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete derived and local event when not using this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit base')
})
.waitForElementVisible(autoCompleteLineElement('BaseEvent'))
.click(autoCompleteLineElement('BaseEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('msg.sender')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit MyEv')
})
.waitForElementVisible(autoCompleteLineElement('MyEvent'))
.click(autoCompleteLineElement('MyEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
},
'Should type and get msg options #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('msg.')
})
.waitForElementVisible(autoCompleteLineElement('sender'))
.waitForElementVisible(autoCompleteLineElement('data'))
.waitForElementVisible(autoCompleteLineElement('value'))
.waitForElementVisible(autoCompleteLineElement('gas'))
.waitForElementVisible(autoCompleteLineElement('sig'))
.click(autoCompleteLineElement('sender'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('code'))
.waitForElementVisible(autoCompleteLineElement('codehash'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should bo and get book #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('bo')
})
.waitForElementVisible(autoCompleteLineElement('book'))
.click(autoCompleteLineElement('book'))
},
'Should autcomplete derived struct #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should bo and get basebook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('base')
})
.waitForElementVisible(autoCompleteLineElement('basebook'))
.click(autoCompleteLineElement('basebook'))
},
'Should autcomplete derived struct from base class #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped localbbook #group1': function (browser: NightwatchBrowser) {
browser.pause(4000).
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('localb')
})
.waitForElementVisible(autoCompleteLineElement('localbbook'))
.click(autoCompleteLineElement('localbbook'))
},
'Should autcomplete derived struct from block localbbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped btextbook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('btext')
})
.waitForElementVisible(autoCompleteLineElement('btextbook'))
.click(autoCompleteLineElement('btextbook'))
},
'Should autcomplete derived struct from block btextbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should find private and internal local functions #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('my')
})
.waitForElementVisible(autoCompleteLineElement('myprivatefunction'))
.waitForElementVisible(autoCompleteLineElement('myinternalfunction'))
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('myinternalfunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should find internal functions and var from base and owner #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('intern')
})
.waitForElementVisible(autoCompleteLineElement('internalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('internalstring'))
.waitForElementVisible(autoCompleteLineElement('internalbasestring'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('internal keyword'))
.click(autoCompleteLineElement('internalbasefunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should not find external functions without this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('extern')
})
.waitForElementNotPresent(autoCompleteLineElement('externalbasefunction'))
.waitForElementNotPresent(autoCompleteLineElement('myexternalfunction'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('external keyword'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
})
},
'Should find external functions using this. #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('this.')
})
.waitForElementVisible(autoCompleteLineElement('externalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('myexternalfunction'))
},
'Should find public functions and vars using this. but not private & other types of nodes #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible(autoCompleteLineElement('"publicbasefunction"'))
.waitForElementVisible(autoCompleteLineElement('"publicstring"'))
.waitForElementVisible(autoCompleteLineElement('"basebook"'))
.waitForElementVisible(autoCompleteLineElement('"mybook"'))
.waitForElementVisible(autoCompleteLineElement('"testing"'))
// but no private functions or vars or other types of nodes
.waitForElementNotPresent(autoCompleteLineElement('"private"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"'))
.waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"publicbasefunction"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete local and derived ENUMS #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('BaseEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
.sendKeys('MyEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
}
}

@ -0,0 +1,236 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => {
browser.useXpath()
.useXpath()
.moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element
.waitForElementVisible(path)
.moveToElement(path, offsetLeft, 0)
.useCss()
.waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000)
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(4000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[contains(text(),'BallotHoverTest')]"
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest')
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0')
checkEditorHoverContent(browser, path, '@title Ballot')
},
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]"
const expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) {
const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]"
const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(58)
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]"
let expectedContent = 'Estimated execution cost'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'"
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(37)
let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]"
let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters'
checkEditorHoverContent(browser, path, expectedContent, 15)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]"
expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent, 3)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]"
expectedContent = 'uint256 internal weight'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) {
let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]"
let expectedContent = 'contract Owner internal cowner'
checkEditorHoverContent(browser, path, expectedContent, 10)
path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]"
expectedContent = 'contract Owner is Owner'
checkEditorHoverContent(browser, path, expectedContent, 10)
},
'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
let expectedContent = 'function getOwner () external view returns (address internal )'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = 'contracts/2_Owner.sol'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = '@dev Return owner address'
checkEditorHoverContent(browser, path, expectedContent, 0)
},
'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(5)
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]"
const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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 BallotHoverTest {
Owner cowner;
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) {
cowner = new Owner();
cowner.getOwner();
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;
}
}
`

@ -0,0 +1,196 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const openReferences = (browser: NightwatchBrowser, path: string) => {
(browser as any).useXpath()
.useXpath()
.waitForElementVisible(path)
.click(path)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
keyDown(this.Keys.SHIFT).
sendKeys(this.Keys.F12)
})
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(10000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show local references': function (browser: NightwatchBrowser) {
browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path)
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE)
},
'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path)
browser.useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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 BallotHoverTest {
Owner cowner;
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) {
cowner = new Owner();
cowner.getOwner();
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;
}
}
`

@ -128,7 +128,7 @@ module.exports = {
'Import From Gist For Valid Gist ID #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.clickLaunchIcon('settings')
.click('*[data-id="settingsTabGenerateContractMetadataLabel"]')
.setValue('[data-id="settingsTabGistAccessToken"]', process.env.gist_token)

@ -287,7 +287,6 @@ module.exports = {
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementVisible('*[data-id="dropdownPanelSolidityLocals"]').pause(1000)
.click('*[data-id="dropdownPanelSolidityLocals"]')
.waitForElementContainsText('*[data-id="solidityLocals"]', 'no locals', 60000)
.goToVMTraceStep(316)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)

@ -14,10 +14,11 @@ import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener, GistHandler } from '@remix-project/core-plugin'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config'
@ -212,7 +213,9 @@ class AppComponent {
}
}
)
const contextualListener = new EditorContextListener(new AstWalker())
const codeParser = new CodeParser(new AstWalker())
this.notification = new NotificationPlugin()
@ -236,14 +239,14 @@ class AppComponent {
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
contextualListener,
codeParser,
fileDecorator,
terminal,
web3Provider,
compileAndRun,
fetchAndCompile,
dGitProvider,
storagePlugin,
fileDecorator,
hardhatProvider,
ganacheProvider,
foundryProvider,
@ -368,7 +371,7 @@ class AppComponent {
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder'])

@ -12,7 +12,7 @@ const sidePanel = {
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView']
methods: ['addView', 'removeView', 'currentFocus']
}
export class SidePanel extends AbstractPanel {

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'addErrorMarker', 'clearErrorMarkers']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers'],
}
class Editor extends Plugin {
@ -211,6 +211,7 @@ class Editor extends Plugin {
}
async _onChange (file) {
this.triggerEvent('didChangeFile', [file])
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
@ -292,6 +293,10 @@ class Editor extends Plugin {
return this.api.findMatches(this.currentFile, string)
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, false)
}
/**
* Display an Empty read-only session
*/
@ -506,11 +511,13 @@ class Editor extends Plugin {
// error markers
async addErrorMarker (error){
this.api.addErrorMarker(error)
const { from } = this.currentRequest
this.api.addErrorMarker(error, from)
}
async clearErrorMarkers(sources){
this.api.clearErrorMarkers(sources)
const { from } = this.currentRequest
this.api.clearErrorMarkers(sources, from)
}
/**

@ -817,6 +817,35 @@ class FileManager extends Plugin {
return exists
}
/**
* Moves a file to a new folder
* @param {string} src path of the source file
* @param {string} dest path of the destrination file
* @returns {void}
*/
async moveFile(src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot copy from ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`)
await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`)
const content = await this.readFile(src)
let movedFilePath = dest + ( '/' + `${helper.extractNameFromKey(src)}`)
movedFilePath = await helper.createNonClashingNameAsync(movedFilePath, this)
await this.writeFile(movedFilePath, content)
await this.remove(src)
} catch (e) {
throw new Error(e)
}
}
}
module.exports = FileManager

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize']
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
}
interface panelState {
@ -30,8 +30,10 @@ export type PanelConfiguration = {
export class Layout extends Plugin {
event: any
panels: panels
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.event = new EventEmitter()
}
@ -64,6 +66,14 @@ export class Layout extends Plugin {
break
}
})
this.on('sidePanel', 'focusChanged', async (name) => {
const current = await this.call('sidePanel', 'currentFocus')
if (this.maximised[current]) {
this.event.emit('maximisesidepanel')
} else {
this.event.emit('resetsidepanel')
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
@ -92,4 +102,16 @@ export class Layout extends Plugin {
this.panels[name].minimized = minimized
this.event.emit('change', null)
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
}
}

@ -0,0 +1,643 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
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 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'
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api'
import { antlr } from './types'
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'],
events: [],
version: '0.0.1'
}
export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
export type genericASTNode =
ContractDefinitionAstNode
| FunctionDefinitionAstNode
| ModifierDefinitionAstNode
| VariableDeclarationAstNode
| StructDefinitionAstNode
| EventDefinitionAstNode
| IdentifierAstNode
| FunctionCallAstNode
| ImportDirectiveAstNode
| SourceUnitAstNode
interface flatReferenceIndexNode {
[id: number]: genericASTNode
}
interface declarationIndexNode {
[id: number]: genericASTNode[]
}
interface codeParserIndex {
declarations: declarationIndexNode,
flatReferences: flatReferenceIndexNode,
nodesPerFile: any
}
export class CodeParser extends Plugin {
antlrParserResult: antlr.ParseResult // contains the simple parsed AST for the current file
compilerAbstract: CompilerAbstract
currentFile: string
nodeIndex: codeParserIndex
astWalker: any
errorState: boolean = false
gastEstimateTimeOut: any
gasService: CodeParserGasService
compilerService: CodeParserCompiler
antlrService: CodeParserAntlrService
parseSolidity: (text: string) => Promise<antlr.ParseResult>
getLastNodeInLine: (ast: string) => Promise<any>
listAstNodes: () => Promise<any>
getANTLRBlockAtPosition: (position: any, text?: string) => Promise<any>
getCurrentFileAST: (text?: string) => Promise<ParseResult>
constructor(astWalker: any) {
super(profile)
this.astWalker = astWalker
this.nodeIndex = {
declarations: [[]],
flatReferences: [],
nodesPerFile: {}
}
}
async onActivation() {
this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(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.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService)
this.on('editor', 'didChangeFile', async (file) => {
await this.call('editor', 'discardLineTexts')
await this.antlrService.getCurrentFileAST()
await this.compilerService.compile()
})
this.on('filePanel', 'setWorkspace', async () => {
await this.call('fileDecorator', 'clearFileDecorators')
})
this.on('fileManager', 'currentFileChanged', async () => {
await this.call('editor', 'discardLineTexts')
await this.antlrService.getCurrentFileAST()
await this.compilerService.compile()
})
this.on('solidity', 'loadingCompiler', async (url) => {
this.compilerService.compiler.loadVersion(true, url)
})
await this.compilerService.init()
}
/**
*
* @returns
*/
async getLastCompilationResult() {
return this.compilerAbstract
}
getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType == "ContractDefinition" && node.contractDependencies;
}
/**
* Builds a flat index and declarations of all the nodes in the compilation result
* @param compilationResult
* @param source
*/
_buildIndex(compilationResult: CompilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) {
if (!this.nodeIndex.declarations[node.referencedDeclaration]) {
this.nodeIndex.declarations[node.referencedDeclaration] = []
}
this.nodeIndex.declarations[node.referencedDeclaration].push(node)
}
this.nodeIndex.flatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
// NODE HELPERS
_getInputParams(node: FunctionDefinitionAstNode) {
const params = []
const target = node.parameters
if (target) {
const children = target.parameters
for (const j in children) {
if (children[j].nodeType === 'VariableDeclaration') {
params.push(children[j].typeDescriptions.typeString)
}
}
}
return '(' + params.toString() + ')'
}
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {}
const contractName: string = contractNode.name
const callback = (node) => {
if(inScope && node.scope !== contractNode.id
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
return
if(inScope) node.isClassNode = true;
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName
node.contractId = contractNode.id
index[node.id] = node
}
this.astWalker.walkFull(contractNode, callback)
return index
}
_extractFileNodes(fileName: string, compilationResult: lastCompilationResult) {
if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) {
const source = compilationResult.data.sources[fileName]
const nodesByContract: any = {}
nodesByContract.imports = {}
nodesByContract.contracts = {}
this.astWalker.walkFull(source.ast, async (node) => {
if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult)
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult)
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes }
const baseNodes = {}
const baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) {
if (id !== node.id) {
const baseContract = await this.getNodeById(id)
const callback = (node) => {
node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id
node.isBaseNode = true;
baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id)
|| node.nodeType === 'EnumDefinition'
|| node.nodeType === 'EventDefinition'
) {
baseNodesWithBaseContractScope[node.id] = node
}
if(node.members){
for(const member of node.members){
member.contractName = (baseContract as any).name
member.contractId = (baseContract as any).id
member.isBaseNode = true;
}
}
}
this.astWalker.walkFull(baseContract, callback)
}
}
}
nodesByContract.contracts[node.name].baseNodes = baseNodes
nodesByContract.contracts[node.name].baseNodesWithBaseContractScope = baseNodesWithBaseContractScope
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult)
}
if (node.nodeType === 'ImportDirective') {
const imported = await this.resolveImports(node, {})
for (const importedNode of (Object.values(imported) as any)) {
if (importedNode.nodes)
for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode
}
}
}
})
return nodesByContract
}
}
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) {
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile]
for (const name in contracts) {
if (name === contractName) {
const contract = contracts[name]
const estimationObj = contract.evm && contract.evm.gasEstimates
let executionCost = null
if (node.nodeType === 'FunctionDefinition') {
const visibility = node.visibility
if (node.kind !== 'constructor') {
const fnName = node.name
const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') {
executionCost = estimationObj === null ? '-' : estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn]
}
return { executionCost }
} else {
return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost,
}
}
}
}
}
}
/**
* Nodes at position where position is a number, offset
* @param position
* @param type
* @returns
*/
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> {
const lastCompilationResult = this.compilerAbstract
if (!lastCompilationResult) return []
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
return nodes
}
return []
}
/**
*
* @param id
* @returns
*/
async getNodeById(id: number) {
for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === id) {
return this.nodeIndex.flatReferences[key]
}
}
}
/**
*
* @param id
* @returns
*/
async getDeclaration(id: number) {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id]
}
/**
*
* @param scope
* @returns
*/
async getNodesWithScope(scope: number) {
const nodes = []
for (const node of Object.values(this.nodeIndex.flatReferences)) {
if (node.scope === scope) nodes.push(node)
}
return nodes
}
/**
*
* @param name
* @returns
*/
async getNodesWithName(name: string) {
const nodes: genericASTNode[] = []
for (const node of Object.values(this.nodeIndex.flatReferences)) {
if (node.name === name) nodes.push(node)
}
return nodes
}
/**
*
* @param node
* @returns
*/
declarationOf<T extends genericASTNode>(node: T) {
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) {
return this.nodeIndex.flatReferences[node.referencedDeclaration]
}
return null
}
/**
*
* @param position
* @returns
*/
async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any
let node: genericASTNode
if (nodes && nodes.length && !this.errorState) {
node = nodes[nodes.length - 1]
nodeDefinition = node
if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node
}
if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) {
nodeDefinition = this.nodeIndex.flatReferences[key]
}
}
}
return nodeDefinition
} else {
const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) {
if (node.range[0] <= position && node.range[1] >= position) {
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) {
nodeDefinition = node
}
if (!nodeDefinition) nodeDefinition = node
}
}
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') {
const nodeForIdentifier = await this.findIdentifier(nodeDefinition)
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier
}
return nodeDefinition
}
}
async getContractNodes(contractName: string) {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
}
return false
}
async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]) {
return this.nodeIndex.nodesPerFile[this.currentFile]
}
return false
}
/**
*
* @param identifierNode
* @returns
*/
async findIdentifier(identifierNode: any) {
const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) {
if (node.name === identifierNode.name && node.nodeType !== 'Identifier') {
return node
}
}
}
/**
*
* @param node
* @returns
*/
async positionOfDefinition(node: genericASTNode): Promise<any | null> {
if (node) {
if (node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
return position
}
}
}
return null
}
/**
*
* @param node
* @param imported
* @returns
*/
async resolveImports(node: any, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
const importNode: any = await this.getNodeById(node.sourceUnit)
imported[importNode.id] = importNode
if (importNode.nodes) {
for (const child of importNode.nodes) {
imported = await this.resolveImports(child, imported)
}
}
}
return imported
}
/**
*
* @param node
* @returns
*/
referencesOf(node: genericASTNode) {
const results: genericASTNode[] = []
const highlights = (id: number) => {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) {
const refs = this.nodeIndex.declarations[id]
for (const ref in refs) {
const node = refs[ref]
results.push(node)
}
}
}
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) {
highlights(node.referencedDeclaration)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current)
} else {
highlights(node.id)
}
return results
}
/**
*
* @param position
* @returns
*/
async referrencesAtPosition(position: any): Promise<genericASTNode[]> {
const nodes = await this.nodesAtPosition(position)
if (nodes && nodes.length) {
const node = nodes[nodes.length - 1]
if (node) {
return this.referencesOf(node)
}
}
}
/**
*
* @returns
*/
async getNodes(): Promise<flatReferenceIndexNode> {
return this.nodeIndex.flatReferences
}
/**
*
* @param node
* @returns
*/
async getNodeLink(node: genericASTNode) {
const lineColumn = await this.getLineColumnOfNode(node)
const position = await this.positionOfDefinition(node)
if (this.compilerAbstract && this.compilerAbstract.source) {
const fileName = this.compilerAbstract.getSourceName(position.file)
return lineColumn ? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}` : null
}
return ''
}
/*
* @param node
*/
async getLineColumnOfNode(node: any) {
const position = await this.positionOfDefinition(node)
return this.getLineColumnOfPosition(position)
}
/*
* @param position
*/
async getLineColumnOfPosition(position: any) {
if (position) {
const fileName = this.compilerAbstract.getSourceName(position.file)
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.compilerAbstract.source.sources[fileName].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks)
return lineColumn
}
}
/**
*
* @param node
* @returns
*/
async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) {
let text = '';
(node.documentation as any).text.split('\n').forEach(line => {
text += `${line.trim()}\n`
})
return text
}
}
/**
*
* @param node
* @returns
*/
async getVariableDeclaration(node: any) {
const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : ''
const nodeName = node.name && node.name.length ? node.name : ''
if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString} ${nodeVisibility}${nodeName}`
} else {
if (node.typeName && node.typeName.name) {
return `${node.typeName.name} ${nodeVisibility}${nodeName}`
}
else if (node.typeName && node.typeName.namePath) {
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}`
}
else {
return `${nodeName}${nodeName}`
}
}
}
/**
*
* @param node
* @returns
*/
async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
/**
*
* @param node
* @returns
*/
async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
}

@ -0,0 +1,158 @@
'use strict'
import { AstNode } from "@remix-project/remix-solidity-ts"
import { CodeParser } from "../code-parser"
import { antlr } from '../types'
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
export default class CodeParserAntlrService {
plugin: CodeParser
constructor(plugin: CodeParser) {
this.plugin = plugin
}
/*
* simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports
*/
async parseSolidity(text: string) {
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
return ast
}
/**
* Tries to parse the current file or the given text and returns the AST
* If the parsing fails it returns the last successful AST for this file
* @param text
* @returns
*/
async getCurrentFileAST(text: string | null = null) {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
if (!this.plugin.currentFile) return
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
try {
const ast = await this.parseSolidity(fileContent)
this.plugin.antlrParserResult = ast
} catch (e) {
// do nothing
}
return this.plugin.antlrParserResult
}
}
/**
* Lists the AST nodes from the current file parser
* These nodes need to be changed to match the node types returned by the compiler
* @returns
*/
async listAstNodes() {
await this.getCurrentFileAST();
const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.plugin.antlrParserResult, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) {
for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null })
}
}
},
VariableDeclaration: (node: antlr.VariableDeclaration) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
FunctionDefinition: (node: antlr.FunctionDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ContractDefinition: (node: antlr.ContractDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
MemberAccess: function (node: antlr.MemberAccess) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
Identifier: function (node: antlr.Identifier) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EventDefinition: function (node: antlr.EventDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ModifierDefinition: function (node: antlr.ModifierDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
InvalidNode: function (node: antlr.InvalidNode) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EnumDefinition: function (node: antlr.EnumDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}
})
return nodes
}
/**
*
* @param ast
* @returns
*/
async getLastNodeInLine(ast: string) {
let lastNode: any
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node)
},
Identifier: function (node: antlr.Identifier) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression) {
return lastNode.expression
}
return lastNode
}
/**
* Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function
* @param {position} position
* @param {string} text // optional
* @return {any}
* */
async getANTLRBlockAtPosition(position: any, text: string = null) {
await this.getCurrentFileAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
const walkAst = (node: any) => {
if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) {
const children = node.children || node.subNodes
if (children && allowedTypes.indexOf(node.type) !== -1) {
for (const child of children) {
const result = walkAst(child)
if (result) return result
}
}
return node
}
return null
}
if (!this.plugin.antlrParserResult) return
return walkAst(this.plugin.antlrParserResult)
}
}

@ -0,0 +1,235 @@
'use strict'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { Compiler } from '@remix-project/remix-solidity'
import { CompilationResult, CompilationSource } from '@remix-project/remix-solidity'
import { CodeParser } from "../code-parser";
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';
type errorMarker = {
message: string
severity: MarkerSeverity
position: {
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
onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any
constructor(
plugin: CodeParser
) {
this.plugin = plugin
}
init() {
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
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
}
}
, file: filePath
}]
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if(displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
this.plugin._buildIndex(data, source)
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('compilationFinished', this.onAstFinished)
}
// COMPILER
/**
*
* @returns
*/
async compile() {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const state = await this.plugin.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', true)
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger)
const configFileContent = {
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["evm.gasEstimates"]
}
},
"evmVersion": state.evmVersion && state.evmVersion.toString() || "byzantium",
}
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
}
} catch (e) {
// 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[]} = {}
for (const error of allErrors) {
if (!errorsPerFiles[error.file]) {
errorsPerFiles[error.file] = []
}
errorsPerFiles[error.file].push(error)
}
const errorPriority = {
'error': 0,
'warning': 1,
}
// sort errorPerFiles by error priority
const sortedErrorsPerFiles: {[fileName: string]: errorMarker[]} = {}
for (const fileName in errorsPerFiles) {
const errors = errorsPerFiles[fileName]
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
}
async clearDecorators(sources: any) {
const decorators: fileDecoration[] = []
for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
}
}

@ -0,0 +1,76 @@
import { CodeParser, genericASTNode } from "../code-parser";
import { lineText } from '@remix-ui/editor'
export default class CodeParserGasService {
plugin: CodeParser
constructor(plugin: CodeParser) {
this.plugin = plugin
}
async getGasEstimates(fileName: string) {
if (!fileName) {
fileName = await this.plugin.currentFile
}
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
for (const node of Object.values(nodes) as any[]) {
if (node.gasEstimate) {
estimates.push({
node,
range: await this.plugin.getLineColumnOfNode(node)
})
}
}
}
}
return estimates
}
}
async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)
const friendlyNames = {
'executionCost': 'Estimated execution cost',
'codeDepositCost': 'Estimated code deposit cost',
'creationCost': 'Estimated creation cost',
}
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
}
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName)
}
}
}
}

@ -0,0 +1,732 @@
// Base on the original type definitions for solidity-parser-antlr 0.2
// by Leonid Logvinov <https://github.com/LogvinovLeon>
// Alex Browne <https://github.com/albrow>
// Xiao Liang <https://github.com/yxliang01>
export type ParseResult = SourceUnit & {
errors?: any[]
tokens?: Token[]
}
interface Token {
type: string
value: string | undefined
range?: [number, number]
loc?: {
start: {
line: number
column: number
}
end: {
line: number
column: number
}
}
}
interface Location {
start: {
line: number
column: number
}
end: {
line: number
column: number
}
}
export interface BaseASTNode {
type: ASTNodeTypeString
range?: [number, number]
loc?: Location
}
export interface SourceUnit extends BaseASTNode {
type: 'SourceUnit'
children: ASTNode[]
}
export interface ContractDefinition extends BaseASTNode {
type: 'ContractDefinition'
name: string
baseContracts: InheritanceSpecifier[]
kind: string
subNodes: BaseASTNode[]
}
export interface InheritanceSpecifier extends BaseASTNode {
type: 'InheritanceSpecifier'
baseName: UserDefinedTypeName
arguments: Expression[]
}
export interface UserDefinedTypeName extends BaseASTNode {
type: 'UserDefinedTypeName'
namePath: string
}
export type ASTNodeTypeString = typeof astNodeTypes[number]
export interface InvalidNode extends BaseASTNode {
type: 'InvalidNode'
name?: string
value?: string
}
export interface PragmaDirective extends BaseASTNode {
type: 'PragmaDirective'
name: string
value: string
}
export interface ImportDirective extends BaseASTNode {
type: 'ImportDirective'
path: string
pathLiteral: StringLiteral
unitAlias: string | null
unitAliasIdentifier: Identifier | null
symbolAliases: Array<[string, string | null]> | null
symbolAliasesIdentifiers: Array<[Identifier, Identifier | null]> | null
}
export interface StateVariableDeclaration extends BaseASTNode {
type: 'StateVariableDeclaration'
variables: StateVariableDeclarationVariable[]
initialValue: Expression | null
}
export interface FileLevelConstant extends BaseASTNode {
type: 'FileLevelConstant'
typeName: TypeName
name: string
initialValue: Expression
isDeclaredConst: boolean
isImmutable: boolean
}
export interface UsingForDeclaration extends BaseASTNode {
type: 'UsingForDeclaration'
typeName: TypeName | null
functions: string[]
libraryName: string | null
isGlobal: boolean;
}
export interface StructDefinition extends BaseASTNode {
type: 'StructDefinition'
name: string
members: VariableDeclaration[]
}
export interface ModifierDefinition extends BaseASTNode {
type: 'ModifierDefinition'
name: string
parameters: null | VariableDeclaration[]
isVirtual: boolean
override: null | UserDefinedTypeName[]
body: Block | null
}
export interface ModifierInvocation extends BaseASTNode {
type: 'ModifierInvocation'
name: string
arguments: Expression[] | null
}
export interface FunctionDefinition extends BaseASTNode {
type: 'FunctionDefinition'
name: string | null
parameters: VariableDeclaration[]
modifiers: ModifierInvocation[]
stateMutability: 'pure' | 'constant' | 'payable' | 'view' | null
visibility: 'default' | 'external' | 'internal' | 'public' | 'private'
returnParameters: VariableDeclaration[] | null
body: Block | null
override: UserDefinedTypeName[] | null
isConstructor: boolean
isReceiveEther: boolean
isFallback: boolean
isVirtual: boolean
}
export interface CustomErrorDefinition extends BaseASTNode {
type: 'CustomErrorDefinition'
name: string
parameters: VariableDeclaration[]
}
export interface TypeDefinition extends BaseASTNode {
type: 'TypeDefinition'
name: string
definition: ElementaryTypeName
}
export interface RevertStatement extends BaseASTNode {
type: 'RevertStatement'
revertCall: FunctionCall
}
export interface EventDefinition extends BaseASTNode {
type: 'EventDefinition'
name: string
parameters: VariableDeclaration[]
isAnonymous: boolean
}
export interface EnumValue extends BaseASTNode {
type: 'EnumValue'
name: string
}
export interface EnumDefinition extends BaseASTNode {
type: 'EnumDefinition'
name: string
members: EnumValue[]
}
export interface VariableDeclaration extends BaseASTNode {
type: 'VariableDeclaration'
isIndexed: boolean
isStateVar: boolean
typeName: TypeName | null
name: string | null
identifier: Identifier | null
isDeclaredConst?: boolean
storageLocation: string | null
expression: Expression | null
visibility?: 'public' | 'private' | 'internal' | 'default'
}
export interface StateVariableDeclarationVariable extends VariableDeclaration {
override: null | UserDefinedTypeName[]
isImmutable: boolean
}
export interface ArrayTypeName extends BaseASTNode {
type: 'ArrayTypeName'
baseTypeName: TypeName
length: Expression | null
}
export interface Mapping extends BaseASTNode {
type: 'Mapping'
keyType: ElementaryTypeName | UserDefinedTypeName
valueType: TypeName
}
export interface FunctionTypeName extends BaseASTNode {
type: 'FunctionTypeName'
parameterTypes: VariableDeclaration[]
returnTypes: VariableDeclaration[]
visibility: string
stateMutability: string | null
}
export interface Block extends BaseASTNode {
type: 'Block'
statements: BaseASTNode[]
}
export interface ExpressionStatement extends BaseASTNode {
type: 'ExpressionStatement'
expression: Expression | null
}
export interface IfStatement extends BaseASTNode {
type: 'IfStatement'
condition: Expression
trueBody: Statement
falseBody: Statement | null
}
export interface UncheckedStatement extends BaseASTNode {
type: 'UncheckedStatement'
block: Block
}
export interface TryStatement extends BaseASTNode {
type: 'TryStatement'
expression: Expression
returnParameters: VariableDeclaration[] | null
body: Block
catchClauses: CatchClause[]
}
export interface CatchClause extends BaseASTNode {
type: 'CatchClause'
isReasonStringType: boolean
kind: string | null
parameters: VariableDeclaration[] | null
body: Block
}
export interface WhileStatement extends BaseASTNode {
type: 'WhileStatement'
condition: Expression
body: Statement
}
export interface ForStatement extends BaseASTNode {
type: 'ForStatement'
initExpression: SimpleStatement | null
conditionExpression?: Expression
loopExpression: ExpressionStatement
body: Statement
}
export interface InlineAssemblyStatement extends BaseASTNode {
type: 'InlineAssemblyStatement'
language: string | null
flags: string[]
body: AssemblyBlock
}
export interface DoWhileStatement extends BaseASTNode {
type: 'DoWhileStatement'
condition: Expression
body: Statement
}
export interface ContinueStatement extends BaseASTNode {
type: 'ContinueStatement'
}
export interface Break extends BaseASTNode {
type: 'Break'
}
export interface Continue extends BaseASTNode {
type: 'Continue'
}
export interface BreakStatement extends BaseASTNode {
type: 'BreakStatement'
}
export interface ReturnStatement extends BaseASTNode {
type: 'ReturnStatement'
expression: Expression | null
}
export interface EmitStatement extends BaseASTNode {
type: 'EmitStatement'
eventCall: FunctionCall
}
export interface ThrowStatement extends BaseASTNode {
type: 'ThrowStatement'
}
export interface VariableDeclarationStatement extends BaseASTNode {
type: 'VariableDeclarationStatement'
variables: Array<BaseASTNode | null>
initialValue: Expression | null
}
export interface ElementaryTypeName extends BaseASTNode {
type: 'ElementaryTypeName'
name: string
stateMutability: string | null
}
export interface FunctionCall extends BaseASTNode {
type: 'FunctionCall'
expression: Expression
arguments: Expression[]
names: string[]
identifiers: Identifier[]
}
export interface AssemblyBlock extends BaseASTNode {
type: 'AssemblyBlock'
operations: AssemblyItem[]
}
export interface AssemblyCall extends BaseASTNode {
type: 'AssemblyCall'
functionName: string
arguments: AssemblyExpression[]
}
export interface AssemblyLocalDefinition extends BaseASTNode {
type: 'AssemblyLocalDefinition'
names: Identifier[] | AssemblyMemberAccess[]
expression: AssemblyExpression | null
}
export interface AssemblyAssignment extends BaseASTNode {
type: 'AssemblyAssignment'
names: Identifier[] | AssemblyMemberAccess[]
expression: AssemblyExpression
}
export interface AssemblyStackAssignment extends BaseASTNode {
type: 'AssemblyStackAssignment'
name: string
expression: AssemblyExpression
}
export interface LabelDefinition extends BaseASTNode {
type: 'LabelDefinition'
name: string
}
export interface AssemblySwitch extends BaseASTNode {
type: 'AssemblySwitch'
expression: AssemblyExpression
cases: AssemblyCase[]
}
export interface AssemblyCase extends BaseASTNode {
type: 'AssemblyCase'
value: AssemblyLiteral | null
block: AssemblyBlock
default: boolean
}
export interface AssemblyFunctionDefinition extends BaseASTNode {
type: 'AssemblyFunctionDefinition'
name: string
arguments: Identifier[]
returnArguments: Identifier[]
body: AssemblyBlock
}
export interface AssemblyFunctionReturns extends BaseASTNode {
type: 'AssemblyFunctionReturns'
}
export interface AssemblyFor extends BaseASTNode {
type: 'AssemblyFor'
pre: AssemblyBlock | AssemblyExpression
condition: AssemblyExpression
post: AssemblyBlock | AssemblyExpression
body: AssemblyBlock
}
export interface AssemblyIf extends BaseASTNode {
type: 'AssemblyIf'
condition: AssemblyExpression
body: AssemblyBlock
}
export type AssemblyLiteral =
| StringLiteral
| DecimalNumber
| HexNumber
| HexLiteral
export interface SubAssembly extends BaseASTNode {
type: 'SubAssembly'
}
export interface AssemblyMemberAccess extends BaseASTNode {
type: 'AssemblyMemberAccess'
expression: Identifier
memberName: Identifier
}
export interface NewExpression extends BaseASTNode {
type: 'NewExpression'
typeName: TypeName
}
export interface TupleExpression extends BaseASTNode {
type: 'TupleExpression'
components: Array<BaseASTNode | null>
isArray: boolean
}
export interface NameValueExpression extends BaseASTNode {
type: 'NameValueExpression'
expression: Expression
arguments: NameValueList
}
export interface NumberLiteral extends BaseASTNode {
type: 'NumberLiteral'
number: string
subdenomination:
| null
| 'wei'
| 'szabo'
| 'finney'
| 'ether'
| 'seconds'
| 'minutes'
| 'hours'
| 'days'
| 'weeks'
| 'years'
}
export interface BooleanLiteral extends BaseASTNode {
type: 'BooleanLiteral'
value: boolean
}
export interface HexLiteral extends BaseASTNode {
type: 'HexLiteral'
value: string
parts: string[]
}
export interface StringLiteral extends BaseASTNode {
type: 'StringLiteral'
value: string
parts: string[]
isUnicode: boolean[]
}
export interface Identifier extends BaseASTNode {
type: 'Identifier'
name: string
}
export interface BinaryOperation extends BaseASTNode {
type: 'BinaryOperation'
left: Expression
right: Expression
operator: BinOp
}
export interface UnaryOperation extends BaseASTNode {
type: 'UnaryOperation'
operator: UnaryOp
subExpression: Expression
isPrefix: boolean
}
export interface Conditional extends BaseASTNode {
type: 'Conditional'
condition: Expression
trueExpression: Expression
falseExpression: Expression
}
export interface IndexAccess extends BaseASTNode {
type: 'IndexAccess'
base: Expression
index: Expression
}
export interface IndexRangeAccess extends BaseASTNode {
type: 'IndexRangeAccess'
base: Expression
indexStart?: Expression
indexEnd?: Expression
}
export interface MemberAccess extends BaseASTNode {
type: 'MemberAccess'
expression: Expression
memberName: string
}
export interface HexNumber extends BaseASTNode {
type: 'HexNumber'
value: string
}
export interface DecimalNumber extends BaseASTNode {
type: 'DecimalNumber'
value: string
}
export interface NameValueList extends BaseASTNode {
type: 'NameValueList'
names: string[]
identifiers: Identifier[]
arguments: Expression[]
}
export type ASTNode =
| SourceUnit
| PragmaDirective
| ImportDirective
| ContractDefinition
| InheritanceSpecifier
| StateVariableDeclaration
| UsingForDeclaration
| StructDefinition
| ModifierDefinition
| ModifierInvocation
| FunctionDefinition
| EventDefinition
| CustomErrorDefinition
| EnumValue
| EnumDefinition
| VariableDeclaration
| TypeName
| UserDefinedTypeName
| Mapping
| FunctionTypeName
| Block
| Statement
| ElementaryTypeName
| AssemblyBlock
| AssemblyCall
| AssemblyLocalDefinition
| AssemblyAssignment
| AssemblyStackAssignment
| LabelDefinition
| AssemblySwitch
| AssemblyCase
| AssemblyFunctionDefinition
| AssemblyFunctionReturns
| AssemblyFor
| AssemblyIf
| AssemblyLiteral
| SubAssembly
| TupleExpression
| BinaryOperation
| Conditional
| IndexAccess
| IndexRangeAccess
| AssemblyItem
| Expression
| NameValueList
| AssemblyMemberAccess
| CatchClause
| FileLevelConstant
| TypeDefinition
| InvalidNode
export type AssemblyItem =
| Identifier
| AssemblyBlock
| AssemblyExpression
| AssemblyLocalDefinition
| AssemblyAssignment
| AssemblyStackAssignment
| LabelDefinition
| AssemblySwitch
| AssemblyFunctionDefinition
| AssemblyFor
| AssemblyIf
| Break
| Continue
| SubAssembly
| NumberLiteral
| StringLiteral
| HexNumber
| HexLiteral
| DecimalNumber
export type AssemblyExpression = AssemblyCall | AssemblyLiteral
export type Expression =
| IndexAccess
| IndexRangeAccess
| TupleExpression
| BinaryOperation
| Conditional
| MemberAccess
| FunctionCall
| UnaryOperation
| NewExpression
| PrimaryExpression
| NameValueExpression
export type PrimaryExpression =
| BooleanLiteral
| HexLiteral
| StringLiteral
| NumberLiteral
| Identifier
| TupleExpression
| TypeName
export type SimpleStatement = VariableDeclarationStatement | ExpressionStatement
export type TypeName =
| ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
export type Statement =
| IfStatement
| WhileStatement
| ForStatement
| Block
| InlineAssemblyStatement
| DoWhileStatement
| ContinueStatement
| BreakStatement
| ReturnStatement
| EmitStatement
| ThrowStatement
| SimpleStatement
| VariableDeclarationStatement
| UncheckedStatement
| TryStatement
| RevertStatement
type ASTMap<U> = { [K in ASTNodeTypeString]: U extends { type: K } ? U : never }
type ASTTypeMap = ASTMap<ASTNode>
export const astNodeTypes = [
'SourceUnit',
'PragmaDirective',
'ImportDirective',
'ContractDefinition',
'InheritanceSpecifier',
'StateVariableDeclaration',
'UsingForDeclaration',
'StructDefinition',
'ModifierDefinition',
'ModifierInvocation',
'FunctionDefinition',
'EventDefinition',
'CustomErrorDefinition',
'RevertStatement',
'EnumValue',
'EnumDefinition',
'VariableDeclaration',
'UserDefinedTypeName',
'Mapping',
'ArrayTypeName',
'FunctionTypeName',
'Block',
'ExpressionStatement',
'IfStatement',
'WhileStatement',
'ForStatement',
'InlineAssemblyStatement',
'DoWhileStatement',
'ContinueStatement',
'Break',
'Continue',
'BreakStatement',
'ReturnStatement',
'EmitStatement',
'ThrowStatement',
'VariableDeclarationStatement',
'ElementaryTypeName',
'FunctionCall',
'AssemblyBlock',
'AssemblyCall',
'AssemblyLocalDefinition',
'AssemblyAssignment',
'AssemblyStackAssignment',
'LabelDefinition',
'AssemblySwitch',
'AssemblyCase',
'AssemblyFunctionDefinition',
'AssemblyFunctionReturns',
'AssemblyFor',
'AssemblyIf',
'SubAssembly',
'TupleExpression',
'NameValueExpression',
'BooleanLiteral',
'NumberLiteral',
'Identifier',
'BinaryOperation',
'UnaryOperation',
'NewExpression',
'Conditional',
'StringLiteral',
'HexLiteral',
'HexNumber',
'DecimalNumber',
'MemberAccess',
'IndexAccess',
'IndexRangeAccess',
'NameValueList',
'UncheckedStatement',
'TryStatement',
'CatchClause',
'FileLevelConstant',
'AssemblyMemberAccess',
'TypeDefinition',
'InvalidNode'
] as const
export const binaryOpValues = [
'+',
'-',
'*',
'/',
'**',
'%',
'<<',
'>>',
'&&',
'||',
',,',
'&',
',',
'^',
'<',
'>',
'<=',
'>=',
'==',
'!=',
'=',
',=',
'^=',
'&=',
'<<=',
'>>=',
'+=',
'-=',
'*=',
'/=',
'%=',
'|',
'|=',
] as const
export type BinOp = typeof binaryOpValues[number]
export const unaryOpValues = [
'-',
'+',
'++',
'--',
'~',
'after',
'delete',
'!',
] as const
export type UnaryOp = typeof unaryOpValues[number]

@ -0,0 +1 @@
export * as antlr from './antlr-types'

@ -30,7 +30,9 @@
--info:#3399f3;
--warning:#d47500;
--danger:#cd0200;
--text:#eee;
--text: #343a40;
/* --text:#eee; */
/* --text-background: #; */
--light:#eee;
--dark:#333;
--body-bg:#fff;

@ -0,0 +1,251 @@
T__0=1
T__1=2
T__2=3
T__3=4
T__4=5
T__5=6
T__6=7
T__7=8
T__8=9
T__9=10
T__10=11
T__11=12
T__12=13
T__13=14
T__14=15
T__15=16
T__16=17
T__17=18
T__18=19
T__19=20
T__20=21
T__21=22
T__22=23
T__23=24
T__24=25
T__25=26
T__26=27
T__27=28
T__28=29
T__29=30
T__30=31
T__31=32
T__32=33
T__33=34
T__34=35
T__35=36
T__36=37
T__37=38
T__38=39
T__39=40
T__40=41
T__41=42
T__42=43
T__43=44
T__44=45
T__45=46
T__46=47
T__47=48
T__48=49
T__49=50
T__50=51
T__51=52
T__52=53
T__53=54
T__54=55
T__55=56
T__56=57
T__57=58
T__58=59
T__59=60
T__60=61
T__61=62
T__62=63
T__63=64
T__64=65
T__65=66
T__66=67
T__67=68
T__68=69
T__69=70
T__70=71
T__71=72
T__72=73
T__73=74
T__74=75
T__75=76
T__76=77
T__77=78
T__78=79
T__79=80
T__80=81
T__81=82
T__82=83
T__83=84
T__84=85
T__85=86
T__86=87
T__87=88
T__88=89
T__89=90
T__90=91
T__91=92
T__92=93
T__93=94
T__94=95
T__95=96
T__96=97
Int=98
Uint=99
Byte=100
Fixed=101
Ufixed=102
BooleanLiteral=103
DecimalNumber=104
HexNumber=105
NumberUnit=106
HexLiteralFragment=107
ReservedKeyword=108
AnonymousKeyword=109
BreakKeyword=110
ConstantKeyword=111
ImmutableKeyword=112
ContinueKeyword=113
LeaveKeyword=114
ExternalKeyword=115
IndexedKeyword=116
InternalKeyword=117
PayableKeyword=118
PrivateKeyword=119
PublicKeyword=120
VirtualKeyword=121
PureKeyword=122
TypeKeyword=123
ViewKeyword=124
GlobalKeyword=125
ConstructorKeyword=126
FallbackKeyword=127
ReceiveKeyword=128
Identifier=129
StringLiteralFragment=130
VersionLiteral=131
WS=132
COMMENT=133
LINE_COMMENT=134
'pragma'=1
';'=2
'*'=3
'||'=4
'^'=5
'~'=6
'>='=7
'>'=8
'<'=9
'<='=10
'='=11
'as'=12
'import'=13
'from'=14
'{'=15
','=16
'}'=17
'abstract'=18
'contract'=19
'interface'=20
'library'=21
'is'=22
'('=23
')'=24
'error'=25
'using'=26
'for'=27
'struct'=28
'modifier'=29
'function'=30
'returns'=31
'event'=32
'enum'=33
'['=34
']'=35
'address'=36
'.'=37
'mapping'=38
'=>'=39
'memory'=40
'storage'=41
'calldata'=42
'if'=43
'else'=44
'try'=45
'catch'=46
'while'=47
'unchecked'=48
'assembly'=49
'do'=50
'return'=51
'throw'=52
'emit'=53
'revert'=54
'var'=55
'bool'=56
'string'=57
'byte'=58
'++'=59
'--'=60
'new'=61
':'=62
'+'=63
'-'=64
'after'=65
'delete'=66
'!'=67
'**'=68
'/'=69
'%'=70
'<<'=71
'>>'=72
'&'=73
'|'=74
'=='=75
'!='=76
'&&'=77
'?'=78
'|='=79
'^='=80
'&='=81
'<<='=82
'>>='=83
'+='=84
'-='=85
'*='=86
'/='=87
'%='=88
'let'=89
':='=90
'=:'=91
'switch'=92
'case'=93
'default'=94
'->'=95
'callback'=96
'override'=97
'anonymous'=109
'break'=110
'constant'=111
'immutable'=112
'continue'=113
'leave'=114
'external'=115
'indexed'=116
'internal'=117
'payable'=118
'private'=119
'public'=120
'virtual'=121
'pure'=122
'type'=123
'view'=124
'global'=125
'constructor'=126
'fallback'=127
'receive'=128

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -41,6 +41,7 @@
<script type="text/javascript" src="assets/js/loader.js"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script>
<script type="text/javascript" src="assets/js/parser/antlr.js"></script>
</body>
</html>

@ -219,6 +219,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.data.loading = true
this.data.loadingUrl = url
this.statusChanged({ key: 'loading', title: 'loading compiler...', type: 'info' })
this.emit('loadingCompiler', url)
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)

@ -0,0 +1,4 @@
const nxPreset = require('@nrwl/jest/preset');
module.exports = { ...nxPreset }

@ -168,7 +168,7 @@ export interface ContractDefinitionAstNode {
nodeType: 'ContractDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
contractKind: 'interface' | 'contract' | 'library'
abstract: boolean
fullyImplemented: boolean
@ -241,7 +241,7 @@ export interface FunctionDefinitionAstNode {
nodeType: 'FunctionDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
kind: string
stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable'
visibility: string
@ -281,7 +281,7 @@ export interface ModifierDefinitionAstNode {
nodeType: 'ModifierDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
visibility: string
parameters: ParameterListAstNode
virtual: boolean
@ -303,7 +303,7 @@ export interface EventDefinitionAstNode {
nodeType: 'EventDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
parameters: ParameterListAstNode
anonymous: boolean
}

@ -3,7 +3,6 @@ export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener'
export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'

@ -1,244 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = {
name: 'contextualListener',
methods: ['referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf'],
events: [],
version: '0.0.1'
}
/*
trigger contextChanged(nodes)
*/
export class EditorContextListener extends Plugin {
_index: any
_activeHighlights: Array<any>
astWalker: any
currentPosition: any
currentFile: string
nodes: Array<any>
results: any
estimationObj: any
creationCost: any
codeDepositCost: any
contract: any
activated: boolean
constructor (astWalker) {
super(profile)
this.activated = false
this._index = {
Declarations: {},
FlatReferences: {}
}
this._activeHighlights = []
this.astWalker = astWalker
}
onActivation () {
this.on('editor', 'contentChanged', () => { this._stopHighlighting() })
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
if (languageVersion.indexOf('soljson') !== 0) return
this._stopHighlighting()
this._index = {
Declarations: {},
FlatReferences: {}
}
this._buildIndex(data, source)
})
setInterval(async () => {
const compilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult')
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let currentFile
try {
currentFile = await this.call('fileManager', 'file')
} catch (error) {
if (error.message !== 'Error: No such file or directory No file selected') throw error
}
this._highlightItems(
await this.call('editor', 'getCursorPosition'),
compilationResult,
currentFile
)
}
}, 1000)
this.activated = true
}
getActiveHighlights () {
return [...this._activeHighlights]
}
declarationOf (node) {
if (node && node.referencedDeclaration) {
return this._index.FlatReferences[node.referencedDeclaration]
}
return null
}
referencesOf (node) {
return this._index.Declarations[node.id]
}
async _highlightItems (cursorPosition, compilationResult, file) {
if (this.currentPosition === cursorPosition) return
this._stopHighlighting()
this.currentPosition = cursorPosition
this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources && compilationResult.data.sources[file]) {
const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) {
await this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
}
this.emit('contextChanged', nodes)
}
}
_buildIndex (compilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node) => {
if (node && node.referencedDeclaration) {
if (!this._index.Declarations[node.referencedDeclaration]) {
this._index.Declarations[node.referencedDeclaration] = []
}
this._index.Declarations[node.referencedDeclaration].push(node)
}
this._index.FlatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
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, nodeId: node.id })
}
}
async _highlightInternal (position, node, compilationResult) {
if (node.nodeType === 'Block') return
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, compilationResult.getSourceCode().sources, compilationResult.getAsts())
if (node.nodes && node.nodes.length) {
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
lineColumn = {
start: {
line: lineColumn.start.line,
column: 0
},
end: {
line: lineColumn.start.line + 1,
column: 0
}
}
}
const fileName = compilationResult.getSourceName(position.file)
if (fileName) {
return await this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false })
}
}
return null
}
async _highlightExpressions (node, compilationResult) {
const highlights = async (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
for (const ref in refs) {
const node = refs[ref]
await this._highlight(node, compilationResult)
}
}
}
if (node && node.referencedDeclaration) {
await highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[node.referencedDeclaration]
await this._highlight(current, compilationResult)
} else {
await highlights(node.id)
await this._highlight(node, compilationResult)
}
this.results = compilationResult
}
_stopHighlighting () {
this.call('editor', 'discardHighlight')
this.emit('stopHighlighting')
this._activeHighlights = []
}
gasEstimation (node) {
this._loadContractInfos(node)
let executionCost, codeDepositCost
if (node.nodeType === 'FunctionDefinition') {
const visibility = node.visibility
if (node.kind !== 'constructor') {
const fnName = node.name
const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') {
executionCost = this.estimationObj === null ? '-' : this.estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = this.estimationObj === null ? '-' : this.estimationObj.internal[fn]
}
} else {
executionCost = this.creationCost
codeDepositCost = this.codeDepositCost
}
} else {
executionCost = '-'
}
return { executionCost, codeDepositCost }
}
_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[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
}
}
}
}
_getInputParams (node) {
const params = []
const target = node.parameters
// for (const i in node.children) {
// if (node.children[i].name === 'ParameterList') {
// target = node.children[i]
// break
// }
// }
if (target) {
const children = target.parameters
for (const j in children) {
if (children[j].nodeType === 'VariableDeclaration') {
params.push(children[j].typeDescriptions.typeString)
}
}
}
return '(' + params.toString() + ')'
}
}

@ -3,10 +3,14 @@
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": ["**/*.spec.ts"],
"exclude": [
"**/*.spec.ts",
"tests/"
],
"include": ["**/*.ts"]
}

@ -15,7 +15,7 @@ export class DebuggerStepManager {
this.event = new EventManager()
this.debugger = _debugger
this.traceManager = traceManager
this.currentStepIndex = 0
this.currentStepIndex = -1
this.traceLength = 0
this.codeTraceLength = 0
this.revertionPoint = null
@ -34,13 +34,18 @@ export class DebuggerStepManager {
this.traceLength = newLength
this.codeTraceLength = this.calculateCodeLength()
}
this.jumpTo(0)
setTimeout(() => {
this.jumpTo(0) // wait for the ui to render
}, 500)
})
})
this.debugger.callTree.event.register('callTreeReady', () => {
if (this.debugger.callTree.functionCallStack.length) {
this.jumpTo(this.debugger.callTree.functionCallStack[0])
setTimeout(() => {
this.jumpTo(this.debugger.callTree.functionCallStack[0]) // wait for the ui to be render
}, 500)
}
})

@ -45,6 +45,7 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
self.postMessage({
cmd: 'compiled',
job: data.job,
timestamp: data.timestamp,
data: compileJSON(data.input),
input: data.input,
missingInputs: missingInputs

@ -9,7 +9,7 @@ import {
Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult,
visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError,
gatherImportsCallbackInterface,
isFunctionDescription
isFunctionDescription, CompilerRetriggerMode
} from './types'
/*
@ -19,7 +19,7 @@ export class Compiler {
event
state: CompilerState
constructor (public handleImportCall?: (fileurl: string, cb) => void) {
constructor(public handleImportCall?: (fileurl: string, cb) => void) {
this.event = new EventManager()
this.state = {
compileJSON: null,
@ -34,6 +34,7 @@ export class Compiler {
target: null,
useFileConfiguration: false,
configFileContent: '',
compilerRetriggerMode: CompilerRetriggerMode.none,
lastCompilationResult: {
data: null,
source: null
@ -48,7 +49,6 @@ export class Compiler {
})
this.event.register('compilationStarted', () => {
this.state.compilationStartTime = new Date().getTime()
})
}
@ -58,7 +58,7 @@ export class Compiler {
* @param value value of key in CompilerState
*/
set <K extends keyof CompilerState> (key: K, value: CompilerState[K]): void {
set<K extends keyof CompilerState>(key: K, value: CompilerState[K]): void {
this.state[key] = value
if (key === 'runs') this.state['runs'] = parseInt(value)
}
@ -69,7 +69,7 @@ export class Compiler {
* @param missingInputs missing import file path list
*/
internalCompile (files: Source, missingInputs?: string[]): void {
internalCompile(files: Source, missingInputs?: string[]): void {
this.gatherImports(files, missingInputs, (error, input) => {
if (error) {
this.state.lastCompilationResult = null
@ -84,8 +84,9 @@ export class Compiler {
* @param target target file name (This is passed as it is to IDE)
*/
compile (files: Source, target: string): void {
compile(files: Source, target: string): void {
this.state.target = target
this.state.compilationStartTime = new Date().getTime()
this.event.trigger('compilationStarted', [])
this.internalCompile(files)
}
@ -105,7 +106,7 @@ export class Compiler {
* @dev Called when compiler is loaded internally (without worker)
*/
onInternalCompilerLoaded (): void {
onInternalCompilerLoaded(): void {
if (this.state.worker === null) {
const compiler: any = typeof (window) !== 'undefined' && window['Module'] ? require('solc/wrapper')(window['Module']) : require('solc') // eslint-disable-line
this.state.compileJSON = (source: SourceWithTarget) => {
@ -144,7 +145,7 @@ export class Compiler {
* @param source Source
*/
onCompilationFinished (data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void {
onCompilationFinished(data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void {
let noFatalErrors = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => {
@ -179,7 +180,7 @@ export class Compiler {
* @param version compiler version
*/
loadRemoteVersion (version: string): void {
loadRemoteVersion(version: string): void {
console.log(`Loading remote solc version ${version} ...`)
const compiler: any = require('solc') // eslint-disable-line
compiler.loadRemoteVersion(version, (err, remoteCompiler) => {
@ -224,7 +225,7 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadVersion (usingWorker: boolean, url: string): void {
loadVersion(usingWorker: boolean, url: string): void {
console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker'))
this.event.trigger('loadingCompiler', [url, usingWorker])
if (this.state.worker) {
@ -243,7 +244,7 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadInternal (url: string): void {
loadInternal(url: string): void {
delete window['Module']
// NOTE: workaround some browsers?
window['Module'] = undefined
@ -269,12 +270,15 @@ export class Compiler {
* @param url URL to load compiler from
*/
loadWorker (url: string): void {
loadWorker(url: string): void {
this.state.worker = webworkify(require.resolve('./compiler-worker'))
const jobs: Record<'sources', SourceWithTarget> [] = []
const jobs: Record<'sources', SourceWithTarget>[] = []
this.state.worker.addEventListener('message', (msg: Record <'data', MessageFromWorker>) => {
this.state.worker.addEventListener('message', (msg: Record<'data', MessageFromWorker>) => {
const data: MessageFromWorker = msg.data
if (this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger && data.timestamp !== this.state.compilationStartTime) {
return
}
switch (data.cmd) {
case 'versionLoaded':
if (data.data && data.license) this.onCompilerLoaded(data.data, data.license)
@ -300,7 +304,7 @@ export class Compiler {
}
})
this.state.worker.addEventListener('error', (msg: Record <'data', MessageFromWorker>) => {
this.state.worker.addEventListener('error', (msg: Record<'data', MessageFromWorker>) => {
const formattedMessage = `Worker error: ${msg.data && msg.data !== undefined ? msg.data : msg['message']}`
this.onCompilationFinished({ error: { formattedMessage } })
})
@ -326,7 +330,8 @@ export class Compiler {
this.state.worker.postMessage({
cmd: 'compile',
job: jobs.length - 1,
input: input
input: input,
timestamp: this.state.compilationStartTime
})
}
}
@ -344,7 +349,7 @@ export class Compiler {
* @param cb callback
*/
gatherImports (files: Source, importHints?: string[], cb?: gatherImportsCallbackInterface): void {
gatherImports(files: Source, importHints?: string[], cb?: gatherImportsCallbackInterface): void {
importHints = importHints || []
// FIXME: This will only match imports if the file begins with one '.'
// It should tokenize by lines and check each.
@ -383,7 +388,7 @@ export class Compiler {
* @param version version
*/
truncateVersion (version: string): string {
truncateVersion(version: string): string {
const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version)
return tmp ? tmp[1] : version
}
@ -393,8 +398,8 @@ export class Compiler {
* @param data Compilation result
*/
updateInterface (data: CompilationResult) : CompilationResult {
txHelper.visitContracts(data.contracts, (contract : visitContractsCallbackParam) => {
updateInterface(data: CompilationResult): CompilationResult {
txHelper.visitContracts(data.contracts, (contract: visitContractsCallbackParam) => {
if (!contract.object.abi) contract.object.abi = []
if (this.state.language === 'Yul' && contract.object.abi.length === 0) {
// yul compiler does not return any abi,
@ -426,7 +431,7 @@ export class Compiler {
* @param name contract name
*/
getContract (name: string): Record<string, any> | null {
getContract(name: string): Record<string, any> | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.getContract(name, this.state.lastCompilationResult.data.contracts)
}
@ -438,7 +443,7 @@ export class Compiler {
* @param cb callback
*/
visitContracts (cb: visitContractsCallbackInterface) : void | null {
visitContracts(cb: visitContractsCallbackInterface): void | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.visitContracts(this.state.lastCompilationResult.data.contracts, cb)
}
@ -449,7 +454,7 @@ export class Compiler {
* @dev Get the compiled contracts data from last compilation result
*/
getContracts () : CompilationResult['contracts'] | null {
getContracts(): CompilationResult['contracts'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return this.state.lastCompilationResult.data.contracts
}
@ -460,7 +465,7 @@ export class Compiler {
* @dev Get sources from last compilation result
*/
getSources () : Source | null | undefined {
getSources(): Source | null | undefined {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source) {
return this.state.lastCompilationResult.source.sources
}
@ -472,7 +477,7 @@ export class Compiler {
* @param fileName file name
*/
getSource (fileName: string) : Source['filename'] | null {
getSource(fileName: string): Source['filename'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source && this.state.lastCompilationResult.source.sources) {
return this.state.lastCompilationResult.source.sources[fileName]
}
@ -484,7 +489,7 @@ export class Compiler {
* @param index - index of the source
*/
getSourceName (index: number): string | null {
getSourceName(index: number): string | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.sources) {
return Object.keys(this.state.lastCompilationResult.data.sources)[index]
}

@ -154,6 +154,11 @@ export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | '
export type Language = 'Solidity' | 'Yul'
export enum CompilerRetriggerMode {
'none' ,
'retrigger'
}
export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
@ -167,6 +172,7 @@ export interface CompilerState {
target: string | null,
useFileConfiguration: boolean,
configFileContent: string,
compilerRetriggerMode: CompilerRetriggerMode,
lastCompilationResult: {
data: CompilationResult | null,
source: SourceWithTarget | null | undefined
@ -183,6 +189,7 @@ export interface MessageToWorker {
job?: number,
input?: CompilerInput,
data?: string
timestamp?: number
}
export interface MessageFromWorker {
@ -192,6 +199,7 @@ export interface MessageFromWorker {
missingInputs?: string[],
input?: any,
data?: string
timestamp?: number
}
export interface visitContractsCallbackParam {

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import Draggable from 'react-draggable'
import './dragbar.css'
@ -7,18 +7,37 @@ interface IRemixDragBarUi {
setHideStatus: (hide: boolean) => void;
hidden: boolean
minWidth: number
maximiseTrigger: number
resetTrigger: number
}
const DragBar = (props: IRemixDragBarUi) => {
const [dragState, setDragState] = useState<boolean>(false)
const [dragBarPosX, setDragBarPosX] = useState<number>(0)
const [offset, setOffSet] = useState<number>(0)
const initialWidth = useRef<number>(props.minWidth)
const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth))
}, [props.hidden, offset])
useEffect(() => {
initialWidth.current = props.refObject.current.clientWidth
if (props.maximiseTrigger > 0) {
const width = 0.4 * window.innerWidth
props.refObject.current.style.width = width + 'px'
setDragBarPosX(offset + width)
}
}, [props.maximiseTrigger])
useEffect(() => {
if (props.maximiseTrigger > 0) {
props.refObject.current.style.width = initialWidth.current + 'px'
setDragBarPosX(offset + initialWidth.current)
}
}, [props.resetTrigger])
const handleResize = () => {
setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth)

@ -17,6 +17,8 @@ interface IRemixAppUi {
const RemixApp = (props: IRemixAppUi) => {
const [appReady, setAppReady] = useState<boolean>(false)
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0)
const [resetTrigger, setResetTrigger] = useState<number>(0)
const sidePanelRef = useRef(null)
useEffect(() => {
@ -48,6 +50,18 @@ const RemixApp = (props: IRemixAppUi) => {
setHideSidePanel(true)
}, 1000)
})
props.app.layout.event.on('maximisesidepanel', () => {
setMaximiseTrigger(prev => {
return prev + 1
})
})
props.app.layout.event.on('resetsidepanel', () => {
setResetTrigger(prev => {
return prev + 1
})
})
}
const value = {
@ -66,7 +80,7 @@ const RemixApp = (props: IRemixAppUi) => {
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
<div id="icon-panel" data-id="remixIdeIconPanel" className="iconpanel bg-light">{props.app.menuicons.render()}</div>
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>{props.app.sidePanel.render()}</div>
<DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
<DragBar resetTrigger={resetTrigger} maximiseTrigger={maximiseTrigger} minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel'>
<RemixUIMainPanel Context={AppContext}></RemixUIMainPanel>
</div>

@ -31,7 +31,8 @@ pre {
.sidepanel {
display : flex;
flex-direction : row-reverse;
width: 320px;
width : 320px;
transition : width 1s;
}
.highlightcode {

@ -57,7 +57,6 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive])
useEffect(() => {
return unLoad()
}, [])
@ -191,6 +190,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
const unLoad = () => {
debuggerModule.onStopDebugging()
if (state.debugger) state.debugger.unload()
setState(prevState => {
return {
@ -282,6 +282,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
setTimeout(async() => {
debuggerModule.onStartDebugging()
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
@ -349,7 +350,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<Toaster message={state.toastMessage} />
<div className="px-2" ref={debuggerTopRef}>
<div>
<div className="mb-2 debuggerConfig custom-control custom-checkbox" title="Using Generated Sources lets you step into compiler outputs while debugging.">
<p className="my-2 debuggerLabel">Debugger Configuration</p>
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }

@ -46,8 +46,6 @@ export type onEnvChangedListener = (provider: string) => void
export interface IDebuggerApi {
offsetToLineColumnConverter: { offsetToLineColumn: (sourceLocation: RawLocation, file: number, contents: Sources, asts: Asts) => Promise<LineColumnLocation> }
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onRemoveHighlights: (listener: VoidFunction) => void
onDebugRequested: (listener: onDebugRequested) => void
@ -63,6 +61,8 @@ export interface IDebuggerApi {
getDebugWeb3: () => any // returns an instance of web3.js, if applicable (mainet, goerli, ...) it returns a reference to a node from devops (so we are sure debug endpoint is available)
web3: () => any // returns an instance of web3.js
showMessage (title: string, message: string): void
onStartDebugging (): void // called when debug starts
onStopDebugging (): void // called when debug stops
}
export interface DebuggerUIProps {

@ -122,9 +122,10 @@ export const AssemblyItems = ({ registerEvent }) => {
}
return (
<div className="border rounded px-1 mt-1 bg-light">
<div className="h-100 border rounded px-1 mt-1 bg-light">
<div className='dropdownpanel'>
<div className='dropdowncontent'>
<div className='dropdowncontent pb-2'>
{ assemblyItems.display.length == 0 && <div>No data available</div> }
<div className="pl-2 my-1 small instructions" data-id="asmitems" id='asmitems' ref={asmItemsRef}>
{
assemblyItems.display.map((item, i) => {

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const CalldataPanel = ({ calldata }) => {
export const CalldataPanel = ({ calldata, className = "" }) => {
return (
<div id='calldatapanel'>
<div id='calldatapanel' className={className}>
<DropdownPanel dropdownName='Call Data' calldata={calldata || {}} />
</div>
)

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const CallstackPanel = ({ calldata }) => {
export const CallstackPanel = ({ calldata, className }) => {
return (
<div id='callstackpanel'>
<div id='callstackpanel' className={className}>
<DropdownPanel dropdownName='Call Stack' calldata={calldata || {}} />
</div>
)

@ -1,9 +1,9 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line
import AssemblyItems from './assembly-items' // eslint-disable-line
export const CodeListView = ({ registerEvent }) => {
export const CodeListView = ({ registerEvent, className = ""}) => {
return (
<div id='asmcodes'>
<div className={className} id='asmcodes'>
<AssemblyItems registerEvent={registerEvent} />
</div>
)

@ -7,7 +7,7 @@ import './styles/dropdown-panel.css'
export const DropdownPanel = (props: DropdownPanelProps) => {
const [calldataObj, dispatch] = useReducer(reducer, initialState)
const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent, headStyle, bodyStyle, hexHighlight } = props
const { dropdownName, className, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent, headStyle, bodyStyle, hexHighlight } = props
const extractDataDefault: ExtractFunc = (item, parent?) => {
const ret: ExtractData = {}
@ -54,7 +54,7 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
}
const [state, setState] = useState({
header: '',
toggleDropdown: false,
toggleDropdown: true,
message: {
innerText: 'No data available.',
display: 'block'
@ -163,7 +163,7 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
display: isEmpty ? 'block' : 'none'
},
updating: false,
toggleDropdown: !isEmpty,
toggleDropdown: true,
data: calldata
}
})
@ -194,7 +194,7 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
const uniquePanelName = dropdownName.split(' ').join('')
return (
<div className="border rounded px-1 mt-1 bg-light">
<div className={className + " border rounded px-1 mt-1 bg-light"}>
<div className="py-0 px-1 title" style={headStyle}>
<div className={state.toggleDropdown ? 'icon fas fa-caret-down' : 'icon fas fa-caret-right'} onClick={handleToggle}></div>
<div className="name" data-id={`dropdownPanel${uniquePanelName}`} onClick={handleToggle}>{dropdownName}</div><span className="nameDetail" onClick={handleToggle}>{header}</span>
@ -202,7 +202,7 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
</div>
<div className='dropdownpanel' style={{ display: state.toggleDropdown ? 'block' : 'none' }}>
<i className="refresh fas fa-sync" style={{ display: state.updating ? 'inline-block' : 'none' }} aria-hidden="true"></i>
<div className='dropdowncontent' style={{ display: state.dropdownContent.display, ...bodyStyle }}>
<div className='dropdowncontent pb-2' style={{ display: state.dropdownContent.display, ...bodyStyle }}>
{
state.data &&
<TreeView id="treeView">

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import { DropdownPanel } from './dropdown-panel' // eslint-disable-line
export const FullStoragesChanges = ({ calldata }) => {
export const FullStoragesChanges = ({ calldata, className = "" }) => {
return (
<div id='fullstorageschangespanel'>
<div className={className} id='fullstorageschangespanel'>
<DropdownPanel dropdownName='Full Storage Changes' calldata={ calldata || {}} />
</div>
)

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
import { default as deepequal } from 'deep-equal' // eslint-disable-line
export const FunctionPanel = ({ data }) => {
export const FunctionPanel = ({ data, className }) => {
const [calldata, setCalldata] = useState(null)
useEffect(() => {
@ -10,7 +10,7 @@ export const FunctionPanel = ({ data }) => {
}, [data])
return (
<div id='FunctionPanel' data-id='functionPanel'>
<div id='FunctionPanel' className={className} data-id='functionPanel'>
<DropdownPanel dropdownName='Function Stack' calldata={calldata || {}} />
</div>
)

@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
import { BN } from 'ethereumjs-util'
import Web3 from 'web3'
export const GlobalVariables = ({ block, receipt, tx }) => {
export const GlobalVariables = ({ block, receipt, tx, className }) => {
// see https://docs.soliditylang.org/en/latest/units-and-global-variables.html#block-and-transaction-properties
const globals = {
'block.chainid': tx.chainId,
@ -22,7 +22,7 @@ export const GlobalVariables = ({ block, receipt, tx }) => {
}
return (
<div id='globalvariable' data-id='globalvariable'>
<div id='globalvariable' data-id='globalvariable' className={className}>
<DropdownPanel hexHighlight={false} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Global Variables' calldata={globals || {}} />
</div>
)

@ -1,9 +1,11 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const MemoryPanel = ({ calldata }) => {
export const MemoryPanel = ({ calldata, className}) => {
return (
<div className={className} >
<DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Memory' calldata={calldata || {}} />
</div>
)
}

@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
import { extractData } from '../../utils/solidityTypeFormatter' // eslint-disable-line
import { ExtractData } from '../../types' // eslint-disable-line
export const SolidityLocals = ({ data, message, registerEvent, triggerEvent }) => {
export const SolidityLocals = ({ data, message, registerEvent, triggerEvent, className = "" }) => {
const [calldata, setCalldata] = useState(null)
useEffect(() => {
@ -43,7 +43,7 @@ export const SolidityLocals = ({ data, message, registerEvent, triggerEvent }) =
}
return (
<div id='soliditylocals' data-id="solidityLocals">
<div className={className} id='soliditylocals' data-id="solidityLocals">
<DropdownPanel
dropdownName='Solidity Locals'
dropdownMessage={message}

@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
import { extractData } from '../../utils/solidityTypeFormatter'
import { ExtractData } from '../../types' // eslint-disable-line
export const SolidityState = ({ calldata, message }) => {
export const SolidityState = ({ calldata, message, className }) => {
const formatSelf = (key: string, data: ExtractData) => {
try {
let color = 'var(--primary)'
@ -38,7 +38,7 @@ export const SolidityState = ({ calldata, message }) => {
}
return (
<div id='soliditystate' data-id='soliditystate'>
<div id='soliditystate' data-id='soliditystate' className={className}>
{
<DropdownPanel dropdownName='Solidity State' calldata={calldata || {}} formatSelfFunc={formatSelf} extractFunc={extractData} />
}

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StackPanel = ({ calldata }) => {
export const StackPanel = ({ calldata, className }) => {
return (
<div id='stackpanel'>
<div id='stackpanel' className={className}>
<DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Stack' calldata={calldata || {}} />
</div>
)

@ -1,10 +1,10 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StepDetail = ({ stepDetail }) => {
export const StepDetail = ({ stepDetail, className = "" }) => {
return (
<div id='stepdetail' data-id='stepdetail'>
<DropdownPanel hexHighlight={false} dropdownName='Step details' calldata={stepDetail || {}} />
<div className={className} id='stepdetail' data-id='stepdetail'>
<DropdownPanel className={className} hexHighlight={false} dropdownName='Step details' calldata={stepDetail || {}} />
</div>
)
}

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line
import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StoragePanel = ({ calldata, header }) => {
export const StoragePanel = ({ calldata, header, className }) => {
return (
<div id='storagepanel'>
<div id='storagepanel' className={className}>
<DropdownPanel dropdownName='Storage' calldata={calldata || {}} header={header} />
</div>
)

@ -98,15 +98,15 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } })
}, [registerEvent])
return (
<div id='vmheadView' className="mt-1 px-2">
<div className='d-flex flex-column'>
<div className='w-100'>
<FunctionPanel data={functionPanel} />
<SolidityLocals data={solidityLocals.calldata} message={solidityLocals.message} registerEvent={registerEvent} triggerEvent={triggerEvent} />
<SolidityState calldata={solidityState.calldata} message={solidityState.message} />
<div id='vmheadView' className="mt-1 px-2 d-flex">
<div className='d-flex flex-column pr-2' style={{ flex:1 }}>
<FunctionPanel className="pb-1" data={functionPanel} />
<SolidityLocals className="pb-1" data={solidityLocals.calldata} message={solidityLocals.message} registerEvent={registerEvent} triggerEvent={triggerEvent} />
<CodeListView className="pb-2 flex-grow-1" registerEvent={registerEvent} />
</div>
<div className='w-100'><CodeListView registerEvent={registerEvent} /></div>
<div className='w-100'><StepDetail stepDetail={stepDetail} /></div>
<div className='d-flex flex-column pl-2' style={{ flex:1 }}>
<SolidityState className="pb-1" calldata={solidityState.calldata} message={solidityState.message} />
<StepDetail className="pb-1 pb-2 h-100 flex-grow-1" stepDetail={stepDetail} />
</div>
</div>
)

@ -52,16 +52,18 @@ export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, curren
}, [registerEvent])
return (
<div id='vmdebugger' className="px-2">
<div>
<StackPanel calldata={stackPanel} />
<MemoryPanel calldata={memoryPanel} />
<StoragePanel calldata={storagePanel.calldata} header={storagePanel.header} />
<CallstackPanel calldata={callStackPanel} />
<CalldataPanel calldata={calldataPanel} />
<GlobalVariables block={currentBlock} receipt={currentReceipt} tx={currentTransaction} />
<ReturnValuesPanel dropdownName='Return Value' calldata={returnValuesPanel || {}} />
<FullStoragesChangesPanel calldata={fullStoragesChangesPanel} />
<div id='vmdebugger' className="d-flex">
<div className='d-flex flex-column px-2 pr-2' style={{ flex: 1 }}>
<CallstackPanel className="pb-1" calldata={callStackPanel} />
<StackPanel className="pb-1" calldata={stackPanel} />
<MemoryPanel className="pb-1" calldata={memoryPanel} />
<StoragePanel className="pb-1" calldata={storagePanel.calldata} header={storagePanel.header} />
<ReturnValuesPanel className="pb-1" dropdownName='Return Value' calldata={returnValuesPanel || {}} />
<GlobalVariables className="pb-1" block={currentBlock} receipt={currentReceipt} tx={currentTransaction} />
</div>
<div className='d-flex flex-column px-2 pl-2' style={{ flex: 1 }}>
<FullStoragesChangesPanel className="pb-1" calldata={fullStoragesChangesPanel} />
<CalldataPanel className="pb-1" calldata={calldataPanel} />
</div>
</div>
)

@ -20,6 +20,7 @@ export type RegisterEventType = (type: string, listener: any) => void // listene
export type TriggerEventType = (type: string, payload: Array<any>) => void
export interface DropdownPanelProps {
dropdownName: string,
className?: string,
dropdownMessage?: string,
calldata?: {
[key: string]: string

@ -3,7 +3,7 @@
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"extends": "../../../.eslintrc.json",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"

@ -0,0 +1,7 @@
# remix-ui-drag-n-drop
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-drag-n-drop` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1,9 @@
module.exports = {
displayName: 'remix-ui-drag-n-drop',
preset: '../../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../../coverage/libs/remix-ui/drag-n-drop'
};

@ -0,0 +1,2 @@
export * from './lib/remix-ui-drag-n-drop';

@ -0,0 +1,112 @@
import React, {
createContext,
ReactNode,
useContext,
useRef,
useState,
} from "react";
export interface FileType {
path: string,
name: string,
isDirectory: boolean,
type: 'folder' | 'file' | 'gist',
child?: File[]
}
interface MoveContextType {
dragged: string;
isDraggable?: boolean;
moveFile: (dest: string, dragged: string) => void;
currentlyMoved: (path: string) => void;
}
interface DraggableType {
children: ReactNode;
file: FileType;
isDraggable?: boolean;
expandedPath: string[];
handleClickFolder: (path: string, type: string) => void;
}
interface DragType {
children: ReactNode;
onFileMoved: (dest: string, dragged: string) => void;
}
export const MoveContext = createContext<MoveContextType>({
dragged: "",
moveFile: () => {},
currentlyMoved: () => {},
});
export const Drag = (props: DragType) => {
const [dragged, setDragged] = useState<string>("");
return (
<MoveContext.Provider
value={{
dragged: dragged,
moveFile: props.onFileMoved,
currentlyMoved: (path) => {
setDragged(() => path);
},
}}
>
{props.children}
</MoveContext.Provider>
);
};
export const Draggable = (props: DraggableType) => {
const dragRef = useRef<HTMLSpanElement | null>(null),
file = props.file,
context = useContext(MoveContext);
const handleDrop = (event: React.DragEvent<HTMLSpanElement>) => {
event.preventDefault();
if (file.isDirectory) {
context.moveFile(file.path, context.dragged);
}
};
const handleDragover = (event: React.DragEvent<HTMLSpanElement>) => {
//Checks if the folder is opened
event.preventDefault();
if (file.isDirectory && !props.expandedPath.includes(file.path)) {
props.handleClickFolder(file.path, file.type);
}
};
const handleDrag = () => {
if (context.dragged !== file.path) {
context.currentlyMoved(file.path);
}
};
if (props.isDraggable) {
return <>{props.children}</>;
}
return (
<span
ref={dragRef}
draggable
onDrop={(event) => {
handleDrop(event);
}}
onDragStart={() => {
if (file) {
handleDrag();
}
}}
onDragOver={(event) => {
if (file && file.isDirectory) {
handleDragover(event);
}
}}
>
{props.children}
</span>
);
};

@ -0,0 +1,23 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

@ -1,7 +0,0 @@
# remix-ui-editor-context-view
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-editor-context-view` to execute the unit tests via [Jest](https://jestjs.io).

@ -1 +0,0 @@
export * from './lib/remix-ui-editor-context-view'

@ -1,43 +0,0 @@
.container-context-view {
padding : 1px 15px;
}
.line {
display : flex;
align-items : center;
text-overflow : ellipsis;
overflow : hidden;
white-space : nowrap;
font-size : 13px;
}
.type {
font-style : italic;
margin-right : 5px;
}
.name {
font-weight : bold;
}
.jump {
cursor : pointer;
margin : 0 5px;
}
.jump:hover {
color : var(--secondary);
}
.referencesnb {
float : right;
margin-left : 15px;
}
.gasEstimation {
margin-right : 15px;
display : flex;
align-items : center;
}
.gasStationIcon {
margin-right : 5px;
}
.contextviewcontainer {
z-index : 50;
border-radius : 1px;
border : 2px solid var(--secondary);
}

@ -1,207 +0,0 @@
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import './remix-ui-editor-context-view.css'
/* eslint-disable-next-line */
export type astNode = {
name: string,
id: number,
children?: Array<any>,
typeDescriptions: any,
nodeType: string,
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<astNode>) => void
export type ononCurrentFileChangedListener = (name: string) => void
export type gasEstimationType = {
executionCost: string,
codeDepositCost: string
}
export interface RemixUiEditorContextViewProps {
hide: boolean,
gotoLine: (line: number, column: number) => void,
openFile: (fileName: string) => void,
getLastCompilationResult: () => any,
offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any,
getCurrentFileName: () => string
onContextListenerChanged: (listener: onContextListenerChangedListener) => void
onCurrentFileChanged: (listener: ononCurrentFileChangedListener) => void
referencesOf: (nodes: astNode) => Array<astNode>
getActiveHighlights: () => Array<astNodeLight>
gasEstimation: (node: astNode) => gasEstimationType
declarationOf: (node: astNode) => astNode
}
function isDefinition (node: any) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
type nullableAstNode = astNode | null
export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) {
const loopOverReferences = useRef(0)
const currentNodeDeclaration = useRef<nullableAstNode>(null)
const [state, setState] = useState<{
nodes: Array<astNode>,
activeHighlights: Array<any>
gasEstimation: gasEstimationType
}>({
nodes: [],
activeHighlights: [],
gasEstimation: { executionCost: '', codeDepositCost: '' }
})
useEffect(() => {
props.onCurrentFileChanged(() => {
currentNodeDeclaration.current = null
setState(prevState => {
return { ...prevState, nodes: [], activeHighlights: [] }
})
})
props.onContextListenerChanged(async (nodes: Array<astNode>) => {
let nextNodeDeclaration
let nextNode
if (!props.hide && nodes && nodes.length) {
nextNode = nodes[nodes.length - 1]
if (!isDefinition(nextNode)) {
nextNodeDeclaration = await props.declarationOf(nextNode)
} else {
nextNodeDeclaration = nextNode
}
}
if (nextNodeDeclaration && currentNodeDeclaration.current && nextNodeDeclaration.id === currentNodeDeclaration.current.id) return
currentNodeDeclaration.current = nextNodeDeclaration
let gasEstimation
if (currentNodeDeclaration.current) {
if (currentNodeDeclaration.current.nodeType === 'FunctionDefinition') {
gasEstimation = await props.gasEstimation(currentNodeDeclaration.current)
}
}
const activeHighlights: Array<astNodeLight> = 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, activeHighlights, gasEstimation }
})
})
}, [])
/*
* show gas estimation
*/
const gasEstimation = (node) => {
if (node.nodeType === 'FunctionDefinition') {
const result: gasEstimationType = state.gasEstimation
const executionCost = ' Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return (
<div className="gasEstimation">
<i className="fas fa-gas-pump gasStationIcon" title='Gas estimation'></i>
<span>{estimatedGas}</span>
</div>
)
} else {
return (<div></div>)
}
}
/*
* onClick jump to ast node in the editor
*/
const _jumpToInternal = async (position: any) => {
const jumpToLine = async (fileName: string, lineColumn: any) => {
if (fileName !== await props.getCurrentFileName()) {
await props.openFile(fileName)
}
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) {
props.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = await props.getLastCompilationResult()
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = await props.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
jumpToLine(filename, lineColumn)
}
}
const _render = () => {
const node = currentNodeDeclaration.current
if (!node) return (<div></div>)
const references = state.activeHighlights
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
const referencesCount = `${references ? references.length : '0'} reference(s)`
const nodes: Array<astNodeLight> = state.activeHighlights
const jumpTo = () => {
if (node && node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
_jumpToInternal(position)
}
}
}
// JUMP BETWEEN REFERENCES
const jump = (e: any) => {
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 (
<div className="line">{gasEstimation(node)}
<div title={type} className="type">{type}</div>
<div title={node.name} className="name mr-2">{node.name}</div>
<i className="fas fa-share jump" data-action='gotoref' aria-hidden="true" onClick={jumpTo}></i>
<span className="referencesnb">{referencesCount}</span>
<i data-action='previous' className="fas fa-chevron-up jump" aria-hidden="true" onClick={jump}></i>
<i data-action='next' className="fas fa-chevron-down jump" aria-hidden="true" onClick={jump}></i>
</div>
)
}
return (
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0 py-1">
{_render()}
</div>
)
}
export default RemixUiEditorContextView

@ -1,16 +0,0 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -21,7 +21,12 @@ export const reducerActions = (models = initialState, action: Action) => {
const readOnly = action.payload.readOnly
if (models[uri]) return models // already existing
models[uri] = { language, uri, readOnly }
const model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri))
let model
try {
model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri))
} catch (e) {
}
models[uri].model = model
model.onDidChangeContent(() => action.payload.events.onDidChangeContent(uri))
return models
@ -54,20 +59,20 @@ export const reducerActions = (models = initialState, action: Action) => {
case 'REVEAL_RANGE': {
if (!editor) return models
const range: IRange = {
startLineNumber: action.payload.startLineNumber +1,
startLineNumber: action.payload.startLineNumber + 1,
startColumn: action.payload.startColumn,
endLineNumber: action.payload.endLineNumber + 1,
endColumn: action.payload.endColumn
}
// reset to start of line
if(action.payload.startColumn < 100){
if (action.payload.startColumn < 100) {
editor.revealRange({
startLineNumber: range.startLineNumber,
startColumn: 1,
endLineNumber: range.endLineNumber,
endColumn: 1
})
}else{
} else {
editor.revealRangeInCenter(range)
}
return models

@ -0,0 +1,662 @@
import { IRange } from "monaco-editor";
import monaco from "../../../types/monaco";
export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: 'concatenate an arbitrary number of string values',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'concat(${1:string})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'concat()',
range,
},
]
}
export function getBytesCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: 'concatenate an arbitrary number of values',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'concat(${1:bytes})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'concat()',
range,
},
]
}
export function getBlockCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: '(address): Current block miner’s address',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'coinbase',
label: 'coinbase',
range,
},
{
detail: '(uint): Current block’s base fee',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'basefee',
label: 'basefee',
range,
},
{
detail: '(uint): Current chain id',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'chainid',
label: 'chainid',
range,
},
{
detail: '(bytes32): DEPRICATED In 0.4.22 use blockhash(uint) instead. Hash of the given block - only works for 256 most recent blocks excluding current',
insertText: 'blockhash(${1:blockNumber});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'blockhash',
range
},
{
detail: '(uint): current block difficulty',
kind: monaco.languages.CompletionItemKind.Property,
label: 'difficulty',
insertText: 'difficulty',
range
},
{
detail: '(uint): current block gaslimit',
kind: monaco.languages.CompletionItemKind.Property,
label: 'gaslimit',
insertText: 'gaslimit',
range
},
{
detail: '(uint): current block number',
kind: monaco.languages.CompletionItemKind.Property,
label: 'number',
insertText: 'number',
range
},
{
detail: '(uint): current block timestamp as seconds since unix epoch',
kind: monaco.languages.CompletionItemKind.Property,
label: 'timestamp',
insertText: 'timestamp',
range
},
];
}
export function getCompletionSnippets(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
label: 'contract',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'contract ${1:Name} {\n\t$0\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'library',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'library ${1:Name} {\n\t$0\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'interface',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'interface ${1:Name} {\n\t$0\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'enum',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'enum ${1:Name} {${2:item1}, ${3:item2} }',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'function',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'function ${1:name}(${2:params}) {\n\t${3:code}\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'constructor',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'constructor(${1:params}) {\n\t${2:code}\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'ifstatement',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'if (${1:condition}) {\n\t${2:code}\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'ifstatementelse',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'if (${1:condition}) {\n\t${2:code}\n} else {\n\t${3:code}\n}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
},
{
label: 'pragma',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: '// SPDX-License-Identifier: MIT\npragma solidity ${1:version};',
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,
insertText: '// SPDX-License-Identifier: MIT',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
}
]
}
export function getTxCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: '(uint): gas price of the transaction',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'gas',
label: 'gas',
range
},
{
detail: '(address): sender of the transaction (full call chain)',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'origin',
label: 'origin',
range
},
];
}
export function getMsgCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: '(bytes): complete calldata',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'data',
label: 'data',
range
},
{
detail: '(uint): remaining gas DEPRICATED in 0.4.21 use gasleft()',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'gas',
label: 'gas',
range
},
{
detail: '(address): sender of the message (current call)',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'sender',
label: 'sender',
range
},
{
detail: '(bytes4): first four bytes of the calldata (i.e. export function identifier)',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'sig',
label: 'sig',
range
},
{
detail: '(uint): number of wei sent with the message',
kind: monaco.languages.CompletionItemKind.Property,
insertText: 'value',
label: 'value',
range
},
];
}
export function getAbiCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: 'encode(..) returs (bytes): ABI-encodes the given arguments',
insertText: 'encode(${1:arg});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'encode',
range
},
{
detail: 'encodeCall(function functionPointer, (...)) returns (bytes memory) ABI-encodes a call to functionPointer with the arguments found in the tuple',
insertText: 'encode(${1:arg});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'encodecall',
range
},
{
detail: 'encodePacked(..) returns (bytes): Performes packed encoding of the given arguments',
insertText: 'encodePacked(${1:arg});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'encodePacked',
range
},
{
detail: 'encodeWithSelector(bytes4,...) returns (bytes): ABI-encodes the given arguments starting from the second and prepends the given four-byte selector',
insertText: 'encodeWithSelector(${1:bytes4}, ${2:arg});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'encodeWithSelector',
range
},
{
detail: 'encodeWithSignature(string,...) returns (bytes): Equivalent to abi.encodeWithSelector(bytes4(keccak256(signature), ...)`',
insertText: 'encodeWithSignature(${1:signatureString}, ${2:arg});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'encodeWithSignature',
range
},
];
}
export function GetCompletionTypes(range: IRange, monaco): monaco.languages.CompletionItem[] {
const completionItems = [];
const types = ['address', 'string', 'bytes', 'byte', 'int', 'uint', 'bool', 'hash'];
for (let index = 8; index <= 256; index += 8) {
types.push('int' + index);
types.push('uint' + index);
types.push('bytes' + index / 8);
}
types.forEach(type => {
const completionItem = CreateCompletionItem(type, monaco.languages.CompletionItemKind.Keyword, type + ' type', range);
completionItems.push(completionItem);
});
// add mapping
return completionItems;
}
function CreateCompletionItem(label: string, kind: monaco.languages.CompletionItemKind, detail: string, range: IRange) {
const completionItem: monaco.languages.CompletionItem = {
label,
kind,
detail,
insertText: label,
range
}
completionItem.kind = kind;
completionItem.detail = detail;
return completionItem;
}
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',
'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',
'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 => {
const completionItem: monaco.languages.CompletionItem = {
label: unit,
kind: monaco.languages.CompletionItemKind.Keyword,
detail: unit + ' keyword',
insertText: `${unit} `,
range
}
completionItems.push(completionItem);
});
completionItems.push(CreateCompletionItem('contract', monaco.languages.CompletionItemKind.Class, null, range));
completionItems.push(CreateCompletionItem('library', monaco.languages.CompletionItemKind.Class, null, range));
completionItems.push(CreateCompletionItem('storage', monaco.languages.CompletionItemKind.Field, null, range));
completionItems.push(CreateCompletionItem('calldata', monaco.languages.CompletionItemKind.Field, null, range));
completionItems.push(CreateCompletionItem('memory', monaco.languages.CompletionItemKind.Field, null, range));
completionItems.push(CreateCompletionItem('var', monaco.languages.CompletionItemKind.Field, null, range));
completionItems.push(CreateCompletionItem('constant', monaco.languages.CompletionItemKind.Constant, null, range));
completionItems.push(CreateCompletionItem('immutable', monaco.languages.CompletionItemKind.Keyword, null, range));
completionItems.push(CreateCompletionItem('constructor', monaco.languages.CompletionItemKind.Constructor, null, range));
completionItems.push(CreateCompletionItem('event', monaco.languages.CompletionItemKind.Event, null, range));
completionItems.push(CreateCompletionItem('import', monaco.languages.CompletionItemKind.Module, null, range));
completionItems.push(CreateCompletionItem('enum', monaco.languages.CompletionItemKind.Enum, null, range));
completionItems.push(CreateCompletionItem('struct', monaco.languages.CompletionItemKind.Struct, null, range));
completionItems.push(CreateCompletionItem('function', monaco.languages.CompletionItemKind.Function, null, range));
return completionItems;
}
export function GeCompletionUnits(range: IRange, monaco): monaco.languages.CompletionItem[] {
const completionItems = [];
const etherUnits = ['wei', 'gwei', 'finney', 'szabo', 'ether'];
etherUnits.forEach(unit => {
const completionItem = CreateCompletionItem(unit, monaco.languages.CompletionItemKind.Unit, unit + ': ether unit', range);
completionItems.push(completionItem);
});
const timeUnits = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'years'];
timeUnits.forEach(unit => {
const completionItem = CreateCompletionItem(unit, monaco.languages.CompletionItemKind.Unit, unit + ': time unit', range);
completionItem.kind = monaco.languages.CompletionItemKind.Unit;
if (unit !== 'years') {
completionItem.detail = unit + ': time unit';
} else {
completionItem.detail = 'DEPRECATED: ' + unit + ': time unit';
}
completionItems.push(completionItem);
});
return completionItems;
}
export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: 'Current block',
kind: monaco.languages.CompletionItemKind.Variable,
insertText: 'block',
label: 'block',
range
},
{
detail: 'Current Message',
kind: monaco.languages.CompletionItemKind.Variable,
insertText: 'msg',
label: 'msg',
range
},
{
detail: '(uint): current block timestamp (alias for block.timestamp)',
kind: monaco.languages.CompletionItemKind.Variable,
insertText: 'now',
label: 'now',
range
},
{
detail: 'Current transaction',
kind: monaco.languages.CompletionItemKind.Variable,
label: 'tx',
insertText: 'tx',
range
},
{
detail: 'ABI encoding / decoding',
kind: monaco.languages.CompletionItemKind.Variable,
label: 'abi',
insertText: 'abi',
range
},
{
detail: '',
kind: monaco.languages.CompletionItemKind.Variable,
label: 'this',
insertText: 'this',
range
},
];
}
export function GetGlobalFunctions(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: 'assert(bool condition): throws if the condition is not met - to be used for internal errors.',
insertText: 'assert(${1:condition});',
kind: monaco.languages.CompletionItemKind.Function,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'assert',
range
},
{
detail: 'gasleft(): returns the remaining gas',
insertText: 'gasleft();',
kind: monaco.languages.CompletionItemKind.Function,
label: 'gasleft',
range
},
{
detail: 'unicode: converts string into unicode',
insertText: 'unicode"${1:text}"',
kind: monaco.languages.CompletionItemKind.Function,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'unicode',
range
},
{
detail: 'blockhash(uint blockNumber): hash of the given block - only works for 256 most recent, excluding current, blocks',
insertText: 'blockhash(${1:blockNumber});',
kind: monaco.languages.CompletionItemKind.Function,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'blockhash',
range
},
{
detail: 'require(bool condition): reverts if the condition is not met - to be used for errors in inputs or external components.',
insertText: 'require(${1:condition});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'require',
range
},
{
// tslint:disable-next-line:max-line-length
detail: 'require(bool condition, string message): reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.',
insertText: 'require(${1:condition}, ${2:message});',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'require',
range
},
{
detail: 'revert(): abort execution and revert state changes',
insertText: 'revert();',
kind: monaco.languages.CompletionItemKind.Method,
label: 'revert',
range
},
{
detail: 'addmod(uint x, uint y, uint k) returns (uint):' +
'compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256',
insertText: 'addmod(${1:x}, ${2:y}, ${3:k})',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'addmod',
range
},
{
detail: 'mulmod(uint x, uint y, uint k) returns (uint):' +
'compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256',
insertText: 'mulmod(${1:x}, ${2:y}, ${3:k})',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'mulmod',
range
},
{
detail: 'keccak256(...) returns (bytes32):' +
'compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments',
insertText: 'keccak256(${1:x})',
kind: monaco.languages.CompletionItemKind.Method,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'keccak256',
range
},
{
detail: 'sha256(...) returns (bytes32):' +
'compute the SHA-256 hash of the (tightly packed) arguments',
insertText: 'sha256(${1:x})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
kind: monaco.languages.CompletionItemKind.Method,
label: 'sha256',
range
},
{
detail: 'sha3(...) returns (bytes32):' +
'alias to keccak256',
insertText: 'sha3(${1:x})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
kind: monaco.languages.CompletionItemKind.Method,
label: 'sha3',
range
},
{
detail: 'ripemd160(...) returns (bytes20):' +
'compute RIPEMD-160 hash of the (tightly packed) arguments',
insertText: 'ripemd160(${1:x})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
kind: monaco.languages.CompletionItemKind.Method,
label: 'ripemd160',
range
},
{
detail: 'ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):' +
'recover the address associated with the public key from elliptic curve signature or return zero on error',
insertText: 'ecrecover(${1:hash}, ${2:v}, ${3:r}, ${4:s})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
kind: monaco.languages.CompletionItemKind.Method,
label: 'ecrecover',
range
},
];
}
export function getContextualAutoCompleteByGlobalVariable(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] {
if (word === 'block') {
return getBlockCompletionItems(range, monaco);
}
if (word === 'string') {
return getStringCompletionItems(range, monaco);
}
if (word === 'bytes') {
return getBytesCompletionItems(range, monaco);
}
if (word === 'msg') {
return getMsgCompletionItems(range, monaco);
}
if (word === 'tx') {
return getTxCompletionItems(range, monaco);
}
if (word === 'abi') {
return getAbiCompletionItems(range, monaco);
}
if (word === 'sender') {
return getAddressCompletionItems(range, monaco);
}
return null;
}
export function getArrayCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: '',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'length;',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'length',
range,
},
{
detail: '',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'push(${1:value});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'push(value)',
range,
},
{
detail: '',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'push();',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'push()',
range,
},
{
detail: '',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'pop();',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'pop()',
range,
},
]
}
export function getAddressCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] {
return [
{
detail: '(uint256): balance of the Address in Wei',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'balance;',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'balance',
range,
},
{
detail: '(bytes memory): code at the Address (can be empty)',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'code;',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'code',
range,
},
{
detail: '(bytes32): the codehash of the Address',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'codehash;',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'codehash',
range,
},
{
detail: '(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'send(${1:value});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'send()',
range,
},
{
detail: '(uint256 amount): send given amount of Wei to Address, throws on failure',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'transfer(${1:value});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
label: 'transfer()',
range,
},
]
}
export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] {
if (word === 'ArrayTypeName') {
return getArrayCompletionItems(range, monaco);
}
if (word === 'bytes') {
return getBytesCompletionItems(range, monaco);
}
if (word === 'address') {
return getAddressCompletionItems(range, monaco);
}
return [];
}

@ -0,0 +1,457 @@
import { sourceMappingDecoder } from "@remix-project/remix-debug"
import { AstNode } from "@remix-project/remix-solidity-ts"
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"
export class RemixCompletionProvider implements languages.CompletionItemProvider {
props: EditorUIProps
monaco: any
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
triggerCharacters = ['.', '']
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> {
const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion')
if(!completionSettings) return
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
const line = model.getLineContent(position.lineNumber)
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
// handles completion from for builtin types
if(lastNodeInExpression.memberName === 'sender') { // exception for this member
lastNodeInExpression.name = 'sender'
}
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) {
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 = {};
const filteredNodes = nodes.filter((node) => {
if (node.id) {
if (nodeIds[node.id]) {
return false;
}
nodeIds[node.id] = true;
}
return true;
});
const getNodeLink = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getNodeLink', node)
}
const getDocs = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getNodeDocumentation', node)
}
const getParamaters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node)
}
const completeParameters = async (parameters: any) => {
const localParam = (parameters && parameters.parameters) || (parameters)
if (localParam) {
const params = []
for (const key in localParam) {
params.push('${' + (key + 1) + ':' + localParam[key].name + '}')
}
return `(${params.join(', ')})`
}
}
const getVariableDeclaration = async (node: any) => {
let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node)
if (node.scope) {
const scopeNode = await this.props.plugin.call('codeParser', 'getNodeById', node.scope)
if (scopeNode) {
variableDeclaration = `${scopeNode.name}.${variableDeclaration}`
}
}
return variableDeclaration
}
for (const node of Object.values(filteredNodes) as any[]) {
if (!node.name) continue
if (node.nodeType === 'VariableDeclaration') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${await getVariableDeclaration(node)}` },
kind: this.monaco.languages.CompletionItemKind.Variable,
insertText: node.name,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if (node.nodeType === 'FunctionDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` },
kind: this.monaco.languages.CompletionItemKind.Function,
insertText: `${node.name}${await completeParameters(node.parameters)};`,
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'ContractDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Interface,
insertText: node.name,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'StructDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Struct,
insertText: node.name,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'EnumDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Enum,
insertText: node.name,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'EventDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` },
kind: this.monaco.languages.CompletionItemKind.Event,
insertText: `${node.name}${await completeParameters(node.parameters)};`,
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'ModifierDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Method,
insertText: node.name,
range: range,
documentation: await getDocs(node)
}
suggestions.push(completion)
} else if
(node.nodeType === 'EnumValue' || node.type === 'EnumValue') {
const completion = {
label: { label: `"${node.name}"` },
kind: this.monaco.languages.CompletionItemKind.EnumMember,
insertText: node.name,
range: range,
documentation: null
}
suggestions.push(completion)
}
}
return {
suggestions
}
}
private getBlockNodesAtPosition = async (position: Position) => {
let nodes: any[] = []
const cursorPosition = this.props.editorAPI.getCursorPosition()
const nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
// try to get the block from ANTLR of which the position is in
const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null)
// if the block has a name and a type we can maybe find it in the contract nodes
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes')
if (isArray(nodesAtPosition) && nodesAtPosition.length) {
for (const node of nodesAtPosition) {
// try to find the real block in the AST and get the nodes in that scope
if (node.nodeType === 'ContractDefinition') {
const contractNodes = fileNodes.contracts[node.name].contractNodes
for (const contractNode of Object.values(contractNodes)) {
if (contractNode['name'] === ANTLRBlock.name) {
let nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode as any).id)
nodes = [...nodes, ...nodeOfScope]
if (contractNode['body']) {
nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode['body'] as any).id)
nodes = [...nodes, ...nodeOfScope]
}
}
}
}
// blocks can have statements
/*
if (node.statements){
console.log('statements', node.statements)
for (const statement of node.statements) {
if(statement.expression){
const declaration = await this.props.plugin.call('codeParser', 'declarationOf', statement.expression)
declaration.outSideBlock = true
nodes = [...nodes, declaration]
}
}
}
*/
}
}
// we are only interested in nodes that are in the same block as the cursor
nodes = nodes.filter(node => {
if (node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position.start >= ANTLRBlock.range[0] && (position.start + position.length) <= ANTLRBlock.range[1]) {
return true
}
}
if(node.outSideBlock){ return true }
return false
})
return nodes;
}
private getContractCompletions = async (position: Position) => {
let nodes: any[] = []
const cursorPosition = this.props.editorAPI.getCursorPosition()
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
// if no nodes exits at position, try to get the block of which the position is in
const block = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null)
if (!nodesAtPosition.length) {
if (block) {
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0])
}
}
// find the contract and get the nodes of the contract and the base contracts and imports
if (isArray(nodesAtPosition) && nodesAtPosition.length) {
let contractNode: any = {}
for (const node of nodesAtPosition) {
if (node.nodeType === 'ContractDefinition') {
contractNode = node
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes')
const contractNodes = fileNodes.contracts[node.name]
nodes = [...Object.values(contractNodes.contractScopeNodes), ...nodes]
nodes = [...Object.values(contractNodes.baseNodesWithBaseContractScope), ...nodes]
nodes = [...Object.values(fileNodes.imports), ...nodes]
// at the nodes at the block itself
nodes = [...nodes, ...await this.getBlockNodesAtPosition(position)]
// filter private nodes, only allow them when contract ID is the same as the current contract
nodes = nodes.filter(node => {
if (node.visibility) {
if (node.visibility === 'private') {
return (node.contractId ? node.contractId === contractNode.id : false) || false
}
}
return true
})
break;
}
}
} else {
// get all the nodes from a simple code parser which only parses the current file
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')]
}
return nodes
}
private getThisCompletions = async (position: Position) => {
let nodes: any[] = []
let thisCompletionNodes = await this.getContractCompletions(position)
const allowedTypesForThisCompletion = ['VariableDeclaration', 'FunctionDefinition']
// with this. you can't have internal nodes and no contractDefinitions
thisCompletionNodes = thisCompletionNodes.filter(node => {
if (node.visibility && (node.visibility === 'internal' || node.visibility === 'private')) {
return false
}
if (node.nodeType && !allowedTypesForThisCompletion.includes(node.nodeType)) {
return false
}
return true
})
nodes = [...nodes, ...thisCompletionNodes]
setTimeout(() => {
// eslint-disable-next-line no-debugger
// debugger
}, 2000)
return nodes
}
private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string, range) => {
const contractCompletions = await this.getContractCompletions(position)
let nodes: any[] = []
let suggestions: monaco.languages.CompletionItem[] = []
const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => {
return nodes && nodes.filter(node => {
if (node.visibility) {
if (declarationOf && declarationOf.nodeType && declarationOf.nodeType === 'StructDefinition') {
return true
}
if ((node.visibility === 'internal' && !parentNode.isBaseNode) || node.visibility === 'private') {
return false
}
}
return true
})
}
for (const nodeOfScope of contractCompletions) {
if (nodeOfScope.name === nameOfLastTypedExpression) {
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
nodes = [...nodes,
...filterNodes(declarationOf.nodes, nodeOfScope, declarationOf)
|| filterNodes(declarationOf.members, nodeOfScope, declarationOf)]
const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
for (const baseContract of baseContracts) {
nodes = [...nodes, ...filterNodes(baseContract.nodes, nodeOfScope)]
}
} else if (nodeOfScope.members) {
nodes = [...nodes, ...filterNodes(nodeOfScope.members, nodeOfScope)]
} else if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ArrayTypeName') {
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('ArrayTypeName', range, this.monaco)]
} else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'bytes') {
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('bytes', range, this.monaco)]
} else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'address') {
suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('address', range, this.monaco)]
}
}
}
return { nodes, suggestions }
}
private getlinearizedBaseContracts = async (node: any) => {
let params = []
if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) {
if (id !== node.id) {
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id)
params = [...params, ...[baseContract]]
}
}
}
return params
}
/**
*
* @param lineTextBeforeCursor
* @returns
*/
private async getLastNodeInExpression(lineTextBeforeCursor: string) {
const wrapLineInFunction = async (text: string) => {
return `function() {
${text}
}`
}
let lastNodeInExpression
const linesToCheck =
[
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;",
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}",
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);",
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;)"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode)"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);"),
]
for (const line of linesToCheck) {
try {
const lineAst = await this.props.plugin.call('codeParser', 'parseSolidity', line)
const lastNode = await this.props.plugin.call('codeParser', 'getLastNodeInLine', lineAst)
if (lastNode) {
lastNodeInExpression = lastNode
break
}
} catch (e) {
}
}
return lastNodeInExpression
}
}

@ -0,0 +1,49 @@
import { Monaco } from "@monaco-editor/react"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor"
export class RemixDefinitionProvider implements monaco.languages.DefinitionProvider {
props: EditorUIProps
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise<monaco.languages.Definition | monaco.languages.LocationLink[]> {
const cursorPosition = this.props.editorAPI.getCursorPosition()
await this.jumpToDefinition(cursorPosition)
return null
}
async jumpToDefinition(position: any) {
const node = await this.props.plugin.call('codeParser', 'definitionAtPosition', position)
const sourcePosition = await this.props.plugin.call('codeParser', 'positionOfDefinition', node)
if (sourcePosition) {
await this.jumpToPosition(sourcePosition)
}
}
/*
* onClick jump to position of ast node in the editor
*/
async jumpToPosition(position: any) {
const jumpToLine = async (fileName: string, lineColumn: any) => {
if (fileName !== await this.props.plugin.call('fileManager', 'file')) {
await this.props.plugin.call('contentImport', 'resolveAndSave', fileName, null)
await this.props.plugin.call('fileManager', 'open', fileName)
}
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) {
this.props.plugin.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') // await this.props.plugin.call('compilerArtefacts', 'getLastCompilationResult')
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position)
const filename = lastCompilationResult.getSourceName(position.file)
jumpToLine(filename, lineColumn)
}
}
}

@ -0,0 +1,38 @@
import { Monaco } from "@monaco-editor/react"
import { sourceMappingDecoder } from "@remix-project/remix-debug"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor"
export class RemixHighLightProvider implements monaco.languages.DocumentHighlightProvider {
props: EditorUIProps
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async provideDocumentHighlights(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise<monaco.languages.DocumentHighlight[]> {
const cursorPosition = this.props.editorAPI.getCursorPosition()
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition)
const highlights: monaco.languages.DocumentHighlight[] = []
if (nodes && nodes.length) {
const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult')
const file = await this.props.plugin.call('fileManager', 'file')
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) {
for (const node of nodes) {
const position = sourceMappingDecoder.decode(node.src)
const fileInNode = compilationResult.getSourceName(position.file)
if (fileInNode === file) {
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position)
const range = new this.monaco.Range(lineColumn.start.line + 1, lineColumn.start.column + 1, lineColumn.end.line + 1, lineColumn.end.column + 1)
highlights.push({
range,
})
}
}
}
}
return highlights
}
}

@ -0,0 +1,166 @@
import { Monaco } from '@monaco-editor/react'
import { editor, languages, Position } from 'monaco-editor'
import { EditorUIProps } from '../remix-ui-editor'
export class RemixHoverProvider implements languages.HoverProvider {
props: EditorUIProps
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
provideHover = async function (model: editor.ITextModel, position: Position): Promise<languages.Hover> {
const cursorPosition = this.props.editorAPI.getHoverPosition(position)
const nodeAtPosition = await this.props.plugin.call('codeParser', 'definitionAtPosition', cursorPosition)
const contents = []
const getDocs = async (node: any) => {
contents.push({
value: await this.props.plugin.call('codeParser', 'getNodeDocumentation', node)
})
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const getScope = async (node: any) => {
if (node.id) {
contents.push({
value: `id: ${node.id}`
})
}
if (node.scope) {
contents.push({
value: `scope: ${node.scope}`
})
}
}
const getLinks = async (node: any) => {
contents.push({
value: await this.props.plugin.call('codeParser', 'getNodeLink', node)
})
}
const getVariableDeclaration = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getVariableDeclaration', node)
}
const getParamaters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node)
}
const getReturnParameters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionReturnParameters', node)
}
const getOverrides = async (node: any) => {
if (node.overrides) {
const overrides = []
for (const override of node.overrides.overrides) {
overrides.push(override.name)
}
if (overrides.length)
return ` overrides (${overrides.join(', ')})`
}
return ''
}
const getlinearizedBaseContracts = async (node: any) => {
const params = []
if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) {
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id)
params.push(
baseContract.name
)
}
if (params.length)
return `is ${params.join(', ')}`
}
return ''
}
if (nodeAtPosition) {
if (nodeAtPosition.absolutePath) {
const target = await this.props.plugin.call('fileManager', 'getPathFromUrl', nodeAtPosition.absolutePath)
if (target.file !== nodeAtPosition.absolutePath) {
contents.push({
value: `${target.file}`
})
}
contents.push({
value: `${nodeAtPosition.absolutePath}`
})
}
if (nodeAtPosition.nodeType === 'VariableDeclaration') {
contents.push({
value: await getVariableDeclaration(nodeAtPosition)
})
}
else if (nodeAtPosition.nodeType === 'ElementaryTypeName') {
contents.push({
value: `${nodeAtPosition.typeDescriptions.typeString}`
})
} else if (nodeAtPosition.nodeType === 'FunctionDefinition') {
if (!nodeAtPosition.name) return
const returns = await getReturnParameters(nodeAtPosition)
contents.push({
value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} ${returns ? `returns ${returns}` : ''}`
})
} else if (nodeAtPosition.nodeType === 'ModifierDefinition') {
contents.push({
value: `modifier ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)}`
})
} else if (nodeAtPosition.nodeType === 'EventDefinition') {
contents.push({
value: `modifier ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)}`
})
} else if (nodeAtPosition.nodeType === 'ContractDefinition') {
contents.push({
value: `${nodeAtPosition.contractKind || nodeAtPosition.kind} ${nodeAtPosition.name} ${await getlinearizedBaseContracts(nodeAtPosition)}`
})
} else if (nodeAtPosition.nodeType === 'InvalidNode') {
contents.push({
value: `There are errors in the code.`
})
} else if (nodeAtPosition.nodeType === 'Block') {
} else {
contents.push({
value: `${nodeAtPosition.nodeType}`
})
}
for (const key in contents) {
contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```'
}
getLinks(nodeAtPosition)
getDocs(nodeAtPosition)
// getScope(nodeAtPosition)
}
setTimeout(() => {
// eslint-disable-next-line no-debugger
// debugger
},1000)
return {
range: new this.monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
model.getLineMaxColumn(position.lineNumber)
),
contents: contents
};
}
}

@ -0,0 +1,47 @@
import { Monaco } from "@monaco-editor/react"
import { sourceMappingDecoder } from "@remix-project/remix-debug"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor"
export class RemixReferenceProvider implements monaco.languages.ReferenceProvider {
props: EditorUIProps
monaco: Monaco
constructor(props: any, monaco: any) {
this.props = props
this.monaco = monaco
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async provideReferences(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.ReferenceContext, token: monaco.CancellationToken) {
const cursorPosition = this.props.editorAPI.getCursorPosition()
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition)
const references = []
if (nodes && nodes.length) {
const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult')
const file = await this.props.plugin.call('fileManager', 'file')
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) {
for (const node of nodes) {
const position = sourceMappingDecoder.decode(node.src)
const fileInNode = compilationResult.getSourceName(position.file)
let fileTarget = await this.props.plugin.call('fileManager', 'getPathFromUrl', fileInNode)
fileTarget = fileTarget.file
const fileContent = await this.props.plugin.call('fileManager', 'readFile', fileInNode)
const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position)
console.log(position, fileTarget, lineColumn)
try {
this.props.plugin.call('editor', 'addModel', fileTarget, fileContent)
} catch (e) {
}
const range = new this.monaco.Range(lineColumn.start.line + 1, lineColumn.start.column + 1, lineColumn.end.line + 1, lineColumn.end.column + 1)
references.push({
range,
uri: this.monaco.Uri.parse(fileTarget)
})
}
}
}
return references
}
}

@ -1,23 +1,21 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { RemixUiEditorContextView, astNode } from '@remix-ui/editor-context-view'
import Editor, { loader } from '@monaco-editor/react'
import Editor, { loader, Monaco } from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { IMarkdownString } from 'monaco-editor'
import './remix-ui-editor.css'
import { loadTypes } from './web-types'
import monaco from '../types/monaco'
import { MarkerSeverity } from 'monaco-editor'
import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor'
type cursorPosition = {
startLineNumber: number,
startColumn: number,
endLineNumber: number,
endColumn: number
}
import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider'
type sourceAnnotation = {
row: number,
@ -64,7 +62,7 @@ export type lineText = {
type errorMarker = {
message: string
severity: MarkerSeverity
severity: MarkerSeverity | 'warning' | 'info' | 'error' | 'hint'
position: {
start: {
line: number
@ -105,15 +103,15 @@ export interface EditorUIProps {
findMatches: (uri: string, value: string) => any
getFontSize: () => number,
getValue: (uri: string) => string
getCursorPosition: () => cursorPosition
getCursorPosition: () => number
getHoverPosition: (position: IPosition) => number
addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: []) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}) => void
addErrorMarker: (errors: errorMarker[], from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void
}
}
export const EditorUI = (props: EditorUIProps) => {
const [, setCurrentBreakpoints] = useState({})
const defaultEditorValue = `
@ -136,7 +134,7 @@ export const EditorUI = (props: EditorUIProps) => {
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n
`
const editorRef = useRef(null)
const monacoRef = useRef(null)
const monacoRef = useRef<Monaco>(null)
const currentFileRef = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
// const registeredDecorations = useRef({}) // registered decorations
@ -344,6 +342,18 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
if (typeOfDecoration === 'lineTextPerFile') {
const lineTextDecoration = decoration as lineText
return {
type: typeOfDecoration,
range: new monacoRef.current.Range(lineTextDecoration.position.start.line + 1, lineTextDecoration.position.start.column + 1, lineTextDecoration.position.start.line + 1, 1024),
options: {
after: { content: ` ${lineTextDecoration.content}`, inlineClassName: `${lineTextDecoration.className}` },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage : lineTextDecoration.hoverMessage
},
}
}
}
props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => {
@ -390,7 +400,6 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorModelsState[filePath]?.model
if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
return {
currentDecorations: model.deltaDecorations([], [monacoDecoration]),
registeredDecorations: [{ value: decoration, type: typeOfDecoration }]
@ -401,7 +410,7 @@ export const EditorUI = (props: EditorUIProps) => {
return addDecoration(marker, filePath, typeOfDecoration)
}
props.editorAPI.addErrorMarker = async (errors: errorMarker[]) => {
props.editorAPI.addErrorMarker = async (errors: errorMarker[], from: string) => {
const allMarkersPerfile: Record<string, Array<monaco.editor.IMarkerData>> = {}
@ -419,7 +428,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
if (model) {
const markerData: monaco.editor.IMarkerData = {
severity: errorServerityMap[error.severity],
severity: (typeof error.severity === 'string') ? errorServerityMap[error.severity] : error.severity,
startLineNumber: ((error.position.start && error.position.start.line) || 0),
startColumn: ((error.position.start && error.position.start.column) || 0),
endLineNumber: ((error.position.end && error.position.end.line) || 0),
@ -435,18 +444,18 @@ export const EditorUI = (props: EditorUIProps) => {
for (const filePath in allMarkersPerfile) {
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', allMarkersPerfile[filePath])
monacoRef.current.editor.setModelMarkers(model, from, allMarkersPerfile[filePath])
}
}
}
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}) => {
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}, from: string) => {
if (sources) {
for (const source of (Array.isArray(sources) ? sources : Object.keys(sources))) {
const filePath = source
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', [])
monacoRef.current.editor.setModelMarkers(model, from, [])
}
}
}
@ -474,6 +483,16 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
props.editorAPI.getHoverPosition = (position: monaco.Position) => {
if (!monacoRef.current) return
const model = editorModelsState[currentFileRef.current]?.model
if (model) {
return model.getOffsetAt(position)
} else {
return 0
}
}
props.editorAPI.getFontSize = () => {
if (!editorRef.current) return
return editorRef.current.getOption(43).fontSize
@ -518,10 +537,10 @@ export const EditorUI = (props: EditorUIProps) => {
})
// zoomin zoomout
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_EQUAL, () => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_EQUAL, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize + 1 })
})
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | (monacoRef.current.KeyCode as any).US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 })
})
@ -574,14 +593,20 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.register({ id: 'remix-zokrates' })
// Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig)
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig as any )
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoLanguageConfig as any)
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoLanguageConfig)
monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig as any)
monacoRef.current.languages.setMonarchTokensProvider('remix-zokrates', zokratesTokensProvider)
monacoRef.current.languages.setLanguageConfiguration('remix-zokrates', zokratesLanguageConfig)
monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco))
monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco))
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
loadTypes(monacoRef.current)
}
@ -597,22 +622,7 @@ export const EditorUI = (props: EditorUIProps) => {
options={{ glyphMargin: true, readOnly: true }}
defaultValue={defaultEditorValue}
/>
<div className="contextview">
<RemixUiEditorContextView
hide={false}
gotoLine={(line, column) => props.plugin.call('editor', 'gotoLine', line, column)}
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) }}
declarationOf={(node: astNode) => { return props.plugin.call('contextualListener', 'declarationOf', node) }}
/>
</div>
</div>
)
}

@ -222,5 +222,4 @@ export const loadTypes = async (monaco) => {
}
`
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi)
console.log('loaded monaco types')
}

@ -1866,7 +1866,7 @@ declare namespace monaco.editor {
/**
* Get the language associated with this model.
*/
getModeId(): string;
getModeId?(): string;
/**
* Get the word under or besides `position`.
* @param position The position to look for a word.

@ -9,5 +9,5 @@
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.d.ts"]
}

@ -15,7 +15,7 @@ export function RemixPluginPanel (props: RemixPanelProps) {
<>
{props.header}
<div className="pluginsContainer">
<div className='plugins' id='plugins'>
<div className='plugins pb-1' id='plugins'>
{Object.values(props.plugins).map((pluginRecord) => {
return <RemixUIPanelPlugin key={pluginRecord.profile.name} pluginRecord={pluginRecord} />
})}

@ -338,7 +338,7 @@ export const SearchProvider = ({
async function fetchWorkspace() {
try {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
if (workspace) {
if (workspace && workspace.name) {
value.setCurrentWorkspace(workspace.name)
setFiles(await getDirectory('/', plugin))
}

@ -14,6 +14,9 @@ export const etherscanAccessTokenText2 = 'Go to Etherscan api key page (link bel
export const ethereunVMText = 'Always use Remix VM at load'
export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for Remix Provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const useAutoCompleteText = 'Enable code completion in editor.'
export const useShowGasInEditorText = 'Display gas estimates in editor.'
export const displayErrorsText = 'Display errors in editor while typing.'
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings'

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText } from './constants'
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText, useAutoCompleteText, useShowGasInEditorText, displayErrorsText } from './constants'
import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast } from './settingsAction'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast, useAutoCompletion, useShowGasInEditor, useDisplayErrors } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
@ -23,7 +23,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [, dispatch] = useReducer(settingReducer, initialState)
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState)
const [tokenValue, setTokenValue] = useState({})
const [themeName, ] = useState('')
const [themeName,] = useState('')
const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('')
const [resetState, refresh] = useState(0)
@ -33,13 +33,21 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata')
if (metadataConfig === undefined || metadataConfig === null) generateContractMetadat(props.config, true, dispatch)
const javascriptVM = props.config.get('settings/always-use-vm')
if (javascriptVM === null || javascriptVM === undefined) ethereumVM(props.config, true, dispatch)
const useAutoComplete = props.config.get('settings/use-auto-complete')
if (useAutoComplete === null || useAutoComplete === undefined) useAutoCompletion(props.config, true, dispatch)
const displayErrors = props.config.get('settings/display-errors')
if (displayErrors === null || displayErrors === undefined) useDisplayErrors(props.config, true, dispatch)
const useShowGas = props.config.get('settings/show-gas')
if (useShowGas === null || useShowGas === undefined) useShowGasInEditor(props.config, true, dispatch)
}
useEffect(() => initValue(), [resetState, props.config])
useEffect(() => initValue(), [])
@ -88,7 +96,6 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
setipfsProjectSecret(configipfsProjectSecret)
}
}, [themeName, state.message])
useEffect(() => {
@ -115,6 +122,18 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
useMatomoAnalytics(props.config, event.target.checked, dispatch)
}
const onchangeUseAutoComplete = event => {
useAutoCompletion(props.config, event.target.checked, dispatch)
}
const onchangeShowGasInEditor = event => {
useShowGasInEditor(props.config, event.target.checked, dispatch)
}
const onchangeDisplayErrors = event => {
useDisplayErrors(props.config, event.target.checked, dispatch)
}
const getTextClass = (key) => {
if (props.config.get(key)) {
return textDark
@ -129,7 +148,9 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const isEditorWrapChecked = props.config.get('settings/text-wrap') || false
const isPersonalChecked = props.config.get('settings/personal-mode') || false
const isMatomoChecked = props.config.get('settings/matomo-analytics') || false
const isAutoCompleteChecked = props.config.get('settings/auto-completion') === null ? true:props.config.get('settings/auto-completion')
const isShowGasInEditorChecked = props.config.get('settings/show-gas') === null ? true:props.config.get('settings/show-gas')
const displayErrorsChecked = props.config.get('settings/display-errors') === null ? true:props.config.get('settings/display-errors')
return (
<div className="$border-top">
<div title="Reset to Default settings." className='d-flex justify-content-end pr-4'>
@ -154,26 +175,44 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<div className="card-body pt-3 pb-2">
<h6 className="card-title">General settings</h6>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked = { isMetadataChecked }/>
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked={isMetadataChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/generate-contract-metadata')}`} data-id="settingsTabGenerateContractMetadataLabel" htmlFor="generatecontractmetadata">{generateContractMetadataText}</label>
</div>
<div className="fmt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={ isEthereumVMChecked }/>
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={isEthereumVMChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/always-use-vm')}`} htmlFor="alwaysUseVM">{ethereunVMText}</label>
</div>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked = { isEditorWrapChecked }/>
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked={isEditorWrapChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/text-wrap')}`} htmlFor="editorWrap">{wordWrapText}</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeUseAutoComplete} id="settingsUseAutoComplete" type="checkbox" className="custom-control-input" checked={isAutoCompleteChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/auto-completion')}`} htmlFor="settingsUseAutoComplete">
<span>{useAutoCompleteText}</span>
</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeShowGasInEditor} id="settingsUseShowGas" type="checkbox" className="custom-control-input" checked={isShowGasInEditorChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/show-gas')}`} htmlFor="settingsUseShowGas">
<span>{useShowGasInEditorText}</span>
</label>
</div>
<div className='custom-control custom-checkbox mb-1'>
<input onChange={onchangeDisplayErrors} id="settingsDisplayErrors" type="checkbox" className="custom-control-input" checked={displayErrorsChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/display-errors')}`} htmlFor="settingsDisplayErrors">
<span>{displayErrorsText}</span>
</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked = { isPersonalChecked }/>
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked={isPersonalChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/personal-mode')}`} htmlFor="personal">
<i className="fas fa-exclamation-triangle text-warning" aria-hidden="true"></i> <span> </span>
<span> </span>{enablePersonalModeText} {warnText}
</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={ isMatomoChecked }/>
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={isMatomoChecked} />
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics">
<span>{matomoAnalytics}</span>
<a href="https://medium.com/p/66ef69e14931/" target="_blank"> Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a>
@ -191,7 +230,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const removeToken = (type: string) => {
setTokenValue(prevState => {
return { ...prevState, [type]: ''}
return { ...prevState, [type]: '' }
})
removeTokenToast(props.config, dispatchToast, labels[type].key)
}
@ -199,7 +238,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const handleSaveTokenState = useCallback(
(event, type) => {
setTokenValue(prevState => {
return { ...prevState, [type]: event.target.value}
return { ...prevState, [type]: event.target.value }
})
},
[tokenValue]
@ -208,10 +247,10 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const token = (type: string) => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ labels[type].title }</h6>
<p className="mb-1">{ labels[type].message1 }</p>
<p className="">{ labels[type].message2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{ labels[type].link }</a></p>
<h6 className="card-title">{labels[type].title}</h6>
<p className="mb-1">{labels[type].message1}</p>
<p className="">{labels[type].message2}</p>
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{labels[type].link}</a></p>
<div className=""><label>TOKEN:</label>
<div className="text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] || '' } />
@ -250,12 +289,12 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<h6 className="card-title">{ swarmSettingsTitle }</h6>
<div className="pt-2 pt-2 mb-0 pb-0"><label>PRIVATE BEE ADDRESS:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmprivatebeeaddress" data-id="settingsPrivateBeeAddress" className="form-control" onChange={handleSavePrivateBeeAddress} value={ privateBeeAddress } />
<input id="swarmprivatebeeaddress" data-id="settingsPrivateBeeAddress" className="form-control" onChange={handleSavePrivateBeeAddress} value={privateBeeAddress} />
</div>
</div>
<div className="pt-2 mb-0 pb-0"><label>POSTAGE STAMP ID:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmpostagestamp" data-id="settingsPostageStampId" className="form-control" onChange={handleSavePostageStampId} value={ postageStampId } />
<input id="swarmpostagestamp" data-id="settingsPostageStampId" className="form-control" onChange={handleSavePostageStampId} value={postageStampId} />
<div className="d-flex justify-content-end pt-2">
</div>
</div>

@ -41,6 +41,21 @@ export const useMatomoAnalytics = (config, checked, dispatch) => {
}
}
export const useAutoCompletion = (config, checked, dispatch) => {
config.set('settings/auto-completion', checked)
dispatch({ type: 'useAutoCompletion', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const useShowGasInEditor = (config, checked, dispatch) => {
config.set('settings/show-gas', checked)
dispatch({ type: 'useShowGasInEditor', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const useDisplayErrors = (config, checked, dispatch) => {
config.set('settings/display-errors', checked)
dispatch({ type: 'displayErrors', payload: { isChecked: checked, textClass: checked ? textDark : textSecondary } })
}
export const saveTokenToast = (config, dispatch, tokenValue, key) => {
config.set('settings/' + key, tokenValue)
dispatch({ type: 'save', payload: { message: 'GitHub credentials updated' } })

@ -26,6 +26,21 @@ export const initialState = {
name: 'useMatomoAnalytics',
isChecked: false,
textClass: textSecondary
},
{
name: 'useAutoCompletion',
isChecked: true,
textClass: textSecondary
},
{
name: 'useShowGasInEditor',
isChecked: true,
textClass: textSecondary
},
{
name: 'displayErrors',
isChecked: true,
textClass: textSecondary
}
]
}
@ -82,9 +97,41 @@ export const settingReducer = (state, action) => {
return {
...state
}
case 'useAutoCompletion':
state.elementState.map(element => {
if (element.name === 'useAutoCompletion') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'displayErrors':
state.elementState.map(element => {
if (element.name === 'displayErrors') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'useShowGasInEditor':
state.elementState.map(element => {
if (element.name === 'useShowGasInEditor') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
default:
return initialState
}
}
export const toastInitialState = {

@ -23,4 +23,5 @@ export interface TreeViewItemProps {
onContextMenu?: (...args: any) => void,
onBlur?: (...args: any) => void,
showIcon?: boolean
expandedPaths?: string[];
}

@ -462,3 +462,13 @@ const saveAs = (blob, name) => {
}
}, 0) // 40s
}
export const moveFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
await fileManager.moveFile(src, dest)
} catch (error) {
dispatch(displayPopUp('Oops! An error ocurred while performing moveFile operation.' + error))
}
}

@ -10,6 +10,7 @@ import '../css/file-explorer.css'
import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath } from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileRender } from './file-render'
import { Drag } from "@remix-ui/drag-n-drop"
export const FileExplorer = (props: FileExplorerProps) => {
const { name, contextMenuItems, removedContextMenuItems, files, fileState } = props
@ -409,7 +410,16 @@ export const FileExplorer = (props: FileExplorerProps) => {
props.dispatchHandleExpandPath(expandPath)
}
const handleFileMove = (dest: string, dragged:string)=>{
try {
props.dispatchMoveFile(dragged, dest)
} catch (error) {
props.modal('Moving File Failed', 'Unexpected error while moving file: ' + dragged, 'Close', async () => {})
}
}
return (
<Drag onFileMoved={handleFileMove}>
<div ref={treeRef} tabIndex={0} style={{ outline: "none" }}>
<TreeView id='treeView'>
<TreeViewItem id="treeViewItem"
@ -444,6 +454,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
handleClickFolder={handleClickFolder}
handleContextMenu={handleContextMenu}
key={index}
/>)
}
</TreeView>
@ -473,6 +484,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
/>
}
</div>
</Drag>
)
}

@ -7,7 +7,7 @@ import { getPathIcon } from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileLabel } from './file-label'
import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'
import {Draggable} from "@remix-ui/drag-n-drop"
@ -82,7 +82,14 @@ export const FileRender = (props: RenderFileProps) => {
iconX='pr-3 fa fa-folder'
iconY='pr-3 fa fa-folder-open'
key={`${file.path + props.index}`}
label={<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />}
label={<>
<div className="d-flex flex-row">
<Draggable isDraggable={props.focusEdit.element!==null} file={file} expandedPath={props.expandPath} handleClickFolder={props.handleClickFolder}>
<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />
</Draggable>
<FileDecorationIcons file={file} fileDecorations={props.fileDecorations}/>
</div>
</>}
onClick={handleFolderClick}
onContextMenu={handleContextMenu}
labelClass={labelClass}
@ -90,6 +97,7 @@ export const FileRender = (props: RenderFileProps) => {
expand={props.expandPath.includes(file.path)}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
expandedPaths={props.expandPath}
>
{
file.child ? <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }>{
@ -121,7 +129,9 @@ export const FileRender = (props: RenderFileProps) => {
label={
<>
<div className="d-flex flex-row">
<FileLabel file={file} fileDecorations={props.fileDecorations} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />
<Draggable isDraggable={props.focusEdit.element!==null} file={file} expandedPath={props.expandPath} handleClickFolder={props.handleClickFolder}>
<FileLabel fileDecorations={props.fileDecorations} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />
</Draggable>
<FileDecorationIcons file={file} fileDecorations={props.fileDecorations}/>
</div>
</>
@ -132,6 +142,7 @@ export const FileRender = (props: RenderFileProps) => {
labelClass={labelClass}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
expandedPaths={props.expandPath}
/>
)
}

@ -29,6 +29,21 @@ export const FileSystemContext = createContext<{
dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise<void>
dispatchHandleExpandPath: (paths: string[]) => Promise<void>,
dispatchHandleDownloadFiles: () => Promise<void>,
dispatchHandleRestoreBackup: () => Promise<void>,
dispatchHandleRestoreBackup: () => Promise<void>
dispatchCloneRepository: (url: string) => Promise<void>
dispatchMoveFile: (src: string, dest: string) => Promise<void>,
}>(null)
interface MoveContextType{
dragged: string,
moveFile: (dest: string) => void
currentlyMoved: (path: string) => void
}
export const MoveContext = createContext<MoveContextType>({
dragged:"",
moveFile:( )=> {},
currentlyMoved: () => {}
})

@ -7,7 +7,7 @@ import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository } from '../actions'
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'
@ -128,7 +128,9 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
const dispatchCloneRepository = async (url: string) => {
await cloneRepository(url)
}
const dispatchMoveFile = async (src: string, dest: string) => {
await moveFile(src, dest)
}
useEffect(() => {
dispatchInitWorkspace()
}, [])
@ -231,7 +233,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchHandleExpandPath,
dispatchHandleDownloadFiles,
dispatchHandleRestoreBackup,
dispatchCloneRepository
dispatchCloneRepository,
dispatchMoveFile
}
return (
<FileSystemContext.Provider value={value}>
@ -244,3 +247,4 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
}
export default FileSystemProvider

@ -330,6 +330,7 @@ export function Workspace () {
dispatchRemoveInputField={global.dispatchRemoveInputField}
dispatchAddInputField={global.dispatchAddInputField}
dispatchHandleExpandPath={global.dispatchHandleExpandPath}
dispatchMoveFile={global.dispatchMoveFile}
/>
}
</div>
@ -367,6 +368,7 @@ export function Workspace () {
dispatchRemoveInputField={global.dispatchRemoveInputField}
dispatchAddInputField={global.dispatchAddInputField}
dispatchHandleExpandPath={global.dispatchHandleExpandPath}
dispatchMoveFile={global.dispatchMoveFile}
/>
}
</div>

@ -98,6 +98,7 @@ export interface FileExplorerProps {
dispatchRemoveInputField:(path: string) => Promise<void>,
dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise<void>,
dispatchHandleExpandPath: (paths: string[]) => Promise<void>
dispatchMoveFile: (src: string, dest: string) => Promise<void>,
}
export interface FileExplorerMenuProps {

@ -184,9 +184,6 @@
"solidity-unit-testing": {
"tags": []
},
"remix-ui-editor-context-view": {
"tags": []
},
"remix-ui-run-tab": {
"tags": []
},
@ -204,6 +201,9 @@
},
"etherscan": {
"tags": []
},
"remix-ui-drag-n-drop": {
"tags": []
}
},
"targetDependencies": {
@ -214,4 +214,5 @@
}
]
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save