rebase from upstream master

pull/1527/head
Andrew Miracle 3 years ago
commit c988ce9c07
  1. 19
      .circleci/config.yml
  2. 3
      .gitignore
  3. 82
      README.md
  4. 11
      apps/debugger/src/app/debugger-api.ts
  5. 30
      apps/remix-ide-e2e/src/commands/acceptAndRemember.ts
  6. 29
      apps/remix-ide-e2e/src/commands/addFile.ts
  7. 46
      apps/remix-ide-e2e/src/commands/addLocalPlugin.ts
  8. 2
      apps/remix-ide-e2e/src/commands/checkAnnotations.ts
  9. 2
      apps/remix-ide-e2e/src/commands/checkAnnotationsNotPresent.ts
  10. 22
      apps/remix-ide-e2e/src/commands/editorScroll.ts
  11. 1
      apps/remix-ide-e2e/src/commands/executeScript.ts
  12. 4
      apps/remix-ide-e2e/src/commands/getEditorValue.ts
  13. 7
      apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts
  14. 4
      apps/remix-ide-e2e/src/commands/removeFile.ts
  15. 17
      apps/remix-ide-e2e/src/commands/scrollToLine.ts
  16. 4
      apps/remix-ide-e2e/src/commands/setEditorValue.ts
  17. 3
      apps/remix-ide-e2e/src/commands/testFunction.ts
  18. 1
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  19. 13
      apps/remix-ide-e2e/src/local-plugin/src/app/Client.ts
  20. 131
      apps/remix-ide-e2e/src/local-plugin/src/app/app.css
  21. 131
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  22. 9
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  23. 17
      apps/remix-ide-e2e/src/local-plugin/src/app/logo.svg
  24. 11
      apps/remix-ide-e2e/src/local-plugin/src/app/star.svg
  25. 5
      apps/remix-ide-e2e/src/local-plugin/src/index.html
  26. 18
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  27. 5
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.spec.ts
  28. 2
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  29. 15
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  30. 7
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  31. 122
      apps/remix-ide-e2e/src/tests/editor.spec.ts
  32. 13
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  33. 12
      apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
  34. 16
      apps/remix-ide-e2e/src/tests/gist.spec.ts
  35. 1
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  36. 78
      apps/remix-ide-e2e/src/tests/pluginManager.test.ts
  37. 327
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  38. 14
      apps/remix-ide-e2e/src/tests/recorder.spec.ts
  39. 3
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  40. 4
      apps/remix-ide-e2e/src/tests/solidityImport.spec.ts
  41. 112
      apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
  42. 37
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  43. 15
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  44. 24
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  45. 8
      apps/remix-ide-e2e/src/types/index.d.ts
  46. 2
      apps/remix-ide/ci/browser_tests_plugin_api.sh
  47. 596
      apps/remix-ide/src/app.js
  48. 2
      apps/remix-ide/src/app/components/main-panel.js
  49. 84
      apps/remix-ide/src/app/editor/SourceHighlighters.js
  50. 16
      apps/remix-ide/src/app/editor/contextualListener.js
  51. 530
      apps/remix-ide/src/app/editor/editor.js
  52. 86
      apps/remix-ide/src/app/editor/sourceHighlighter.js
  53. 4
      apps/remix-ide/src/app/files/dgitProvider.js
  54. 10
      apps/remix-ide/src/app/files/fileManager.js
  55. 25
      apps/remix-ide/src/app/files/remixDProvider.js
  56. 24
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  57. 274
      apps/remix-ide/src/app/panels/file-panel.js
  58. 2
      apps/remix-ide/src/app/panels/main-view.js
  59. 194
      apps/remix-ide/src/app/panels/tab-proxy.js
  60. 830
      apps/remix-ide/src/app/panels/terminal.js
  61. 6
      apps/remix-ide/src/app/tabs/analysis-tab.js
  62. 24
      apps/remix-ide/src/app/tabs/compile-tab.js
  63. 9
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  64. 8
      apps/remix-ide/src/app/tabs/runTab/settings.js
  65. 54
      apps/remix-ide/src/app/tabs/test-tab.js
  66. 2
      apps/remix-ide/src/app/tabs/theme-module.js
  67. 1
      apps/remix-ide/src/assets/css/intro.js/2.7.0/introjs.min.css
  68. 2
      apps/remix-ide/src/assets/css/intro.js/4.1.0/introjs.min.css
  69. 8816
      apps/remix-ide/src/assets/css/themes/remix-night_owl.css
  70. 1
      apps/remix-ide/src/blockchain/execution-context.js
  71. 1
      apps/remix-ide/src/blockchain/providers/vm.js
  72. 3
      apps/remix-ide/src/lib/cmdInterpreterAPI.js
  73. 2
      apps/remix-ide/src/remixAppManager.js
  74. 1
      apps/remix-ide/src/remixEngine.js
  75. 2
      apps/solidity-compiler/src/app/app.tsx
  76. 1
      apps/solidity-compiler/src/app/compiler.ts
  77. 3
      libs/remix-analyzer/.eslintrc
  78. 14
      libs/remix-analyzer/package.json
  79. 3
      libs/remix-astwalker/.eslintrc
  80. 12
      libs/remix-astwalker/package.json
  81. 1
      libs/remix-astwalker/src/astWalker.ts
  82. 1
      libs/remix-astwalker/src/sourceMappings.ts
  83. 2
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  84. 23
      libs/remix-debug/package.json
  85. 3
      libs/remix-lib/.eslintrc
  86. 11
      libs/remix-lib/package.json
  87. 12
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  88. 14
      libs/remix-simulator/package.json
  89. 28
      libs/remix-simulator/test/blocks.ts
  90. 3
      libs/remix-solidity/.eslintrc
  91. 12
      libs/remix-solidity/package.json
  92. 2
      libs/remix-solidity/src/compiler/types.ts
  93. 3
      libs/remix-tests/.eslintrc
  94. 2
      libs/remix-tests/jest.config.js
  95. 18
      libs/remix-tests/package.json
  96. 3
      libs/remix-tests/src/compiler.ts
  97. 5
      libs/remix-tests/src/deployer.ts
  98. 4
      libs/remix-tests/src/run.ts
  99. 4
      libs/remix-tests/src/runTestFiles.ts
  100. 8
      libs/remix-tests/src/runTestSources.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -51,6 +51,7 @@ jobs:
- checkout
- run: npm install
- run: npm run build:libs
- run: cd dist/libs/remix-tests && npm install
- run: npm run test:libs
remix-ide-chrome-1:
@ -220,7 +221,7 @@ jobs:
- store_artifacts:
path: ./reports/screenshots
remix-ide-plugin-manager:
remix-ide-plugin-api:
docker:
# specify the version you desire here
- image: circleci/node:14.17.6-browsers
@ -248,13 +249,11 @@ jobs:
name: Start Selenium
command: ./node_modules/.bin/selenium-standalone start --drivers.chrome.version=2.39 --drivers.chrome.baseURL=https://chromedriver.storage.googleapis.com
background: true
- run: ./apps/remix-ide/ci/browser_tests_plugin_manager.sh
- run: ./apps/remix-ide/ci/browser_tests_plugin_api.sh
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
deploy-remix-live:
docker:
# specify the version you desire here
@ -374,6 +373,9 @@ workflows:
- remix-libs:
requires:
- lint
- remix-ide-plugin-api:
requires:
- lint
- remix-ide-chrome-1:
requires:
- lint
@ -389,9 +391,6 @@ workflows:
- remix-ide-run-deploy:
requires:
- lint
- remix-ide-plugin-manager:
requires:
- lint
- publish:
requires:
- lint
@ -402,7 +401,7 @@ workflows:
- remix-ide-firefox-1
- remix-ide-firefox-2
- remix-ide-run-deploy
- remix-ide-plugin-manager
- remix-ide-plugin-api
filters:
branches:
only: remix_live
@ -413,7 +412,7 @@ workflows:
- remix-ide-firefox-1
- remix-ide-firefox-2
- remix-ide-run-deploy
- remix-ide-plugin-manager
- remix-ide-plugin-api
filters:
branches:
only: master
@ -424,7 +423,7 @@ workflows:
- remix-ide-firefox-1
- remix-ide-firefox-2
- remix-ide-run-deploy
- remix-ide-plugin-manager
- remix-ide-plugin-api
filters:
branches:
only: remix_beta

3
.gitignore vendored

@ -30,6 +30,7 @@ soljson.js
*.launch
.settings/
*.sublime-workspace
.vscode/
# IDE - VSCode
.vscode/*
@ -51,3 +52,5 @@ testem.log
# System Files
.DS_Store
.vscode/settings.json
.vscode/launch.json

@ -1,66 +1,75 @@
[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project)
[![Documentation Status](https://readthedocs.org/projects/docs/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix)
# Remix Project
**Remix Project** is a platform for development tools that use a plugin architecture. It encompasses sub-projects including Remix Plugin Engine, Remix Libraries, and of course Remix IDE.
**Remix IDE** is an open source web and desktop application. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. Remix is used for the **entire journey of contract development with [Solidity language](https://soliditylang.org/)** in as well as being a playground for learning and teaching Ethereum.
**Remix IDE** is an open source web and desktop application. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. Remix is used for the **entire journey of contract development with [Solidity language](https://soliditylang.org/)** as well as a playground for learning and teaching [Ethereum](https://ethereum.org/).
To try web app, visit: [https://remix.ethereum.org](https://remix.ethereum.org).
Start developing using Remix on browser, visit: [https://remix.ethereum.org](https://remix.ethereum.org)
For desktop version, See releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases)
For desktop version, see releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases)
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix_screenshot.png)
**Remix libraries** work as a core of native plugins of Remix IDE. Read more about libraries [here](libs/README.md)
:point_right: **Remix libraries** work as a core of native plugins of Remix IDE. Read more about libraries [here](libs/README.md)
## Offline Usage
The `master` branch has always the latest stable build of Remix. It also contains a ZIP file with the entire build. Download it to use offline.
The `gh-pages` branch of [remix-live](https://github.com/ethereum/remix-live) always has the latest stable build of Remix. It contains a ZIP file with the entire build. Download it to use offline.
Note: It contains the latest release of Solidity available at the time of the packaging. No other compiler versions are supported.
Note: It contains the latest supported version of Solidity available at the time of the packaging. Other compiler versions can be used online only.
## Setup
Install **npm** and **node.js** (see https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), then
install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**.
* Install **NPM** and **Node.js**. See [Guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) <br/>
*Supported versions:*
```bash
"engines": {
"node": "^14.17.6",
"npm": "^6.14.15"
}
```
* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**.
```bash
npm install -g @nrwl/cli
```
Clone the github repository (`wget` need to be installed first):
* Clone the github repository (`wget` need to be installed first):
```bash
git clone https://github.com/ethereum/remix-project.git
```
And build it:
* Build `remix-project`:
```bash
cd remix-project
npm install
nx build remix-ide --with-deps
nx build
nx serve
```
Run `nx serve` and open `http://127.0.0.1:8080` in your browser.
Open `http://127.0.0.1:8080` in your browser to load Remix IDE locally.
Then open your `text editor` and start developing.
The browser will automatically refresh when files are saved.
Go to your `text editor` and start developing. Browser will automatically refresh when files are saved.
## Production Build
To generate react production builds for remix-project.
```bash
npm run build:production
```
build can be found in `remix-project/dist/apps/remix-ide` directory.
Build can be found in `remix-project/dist/apps/remix-ide` directory.
```bash
npm run serve:production
```
production build will be served by default to `http://localhost:8080/` or `http://127.0.0.1:8080/`
Production build will be served by default to `http://localhost:8080/` or `http://127.0.0.1:8080/`
## Docker:
@ -99,9 +108,9 @@ To fetch docker-compose file without cloning this repo run:
curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-compose.yaml > docker-compose.yaml
```
### Troubleshooting building
### Troubleshooting
If you have trouble building the package, Make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure you have [Nx CLI](https://nx.dev/react/cli/overview) installed globally.
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/react/cli/overview) is installed globally.
Run:
@ -115,22 +124,23 @@ In Debian based OS such as Ubuntu 14.04LTS you may need to run `apt-get install
## Unit Testing
Run the unit tests via: `nx test <project-name>`
```bash
nx test remix-analyzer
```
Run the unit tests using library name like: `nx test <project-name>`
Running unit tests via `nx test` requires at least node v10.0.0
For example, to run unit tests of `remix-analyzer`, use `nx test remix-analyzer`
## Browser Testing
To run the Selenium tests via Nightwatch:
- Build Remix IDE and serve it: `nx build remix-ide --with-deps && nx serve` # starts web server at localhost:8080
- Make sure Selenium is installed `npm run selenium-install` # don't need to repeat
- Run a selenium server `npm run selenium`
- Run all the tests `npm run nightwatch_local_firefox` or `npm run nightwatch_local_chrome`
- Or run a specific test case:
- Install Selenium for first time: `npm run selenium-install`
- Run a selenium server: `npm run selenium`
- Build & Serve Remix: `nx serve`
- Run all the end-to-end tests:
for Firefox: `npm run nightwatch_local_firefox`, or
for Google Chrome: `npm run nightwatch_local_chrome`
- Run a specific test case instead, use one of following commands:
- npm run nightwatch_local_ballot
@ -185,16 +195,14 @@ To run the Selenium tests via Nightwatch:
**NOTE:**
- **the `ballot` tests suite** requires to run `ganache-cli` locally.
- **The `ballot` tests suite** requires to run `ganache-cli` locally.
- **the `remixd` tests suite** requires to run `remixd` locally.
- **The `remixd` tests suite** requires to run `remixd` locally.
- **the `gist` tests suite** requires specifying a github access token in **.env file**.
- **The `gist` tests suite** requires specifying a github access token in **.env file**.
```
gist_token = <token>
gist_token = <token> // token should have permission to create a gist
```
**note that this token should have permission to create a gist.**
## Important Links

@ -4,6 +4,9 @@ import { CompilationOutput, Sources } from '@remix-ui/debugger-ui'
import type { CompilationResult } from '@remix-project/remix-solidity-ts'
export const DebuggerApiMixin = (Base) => class extends Base {
initialWeb3
initDebuggerApi () {
this.debugHash = null
@ -16,6 +19,8 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
}
this._web3 = new Web3(this.web3Provider)
// this._web3 can be overwritten and reset to initial value in 'debug' method
this.initialWeb3 = this._web3
remixDebug.init.extendWeb3(this._web3)
this.offsetToLineColumnConverter = {
@ -39,7 +44,7 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
async highlight (lineColumnPos, path) {
await this.call('editor', 'highlight', lineColumnPos, path)
await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true })
}
async getFile (path) {
@ -123,7 +128,9 @@ export const DebuggerApiMixin = (Base) => class extends Base {
debug (hash, web3?) {
this.debugHash = hash
if (web3) remixDebug.init.extendWeb3(web3)
if (web3) this._web3 = web3
else this._web3 = this.initialWeb3
remixDebug.init.extendWeb3(this._web3)
if (this.onDebugRequestedListener) this.onDebugRequestedListener(hash, web3)
}

@ -0,0 +1,30 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class AcceptAndRemember extends EventEmitter {
command (this: NightwatchBrowser, remember:boolean, accept: boolean): NightwatchBrowser {
this.api.perform((done) => {
acceptAndRemember(this.api, remember, accept, () => {
done()
this.emit('complete')
})
})
return this
}
}
function acceptAndRemember (browser: NightwatchBrowser, remember: boolean, accept: boolean, callback: VoidFunction) {
browser.useXpath().waitForElementVisible('//*[@data-id="modalDialogModalBody"]')
if (remember) {
browser.click('//*[@id="remember"]', () => {
if (accept) {
browser.click('//*[@id="modal-footer-ok"]')
} else {
browser.click('//*[@id="modal-footer-cancel"]')
}
browser.perform(function () { callback() })
})
}
}
module.exports = AcceptAndRemember

@ -17,16 +17,25 @@ function addFile (browser: NightwatchBrowser, name: string, content: NightwatchC
browser.clickLaunchIcon('udapp')
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('.newFile')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
.waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000)
.setEditorValue(content.content)
.pause(1000)
.perform(function () {
done()
.elements('css selector', `li[data-id="treeViewLitreeViewItem${name}"]`, (res) => {
if (res.value && (res.value as any).length > 0) {
browser.openFile(name)
.perform(function () {
done()
})
} else {
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
.waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000)
.setEditorValue(content.content)
.pause(1000)
.perform(function () {
done()
})
}
})
}

@ -0,0 +1,46 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
import { ExternalProfile, LocationProfile, Profile } from '@remixproject/plugin-utils'
class AddLocalPlugin extends EventEmitter {
command (this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser {
this.api.perform((done) => {
addLocalPlugin(this.api, profile, () => {
done()
this.emit('complete')
})
})
return this
}
}
function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, callback: VoidFunction) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000).element('css selector', '*[data-id="pluginManagerComponentPluginManager"]', function (result) {
if (result.status === 0) {
browser.click('*[plugin="pluginManager"]')
}
})
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
window.testmode = true
})
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', profile.name)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', profile.displayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', profile.url)
.clearValue('*[data-id="localPluginCanActivate"]').setValue('*[data-id="localPluginCanActivate"]', profile.canActivate ? profile.canActivate.join(',') : '')
.click('*[data-id="localPluginRadioButtoniframe"]')
.click(profile.location === 'sidePanel' ? '*[data-id="localPluginRadioButtonsidePanel"]' : '*[data-id="localPluginRadioButtonmainPanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
.waitForElementVisible('[data-id="verticalIconsKindlocalPlugin"]')
.click('[data-id="verticalIconsKindlocalPlugin"]')
.perform(function () { callback() })
}
module.exports = AddLocalPlugin

@ -3,7 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
class checkAnnotations extends EventEmitter {
command (this: NightwatchBrowser, type: string, line: number): NightwatchBrowser {
this.api.assert.containsText(`.ace_${type}`, line.toString()).perform(() => this.emit('complete'))
this.api.assert.containsText(`.margin-view-overlays .${type} + div`, line.toString()).perform(() => this.emit('complete'))
return this
}
}

@ -3,7 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
class checkAnnotationsNotPresent extends EventEmitter {
command (this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.waitForElementNotPresent(`.ace_${type}`).perform(() => this.emit('complete'))
this.api.waitForElementNotPresent(`.margin-view-overlays .${type}`).perform(() => this.emit('complete'))
return this
}
}

@ -1,22 +0,0 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
// fix for editor scroll
class ScrollEditor extends EventEmitter {
command (this: NightwatchBrowser, direction: 'up' | 'down', numberOfTimes: number): NightwatchBrowser {
const browser = this.api
browser.waitForElementPresent('.ace_text-input')
for (let i = 0; i < numberOfTimes; i++) {
if (direction.toLowerCase() === 'up') browser.sendKeys('.ace_text-input', browser.Keys.ARROW_UP)
if (direction.toLowerCase() === 'down') browser.sendKeys('.ace_text-input', browser.Keys.ARROW_DOWN)
}
browser.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = ScrollEditor

@ -6,6 +6,7 @@ class ExecuteScript extends EventEmitter {
this.api
.clearEditableContent('*[data-id="terminalCliInput"]')
.click('*[data-id="terminalCli"]')
.setValue('*[data-id="terminalCliInput"]', [this.api.Keys.CONTROL, 'a', this.api.Keys.DELETE])
.sendKeys('*[data-id="terminalCliInput"]', script)
.sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER)
.sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER)

@ -5,9 +5,9 @@ class GetEditorValue extends EventEmitter {
command (this: NightwatchBrowser, callback: (content: string) => void): NightwatchBrowser {
this.api.perform((client, done) => {
this.api.execute(function () {
const elem: any = document.getElementById('input')
const elem: any = document.getElementById('editorView')
return elem.editor.getValue()
return elem.currentContent()
}, [], (result) => {
done()
const value = typeof result.value === 'string' ? result.value : null

@ -7,11 +7,12 @@ import EventEmitter from 'events'
class JournalLastChildIncludes extends EventEmitter {
command (this: NightwatchBrowser, val: string): NightwatchBrowser {
this.api
.waitForElementVisible('*[data-id="terminalJournal"] > div:last-child', 10000)
.getText('*[data-id="terminalJournal"] > div:last-child', (result) => {
.waitForElementVisible('*[data-id="terminalJournal"]', 10000)
.pause(1000)
.getText('*[data-id="terminalJournal"]', (result) => {
console.log('JournalLastChildIncludes', result.value)
if (typeof result.value === 'string' && result.value.indexOf(val) === -1) return this.api.assert.fail(`wait for ${val} in ${result.value}`)
else this.api.assert.ok(true, `<*[data-id="terminalJournal"] > div:last-child> contains ${val}.`)
else this.api.assert.ok(true, `<*[data-id="terminalJournal"]> contains ${val}.`)
this.emit('complete')
})
return this

@ -39,8 +39,8 @@ function removeFile (browser: NightwatchBrowser, path: string, workspace: string
.pause(2000)
.perform(() => {
console.log(path, 'to remove')
browser.waitForElementVisible('*[data-id="' + workspace + 'ModalDialogContainer-react"] .modal-ok')
.click('*[data-id="' + workspace + 'ModalDialogContainer-react"] .modal-ok')
browser.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementNotPresent('[data-path="' + path + '"]')
done()
})

@ -0,0 +1,17 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ScrollToLine extends EventEmitter {
command (this: NightwatchBrowser, line: number): NightwatchBrowser {
this.api.execute(function (line) {
const elem: any = document.getElementById('editorView')
elem.gotoLine(line)
}, [line], () => {
this.emit('complete')
})
return this
}
}
module.exports = ScrollToLine

@ -5,9 +5,9 @@ class SetEditorValue extends EventEmitter {
command (this: NightwatchBrowser, value: string, callback?: VoidFunction): NightwatchBrowser {
this.api.perform((client, done) => {
this.api.execute(function (value) {
const elem: any = document.getElementById('input')
const elem: any = document.getElementById('editorView')
elem.editor.session.setValue(value)
elem.setCurrentContent(value)
}, [value], () => {
done()
if (callback) {

@ -24,8 +24,8 @@ class TestFunction extends EventEmitter {
.perform((done) => {
browser.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
.click(`[data-id="block_tx${txHash}"]`)
.pause(3000)
.waitForElementVisible(`*[data-id="txLoggerTable${txHash}"]`, 60000)
.pause(10000)
// fetch and format transaction logs as key => pair object
.elements('css selector', `*[data-shared="key_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
@ -58,7 +58,6 @@ class TestFunction extends EventEmitter {
.perform(() => {
Object.keys(expectedValue).forEach(key => {
let equal = false
try {
const receivedValue = JSON.parse(logs[key])

@ -17,6 +17,7 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
browser
.clickLaunchIcon('solidity')
.pause(opts.wait)
.pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => {
if (opts.version) {

@ -0,0 +1,13 @@
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
export class RemixPlugin extends PluginClient {
constructor () {
super()
this.methods = ['testCommand']
createClient(this)
}
async testCommand (data: any) {
}
}

@ -1,128 +1,3 @@
.app {
font-family: sans-serif;
min-width: 300px;
max-width: 600px;
margin: 50px auto;
}
.app .gutter-left {
margin-left: 9px;
}
.app .col-span-2 {
grid-column: span 2;
}
.app .flex {
display: flex;
align-items: center;
justify-content: center;
}
.app header {
background-color: #143055;
color: white;
padding: 5px;
border-radius: 3px;
}
.app main {
padding: 0 36px;
}
.app p {
text-align: center;
}
.app h1 {
text-align: center;
margin-left: 18px;
font-size: 24px;
}
.app h2 {
text-align: center;
font-size: 20px;
margin: 40px 0 10px 0;
}
.app .resources {
text-align: center;
list-style: none;
padding: 0;
display: grid;
grid-gap: 9px;
grid-template-columns: 1fr 1fr;
}
.app .resource {
color: #0094ba;
height: 36px;
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
padding: 3px 9px;
text-decoration: none;
}
.app .resource:hover {
background-color: rgba(68, 138, 255, 0.04);
}
.app pre {
padding: 9px;
border-radius: 4px;
background-color: black;
color: #eee;
}
.app details {
border-radius: 4px;
color: #333;
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0.12);
padding: 3px 9px;
margin-bottom: 9px;
}
.app summary {
outline: none;
height: 36px;
line-height: 36px;
}
.app .github-star-container {
margin-top: 12px;
line-height: 20px;
}
.app .github-star-container a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
}
.app .github-star-badge {
color: #24292e;
display: flex;
align-items: center;
font-size: 12px;
padding: 3px 10px;
border: 1px solid rgba(27, 31, 35, 0.2);
border-radius: 3px;
background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%);
margin-left: 4px;
font-weight: 600;
}
.app .github-star-badge:hover {
background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%);
border-color: rgba(27, 31, 35, 0.35);
background-position: -0.5em;
}
.app .github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;
}
.jumbotron {
max-height: 25vh;
}

@ -1,37 +1,126 @@
import React, { useEffect, useState } from 'react'
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
import { RemixPlugin } from './Client'
import { Logger } from './logger'
import { filePanelProfile } from '@remixproject/plugin-api/lib/file-system/file-panel/profile'
import { filSystemProfile } from '@remixproject/plugin-api/lib/file-system/file-manager/profile'
import { dGitProfile } from '@remixproject/plugin-api/lib/dgit/profile'
import { editorProfile } from '@remixproject/plugin-api/lib/editor/profile'
import { settingsProfile } from '@remixproject/plugin-api/lib/settings/profile'
import { networkProfile } from '@remixproject/plugin-api/lib/network/profile'
import { terminalProfile } from '@remixproject/plugin-api/lib/terminal/profile'
import { udappProfile } from '@remixproject/plugin-api/lib/udapp'
import { compilerProfile } from '@remixproject/plugin-api/lib/compiler'
import { contentImportProfile } from '@remixproject/plugin-api/lib/content-import'
import { windowProfile } from '@remixproject/plugin-api/lib/window'
import { pluginManagerProfile } from '@remixproject/plugin-api/lib/plugin-manager'
import { Profile } from '@remixproject/plugin-utils'
import './app.css'
import { ReactComponent as Logo } from './logo.svg'
const client = new RemixPlugin()
function App () {
const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false)
const [events, setEvents] = useState<any>()
const [profiles, setProfiles] = useState<Profile[]>([pluginManagerProfile, filePanelProfile, filSystemProfile, dGitProfile, networkProfile, settingsProfile, editorProfile, terminalProfile, compilerProfile, udappProfile, contentImportProfile, windowProfile])
export const App = () => {
const [remixClient, setRemixClient] = useState(null)
const handleChange = ({ target }: any) => {
setPayload(target.value)
}
useEffect(() => {
(async () => {
const client = createClient(new PluginClient())
client.onload(async () => {
const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting']
client.testCommand = async (data: any) => {
console.log(data)
methodLog(data)
}
let addProfiles = []
for (const name of customProfiles) {
const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p]
}
setProfiles(profiles => [...profiles, ...addProfiles])
await client.onload()
console.log('Local plugin loaded')
setRemixClient(client)
})()
profiles.map((profile: Profile) => {
if (profile.events) {
profile.events.map((event: string) => {
client.on(profile.name as any, event, (...args: any) => {
console.log('event :', event, args)
eventLog({
event: event,
args: args
})
})
})
}
})
})
}, [])
const handleClick = () => {
remixClient.call('manager', 'activatePlugin', 'LearnEth')
const methodLog = (log: any) => {
const addValue = typeof log === 'string' ? log : JSON.stringify(log)
setLog((value) => `${value} ${addValue}`)
}
const eventLog = (log: any) => {
const addValue = typeof log === 'string' ? log : JSON.stringify(log)
setEvents((value) => `${value} ${addValue}`)
}
const clientMethod = async (profile: Profile, method: string) => {
try {
let ob: any = null
try {
ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] }
} catch (e) { }
const args = ob || [payload]
setStarted(true)
setLog('')
setEvents('')
console.log('calling :', profile.name, method, ...args)
await client.call('manager', 'activatePlugin', profile.name)
const result = await client.call(profile.name as any, method, ...args)
console.log('result :', result)
methodLog(result)
} catch (e) {
methodLog(e.message)
}
setStarted(false)
}
return (
<div className="app">
<header className="flex">
<Logo width="75" height="75" />
<h1>Welcome to local-plugin!</h1>
</header>
<main>
<button data-id="btnActivateRemixd" onClick={handleClick}>Activate Learneth</button>
</main>
<div className="App container-fluid">
<h5>PLUGIN API TESTER</h5>
<label id='callStatus'>{started ? <>start</> : <>stop</> }</label><br></br>
<label>method results</label>
<Logger id='methods' log={log}></Logger>
<label>events</label>
<Logger id='events' log={events}></Logger>
<input
className='form-control w-100'
type="text"
id="payload"
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
/>
{profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => {
return <button data-id={`${profile.name}:${method}`} key={method} className='btn btn-primary btn-sm ml-1 mb-1' onClick={async () => await clientMethod(profile, method)}>{method}</button>
})
const events = profile.events ? profile.events.map((event: string) => {
return <label key={event} className='m-1'>{event}</label>
}) : null
return <div key={profile.name} className='small border-bottom'><label className='text-uppercase'>{profile.name}</label><br></br>{methods}<br></br>{events ? <label>EVENTS:</label> : null}{events}</div>
})}
</div>
)
}

@ -0,0 +1,9 @@
import React from 'react'
interface loggerProps {
log: any,
id: string
}
export const Logger: React.FC<loggerProps> = (props) => {
return (<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">{props.log}</div>)
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="262px" height="163px" viewBox="0 0 262 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Styles-&amp;-Quick-Wins" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Nx---Quick-Wins" transform="translate(-476.000000, -1284.000000)" fill-rule="nonzero">
<g id="Logos" transform="translate(-11.000000, 782.000000)">
<g id="Nx_Flat_White" transform="translate(487.000000, 502.000000)">
<polygon id="Path" fill="#FFFFFF" points="130.68 104.59 97.49 52.71 97.44 96.3 40.24 0 0 0 0 162.57 39.79 162.57 39.92 66.39 96.53 158.26"></polygon>
<polygon id="Path" fill="#FFFFFF" points="97.5 41.79 137.24 41.79 137.33 41.33 137.33 0 97.54 0 97.49 41.33"></polygon>
<path d="M198.66,86.86 C189.139872,86.6795216 180.538723,92.516445 177.19,101.43 C182.764789,93.0931021 193.379673,89.7432211 202.73,93.37 C207.05,95.13 212.73,97.97 217.23,96.45 C212.950306,90.4438814 206.034895,86.8725952 198.66,86.86 L198.66,86.86 Z" id="Path" fill="#96D8E9"></path>
<path d="M243.75,106.42 C243.75,101.55 241.1,100.42 235.6,98.42 C231.52,97 226.89,95.4 223.52,91 C222.86,90.13 222.25,89.15 221.6,88.11 C220.14382,85.4164099 218.169266,83.037429 215.79,81.11 C212.58,78.75 208.37,77.6 202.91,77.6 C191.954261,77.6076705 182.084192,84.2206169 177.91,94.35 C183.186964,87.0278244 191.956716,83.0605026 200.940147,83.9314609 C209.923578,84.8024193 217.767888,90.3805017 221.54,98.58 C223.424615,101.689762 227.141337,103.174819 230.65,102.22 C236.02,101.07 235.65,106.15 243.76,107.87 L243.75,106.42 Z" id="Path" fill="#48C4E5"></path>
<path d="M261.46,105.38 L261.46,105.27 C261.34,73.03 235.17,45.45 202.91,45.45 C183.207085,45.4363165 164.821777,55.3450614 154,71.81 L153.79,71.45 L137.23,45.45 L97.5,45.4499858 L135.25,104.57 L98.41,162.57 L137,162.57 L153.79,136.78 L170.88,162.57 L209.48,162.57 L174.48,107.49 C173.899005,106.416838 173.583536,105.220114 173.56,104 C173.557346,96.2203871 176.64661,88.7586448 182.147627,83.2576275 C187.648645,77.7566101 195.110387,74.6673462 202.89,74.67 C219.11,74.67 221.82,84.37 225.32,88.93 C232.23,97.93 246.03,93.99 246.03,105.73 L246.03,105.73 C246.071086,108.480945 247.576662,111.001004 249.979593,112.340896 C252.382524,113.680787 255.317747,113.636949 257.679593,112.225896 C260.041438,110.814842 261.471086,108.250945 261.43,105.5 L261.43,105.5 L261.43,105.38 L261.46,105.38 Z" id="Path" fill="#FFFFFF"></path>
<path d="M261.5,113.68 C261.892278,116.421801 261.504116,119.218653 260.38,121.75 C258.18,126.84 254.51,125.14 254.51,125.14 C254.51,125.14 251.35,123.6 253.27,120.65 C255.4,117.36 259.61,117.74 261.5,113.68 Z" id="Path" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
className="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>

Before

Width:  |  Height:  |  Size: 347 B

@ -1,10 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>LocalPlugin</title>
<base href="/" />
<meta charset="utf-8" />
<title>Remix Plugin API Tester</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>

@ -56,8 +56,7 @@ module.exports = {
.waitForElementVisible('#stepdetail')
.goToVMTraceStep(144)
.pause(2000)
// Should be uncommented while fixing https://github.com/ethereum/remix-project/issues/1644
// .checkVariableDebug('soliditystate', stateCheck)
.checkVariableDebug('soliditystate', stateCheck)
.checkVariableDebug('soliditylocals', localsCheck)
},
@ -82,11 +81,20 @@ module.exports = {
'Deploy and use Ballot using external web3': function (browser: NightwatchBrowser) {
browser
.openFile('Untitled.sol')
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick()
.execute(function () {
const env: any = document.getElementById('selectExEnvOptions')
return env.value
}, [], function (result) {
console.log({ result })
browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected')
})
.clickLaunchIcon('solidity')
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp')
.pause(2000)
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.clickInstance(0)
@ -117,7 +125,7 @@ const localsCheck = {
type: 'address'
}
}
/*
const stateCheck = {
chairperson: {
value: '0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C',
@ -175,8 +183,6 @@ const stateCheck = {
immutable: false
}
}
*/
const ballotABI = `[
{
"inputs": [

@ -28,7 +28,6 @@ module.exports = {
'Deploy Ballot': function (browser: NightwatchBrowser) {
browser.pause(500)
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.setValue('input[placeholder="uint8 _numProposals"]', '2')
@ -77,11 +76,13 @@ module.exports = {
'Deploy and use Ballot using external web3': function (browser: NightwatchBrowser) {
browser
.openFile('Untitled.sol')
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick()
.clickLaunchIcon('solidity')
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp')
.pause(2000)
.setValue('input[placeholder="uint8 _numProposals"]', '2')
.click('*[data-id="Deploy - transact (not payable)"]')
.clickInstance(0)

@ -21,7 +21,7 @@ module.exports = {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion": "0.6.8+commit.0bbfe453"', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion":"0.6.8+commit.0bbfe453"', 60000)
.click('*[data-id="terminalClearConsole"]')
},

@ -62,9 +62,13 @@ module.exports = {
},
'Should jump through breakpoints': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
.click('.ace_gutter-cell:nth-of-type(10)')
.click('.ace_gutter-cell:nth-of-type(20)')
browser.waitForElementVisible('#editorView')
.execute(() => {
(window as any).addRemixBreakpoint(11)
}, [], () => {})
.execute(() => {
(window as any).addRemixBreakpoint(21)
}, [], () => {})
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000)
@ -127,7 +131,7 @@ module.exports = {
But the debugger uses now validSourcelocation, which means file is not -1.
In that case the source highlight at 261 should be the same as for step 262
*/
.waitForElementPresent('.highlightLine7')
.waitForElementPresent('.highlightLine8')
.goToVMTraceStep(266)
.pause(1000)
.checkVariableDebug('soliditylocals', localVariable_step266_ABIEncoder) // locals should not be initiated at this point, only idAsk should
@ -187,7 +191,8 @@ module.exports = {
browser
.addFile('test_jsGetTrace.js', { content: jsGetTrace })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result { "gas": "0x575f", "return": "0x0000000000000000000000000000000000000000000000000000000000000000", "structLogs":', 60000)
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '{"gas":"0x575f","return":"0x0000000000000000000000000000000000000000000000000000000000000000","structLogs":', 60000)
},
'Should call the debugger api: debug': function (browser: NightwatchBrowser) {

@ -50,11 +50,11 @@ module.exports = {
'Toggles Terminal': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="terminalContainer"]')
.assert.visible('div[data-id="terminalContainerDisplay"]')
.assert.elementPresent('div[data-id="terminalContainerDisplay"]')
.click('i[data-id="terminalToggleIcon"]')
.checkElementStyle('div[data-id="terminalToggleMenu"]', 'height', '35px')
.click('i[data-id="terminalToggleIcon"]')
.assert.visible('div[data-id="terminalContainerDisplay"]')
.assert.elementPresent('div[data-id="terminalContainerDisplay"]')
},
'Switch Tabs using tabs icon': function (browser: NightwatchBrowser) {
@ -63,9 +63,6 @@ module.exports = {
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol')
.assert.containsText('div[title="default_workspace/contracts/3_Ballot.sol"]', '3_Ballot.sol')
.click('span[class^=dropdownCaret]')
.click('#homeItem')
.assert.containsText('div[title="home"]', 'Home')
.end()
}
}

@ -15,37 +15,35 @@ module.exports = {
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('*[data-id="editorInput"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '12px')
.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '14px')
.click('*[data-id="tabProxyZoomIn"]')
.click('*[data-id="tabProxyZoomIn"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '14px')
.checkElementStyle('.view-lines', 'font-size', '16px')
},
'Should zoom out editor': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '14px')
browser.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16px')
.click('*[data-id="tabProxyZoomOut"]')
.click('*[data-id="tabProxyZoomOut"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '12px')
.checkElementStyle('.view-lines', 'font-size', '14px')
},
'Should display compile error in editor': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
.waitForElementVisible('*[class="ace_content"]')
.click('*[class="ace_content"]')
.sendKeys('*[class="ace_text-input"]', 'error')
browser.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError + 'error')
.pause(2000)
.waitForElementVisible('.ace_error', 120000)
.checkAnnotations('error', 28)
.waitForElementVisible('.margin-view-overlays .fa-exclamation-square', 120000)
.checkAnnotations('fa-exclamation-square', 29) // error
.clickLaunchIcon('udapp')
.checkAnnotationsNotPresent('error')
.checkAnnotationsNotPresent('fa-exclamation-square') // error
.clickLaunchIcon('solidity')
.checkAnnotations('error', 28)
.checkAnnotations('fa-exclamation-square', 29) // error
},
'Should minimize and maximize codeblock in editor': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
'Should minimize and maximize codeblock in editor': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.waitForElementVisible('.ace_open')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_closed')
@ -54,27 +52,29 @@ module.exports = {
},
'Should add breakpoint to editor': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
.waitForElementNotPresent('.ace_breakpoint')
.click('.ace_gutter-cell:nth-of-type(1)')
.waitForElementVisible('.ace_breakpoint')
browser.waitForElementVisible('#editorView')
.waitForElementNotPresent('.margin-view-overlays .fa-circle')
.execute(() => {
(window as any).addRemixBreakpoint(1)
}, [], () => {})
.waitForElementVisible('.margin-view-overlays .fa-circle')
},
'Should load syntax highlighter for ace light theme': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="editorInput"]')
'Should load syntax highlighter for ace light theme': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
},
'Should load syntax highlighter for ace dark theme': function (browser: NightwatchBrowser) {
'Should load syntax highlighter for ace dark theme': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
.click('*[data-id="settingsTabThemeLabelDark"]')
.pause(2000)
.waitForElementVisible('*[data-id="editorInput"]')
.waitForElementVisible('#editorView')
/* @todo(#2863) ch for class and not colors
.checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment)
@ -87,20 +87,21 @@ module.exports = {
// include all files here because switching between plugins in side-panel removes highlight
browser
.addFile('sourcehighlight.js', sourcehighlightScript)
.addFile('removeSourcehighlightScript.js', removeSourcehighlightScript)
.addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript)
.openFile('sourcehighlight.js')
.executeScript('remix.exeCurrent()')
.editorScroll('down', 60)
.waitForElementPresent('.highlightLine32', 60000)
.checkElementStyle('.highlightLine32', 'background-color', 'rgb(8, 108, 181)')
.waitForElementPresent('.highlightLine40', 60000)
.checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)')
.waitForElementPresent('.highlightLine50', 60000)
.checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)')
.scrollToLine(32)
.waitForElementPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine33', 'background-color', 'rgb(52, 152, 219)')
.scrollToLine(40)
.waitForElementPresent('.highlightLine41', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)')
.scrollToLine(50)
.waitForElementPresent('.highlightLine51', 60000)
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove 1 highlight from source code': function (browser: NightwatchBrowser) {
'Should remove 1 highlight from source code': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000)
@ -109,9 +110,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine32', 60000)
.checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)')
.checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)')
.waitForElementNotPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove all highlights from source code': function (browser: NightwatchBrowser) {
@ -122,9 +123,9 @@ module.exports = {
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.pause(2000)
.waitForElementNotPresent('.highlightLine32', 60000)
.waitForElementNotPresent('.highlightLine40', 60000)
.waitForElementNotPresent('.highlightLine50', 60000)
.waitForElementNotPresent('.highlightLine33', 60000)
.waitForElementNotPresent('.highlightLine41', 60000)
.waitForElementNotPresent('.highlightLine51', 60000)
.end()
}
}
@ -148,6 +149,7 @@ const sourcehighlightScript = {
content: `
(async () => {
try {
await remix.call('fileManager', 'open', 'contracts/3_Ballot.sol')
const pos = {
start: {
line: 32,
@ -190,18 +192,6 @@ const sourcehighlightScript = {
`
}
const removeSourcehighlightScript = {
content: `
(async () => {
try {
await remix.call('editor', 'discardHighlightAt', 32, 'contracts/3_Ballot.sol')
} catch (e) {
console.log(e.message)
}
})()
`
}
const removeAllSourcehighlightScript = {
content: `
(async () => {
@ -213,3 +203,33 @@ const removeAllSourcehighlightScript = {
})()
`
}
const storageContractWithError = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}`

@ -68,9 +68,9 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]')
.rightClick('[data-path="Browser_E2E_Tests"]')
.click('*[id="menuitemdelete"]')
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]', 60000)
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok', 60000)
.pause(2000)
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]')
},
@ -81,13 +81,13 @@ module.exports = {
.pause(10000)
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]', 60000)
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok', 60000)
.pause(2000)
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.pause(2000)
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]', 60000)
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok', 60000)
.pause(2000)
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.pause(2000)
.perform((done) => {
if (runtimeBrowser === 'chrome') {
@ -101,6 +101,7 @@ module.exports = {
'Should open local filesystem explorer': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.click('[data-id="remixUIWorkspaceExplorer"]')
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)

@ -11,6 +11,7 @@ module.exports = {
browser
.addFile('file.js', { content: executeFile })
.executeScript('remix.exeCurrent()')
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'file.js', 60000)
},
@ -35,7 +36,9 @@ module.exports = {
.executeScript('remix.exeCurrent()')
.pause(2000)
.openFile('new_contract.sol')
.assert.containsText('[data-id="editorInput"]', 'pragma solidity ^0.6.0')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('pragma solidity ^0.6.0') !== -1, 'content does not contain "pragma solidity ^0.6.0"')
})
},
'Should execute `readFile` api from file manager external api': function (browser: NightwatchBrowser) {
@ -72,7 +75,8 @@ module.exports = {
browser
.addFile('readdirFile.js', { content: executeReaddir })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory true', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'true', 5000)
},
'Should execute `remove` api from file manager external api': function (browser: NightwatchBrowser) {
@ -175,8 +179,8 @@ const executeMkdir = `
const executeReaddir = `
const run = async () => {
const result = await remix.call('fileManager', 'readdir', '/')
console.log('Test_Folder isDirectory ', result["Test_Folder"].isDirectory)
const output = result["Test_Folder"].isDirectory
console.log('Test_Folder isDirectory ', output)
}
run()

@ -103,14 +103,18 @@ module.exports = {
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]')
.pause(2000)
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(10000)
.getText('[data-id="default_workspaceModalDialogModalBody-react"]', (result) => {
browser.assert.ok(result.value === 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Assert failed. Gist token error message not displayed.')
.perform((done) => {
browser.getText('[data-id="fileSystemModalDialogModalBody-react"]', (result) => {
console.log('result.value: ', result.value)
browser.assert.ok(result.value === 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Assert failed. Gist token error message not displayed.')
done()
})
})
.click('[data-id="default_workspace-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
},
'Import From Gist For Valid Gist ID': function (browser: NightwatchBrowser) {

@ -77,6 +77,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract('')
.pause(2000)
.getText('div[class^="terminal"]', (value) => {
console.log('value: ', value)
})

@ -12,13 +12,6 @@ const testData = {
pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/'
}
const localPluginData = {
pluginName: 'localPlugin',
pluginDisplayName: 'Local Plugin',
pluginCanActivate: 'LearnEth',
pluginUrl: 'http://localhost:2020'
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
@ -76,44 +69,6 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]', 60000)
},
/*
'Should grant plugin permission (ZOKRATES)': function (browser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerPermissionsButton"]')
.waitForElementVisible('*[data-id="pluginManagerSettingsPermissionForm"]')
.assert.containsText('*[data-id="pluginManagerSettingsPermissionForm"]', 'No Permission requested yet')
.modalFooterOKClick()
.click('*[data-id="verticalIconsFileExplorerIcons"]')
.openFile('3_Ballot.sol')
.click('*[plugin="ZoKrates"]')
.pause(5000)
.frame(0)
.useXpath().click("//span[text()='Compile']")
.pause(2000)
.frameParent()
.useCss().waitForElementVisible('*[data-id="modalDialogContainer"]')
.assert.containsText('*[data-id="permissionHandlerMessage"]', 'ZOKRATES" WOULD LIKE TO ACCESS "FILE MANAGER" :')
.pause(2000)
.click('*[data-id="permissionHandlerRememberChoice"]')
.pause(2000)
.modalFooterOKClick()
},
'Should revert plugin permission (ZOKRATES)': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsSettingsIcons"]')
.click('*[data-id="verticalIconsSettingsIcons"]')
.waitForElementVisible('*[data-id="pluginManagerPermissionsButton"]')
.click('*[data-id="pluginManagerPermissionsButton"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.click('*[data-id="pluginManagerSettingsPermissionForm"]')
.pause(2000)
.click('*[data-id="pluginManagerSettingsClearAllPermission"]')
.pause(2000)
.assert.containsText('*[data-id="pluginManagerSettingsPermissionForm"]', 'No Permission requested yet')
.modalFooterOKClick()
},
*/
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
@ -130,8 +85,6 @@ module.exports = {
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
// .modalFooterOKClick()
// .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonremixIde"]', 60000)
},
'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) {
@ -154,46 +107,17 @@ module.exports = {
.assert.containsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
},
'Local plugin should activate LearnEth plugin': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', localPluginData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', localPluginData.pluginDisplayName)
.clearValue('*[data-id="localPluginCanActivate"]').setValue('*[data-id="localPluginCanActivate"]', localPluginData.pluginCanActivate)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', localPluginData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
.waitForElementVisible('[data-id="verticalIconsKindlocalPlugin"]')
.click('[data-id="verticalIconsKindlocalPlugin"]')
.waitForElementNotPresent('[data-id="verticalIconsKindLearnEth"]')
.pause(2000)
// @ts-ignore
.frame('plugin-localPlugin')
.useXpath().click("//button[text()='Activate Learneth']")
.pause(2000)
.frameParent()
.useCss().waitForElementPresent('[data-id="verticalIconsKindLearnEth"]')
},
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="pluginManager"]')
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.getInstalledPlugins((plugins) => {
browser.refresh()
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.perform((done) => {
// const filtered = plugins.filter(plugin => plugin !== 'testremixIde') // remove this when localplugin bug is resolved
plugins.forEach(plugin => {
if ((plugin !== testData.pluginName) && plugin !== localPluginData.pluginName) {
if (plugin !== testData.pluginName) {
browser.waitForElementVisible(`[plugin="${plugin}"`)
}
})

@ -0,0 +1,327 @@
'use strict'
import { ExternalProfile, LocationProfile, Profile } from '@remixproject/plugin-utils'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testmode: boolean; }
}
const localPluginData: Profile & LocationProfile & ExternalProfile = {
name: 'localPlugin',
displayName: 'Local Plugin',
canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting'],
url: 'http://localhost:2020',
location: 'sidePanel'
}
const getBrowserLogs = async function (browser: NightwatchBrowser) {
browser.getLog('browser', (logEntries) => {
if (logEntries && logEntries.length > 0) {
console.log('Browser log:')
console.log(logEntries)
}
})
}
const debugValues = async function (browser: NightwatchBrowser, field: string, expected: any) {
return new Promise((resolve) => {
if (!expected) {
resolve(true)
return
}
browser.waitForElementVisible(`//*[@id="${field}"]`).getText(`//*[@id="${field}"]`, (result) => {
console.log(result)
if (!result.value.toString().includes(expected)) {
console.log('Actual result:')
console.log(result.value.toString())
console.log('Expected result:')
console.log(expected)
getBrowserLogs(browser)
browser.assert.ok(false, 'Returned value from call does not match expected value.')
} else {
browser.assert.ok(true)
}
resolve(true)
})
})
}
const setPayload = async (browser: NightwatchBrowser, payload: any) => {
return new Promise((resolve) => {
if (typeof payload !== 'string') payload = JSON.stringify(payload)
browser.clearValue('//*[@id="payload"]').setValue('//*[@id="payload"]', payload, (result) => {
resolve(result)
})
})
}
const clearPayLoad = async (browser: NightwatchBrowser) => {
return new Promise((resolve) => {
browser.clearValue('//*[@id="payload"]', () => {
resolve(true)
})
})
}
const clickButton = async (browser: NightwatchBrowser, buttonText: string) => {
return new Promise((resolve) => {
browser.useXpath().waitForElementVisible(`//*[@data-id='${buttonText}']`).pause(100)
.click(`//*[@data-id='${buttonText}']`, async () => {
await checkForAcceptAndRemember(browser)
browser.waitForElementContainsText('//*[@id="callStatus"]', 'stop').perform(() => resolve(true))
})
})
}
const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
return new Promise((resolve) => {
browser.frameParent(() => {
browser.pause(1000).element('xpath', '//*[@data-id="permissionHandlerRememberUnchecked"]', (visible:any) => {
if (visible.status && visible.status === -1) {
// @ts-ignore
browser.frame(0, () => { resolve(true) })
} else {
browser.waitForElementVisible('//*[@data-id="permissionHandlerRememberUnchecked"]').click('//*[@data-id="permissionHandlerRememberUnchecked"]').waitForElementVisible('//*[@id="modal-footer-ok"]').click('//*[@id="modal-footer-ok"]', () => {
// @ts-ignore
browser.frame(0, () => { resolve(true) })
})
}
})
})
})
}
/**
* performs an action on the test local plugin calling a method on a plugin
*
* @param {NightwatchBrowser} browser
* @param {string} buttonText the button which needs to be clicked formatted as pluginname:methodname, ie 'fileManager:writeFile'
* @param {any} methodResult can be a string expected or an object. it is the result of the method called.
* @param {any} eventResult can be a string expected or an object. it is the event generated by the method called.
* @param {any} payload can be a string expected or an object. it is the payload passed to the call
* @return {Promise}
*/
const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any) => {
if (payload) {
await setPayload(browser, payload)
} else {
await clearPayLoad(browser)
}
if (methodResult && typeof methodResult !== 'string') { methodResult = JSON.stringify(methodResult) }
if (eventResult && typeof eventResult !== 'string') { eventResult = JSON.stringify(eventResult) }
if (buttonText) {
await clickButton(browser, buttonText)
}
await debugValues(browser, 'methods', methodResult)
await debugValues(browser, 'events', eventResult)
}
const assertPluginIsActive = function (browser: NightwatchBrowser, id: string, shouldBeVisible: boolean) {
if (shouldBeVisible) {
browser.waitForElementVisible(`//*[@data-id="verticalIconsKind${id}"]`)
} else {
browser.waitForElementNotPresent(`//*[@data-id="verticalIconsKind${id}"]`)
}
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
afterEach: function (browser: NightwatchBrowser) {
browser.getLog('browser', (logEntries) => {
console.log(logEntries)
})
},
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.addLocalPlugin(localPluginData)
// @ts-ignore
.frame(0).useXpath()
},
// UDAPP
'Should get accounts': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'udapp:getAccounts', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', null, null)
},
// context menu item
'Should create context menu item': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:registerContextMenuItem', null, null, {
id: 'localPlugin',
name: 'testCommand',
label: 'testCommand',
type: [],
extension: ['.sol'],
path: [],
pattern: []
})
await browser.useXpath().frameParent(async () => {
browser.useCss().clickLaunchIcon('filePanel')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts"]').element('css selector', '[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', (visible: any) => {
if (visible.status && visible.status === -1) {
browser.click('[data-id="treeViewLitreeViewItemcontracts"]')
}
})
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.rightClick('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]').useXpath().waitForElementVisible('//*[@id="menuitemtestcommand"]').click('//*[@id="menuitemtestcommand"]', async () => {
// @ts-ignore
browser.click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
await clickAndCheckLog(browser, null, { id: 'localPlugin', name: 'testCommand', label: 'testCommand', type: [], extension: ['.sol'], path: ['contracts/1_Storage.sol'], pattern: [] }, null, null)
})
})
})
},
// FILESYSTEM
'Should get current workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'default_workspace', isLocalhost: false, absolutePath: '.workspaces/default_workspace' }, null, null)
},
'Should get current files': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/')
},
'Should throw error on current file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null)
},
'Should open readme.txt': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:open', null, { event: 'currentFileChanged', args: ['README.txt'] }, 'README.txt')
},
'Should have current file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'README.txt', null, null)
},
'Should create dir': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:mkdir', null, null, 'testdir')
await clickAndCheckLog(browser, 'fileManager:readdir', 'testdir', null, '/')
},
'Should get file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getFile', 'REMIX EXAMPLE PROJECT', null, 'README.txt')
},
'Should close all files': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:closeAllFiles', null, { event: 'noFileSelected', args: [] }, null)
},
'Should switch to file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:switchFile', null, { event: 'currentFileChanged', args: ['contracts/1_Storage.sol'] }, 'contracts/1_Storage.sol')
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'contracts/1_Storage.sol', null, null)
await clickAndCheckLog(browser, 'fileManager:switchFile', null, { event: 'currentFileChanged', args: ['README.txt'] }, 'README.txt')
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'README.txt', null, null)
},
'Should write to file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:writeFile', null, { event: 'fileSaved', args: ['README.txt'] }, ['README.txt', 'test'])
await clickAndCheckLog(browser, 'fileManager:readFile', 'test', null, 'README.txt')
},
'Should set file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:setFile', null, { event: 'fileAdded', args: ['new.sol'] }, ['new.sol', 'test'])
await clickAndCheckLog(browser, 'fileManager:readFile', 'test', null, 'new.sol')
},
'Should write to new file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:writeFile', null, { event: 'fileAdded', args: ['testing.txt'] }, ['testing.txt', 'test'])
await clickAndCheckLog(browser, 'fileManager:readFile', 'test', null, 'testing.txt')
},
'Should rename file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:rename', null, null, ['testing.txt', 'testrename.txt'])
await clickAndCheckLog(browser, 'fileManager:readFile', 'test', null, 'testrename.txt')
},
'Should create empty workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, ['emptyworkspace', true])
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'emptyworkspace', isLocalhost: false, absolutePath: '.workspaces/emptyworkspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {}, null, '/')
},
'Should create workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null)
},
'Should get all workspaces': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null)
},
'Should have set workspace event': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
},
'Should have event when switching workspace': async function (browser: NightwatchBrowser) {
// @ts-ignore
browser.frameParent().useCss().clickLaunchIcon('filePanel').click('*[data-id="workspacesSelect"] option[value="default_workspace"]').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
await clickAndCheckLog(browser, null, null, { event: 'setWorkspace', args: [{ name: 'default_workspace', isLocalhost: false }] }, null)
})
},
'Should rename workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'testspace', 'newspace', 'renamed'], null, null)
},
'Should delete workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'newspace', 'renamed'], null, null)
},
// DGIT
'Should have changes on new workspace': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit')
await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 0], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_ethers.js', 0, 2, 0], ['scripts/deploy_web3.js', 0, 2, 0], ['tests/4_Ballot_test.sol', 0, 2, 0]], null, null)
},
'Should stage contract': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:add', null, null, {
filepath: 'contracts/1_Storage.sol'
})
await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 2], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_ethers.js', 0, 2, 0], ['scripts/deploy_web3.js', 0, 2, 0], ['tests/4_Ballot_test.sol', 0, 2, 0]], null, null)
},
'Should commit changes': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' })
await clickAndCheckLog(browser, 'dGitProvider:log', 'commit-message', null, null)
},
'Should have git log': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:log', 'commit-message', null, null)
},
'Should have branches': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:branches', [{ name: 'main' }], null, null)
},
// resolver
'Should resolve url': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'contentImport:resolve', '# Remix Project', null, 'https://github.com/ethereum/remix-project/blob/master/README.md')
},
'Should resolve and save url': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'contentImport:resolveAndSave', '# Remix Project', { event: 'fileAdded', args: ['.deps/github/ethereum/remix-project/README.md'] }, 'https://github.com/ethereum/remix-project/blob/master/README.md')
},
// UNIT TESTING
'Should activate solidityUnitTesting': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'manager:activatePlugin', null, null, 'solidityUnitTesting')
browser.frameParent()
assertPluginIsActive(browser, 'solidityUnitTesting', true)
// @ts-ignore
browser.frame(0)
await clickAndCheckLog(browser, 'manager:isActive', true, null, 'solidityUnitTesting')
},
'Should test from path with solidityUnitTesting': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'solidityUnitTesting:testFromPath', '"totalPassing":2,"totalFailing":0', null, 'tests/4_Ballot_test.sol')
},
'Should deactivate solidityUnitTesting': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'manager:deactivatePlugin', null, null, 'solidityUnitTesting')
browser.frameParent()
assertPluginIsActive(browser, 'solidityUnitTesting', false)
// @ts-ignore
browser.frame(0)
await clickAndCheckLog(browser, 'manager:isActive', false, null, 'solidityUnitTesting')
},
// COMPILER
'Should compile a file': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'solidity:compile', null, null, 'contracts/1_Storage.sol')
browser.pause(5000, async () => {
await clickAndCheckLog(browser, 'solidity:compile', null, 'compilationFinished', null)
})
},
'Should get compilationresults': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'solidity:getCompilationResult', 'contracts/1_Storage.sol', null, null)
}
}

@ -129,7 +129,7 @@ contract t2est {
const records = `{
"accounts": {
"account{2}": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"
"account{10}": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"
},
"linkReferences": {
"testLib": "created{1512830014773}"
@ -146,7 +146,7 @@ const records = `{
"linkReferences": {},
"inputs": "()",
"type": "constructor",
"from": "account{2}"
"from": "account{10}"
}
},
{
@ -172,7 +172,7 @@ const records = `{
"name": "",
"type": "constructor",
"inputs": "(uint256)",
"from": "account{2}"
"from": "account{10}"
}
},
{
@ -188,7 +188,7 @@ const records = `{
"name": "set",
"inputs": "(uint256,address)",
"type": "function",
"from": "account{2}"
"from": "account{10}"
}
}
],
@ -287,7 +287,7 @@ const records = `{
const scenario = {
accounts: {
'account{2}': '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
'account{10}': '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
},
linkReferences: {},
transactions: [
@ -305,7 +305,7 @@ const scenario = {
name: '',
type: 'constructor',
inputs: '(uint256)',
from: 'account{2}'
from: 'account{10}'
}
},
{
@ -320,7 +320,7 @@ const scenario = {
name: 'set',
inputs: '(uint256)',
type: 'function',
from: 'account{2}'
from: 'account{10}'
}
}
],

@ -85,7 +85,7 @@ module.exports = {
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
browser
.click('[data-id="staticAnalysisModuleMiscellaneous1"')
.waitForElementPresent('.highlightLine15', 60000)
.waitForElementPresent('.highlightLine16', 60000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'function _sendLogPayload(bytes memory payload) private view {') !== -1,
@ -153,7 +153,6 @@ function runTests (browser: NightwatchBrowser) {
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-path="folder1"]')
.click('[data-path="folder1"]')
.click('[data-path="folder1"]') // click twice because remixd does not return nested folder details after update
.waitForElementVisible('[data-path="folder1/contract1.sol"]')
.waitForElementVisible('[data-path="folder1/renamed_contract_' + browserName + '.sol"]') // check if renamed file is preset
.waitForElementNotPresent('[data-path="folder1/contract_' + browserName + '.sol"]') // check if renamed (old) file is not present

@ -75,7 +75,9 @@ module.exports = {
.waitForElementVisible('[data-id="https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"]', 120000)
.scrollAndClick('[data-id="https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"]') // click on error which point to ERC20 code
.pause(5000)
.waitForElementContainsText('#input', 'contract ERC20 is Context, IERC20', 60000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract ERC20 is Context, IERC20') !== -1, 'content does not contain "contract ERC20 is Context, IERC20"')
})
},
'Test NPM Import (with unpkg.com)': function (browser: NightwatchBrowser) {

@ -32,9 +32,10 @@ module.exports = {
.click('*[data-id="verticalIconsKindsolidityUnitTesting"]')
.waitForElementPresent('*[data-id="testTabGenerateTestFile"]')
.click('*[data-id="testTabGenerateTestFile"]')
.waitForElementPresent('*[title="tests/simple_storage_test.sol"]')
.waitForElementPresent('*[title="default_workspace/tests/simple_storage_test.sol"]')
.clickLaunchIcon('filePanel')
.pause(10000)
.waitForElementPresent('[data-id="treeViewDivtreeViewItemtests"]')
.click('[data-id="treeViewDivtreeViewItemtests"]')
.openFile('tests/simple_storage_test.sol')
.removeFile('tests/simple_storage_test.sol', 'default_workspace')
},
@ -93,7 +94,7 @@ module.exports = {
'Should fail on compilation, open file on error click, not disappear error': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/compilationError_test.sol', sources[0]['compilationError_test.sol'])
.click('div[title="default_workspace/tests/compilationError_test.sol"] span[class="close"]')
.click('div[title="default_workspace/tests/compilationError_test.sol"] span[class="close-tabs"]')
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
.click('*[data-id="testTabCheckAllTests"]')
@ -101,12 +102,12 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.waitForElementContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'SyntaxError: No visibility specified', 120000)
.waitForElementContainsText('*[data-id="testTabTestsExecutionStoppedError"]', 'The test execution has been stopped because of error(s) in your test file', 120000)
.click('*[data-id="tests/compilationError_test.sol"]')
.click('#solidityUnittestsOutput *[data-id="tests/compilationError_test.sol"]')
.pause(1000)
.getEditorValue((content) => browser.assert.ok(content.indexOf('contract failOnCompilation {') !== -1))
// Verify that compilation error is still present after a file is opened
// usually, tests result is cleared on opening a new file
.verify.elementPresent('*[data-id="tests/compilationError_test.sol"]')
.verify.elementPresent('#solidityUnittestsOutput *[data-id="tests/compilationError_test.sol"]')
},
'Should fail on deploy': function (browser: NightwatchBrowser) {
@ -164,8 +165,9 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesSelect"] option[value="workspace_new"]')
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.execute(function () { (document.querySelector('[data-id="fileSystem-modal-footer-ok-react"]') as HTMLElement).click() })
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_new"]')
// end of creating
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
@ -181,6 +183,7 @@ module.exports = {
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
.verify.attributeEquals('*[data-id="uiPathInput"]', 'value', 'tests')
.pause(2000)
.scrollAndClick('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 60000)
@ -190,8 +193,17 @@ module.exports = {
},
'Solidity Unit tests with hardhat console log': function (browser: NightwatchBrowser) {
const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.perform((done) => {
if (runtimeBrowser !== 'chrome') {
browser.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-id="treeViewLitreeViewItemtests"]')
}
done()
})
.addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/4_Ballot_test.sol"]', 60000)
@ -201,13 +213,13 @@ module.exports = {
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/hhLogs_test.sol', 60000)
.assert.containsText('#journal > div:nth-child(3) > span > div', 'Before all:')
.assert.containsText('#journal > div:nth-child(3) > span > div', 'Inside beforeAll')
.assert.containsText('#journal > div:nth-child(4) > span > div', 'Check sender:')
.assert.containsText('#journal > div:nth-child(4) > span > div', 'msg.sender is 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.assert.containsText('#journal > div:nth-child(5) > span > div', 'Check int logs:')
.assert.containsText('#journal > div:nth-child(5) > span > div', '10 20')
.assert.containsText('#journal > div:nth-child(5) > span > div', 'Number is 25')
.assert.containsText('#journal > div:nth-child(2) > span', 'Before all:')
.assert.containsText('#journal > div:nth-child(2) > span', 'Inside beforeAll')
.assert.containsText('#journal > div:nth-child(3) > span', 'Check sender:')
.assert.containsText('#journal > div:nth-child(3) > span', 'msg.sender is 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.assert.containsText('#journal > div:nth-child(4) > span', 'Check int logs:')
.assert.containsText('#journal > div:nth-child(4) > span', '10 20')
.assert.containsText('#journal > div:nth-child(4) > span', 'Number is 25')
.openFile('tests/hhLogs_test.sol')
.removeFile('tests/hhLogs_test.sol', 'workspace_new')
},
@ -223,13 +235,13 @@ module.exports = {
.pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedLog_test.sol', 60000)
.assert.containsText('#journal > div:nth-child(6) > span > div', 'Check winning proposal:')
.assert.containsText('#journal > div:nth-child(6) > span > div', 'Inside checkWinningProposal')
.assert.containsText('#journal > div:nth-child(5) > span', 'Check winning proposal:')
.assert.containsText('#journal > div:nth-child(5) > span', 'Inside checkWinningProposal')
.openFile('tests/ballotFailedLog_test.sol')
.removeFile('tests/ballotFailedLog_test.sol', 'workspace_new')
},
'Debug failed test using debugger': function (browser: NightwatchBrowser) {
'Debug tests using debugger': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol'])
@ -239,21 +251,50 @@ module.exports = {
.click('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', '✘ Check winning proposal', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', '✘ Check winning proposal failed', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', '✓ Check winning proposal passed', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', '✘ Check winning proposal again', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', '✓ Check winnin proposal with return value', 60000)
.click('.fa-bug')
.click('#Check_winning_proposal_failed')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposal()', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.click('*[data-id="dropdownPanelSolidityLocals"]')
.waitForElementContainsText('*[data-id="solidityLocals"]', 'no locals', 60000)
// eslint-disable-next-line dot-notation
.execute(function () { document.getElementById('slider')['value'] = '235' }) // It only moves slider to 235 but vm traces are not updated
.execute(function () { document.getElementById('slider')['value'] = '315' }) // It only moves slider to 315 but vm traces are not updated
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposal()', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000)
.pause(2000)
// Should be uncommented while fixing https://github.com/ethereum/remix-project/issues/1644
// .checkVariableDebug('soliditylocals', locals)
.pause(1000)
.checkVariableDebug('soliditylocals', locals)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#Check_winning_proposal_passed')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
// eslint-disable-next-line dot-notation
.execute(function () { document.getElementById('slider')['value'] = '1450' })
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
.pause(1000)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#Check_winning_proposal_again')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
// eslint-disable-next-line dot-notation
.execute(function () { document.getElementById('slider')['value'] = '1150' })
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
.pause(1000)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#Check_winnin_proposal_with_return_value')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinninProposalWithReturnValue()', 60000)
// eslint-disable-next-line dot-notation
.execute(function () { document.getElementById('slider')['value'] = '320' })
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinninProposalWithReturnValue()', 60000)
.clickLaunchIcon('filePanel')
.pause(2000)
.openFile('tests/ballotFailedDebug_test.sol')
@ -269,6 +310,7 @@ module.exports = {
.scrollAndClick('[data-id="pluginManagerComponentDeactivateButtonsolidityUnitTesting"]')
.pause(2000)
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityUnitTesting"]')
.pause(5000)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -458,7 +500,7 @@ const sources = [
},
'tests/deployError_test.sol': {
content: `
pragma solidity ^0.7.0;
pragma solidity ^0.8.0;
contract failingDeploy {
constructor() {
@ -469,7 +511,7 @@ const sources = [
},
'tests/methodFailure_test.sol': {
content: `
pragma solidity ^0.7.0;
pragma solidity ^0.8.0;
contract methodfailure {
function add(uint a, uint b) public {
@ -496,11 +538,20 @@ const sources = [
ballotToTest = new Ballot(proposalNames);
}
function checkWinningProposal () public {
ballotToTest.vote(1); // This will revert the transaction
function checkWinningProposalFailed () public {
ballotToTest.vote(1);
Assert.equal(ballotToTest.winningProposal(), uint(0), "proposal at index 0 should be the winning proposal");
}
function checkWinningProposalPassed () public {
ballotToTest.vote(0);
Assert.equal(ballotToTest.winningProposal(), uint(0), "proposal at index 0 should be the winning proposal");
}
function checkWinningProposalAgain () public {
Assert.equal(ballotToTest.winningProposal(), uint(1), "proposal at index 0 should be the winning proposal");
}
function checkWinninProposalWithReturnValue () public view returns (bool) {
return ballotToTest.winningProposal() == 0;
}
@ -558,7 +609,7 @@ const sources = [
}
}
]
/*
const locals = {
sender: {
value: {
@ -586,4 +637,3 @@ const locals = {
type: 'uint256'
}
}
*/

@ -11,6 +11,7 @@ module.exports = {
browser
.waitForElementVisible('*[data-id="terminalCli"]', 10000)
.executeScript('console.log(1 + 1)')
.pause(2000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '2', 60000)
},
@ -30,24 +31,14 @@ module.exports = {
.assert.visible('*[data-id="autoCompletePopUpAutoCompleteItem"]')
},
'Should execute remix.help() command': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="terminalCli"]')
.executeScript('remix.help()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.loadgist(id)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.loadurl(url)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.execute(filepath)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.exeCurrent()', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.help()', 60000)
},
'Async/Await Script': function (browser: NightwatchBrowser) {
browser
.addFile('asyncAwait.js', { content: asyncAwait })
.openFile('asyncAwait.js')
.executeScript('remix.execute(\'asyncAwait.js\')')
.executeScript('remix.execute("asyncAwait.js")')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Waiting Promise', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result - Promise Resolved', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result - ', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Promise Resolved', 60000)
},
'Call Remix File Manager from a script': function (browser: NightwatchBrowser) {
@ -62,19 +53,19 @@ module.exports = {
'Call web3.eth.getAccounts() using JavaScript VM': function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB", "0x617F2E2fD72FD9D5503197092aC168c91465E7f2", "0x17F6AD8Ef982297579C203069C1DbfFE4348c372", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C"', 80000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]')
},
'Call web3.eth.getAccounts() using Web3 Provider': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick()
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '[ "', 60000) // we check if an array is present, don't need to check for the content
.waitForElementContainsText('*[data-id="terminalJournal"]', '" ]', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '", "', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
.waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '","', 60000)
},
'Call Remix File Resolver (external URL) from a script': function (browser: NightwatchBrowser) {
@ -120,19 +111,23 @@ module.exports = {
.addFile('deployWithEthersJs.js', { content: deployWithEthersJs })
.openFile('deployWithEthersJs.js')
.pause(1000)
.click('[data-id="treeViewDivtreeViewItemcontracts"]')
.openFile('contracts/2_Owner.sol')
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]') // compile Owner
.executeScript('remix.execute(\'deployWithEthersJs.js\')')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address: 0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000)
.addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true)
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]')
.clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' }) // execute the "changeOwner" function
.waitForElementContainsText('*[data-id="terminalJournal"]', 'previousOwner0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'previousOwner', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.end()
}
}

@ -99,7 +99,7 @@ module.exports = {
'decoded output': {
0: 'uint256: _uret 2343242',
1: 'int256: _iret -4324324',
2: 'string: _strret string _ string _ string _ string _ string _ string _ string _ string _ string _ string _'
2: 'string: _strret string _ string _ string _ string _ string _ string _ string _ string _ string _ string _'
}
})
.pause(500)
@ -147,12 +147,13 @@ module.exports = {
.waitForElementPresent('.instance:nth-of-type(3)')
.click('.instance:nth-of-type(3) > div > button')
.clickFunction('g - transact (not payable)')
.pause(5000)
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"value": "2"')
.journalLastChildIncludes('"value": "3"')
.journalLastChildIncludes('"value": "error_string_2"')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')
@ -170,9 +171,9 @@ module.exports = {
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"value": "2"')
.journalLastChildIncludes('"value": "3"')
.journalLastChildIncludes('"value": "error_string_2"')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')

@ -19,7 +19,7 @@ module.exports = {
browser
.pause(5000)
.refresh()
.pause(2000)
.pause(10000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Ballot {') !== -1, 'content doesn\'t include Ballot contract')
})
@ -35,19 +35,26 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]') // create workspace_name
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_name' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(1000)
.addFile('test.sol', { content: 'test' })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspaceCreate"]') // create workspace_name_1
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_name_1' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.pause(2000)
.click('*[data-id="workspacesSelect"] option[value="workspace_name"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
},
@ -59,10 +66,15 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextRename"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextRename"]')['value'] = 'workspace_name_renamed' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.waitForElementPresent('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.pause(2000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
},
@ -70,8 +82,8 @@ module.exports = {
browser
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.end()
},

@ -1,6 +1,6 @@
// Merge custom command types with nightwatch types
import { NightwatchBrowser } from 'nightwatch'
/* eslint-disable no-use-before-define */
import { NightwatchBrowser } from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars
declare module 'nightwatch' {
export interface NightwatchCustomCommands {
@ -27,9 +27,9 @@ declare module 'nightwatch' {
debugTransaction(index: number): NightwatchBrowser,
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser,
editorScroll(direction: 'up' | 'down', numberOfTimes: number): NightwatchBrowser,
renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser,
rightClick(cssSelector: string): NightwatchBrowser,
scrollToLine(line: number): NightwatchBrowser,
waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(): NightwatchBrowser,
@ -57,6 +57,8 @@ declare module 'nightwatch' {
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser
acceptAndRemember (this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
}
export interface NightwatchBrowser {

@ -12,7 +12,7 @@ npx nx serve remix-ide-e2e-src-local-plugin &
sleep 5
npm run build:e2e
npm run nightwatch_local_pluginManager || TEST_EXITCODE=1
npm run nightwatch_local_pluginApi || TEST_EXITCODE=1
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]

@ -162,8 +162,7 @@ class App {
}
init () {
var self = this
run.apply(self)
this.run().catch(console.error)
}
render () {
@ -202,317 +201,322 @@ class App {
`
return self._view.el
}
}
module.exports = App
async function run () {
var self = this
// check the origin and warn message
if (window.location.hostname === 'yann300.github.io') {
modalDialogCustom.alert('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.')
} else if (window.location.hostname === 'remix-alpha.ethereum.org' ||
(window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0)) {
modalDialogCustom.alert('Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.')
} else if (window.location.protocol.indexOf('http') === 0 &&
window.location.hostname !== 'remix.ethereum.org' &&
window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1') {
modalDialogCustom.alert(`The Remix IDE has moved to http://remix.ethereum.org.\n
This instance of Remix you are visiting WILL NOT BE UPDATED.\n
Please make a backup of your contracts and start using http://remix.ethereum.org`)
}
if (window.location.protocol.indexOf('https') === 0) {
toolTip('You are using an `https` connection. Please switch to `http` if you are using Remix against an `http Web3 provider` or allow Mixed Content in your browser.')
}
async run () {
var self = this
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
if (!isElectron() && !hosts.includes(window.location.host)) {
// Oops! Accidentally trigger refresh or bookmark.
window.onbeforeunload = function () {
return 'Are you sure you want to leave?'
// check the origin and warn message
if (window.location.hostname === 'yann300.github.io') {
modalDialogCustom.alert('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.')
} else if (window.location.hostname === 'remix-alpha.ethereum.org' ||
(window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0)) {
modalDialogCustom.alert('Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.')
} else if (window.location.protocol.indexOf('http') === 0 &&
window.location.hostname !== 'remix.ethereum.org' &&
window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1') {
modalDialogCustom.alert(`The Remix IDE has moved to http://remix.ethereum.org.\n
This instance of Remix you are visiting WILL NOT BE UPDATED.\n
Please make a backup of your contracts and start using http://remix.ethereum.org`)
}
}
// APP_MANAGER
const appManager = self.appManager
const pluginLoader = appManager.pluginLoader
const workspace = pluginLoader.get()
const engine = new RemixEngine()
engine.register(appManager)
// SERVICES
// ----------------- theme service ---------------------------------
const themeModule = new ThemeModule(registry)
registry.put({ api: themeModule, name: 'themeModule' })
themeModule.initTheme(() => {
setTimeout(() => {
document.body.removeChild(self._view.splashScreen)
self._view.el.style.visibility = 'visible'
}, 1500)
})
// ----------------- editor service ----------------------------
const editor = new Editor({}, themeModule) // wrapper around ace editor
registry.put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile())
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
registry.put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
const blockchain = new Blockchain(registry.get('config').api)
// ----------------- compilation metadata generation service ---------
const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
registry.put({ api: compilersArtefacts, name: 'compilersartefacts' })
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
registry.put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' })
// -------------------Terminal----------------------------------------
const terminal = new Terminal(
{ appManager, blockchain },
{
getPosition: (event) => {
var limitUp = 36
var limitDown = 20
var height = window.innerHeight
var newpos = (event.pageY < limitUp) ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return height - newpos
}
if (window.location.protocol.indexOf('https') === 0) {
toolTip('You are using an `https` connection. Please switch to `http` if you are using Remix against an `http Web3 provider` or allow Mixed Content in your browser.')
}
)
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const contextualListener = new ContextualListener({ editor })
engine.register([
blockchain,
contentImport,
themeModule,
editor,
fileManager,
compilerMetadataGenerator,
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
contextualListener,
terminal,
web3Provider,
fetchAndCompile,
dGitProvider,
hardhatProvider
])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
const mainview = new MainView(contextualListener, editor, appPanel, fileManager, appManager, terminal)
registry.put({ api: mainview, name: 'mainview' })
engine.register([
appPanel,
mainview.tabProxy
])
// those views depend on app_manager
const menuicons = new VerticalIcons(appManager)
const sidePanel = new SidePanel(appManager, menuicons)
const hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, engine)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(appManager, menuicons, fileManager, filePanel, contentImport)
const settings = new SettingsTab(
registry.get('config').api,
editor,
appManager
)
// adding Views to the DOM
self._view.mainpanel.appendChild(mainview.render())
self._view.iconpanel.appendChild(menuicons.render())
self._view.sidepanel.appendChild(sidePanel.render())
document.body.appendChild(hiddenPanel.render()) // Hidden Panel is display none, it can be directly on body
engine.register([
menuicons,
landingPage,
hiddenPanel,
sidePanel,
filePanel,
pluginManagerComponent,
settings
])
const queryParams = new QueryParams()
const params = queryParams.get()
const onAcceptMatomo = () => {
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
settings.updateMatomoAnalyticsChoice(true)
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
const onDeclineMatomo = () => {
settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut'])
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
const startWalkthroughService = () => {
const walkthroughService = new WalkthroughService(localStorage)
if (!params.code && !params.url && !params.minimizeterminal && !params.gist && !params.minimizesidepanel) {
walkthroughService.start()
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
if (!isElectron() && !hosts.includes(window.location.host)) {
// Oops! Accidentally trigger refresh or bookmark.
window.onbeforeunload = function () {
return 'Are you sure you want to leave?'
}
}
}
// Ask to opt in to Matomo for remix, remix-alpha and remix-beta
const matomoDomains = {
'remix-alpha.ethereum.org': 27,
'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23
}
if (matomoDomains[window.location.hostname] && !registry.get('config').api.exists('settings/matomo-analytics')) {
modalDialog(
'Help us to improve Remix IDE',
yo`
<div>
<p>An Opt-in version of <a href="https://matomo.org" target="_blank">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank">take a look</a>.</p>
<p>We do not collect nor store any personally identifiable information (PII).</p>
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank">Matomo Analyitcs on Remix iDE</a>.</p>
<p>You can change your choice in the Settings panel anytime.</p>
<div class="d-flex justify-content-around pt-3 border-top">
<button class="btn btn-primary ${css.matomoBtn}" onclick=${() => onAcceptMatomo()}>Sure</button>
<button class="btn btn-secondary ${css.matomoBtn}" onclick=${() => onDeclineMatomo()}>Decline</button>
</div>
</div>`,
{
label: '',
fn: null
},
// APP_MANAGER
const appManager = self.appManager
const pluginLoader = appManager.pluginLoader
const workspace = pluginLoader.get()
const engine = new RemixEngine()
engine.register(appManager)
// SERVICES
// ----------------- theme service ---------------------------------
const themeModule = new ThemeModule(registry)
registry.put({ api: themeModule, name: 'themeModule' })
themeModule.initTheme(() => {
setTimeout(() => {
document.body.removeChild(self._view.splashScreen)
self._view.el.style.visibility = 'visible'
}, 1500)
})
// ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor
registry.put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile())
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
registry.put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
const blockchain = new Blockchain(registry.get('config').api)
// ----------------- compilation metadata generation service ---------
const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
registry.put({ api: compilersArtefacts, name: 'compilersartefacts' })
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
registry.put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' })
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{
label: '',
fn: null
getPosition: (event) => {
var limitUp = 36
var limitDown = 20
var height = window.innerHeight
var newpos = (event.pageY < limitUp) ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return height - newpos
}
}
)
} else {
startWalkthroughService()
}
const contextualListener = new ContextualListener({ editor })
engine.register([
blockchain,
contentImport,
themeModule,
editor,
fileManager,
compilerMetadataGenerator,
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
contextualListener,
terminal,
web3Provider,
fetchAndCompile,
dGitProvider,
hardhatProvider
])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
const mainview = new MainView(contextualListener, editor, appPanel, fileManager, appManager, terminal)
registry.put({ api: mainview, name: 'mainview' })
engine.register([
appPanel,
mainview.tabProxy
])
// those views depend on app_manager
const menuicons = new VerticalIcons(appManager)
const sidePanel = new SidePanel(appManager, menuicons)
const hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, engine)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(appManager, menuicons, fileManager, filePanel, contentImport)
const settings = new SettingsTab(
registry.get('config').api,
editor,
appManager
)
// CONTENT VIEWS & DEFAULT PLUGINS
const compileTab = new CompileTab(registry.get('config').api, registry.get('filemanager').api)
const run = new RunTab(
blockchain,
registry.get('config').api,
registry.get('filemanager').api,
registry.get('editor').api,
filePanel,
registry.get('compilersartefacts').api,
networkModule,
mainview,
registry.get('fileproviders/browser').api
)
const analysis = new AnalysisTab(registry)
const debug = new DebuggerTab()
const test = new TestTab(
registry.get('filemanager').api,
registry.get('offsettolinecolumnconverter').api,
filePanel,
compileTab,
appManager,
contentImport
)
engine.register([
compileTab,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
filePanel.gitHandle,
filePanel.hardhatHandle,
filePanel.slitherHandle
])
if (isElectron()) {
appManager.activatePlugin('remixd')
}
// adding Views to the DOM
self._view.mainpanel.appendChild(mainview.render())
self._view.iconpanel.appendChild(menuicons.render())
self._view.sidepanel.appendChild(sidePanel.render())
document.body.appendChild(hiddenPanel.render()) // Hidden Panel is display none, it can be directly on body
engine.register([
menuicons,
landingPage,
hiddenPanel,
sidePanel,
filePanel,
pluginManagerComponent,
settings
])
const queryParams = new QueryParams()
const params = queryParams.get()
const onAcceptMatomo = () => {
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
settings.updateMatomoAnalyticsChoice(true)
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
const onDeclineMatomo = () => {
settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut'])
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
startWalkthroughService()
}
try {
engine.register(await appManager.registeredPlugins())
} catch (e) {
console.log('couldn\'t register iframe plugins', e.message)
}
const startWalkthroughService = () => {
const walkthroughService = new WalkthroughService(localStorage)
if (!params.code && !params.url && !params.minimizeterminal && !params.gist && !params.minimizesidepanel) {
walkthroughService.start()
}
}
await appManager.activatePlugin(['theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await appManager.activatePlugin(['home'])
await appManager.activatePlugin(['settings'])
await appManager.activatePlugin(['hiddenPanel', 'filePanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport'])
await appManager.registerContextMenuItems()
// Set workspace after initial activation
if (Array.isArray(workspace)) {
appManager.activatePlugin(workspace).then(async () => {
try {
if (params.deactivate) {
await appManager.deactivatePlugin(params.deactivate.split(','))
// Ask to opt in to Matomo for remix, remix-alpha and remix-beta
const matomoDomains = {
'remix-alpha.ethereum.org': 27,
'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23
}
if (matomoDomains[window.location.hostname] && !registry.get('config').api.exists('settings/matomo-analytics')) {
modalDialog(
'Help us to improve Remix IDE',
yo`
<div>
<p>An Opt-in version of <a href="https://matomo.org" target="_blank">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank">take a look</a>.</p>
<p>We do not collect nor store any personally identifiable information (PII).</p>
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank">Matomo Analyitcs on Remix iDE</a>.</p>
<p>You can change your choice in the Settings panel anytime.</p>
<div class="d-flex justify-content-around pt-3 border-top">
<button class="btn btn-primary ${css.matomoBtn}" onclick=${() => onAcceptMatomo()}>Sure</button>
<button class="btn btn-secondary ${css.matomoBtn}" onclick=${() => onDeclineMatomo()}>Decline</button>
</div>
</div>`,
{
label: '',
fn: null
},
{
label: '',
fn: null
}
} catch (e) {
console.log(e)
}
)
} else {
startWalkthroughService()
}
if (params.code) {
// if code is given in url we focus on solidity plugin
menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
}
// CONTENT VIEWS & DEFAULT PLUGINS
const compileTab = new CompileTab(registry.get('config').api, registry.get('filemanager').api)
const run = new RunTab(
blockchain,
registry.get('config').api,
registry.get('filemanager').api,
registry.get('editor').api,
filePanel,
registry.get('compilersartefacts').api,
networkModule,
mainview,
registry.get('fileproviders/browser').api
)
const analysis = new AnalysisTab(registry)
const debug = new DebuggerTab()
const test = new TestTab(
registry.get('filemanager').api,
registry.get('offsettolinecolumnconverter').api,
filePanel,
compileTab,
appManager,
contentImport
)
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
toolTip(`initiating ${callDetails[0]} ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
appManager.call(...callDetails).catch(console.error)
}
engine.register([
compileTab,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
filePanel.gitHandle,
filePanel.hardhatHandle,
filePanel.slitherHandle
])
if (isElectron()) {
appManager.activatePlugin('remixd')
}
try {
engine.register(await appManager.registeredPlugins())
} catch (e) {
console.log('couldn\'t register iframe plugins', e.message)
}
await appManager.activatePlugin(['editor'])
await appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await appManager.activatePlugin(['home'])
await appManager.activatePlugin(['settings'])
await appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport'])
appManager.on('filePanel', 'workspaceInitializationCompleted', async () => {
await appManager.registerContextMenuItems()
})
await appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation
appManager.on('editor', 'editorMounted', () => {
if (Array.isArray(workspace)) {
appManager.activatePlugin(workspace).then(async () => {
try {
if (params.deactivate) {
await appManager.deactivatePlugin(params.deactivate.split(','))
}
} catch (e) {
console.log(e)
}
if (params.code) {
// if code is given in url we focus on solidity plugin
menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
}
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
toolTip(`initiating ${callDetails[0]} ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
appManager.call(...callDetails).catch(console.error)
}
}
}).catch(console.error)
} else {
// activate solidity plugin
appManager.activatePlugin(['solidity', 'udapp'])
}
}).catch(console.error)
} else {
// activate solidity plugin
appManager.activatePlugin(['solidity', 'udapp'])
}
})
// Load and start the service who manager layout and frame
const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature)
// Load and start the service who manager layout and frame
const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature)
if (params.embed) framingService.embed()
framingService.start(params)
if (params.embed) framingService.embed()
framingService.start(params)
}
}
module.exports = App

@ -31,7 +31,7 @@ export class MainPanel extends AbstractPanel {
render () {
return yo`
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer">
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer" id='mainPanelPluginsContainer-id'>
${this.view}
</div>`
}

@ -1,84 +0,0 @@
'use strict'
const SourceHighlighter = require('./sourceHighlighter')
class SourceHighlighters {
constructor () {
this.highlighters = {}
}
highlight (position, filePath, hexColor, from) {
// eslint-disable-next-line
try {
if (!this.highlighters[from]) this.highlighters[from] = []
const sourceHighlight = new SourceHighlighter()
if (
!this.highlighters[from].length ||
(this.highlighters[from].length && !this.highlighters[from].find((el) => {
return el.source === filePath && el.position === position
}))
) {
sourceHighlight.currentSourceLocationFromfileName(position, filePath, hexColor)
this.highlighters[from].push(sourceHighlight)
}
} catch (e) {
throw e
}
}
// highlights all locations for @from plugin
highlightAllFrom (from) {
// eslint-disable-next-line
try {
if (!this.highlighters[from]) return
let sourceHighlight
for (const index in this.highlighters[from]) {
sourceHighlight = new SourceHighlighter()
sourceHighlight.currentSourceLocationFromfileName(
this.highlighters[from][index].position,
this.highlighters[from][index].source,
this.highlighters[from][index].style
)
this.highlighters[from][index] = sourceHighlight
}
} catch (e) {
throw e
}
}
discardHighlight (from) {
if (this.highlighters[from]) {
for (const index in this.highlighters[from]) this.highlighters[from][index].currentSourceLocation(null)
}
this.highlighters[from] = []
}
discardAllHighlights () {
for (const from in this.highlighters) {
this.discardHighlight(from)
}
}
hideHighlightsExcept (toStay) {
for (const highlighter in this.highlighters) {
for (const index in this.highlighters[highlighter]) {
this.highlighters[highlighter][index].currentSourceLocation(null)
}
}
this.highlightAllFrom(toStay)
}
discardHighlightAt (line, filePath, from) {
if (this.highlighters[from]) {
for (const index in this.highlighters[from]) {
const highlight = this.highlighters[from][index]
if (highlight.source === filePath &&
(highlight.position.start.line === line || highlight.position.end.line === line)) {
highlight.currentSourceLocation(null)
this.highlighters[from].splice(index, 1)
}
}
}
}
}
module.exports = SourceHighlighters

@ -4,7 +4,6 @@ import * as packageJson from '../../../../../package.json'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const { AstWalker } = require('@remix-project/remix-astwalker')
const csjs = require('csjs-inject')
const EventManager = require('../../lib/events')
const globalRegistry = require('../../global/registry')
@ -127,14 +126,6 @@ class ContextualListener extends Plugin {
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
const css = csjs`
.highlightref_fullLine {
position: absolute;
z-index: 2;
opacity: 0.1;
background-color: var(--info);
}
`
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 = {
@ -150,7 +141,7 @@ class ContextualListener extends Plugin {
}
const fileName = lastCompilationResult.getSourceName(position.file)
if (fileName) {
return this.editor.addMarker(lineColumn, fileName, css.highlightref_fullLine)
return this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false })
}
}
return null
@ -178,10 +169,7 @@ class ContextualListener extends Plugin {
}
_stopHighlighting () {
for (const eventKey in this._activeHighlights) {
const event = this._activeHighlights[eventKey]
this.editor.removeMarker(event.eventId, event.fileTarget)
}
this.call('editor', 'discardHighlight')
this.event.trigger('stopHighlighting', [])
this._activeHighlights = []
}

@ -1,200 +1,95 @@
'use strict'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { EditorUI } from '@remix-ui/editor' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
const EventManager = require('../../lib/events')
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const ace = require('brace')
const globalRegistry = require('../../global/registry')
const SourceHighlighters = require('./SourceHighlighters')
const Range = ace.acequire('ace/range').Range
require('brace/ext/language_tools')
require('brace/ext/searchbox')
const langTools = ace.acequire('ace/ext/language_tools')
require('ace-mode-solidity/build/remix-ide/mode-solidity')
require('ace-mode-move/build/remix-ide/mode-move')
require('ace-mode-zokrates')
require('ace-mode-lexon')
require('brace/mode/javascript')
require('brace/mode/python')
require('brace/mode/json')
require('brace/mode/rust')
require('brace/theme/chrome') // for all light themes
require('brace/theme/chaos') // for all dark themes
require('../../assets/js/editor/darkTheme') // a custom one for remix 'Dark' theme
const css = csjs`
.ace-editor {
width : 100%;
}
`
document.head.appendChild(yo`
<style>
.ace-tm .ace_gutter,
.ace-tm .ace_gutter-active-line,
.ace-tm .ace_marker-layer .ace_active-line {
background-color: var(--secondary);
}
.ace_gutter-cell.ace_breakpoint{
background-color: var(--secondary);
}
</style>
`)
const profile = {
displayName: 'Editor',
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'discardHighlightAt', 'clearAnnotations', 'addAnnotation', 'gotoLine']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine']
}
class Editor extends Plugin {
constructor (opts = {}, themeModule) {
constructor () {
super(profile)
// Dependancies
this._components = {}
this._components.registry = globalRegistry
this._deps = {
config: this._components.registry.get('config').api
}
this._themes = {
light: 'chrome',
dark: 'chaos',
remixDark: 'remixDark'
light: 'light',
dark: 'vs-dark',
remixDark: 'remix-dark'
}
themeModule.events.on('themeChanged', (theme) => {
this.setTheme(theme.name === 'Dark' ? 'remixDark' : theme.quality)
})
// Init
this.event = new EventManager()
this.sessions = {}
this.sourceAnnotationsPerFile = []
this.sourceAnnotationsPerFile = {}
this.markerPerFile = {}
this.readOnlySessions = {}
this.previousInput = ''
this.saveTimeout = null
this.sourceHighlighters = new SourceHighlighters()
this.emptySession = this._createSession('')
this.emptySession = null
this.modes = {
sol: 'ace/mode/solidity',
yul: 'ace/mode/solidity',
mvir: 'ace/mode/move',
js: 'ace/mode/javascript',
py: 'ace/mode/python',
vy: 'ace/mode/python',
zok: 'ace/mode/zokrates',
lex: 'ace/mode/lexon',
txt: 'ace/mode/text',
json: 'ace/mode/json',
abi: 'ace/mode/json',
rs: 'ace/mode/rust'
sol: 'sol',
yul: 'sol',
mvir: 'move',
js: 'javascript',
py: 'python',
vy: 'python',
zok: 'zokrates',
lex: 'lexon',
txt: 'text',
json: 'json',
abi: 'json',
rs: 'rust'
}
// Editor Setup
const el = yo`<div id="input" data-id="editorInput"></div>`
this.editor = ace.edit(el)
ace.acequire('ace/ext/language_tools')
// Unmap ctrl-l & cmd-l
this.editor.commands.bindKeys({
'ctrl-L': null,
'Command-L': null
})
// shortcuts for "Ctrl-"" and "Ctrl+"" to increase/decrease font size of the editor
this.editor.commands.addCommand({
name: 'increasefontsizeEqual',
bindKey: { win: 'Ctrl-=', mac: 'Command-=' },
exec: (editor) => {
this.editorFontSize(1)
},
readOnly: true
})
this.editor.commands.addCommand({
name: 'increasefontsizePlus',
bindKey: { win: 'Ctrl-+', mac: 'Command-+' },
exec: (editor) => {
this.editorFontSize(1)
},
readOnly: true
})
this.editor.commands.addCommand({
name: 'decreasefontsize',
bindKey: { win: 'Ctrl--', mac: 'Command--' },
exec: (editor) => {
this.editorFontSize(-1)
},
readOnly: true
})
this.editor.setShowPrintMargin(false)
this.editor.resize(true)
this.editor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
})
el.className += ' ' + css['ace-editor']
el.editor = this.editor // required to access the editor during tests
this.render = () => el
this.activated = false
// Completer for editor
const flowCompleter = {
getCompletions: (editor, session, pos, prefix, callback) => {
// @TODO add here other propositions
}
this.events = {
onBreakPointAdded: (file, line) => this.triggerEvent('breakpointAdded', [file, line]),
onBreakPointCleared: (file, line) => this.triggerEvent('breakpointCleared', [file, line]),
onDidChangeContent: (file) => this._onChange(file),
onEditorMounted: () => this.triggerEvent('editorMounted', [])
}
langTools.addCompleter(flowCompleter)
// zoom with Ctrl+wheel
window.addEventListener('wheel', (e) => {
if (e.ctrlKey && Math.abs(e.wheelY) > 5) {
this.editorFontSize(e.wheelY > 0 ? 1 : -1)
}
})
// to be implemented by the react component
this.api = {}
}
// EVENTS LISTENERS
render () {
if (this.el) return this.el
// Gutter Mouse down
this.editor.on('guttermousedown', e => {
const target = e.domEvent.target
if (target.className.indexOf('ace_gutter-cell') === -1) {
return
}
const row = e.getDocumentPosition().row
const breakpoints = e.editor.session.getBreakpoints()
for (const k in breakpoints) {
if (k === row.toString()) {
this.triggerEvent('breakpointCleared', [this.currentSession, row])
e.editor.session.clearBreakpoint(row)
e.stop()
return
}
this.el = document.createElement('div')
this.el.setAttribute('id', 'editorView')
this.el.currentContent = () => this.currentContent() // used by e2e test
this.el.setCurrentContent = (value) => {
if (this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(value)
this._onChange(this.currentFile)
}
this.setBreakpoint(row)
this.triggerEvent('breakpointAdded', [this.currentSession, row])
e.stop()
})
}
this.el.gotoLine = (line) => this.gotoLine(line, 0)
return this.el
}
// Do setup on initialisation here
this.editor.on('changeSession', () => {
this._onChange()
this.triggerEvent('sessionSwitched', [])
this.editor.getSession().on('change', () => {
this._onChange()
this.sourceHighlighters.discardAllHighlights()
this.triggerEvent('contentChanged', [])
})
})
renderComponent () {
ReactDOM.render(
<EditorUI
editorAPI={this.api}
theme={this.currentTheme}
currentFile={this.currentFile}
sourceAnnotationsPerFile={this.sourceAnnotationsPerFile}
markerPerFile={this.markerPerFile}
events={this.events}
plugin={this}
/>
, this.el)
}
triggerEvent (name, params) {
@ -203,14 +98,25 @@ class Editor extends Plugin {
}
onActivation () {
this.activated = true
this.on('sidePanel', 'focusChanged', (name) => {
this.sourceHighlighters.hideHighlightsExcept(name)
this.keepAnnotationsFor(name)
this.keepDecorationsFor(name, 'sourceAnnotationsPerFile')
this.keepDecorationsFor(name, 'markerPerFile')
})
this.on('sidePanel', 'pluginDisabled', (name) => {
this.sourceHighlighters.discardHighlight(name)
this.clearAllAnnotationsFor(name)
this.clearAllDecorationsFor(name)
})
const translateTheme = (theme) => this._themes[theme.name === 'Dark' ? 'remixDark' : theme.quality]
this.on('theme', 'themeChanged', (theme) => {
this.currentTheme = translateTheme(theme)
this.renderComponent()
})
this.call('theme', 'currentTheme', (theme) => {
this.currentTheme = translateTheme(theme)
this.renderComponent()
})
this.renderComponent()
}
onDeactivation () {
@ -218,30 +124,14 @@ class Editor extends Plugin {
this.off('sidePanel', 'pluginDisabled')
}
highlight (position, filePath, hexColor) {
const { from } = this.currentRequest
this.sourceHighlighters.highlight(position, filePath, hexColor, from)
}
discardHighlight () {
const { from } = this.currentRequest
this.sourceHighlighters.discardHighlight(from)
}
discardHighlightAt (line, filePath) {
const { from } = this.currentRequest
this.sourceHighlighters.discardHighlightAt(line, filePath, from)
}
setTheme (type) {
this.editor.setTheme('ace/theme/' + this._themes[type])
}
_onChange () {
const currentFile = this._deps.config.get('currentFile')
async _onChange (file) {
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
}
if (currentFile !== file) {
return
}
const input = this.get(currentFile)
if (!input) {
return
@ -257,16 +147,17 @@ class Editor extends Plugin {
if (this.saveTimeout) {
window.clearTimeout(this.saveTimeout)
}
this.triggerEvent('contentChanged', [])
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('requiringToSaveCurrentfile', [])
}, 5000)
}
_switchSession (path) {
this.currentSession = path
this.editor.setSession(this.sessions[this.currentSession])
this.editor.setReadOnly(this.readOnlySessions[this.currentSession])
this.editor.focus()
this.triggerEvent('sessionSwitched', [])
this.currentFile = path
this.renderComponent()
}
/**
@ -283,17 +174,27 @@ class Editor extends Plugin {
}
/**
* Create an Ace session
* Create an editor session
* @param {string} path path of the file
* @param {string} content Content of the file to open
* @param {string} mode Ace Mode for this file [Default is `text`]
* @param {string} mode Mode for this file [Default is `text`]
*/
_createSession (content, mode) {
const s = new ace.EditSession(content)
s.setMode(mode || 'ace/mode/text')
s.setUndoManager(new ace.UndoManager())
s.setTabSize(4)
s.setUseSoftTabs(true)
return s
_createSession (path, content, mode) {
if (!this.activated) return
this.emit('addModel', content, mode, path, false)
return {
path,
language: mode,
setValue: (content) => {
this.emit('setValue', path, content)
},
getValue: () => {
return this.api.getValue(path, content)
},
dispose: () => {
this.emit('disposeModel', path)
}
}
}
/**
@ -301,36 +202,16 @@ class Editor extends Plugin {
* @param {string} string
*/
find (string) {
return this.editor.find(string)
return this.api.findMatches(this.currentFile, string)
}
/**
* Display an Empty read-only session
*/
displayEmptyReadOnlySession () {
this.currentSession = null
this.editor.setSession(this.emptySession)
this.editor.setReadOnly(true)
}
/**
* Sets a breakpoint on the row number
* @param {number} row Line index of the breakpoint
* @param {string} className Class of the breakpoint
*/
setBreakpoint (row, className) {
this.editor.session.setBreakpoint(row, className)
}
/**
* Increment the font size (in pixels) for the editor text.
* @param {number} incr The amount of pixels to add to the font.
*/
editorFontSize (incr) {
const newSize = this.editor.getFontSize() + incr
if (newSize >= 6) {
this.editor.setFontSize(newSize)
}
if (!this.activated) return
this.currentFile = null
this.emit('addModel', '', 'text', '_blank', true)
}
/**
@ -338,8 +219,8 @@ class Editor extends Plugin {
* @param {string} text New text to be place.
*/
setText (text) {
if (this.currentSession && this.sessions[this.currentSession]) {
this.sessions[this.currentSession].setValue(text)
if (this.currentFile && this.sessions[this.currentFile]) {
this.sessions[this.currentFile].setValue(text)
}
}
@ -356,7 +237,7 @@ class Editor extends Plugin {
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
const session = this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = false
} else if (this.sessions[path].getValue() !== content) {
@ -372,7 +253,7 @@ class Editor extends Plugin {
*/
openReadOnly (path, content) {
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
const session = this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = true
}
@ -394,8 +275,8 @@ class Editor extends Plugin {
* @return {String} content of the file referenced by @arg path
*/
get (path) {
if (!path || this.currentSession === path) {
return this.editor.getValue()
if (!path || this.currentFile === path) {
return this.api.getValue(path)
} else if (this.sessions[path]) {
return this.sessions[path].getValue()
}
@ -407,29 +288,23 @@ class Editor extends Plugin {
* @return {String} path of the current session
*/
current () {
if (this.editor.getSession() === this.emptySession) {
return
}
return this.currentSession
return this.currentFile
}
/**
* The position of the cursor
*/
getCursorPosition () {
return this.editor.session.doc.positionToIndex(
this.editor.getCursorPosition(),
0
)
return this.api.getCursorPosition()
}
/**
* Remove the current session from the list of sessions.
*/
discardCurrentSession () {
if (this.sessions[this.currentSession]) {
delete this.sessions[this.currentSession]
this.currentSession = null
if (this.sessions[this.currentFile]) {
delete this.sessions[this.currentFile]
this.currentFile = null
}
}
@ -438,73 +313,56 @@ class Editor extends Plugin {
* @param {string} path
*/
discard (path) {
if (this.sessions[path]) delete this.sessions[path]
if (this.currentSession === path) this.currentSession = null
if (this.sessions[path]) {
this.sessions[path].dispose()
delete this.sessions[path]
}
if (this.currentFile === path) this.currentFile = null
}
/**
* Resize the editor, and sets whether or not line wrapping is enabled.
* @param {boolean} useWrapMode Enable (or disable) wrap mode
* Increment the font size (in pixels) for the editor text.
* @param {number} incr The amount of pixels to add to the font.
*/
resize (useWrapMode) {
this.editor.resize()
const session = this.editor.getSession()
session.setUseWrapMode(useWrapMode)
if (session.getUseWrapMode()) {
const characterWidth = this.editor.renderer.characterWidth
const contentWidth = this.editor.container.ownerDocument.getElementsByClassName(
'ace_scroller'
)[0].clientWidth
if (contentWidth > 0) {
session.setWrapLimit(parseInt(contentWidth / characterWidth, 10))
}
editorFontSize (incr) {
if (!this.activated) return
const newSize = this.api.getFontSize() + incr
if (newSize >= 6) {
this.emit('setFontSize', newSize)
}
}
/**
* Adds a new marker to the given `Range`.
* @param {*} lineColumnPos
* @param {string} source Path of the session to add the mark on.
* @param {string} cssClass css to apply to the mark.
* Resize the editor, and sets whether or not line wrapping is enabled.
* @param {boolean} useWrapMode Enable (or disable) wrap mode
*/
addMarker (lineColumnPos, source, cssClass) {
const currentRange = new Range(
lineColumnPos.start.line,
lineColumnPos.start.column,
lineColumnPos.end.line,
lineColumnPos.end.column
)
if (this.sessions[source]) {
return this.sessions[source].addMarker(currentRange, cssClass)
}
return null
resize (useWrapMode) {
if (!this.activated) return
this.emit('setWordWrap', useWrapMode)
}
/**
* Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
* @param {number} line The line to scroll to
* @param {boolean} center If true
* @param {boolean} animate If true animates scrolling
* @param {Function} callback Function to be called when the animation has finished
* Moves the cursor and focus to the specified line and column number
* @param {number} line
* @param {number} col
*/
scrollToLine (line, center, animate, callback) {
this.editor.scrollToLine(line, center, animate, callback)
gotoLine (line, col) {
if (!this.activated) return
this.emit('focus')
this.emit('revealLine', line + 1, col)
}
/**
* Remove a marker from the session
* @param {string} markerId Id of the marker
* @param {string} source Path of the session
* Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
* @param {number} line The line to scroll to
*/
removeMarker (markerId, source) {
if (this.sessions[source]) {
this.sessions[source].removeMarker(markerId)
}
scrollToLine (line) {
if (!this.activated) return
this.emit('revealLine', line + 1, 0)
}
/**
* Clears all the annotations for the given @arg filePath and @arg plugin, if none is given, the current sesssion is used.
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current sesssion is used.
* An annotation has the following shape:
column: -1
row: -1
@ -512,64 +370,79 @@ class Editor extends Plugin {
type: "warning"
* @param {String} filePath
* @param {String} plugin
* @param {String} typeOfDecoration
*/
clearAnnotationsByPlugin (filePath, plugin) {
clearDecorationsByPlugin (filePath, plugin, typeOfDecoration) {
if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath)
const session = this.sessions[filePath] || this.editor.getSession()
const path = filePath || this.currentSession
const path = filePath || this.currentFile
const currentAnnotations = this.sourceAnnotationsPerFile[path]
const currentAnnotations = this[typeOfDecoration][path]
if (!currentAnnotations) return
const newAnnotations = []
for (const annotation of currentAnnotations) {
if (annotation.from !== plugin) newAnnotations.push(annotation)
}
this.sourceAnnotationsPerFile[path] = newAnnotations
this._setAnnotations(session, path)
this[typeOfDecoration][path] = newAnnotations
this.renderComponent()
}
keepAnnotationsFor (name) {
if (!this.currentSession) return
if (!this.sourceAnnotationsPerFile[this.currentSession]) return
keepDecorationsFor (name, typeOfDecoration) {
if (!this.currentFile) return
if (!this[typeOfDecoration][this.currentFile]) return
const annotations = this.sourceAnnotationsPerFile[this.currentSession]
const annotations = this[typeOfDecoration][this.currentFile]
for (const annotation of annotations) {
annotation.hide = annotation.from !== name
}
this._setAnnotations(this.editor.getSession(), this.currentSession)
this.renderComponent()
}
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* Clears all the decorations and for all the sessions for the given @arg plugin
* An annotation has the following shape:
column: -1
row: -1
text: "browser/Untitled1.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.↵"
type: "warning"
* @param {String} filePath
* @param {String} plugin
*/
clearAnnotations (filePath) {
const { from } = this.currentRequest
this.clearAnnotationsByPlugin(filePath, from)
clearAllDecorationsFor (plugin) {
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, plugin, 'sourceAnnotationsPerFile')
this.clearDecorationsByPlugin(session, plugin, 'markerPerFile')
}
}
/**
* Clears all the annotations and for all the sessions for the given @arg plugin
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* An annotation has the following shape:
column: -1
row: -1
text: "browser/Untitled1.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.↵"
type: "warning"
* @param {String} filePath
* @param {String} plugin
*/
clearAllAnnotationsFor (plugin) {
for (const session in this.sessions) {
this.clearAnnotationsByPlugin(session, plugin)
}
clearAnnotations (filePath) {
filePath = filePath || this.currentFile
const { from } = this.currentRequest
this.clearDecorationsByPlugin(filePath, from, 'sourceAnnotationsPerFile')
}
async addDecoration (decoration, filePath, typeOfDecoration) {
if (!filePath) return
filePath = await this.call('fileManager', 'getPathFromUrl', filePath)
filePath = filePath.file
if (!this.sessions[filePath]) throw new Error('file not found' + filePath)
const path = filePath || this.currentFile
const { from } = this.currentRequest
if (!this[typeOfDecoration][path]) this[typeOfDecoration][path] = []
decoration.from = from
this[typeOfDecoration][path].push(decoration)
this.renderComponent()
}
/**
@ -582,32 +455,25 @@ class Editor extends Plugin {
* @param {Object} annotation
* @param {String} filePath
*/
addAnnotation (annotation, filePath) {
if (filePath && !this.sessions[filePath]) throw new Error('file not found' + filePath)
const session = this.sessions[filePath] || this.editor.getSession()
const path = filePath || this.currentSession
const { from } = this.currentRequest
if (!this.sourceAnnotationsPerFile[path]) this.sourceAnnotationsPerFile[path] = []
annotation.from = from
this.sourceAnnotationsPerFile[path].push(annotation)
this._setAnnotations(session, path)
async addAnnotation (annotation, filePath) {
filePath = filePath || this.currentFile
await this.addDecoration(annotation, filePath, 'sourceAnnotationsPerFile')
}
_setAnnotations (session, path) {
const annotations = this.sourceAnnotationsPerFile[path]
session.setAnnotations(annotations.filter((element) => !element.hide))
async highlight (position, filePath, highlightColor, opt = { focus: true }) {
filePath = filePath || this.currentFile
if (opt.focus) {
await this.call('fileManager', 'open', filePath)
this.scrollToLine(position.start.line)
}
await this.addDecoration({ position }, filePath, 'markerPerFile')
}
/**
* Moves the cursor and focus to the specified line and column number
* @param {number} line
* @param {number} col
*/
gotoLine (line, col) {
this.editor.focus()
this.editor.gotoLine(line + 1, col - 1, true)
discardHighlight () {
const { from } = this.currentRequest
for (const session in this.sessions) {
this.clearDecorationsByPlugin(session, from, 'markerPerFile')
}
}
}

@ -1,86 +0,0 @@
'use strict'
const csjs = require('csjs-inject')
const globlalRegistry = require('../../global/registry')
class SourceHighlighter {
constructor (localRegistry) {
this._components = {}
this._components.registry = localRegistry || globlalRegistry
// dependencies
this._deps = {
editor: this._components.registry.get('editor').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api,
compilerArtefacts: this._components.registry.get('compilersartefacts').api
}
this.position = null
this.statementMarker = null
this.fullLineMarker = null
this.source = null
}
currentSourceLocation (lineColumnPos, location) {
if (this.statementMarker) this._deps.editor.removeMarker(this.statementMarker, this.source)
if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source)
const lastCompilationResult = this._deps.compilerArtefacts.__last
if (location && location.file !== undefined && lastCompilationResult) {
const path = lastCompilationResult.getSourceName(location.file)
if (path) {
this.currentSourceLocationFromfileName(lineColumnPos, path)
}
}
}
async currentSourceLocationFromfileName (lineColumnPos, filePath, style) {
if (this.statementMarker) this._deps.editor.removeMarker(this.statementMarker, this.source)
if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source)
this.statementMarker = null
this.fullLineMarker = null
this.source = null
if (lineColumnPos && lineColumnPos.start && lineColumnPos.end) {
this.source = filePath
this.style = style || 'var(--info)'
// if (!this.source) this.source = this._deps.fileManager.currentFile()
if (this._deps.fileManager.currentFile() !== this.source) {
await this._deps.fileManager.open(this.source)
this.source = this._deps.fileManager.currentFile()
}
const css = csjs`
.highlightcode {
position:absolute;
z-index:20;
opacity: 0.3;
background-color: ${this.style};
}
.highlightcode_fullLine {
position:absolute;
z-index:20;
opacity: 0.5;
background-color: ${this.style};
}
.customBackgroundColor {
background-color: ${this.style};
}
`
this.statementMarker = this._deps.editor.addMarker(lineColumnPos, this.source, css.highlightcode.className + ' ' + css.customBackgroundColor.className + ' ' + `highlightLine${lineColumnPos.start.line}`)
this._deps.editor.scrollToLine(lineColumnPos.start.line, true, true, function () {})
this.position = lineColumnPos
if (lineColumnPos.start.line === lineColumnPos.end.line) {
this.fullLineMarker = this._deps.editor.addMarker({
start: {
line: lineColumnPos.start.line,
column: 0
},
end: {
line: lineColumnPos.start.line + 1,
column: 0
}
}, this.source, css.highlightcode_fullLine.className)
}
}
}
}
module.exports = SourceHighlighter

@ -227,7 +227,7 @@ class DGitProvider extends Plugin {
const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.')
if (!permission) return false
if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.')
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, false)
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, true)
const cmd = {
url: input.url,
@ -459,7 +459,7 @@ class DGitProvider extends Plugin {
if (!permission) return false
if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.')
const cid = cmd.cid
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, false)
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, true)
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
let result
if (cmd.local) {

@ -140,7 +140,7 @@ class FileManager extends Plugin {
refresh () {
const provider = this.fileProviderOf('/')
// emit rootFolderChanged so that File Explorer reloads the file tree
provider.event.emit('rootFolderChanged')
provider.event.emit('rootFolderChanged', provider.workspace || '/')
}
/**
@ -456,7 +456,7 @@ class FileManager extends Plugin {
return this._deps.config.get('currentFile')
}
closeAllFiles () {
async closeAllFiles () {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('filesAllClosed')
this.events.emit('filesAllClosed')
@ -465,7 +465,7 @@ class FileManager extends Plugin {
}
}
closeFile (name) {
async closeFile (name) {
delete this.openedFiles[name]
if (!Object.keys(this.openedFiles).length) {
this._deps.config.set('currentFile', '')
@ -506,7 +506,8 @@ class FileManager extends Plugin {
async setFileContent (path, content) {
if (this.currentRequest) {
const canCall = await this.askUserPermission('writeFile', '')
if (canCall) {
const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) {
// inform the user about modification after permission is granted and even if permission was saved before
toaster(yo`
<div>
@ -615,6 +616,7 @@ class FileManager extends Plugin {
this.emit('noFileSelected')
this.events.emit('noFileSelected')
} else {
file = this.normalize(file)
this.saveCurrentFile()
const resolved = this.getPathFromUrl(file)
file = resolved.file

@ -15,9 +15,9 @@ module.exports = class RemixDProvider extends FileProvider {
_registerEvent () {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((value) => {
this._appManager.on('remixd', value, (event) => {
this.event.emit(value, event)
remixdEvents.forEach((event) => {
this._appManager.on('remixd', event, (value) => {
this.event.emit(event, value)
})
})
@ -41,8 +41,18 @@ module.exports = class RemixDProvider extends FileProvider {
this.event.emit('fileRenamed', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', () => {
this.event.emit('rootFolderChanged')
this._appManager.on('remixd', 'rootFolderChanged', (path) => {
this.event.emit('rootFolderChanged', path)
})
this._appManager.on('remixd', 'removed', (path) => {
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'changed', (path) => {
this.get(path, (_error, content) => {
this.event.emit('fileExternallyChanged', path, content)
})
})
}
@ -57,7 +67,7 @@ module.exports = class RemixDProvider extends FileProvider {
}
preInit () {
this.event.emit('loading')
this.event.emit('loadingLocalhost')
}
init (cb) {
@ -66,6 +76,7 @@ module.exports = class RemixDProvider extends FileProvider {
.then((result) => {
this._isReady = true
this._readOnlyMode = result
this.event.emit('readOnlyModeChanged', result)
this._registerEvent()
this.event.emit('connected')
cb && cb()
@ -177,9 +188,7 @@ module.exports = class RemixDProvider extends FileProvider {
}
resolveDirectory (path, callback) {
var self = this
if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } })
const unprefixedpath = this.removePrefix(path)
if (!this._isReady) return callback && callback('provider not ready')

@ -13,6 +13,7 @@ class WorkspaceFileProvider extends FileProvider {
}
setWorkspace (workspace) {
if (!workspace) return
workspace = workspace.replace(/^\/|\/$/g, '') // remove first and last slash
this.workspace = workspace
}
@ -30,10 +31,15 @@ class WorkspaceFileProvider extends FileProvider {
}
removePrefix (path) {
if (!this.workspace) this.createWorkspace()
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
if (path.startsWith(this.workspace)) return path.replace(this.workspace, this.workspacesPath + '/' + this.workspace)
const splitPath = path.split('/')
if (splitPath[0] === this.workspace) {
splitPath[0] = this.workspacesPath + '/' + this.workspace
path = splitPath.join('/')
return path
}
path = super.removePrefix(path)
let ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
@ -51,7 +57,6 @@ class WorkspaceFileProvider extends FileProvider {
}
resolveDirectory (path, callback) {
if (!this.workspace) this.createWorkspace()
super.resolveDirectory(path, (error, files) => {
if (error) return callback(error)
const unscoped = {}
@ -76,13 +81,18 @@ class WorkspaceFileProvider extends FileProvider {
}
_normalizePath (path) {
if (!this.workspace) this.createWorkspace()
return path.replace(this.workspacesPath + '/' + this.workspace + '/', '')
}
createWorkspace (name) {
if (!name) name = 'default_workspace'
this.event.emit('createWorkspace', name)
async createWorkspace (name) {
try {
if (!name) name = 'default_workspace'
this.setWorkspace(name)
await super.createDir(name)
this.event.emit('createWorkspace', name)
} catch (e) {
throw new Error(e)
}
}
}

@ -3,18 +3,12 @@ import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { Workspace } from '@remix-ui/workspace' // eslint-disable-line
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import { checkSpecialChars, checkSlash } from '../../lib/helper'
import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
const { RemixdHandle } = require('../files/remixd-handle.js')
const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js')
const globalRegistry = require('../../global/registry')
const examples = require('../editor/examples')
const GistHandler = require('../../lib/gist-handler')
const QueryParams = require('../../lib/query-params')
const modalDialogCustom = require('../ui/modal-dialog-custom')
/*
Overview of APIs:
* fileManager: @args fileProviders (browser, shared-folder, swarm, github, etc ...) & config & editor
@ -35,8 +29,8 @@ const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = {
name: 'filePanel',
displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'],
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
description: ' - ',
kind: 'fileexplorer',
@ -47,54 +41,33 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
this._components = {}
this._components.registry = globalRegistry
this._deps = {
fileProviders: this._components.registry.get('fileproviders').api,
fileManager: this._components.registry.get('filemanager').api
}
this.registry = globalRegistry
this.fileProviders = this.registry.get('fileproviders').api
this.fileManager = this.registry.get('filemanager').api
this.el = document.createElement('div')
this.el.setAttribute('id', 'fileExplorerView')
this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager)
this.remixdHandle = new RemixdHandle(this.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
this.hardhatHandle = new HardhatHandle()
this.slitherHandle = new SlitherHandle()
this.registeredMenuItems = []
this.removedMenuItems = []
this.request = {}
this.workspaces = []
this.initialWorkspace = null
this.appManager = appManager
this.currentWorkspaceMetadata = {}
}
onActivation () {
this.renderComponent()
}
render () {
this.initWorkspace().then(() => this.getWorkspaces()).catch(console.error)
return this.el
}
renderComponent () {
ReactDOM.render(
<Workspace
createWorkspace={this.createWorkspace.bind(this)}
renameWorkspace={this.renameWorkspace.bind(this)}
setWorkspace={this.setWorkspace.bind(this)}
workspaceRenamed={this.workspaceRenamed.bind(this)}
workspaceDeleted={this.workspaceDeleted.bind(this)}
workspaceCreated={this.workspaceCreated.bind(this)}
workspace={this._deps.fileProviders.workspace}
browser={this._deps.fileProviders.browser}
localhost={this._deps.fileProviders.localhost}
fileManager={this._deps.fileManager}
registry={this._components.registry}
plugin={this}
request={this.request}
workspaces={this.workspaces}
registeredMenuItems={this.registeredMenuItems}
removedMenuItems={this.removedMenuItems}
initialWorkspace={this.initialWorkspace}
/>
<FileSystemProvider plugin={this} />
, this.el)
}
@ -103,202 +76,103 @@ module.exports = class Filepanel extends ViewPlugin {
* @param callback (...args) => void
*/
registerContextMenuItem (item) {
if (!item) throw new Error('Invalid register context menu argument')
if (!item.name || !item.id) throw new Error('Item name and id is mandatory')
if (!item.type && !item.path && !item.extension && !item.pattern) throw new Error('Invalid file matching criteria provided')
if (this.registeredMenuItems.filter((o) => {
return o.id === item.id && o.name === item.name
}).length) throw new Error(`Action ${item.name} already exists on ${item.id}`)
this.registeredMenuItems = [...this.registeredMenuItems, item]
this.removedMenuItems = this.removedMenuItems.filter(menuItem => item.id !== menuItem.id)
this.renderComponent()
return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
removePluginActions (plugin) {
this.registeredMenuItems = this.registeredMenuItems.filter((item) => {
if (item.id !== plugin.name || item.sticky === true) return true
else {
this.removedMenuItems.push(item)
return false
}
return new Promise((resolve, reject) => {
this.emit('removePluginActionsReducerEvent', plugin, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
this.renderComponent()
}
async getCurrentWorkspace () {
return await this.request.getCurrentWorkspace()
getCurrentWorkspace () {
return this.currentWorkspaceMetadata
}
async getWorkspaces () {
const result = new Promise((resolve, reject) => {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
this._deps.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
if (error) {
console.error(error)
return reject(error)
}
resolve(Object.keys(items)
.filter((item) => items[item].isDirectory)
.map((folder) => folder.replace(workspacesPath + '/', '')))
})
})
try {
this.workspaces = await result
} catch (e) {
modalDialogCustom.alert('Workspaces have not been created on your system. Please use "Migrate old filesystem to workspace" on the home page to transfer your files or start by creating a new workspace in the File Explorers.')
console.log(e)
}
this.renderComponent()
getWorkspaces () {
return this.workspaces
}
async initWorkspace () {
this.renderComponent()
const queryParams = new QueryParams()
const gistHandler = new GistHandler()
const params = queryParams.get()
// get the file from gist
let loadedFromGist = false
if (params.gist) {
await this.processCreateWorkspace('gist-sample')
this._deps.fileProviders.workspace.setWorkspace('gist-sample')
this.initialWorkspace = 'gist-sample'
loadedFromGist = gistHandler.loadFromGist(params, this._deps.fileManager)
}
if (loadedFromGist) return
if (params.code || params.url) {
try {
await this.processCreateWorkspace('code-sample')
this._deps.fileProviders.workspace.setWorkspace('code-sample')
let path = ''
let content = ''
if (params.code) {
var hash = bufferToHex(keccakFromString(params.code))
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await this._deps.fileProviders.workspace.set(path, content)
}
if (params.url) {
const data = await this.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
await this._deps.fileProviders.workspace.set(path, content)
}
this.initialWorkspace = 'code-sample'
await this._deps.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
return
}
setWorkspaces (workspaces) {
this.workspaces = workspaces
}
const self = this
this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this))
// insert example contracts if there are no files to show
createNewFile () {
return new Promise((resolve, reject) => {
this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => {
if (error) return reject(error)
if (Object.keys(filesList).length === 0) {
await this.createWorkspace('default_workspace')
resolve('default_workspace')
} else {
this._deps.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => {
if (error) return reject(error)
if (Object.keys(filesList).length > 0) {
const workspacePath = Object.keys(filesList)[0].split('/').filter(val => val)
const workspaceName = workspacePath[workspacePath.length - 1]
const provider = this.fileManager.currentFileProvider()
const dir = provider.workspace || '/'
this._deps.fileProviders.workspace.setWorkspace(workspaceName)
return resolve(workspaceName)
}
return reject(new Error('Can\'t find available workspace.'))
})
}
this.emit('createNewFileInputReducerEvent', dir, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
async createNewFile () {
return await this.request.createNewFile()
}
uploadFile (target) {
return new Promise((resolve, reject) => {
const provider = this.fileManager.currentFileProvider()
const dir = provider.workspace || '/'
async uploadFile (event) {
return await this.request.uploadFile(event)
return this.emit('uploadFileReducerEvent', dir, target, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
async processCreateWorkspace (name) {
const workspaceProvider = this._deps.fileProviders.workspace
const browserProvider = this._deps.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath
const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath)
const workspacePathExists = await browserProvider.exists(workspacePath)
if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath)
if (!workspacePathExists) browserProvider.createDir(workspacePath)
createWorkspace (workspaceName, isEmpty) {
return new Promise((resolve, reject) => {
this.emit('createWorkspaceReducerEvent', workspaceName, isEmpty, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
async workspaceExists (name) {
const workspaceProvider = this._deps.fileProviders.workspace
const browserProvider = this._deps.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
return browserProvider.exists(workspacePath)
renameWorkspace (oldName, workspaceName) {
return new Promise((resolve, reject) => {
this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
async createWorkspace (workspaceName, setDefaults = true) {
if (!workspaceName) throw new Error('name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
else {
const workspaceProvider = this._deps.fileProviders.workspace
await this.processCreateWorkspace(workspaceName)
workspaceProvider.setWorkspace(workspaceName)
await this.request.setWorkspace(workspaceName) // tells the react component to switch to that workspace
if (setDefaults) {
for (const file in examples) {
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}
}
}
deleteWorkspace (workspaceName) {
return new Promise((resolve, reject) => {
this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => {
if (err) reject(err)
else resolve(data || true)
})
})
}
async renameWorkspace (oldName, workspaceName) {
if (!workspaceName) throw new Error('name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
const browserProvider = this._deps.fileProviders.browser
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
}
setWorkspace (workspace) {
const workspaceProvider = this.fileProviders.workspace
/** these are called by the react component, action is already finished whent it's called */
async setWorkspace (workspace, setEvent = true) {
if (workspace.isLocalhost) {
this.call('manager', 'activatePlugin', 'remixd')
} else if (await this.call('manager', 'isActive', 'remixd')) {
this.call('manager', 'deactivatePlugin', 'remixd')
}
if (setEvent) {
this._deps.fileManager.setMode(workspace.isLocalhost ? 'localhost' : 'browser')
this.emit('setWorkspace', workspace)
}
this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
this.emit('setWorkspace', workspace)
}
workspaceRenamed (workspace) {
this.emit('renameWorkspace', workspace)
workspaceRenamed (oldName, workspaceName) {
this.emit('workspaceRenamed', oldName, workspaceName)
}
workspaceDeleted (workspace) {
this.emit('deleteWorkspace', workspace)
this.emit('workspaceDeleted', workspace)
}
workspaceCreated (workspace) {
this.emit('createWorkspace', workspace)
this.emit('workspaceCreated', workspace)
}
/** end section */
}

@ -54,7 +54,7 @@ export class MainView {
fileManager: self._components.registry.get('filemanager').api
}
self.tabProxy = new TabProxy(self.fileManager, self.editor, self.appManager)
self.tabProxy = new TabProxy(self.fileManager, self.editor)
/*
We listen here on event from the tab component to display / hide the editor and mainpanel
depending on the content that should be displayed

@ -1,17 +1,9 @@
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { Plugin } from '@remixproject/engine'
const yo = require('yo-yo')
const $ = require('jquery')
import { TabsUI } from '@remix-ui/tabs'
const EventEmitter = require('events')
const globalRegistry = require('../../global/registry')
const csjs = require('csjs-inject')
const helper = require('../../lib/helper')
require('remix-tabs')
const css = csjs`
.remix_tabs div[title]{
display: flex;
}
`
const profile = {
name: 'tabs',
@ -21,46 +13,60 @@ const profile = {
// @todo(#650) Merge this with MainPanel into one plugin
export class TabProxy extends Plugin {
constructor (fileManager, editor, appManager) {
constructor (fileManager, editor) {
super(profile)
this.event = new EventEmitter()
this.fileManager = fileManager
this.appManager = appManager
this.editor = editor
this.data = {}
this._view = {}
this._handlers = {}
this.loadedTabs = []
}
globalRegistry.get('themeModule').api.events.on('themeChanged', (theme) => {
// update invert for all icons
onActivation () {
this.on('theme', 'themeChanged', (theme) => {
// update invert for all icons
this.updateImgStyles()
})
fileManager.events.on('filesAllClosed', () => {
this.on('fileManager', 'filesAllClosed', () => {
this.call('manager', 'activatePlugin', 'home')
this._view.filetabs.active = 'home'
this.tabsApi.activateTab('home')
})
fileManager.events.on('fileRemoved', (name) => {
this.on('fileManager', 'fileRemoved', (name) => {
const workspace = this.fileManager.currentWorkspace()
workspace ? this.removeTab(workspace + '/' + name) : this.removeTab(this.fileManager.mode + '/' + name)
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
})
fileManager.events.on('fileClosed', (name) => {
this.on('fileManager', 'fileClosed', (name) => {
const workspace = this.fileManager.currentWorkspace()
workspace ? this.removeTab(workspace + '/' + name) : this.removeTab(this.fileManager.mode + '/' + name)
if (this.fileManager.mode === 'browser') {
name = name.startsWith(workspace + '/') ? name : workspace + '/' + name
this.removeTab(name)
} else {
name = name.startsWith(this.fileManager.mode + '/') ? name : this.fileManager.mode + '/' + name
this.removeTab(name)
}
})
fileManager.events.on('currentFileChanged', (file) => {
this.on('fileManager', 'currentFileChanged', (file) => {
const workspace = this.fileManager.currentWorkspace()
if (workspace) {
if (this.fileManager.mode === 'browser') {
const workspacePath = workspace + '/' + file
if (this._handlers[workspacePath]) {
this._view.filetabs.activateTab(workspacePath)
this.tabsApi.activateTab(workspacePath)
return
}
this.addTab(workspacePath, '', () => {
@ -71,11 +77,12 @@ export class TabProxy extends Plugin {
this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
})
this.tabsApi.activateTab(workspacePath)
} else {
const path = this.fileManager.mode + '/' + file
const path = file.startsWith(this.fileManager.mode + '/') ? file : this.fileManager.mode + '/' + file
if (this._handlers[path]) {
this._view.filetabs.activateTab(path)
this.tabsApi.activateTab(path)
return
}
this.addTab(path, '', () => {
@ -86,13 +93,14 @@ export class TabProxy extends Plugin {
this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
})
this.tabsApi.activateTab(path)
}
})
fileManager.events.on('fileRenamed', (oldName, newName, isFolder) => {
this.on('fileManager', 'fileRenamed', (oldName, newName, isFolder) => {
const workspace = this.fileManager.currentWorkspace()
if (workspace) {
if (this.fileManager.mode === 'browser') {
if (isFolder) {
for (const tab of this.loadedTabs) {
if (tab.name.indexOf(workspace + '/' + oldName + '/') === 0) {
@ -115,11 +123,11 @@ export class TabProxy extends Plugin {
return
}
// should change the tab title too
this.renameTab(this.fileManager.mode + '/' + oldName, workspace + '/' + newName)
this.renameTab(this.fileManager.mode + '/' + oldName, this.fileManager.mode + '/' + newName)
}
})
appManager.event.on('activate', ({ name, location, displayName, icon }) => {
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon }) => {
if (location === 'mainPanel') {
this.addTab(
name,
@ -135,32 +143,32 @@ export class TabProxy extends Plugin {
}
})
appManager.event.on('deactivate', (profile) => {
this.on('manager', 'pluginDeactivated', (profile) => {
this.removeTab(profile.name)
})
}
focus (name) {
this.event.emit('switchApp', name)
this._view.filetabs.activateTab(name)
this.tabsApi.activateTab(name)
}
updateImgStyles () {
const images = this._view.filetabs.getElementsByClassName('iconImage')
const images = this.el.getElementsByClassName('iconImage')
for (const element of images) {
globalRegistry.get('themeModule').api.fixInvert(element)
};
this.call('theme', 'fixInvert', element)
}
}
switchTab (tabName) {
if (this._handlers[tabName]) {
this._handlers[tabName].switchTo()
this._view.filetabs.activateTab(tabName)
this.tabsApi.activateTab(tabName)
}
}
switchNextTab () {
const active = this._view.filetabs.active
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
@ -172,7 +180,7 @@ export class TabProxy extends Plugin {
}
switchPreviousTab () {
const active = this._view.filetabs.active
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
const handlers = Object.keys(this._handlers)
let i = handlers.indexOf(active)
@ -184,7 +192,7 @@ export class TabProxy extends Plugin {
}
switchToActiveTab () {
const active = this._view.filetabs.active
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
this.switchTab(active)
}
@ -203,7 +211,7 @@ export class TabProxy extends Plugin {
}
addTab (name, title, switchTo, close, icon) {
if (this._handlers[name]) return
if (this._handlers[name]) return this.renderComponent()
var slash = name.split('/')
const tabPath = slash.reverse()
@ -219,8 +227,12 @@ export class TabProxy extends Plugin {
title = formatPath.join('/')
const titleLength = formatPath.length
this.loadedTabs.push({
id: name,
name,
title
title,
icon,
tooltip: name,
iconClass: helper.getPathIcon(name)
})
formatPath.shift()
if (formatPath.length > 0) {
@ -230,12 +242,8 @@ export class TabProxy extends Plugin {
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
this.loadedTabs.push({
name: duplicateTabName,
title: duplicateTabTitle
})
this._view.filetabs.removeTab(duplicateTabName)
this._view.filetabs.addTab({
id: duplicateTabName,
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: duplicateTabName,
@ -247,27 +255,25 @@ export class TabProxy extends Plugin {
}
} else {
this.loadedTabs.push({
id: name,
name,
title
title,
icon,
tooltip: name,
iconClass: helper.getPathIcon(name)
})
}
this._view.filetabs.addTab({
id: name,
title,
icon,
tooltip: name,
iconClass: helper.getPathIcon(name)
})
this.renderComponent()
this.updateImgStyles()
this._handlers[name] = { switchTo, close }
}
removeTab (name) {
this._view.filetabs.removeTab(name)
delete this._handlers[name]
this.switchToActiveTab()
this.loadedTabs = this.loadedTabs.filter(tab => tab.name !== name)
this.renderComponent()
this.updateImgStyles()
}
@ -275,62 +281,36 @@ export class TabProxy extends Plugin {
this.handlers[type] = fn
}
onZoomOut () {
this.editor.editorFontSize(-1)
}
renderComponent () {
const onSelect = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].switchTo()
this.event.emit('tabCountChanged', this.loadedTabs.length)
}
}
onZoomIn () {
this.editor.editorFontSize(1)
}
const onClose = (index) => {
if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].close()
this.event.emit('tabCountChanged', this.loadedTabs.length)
}
}
renderTabsbar () {
this._view.filetabs = yo`<remix-tabs class=${css.remix_tabs}></remix-tabs>`
this._view.filetabs.addEventListener('tabClosed', (event) => {
if (this._handlers[event.detail]) this._handlers[event.detail].close()
this.event.emit('tabCountChanged', this._view.filetabs.tabs.length)
})
this._view.filetabs.addEventListener('tabActivated', (event) => {
if (this._handlers[event.detail]) this._handlers[event.detail].switchTo()
this.event.emit('tabCountChanged', this._view.filetabs.tabs.length)
})
const onZoomIn = () => this.editor.editorFontSize(1)
const onZoomOut = () => this.editor.editorFontSize(-1)
this._view.filetabs.canAdd = false
const zoomBtns = yo`
<div class="d-flex flex-row justify-content-center align-items-center">
<span data-id="tabProxyZoomOut" class="btn btn-sm px-1 fas fa-search-minus text-dark" onclick=${() => this.onZoomOut()}></span>
<span data-id="tabProxyZoomIn" class="btn btn-sm px-1 fas fa-search-plus text-dark" onclick=${() => this.onZoomIn()}></span>
</div>
`
// @todo(#2492) remove style after the mainPanel layout fix.
this._view.tabs = yo`
<div class="mainview--tabs-proxy" style="display: -webkit-box; max-height: 32px">
${zoomBtns}
${this._view.filetabs}
</div>
`
// tabs
var $filesEl = $(this._view.filetabs)
// Switch tab
var self = this
$filesEl.on('click', '.file:not(.active)', function (ev) {
ev.preventDefault()
var name = $(this).find('.name').text()
self._handlers[name].switchTo()
return false
})
const onReady = (api) => { this.tabsApi = api }
// Remove current tab
$filesEl.on('click', '.file .remove', function (ev) {
ev.preventDefault()
var name = $(this).parent().find('.name').text()
self._handlers[name].close()
return false
})
ReactDOM.render(
<TabsUI tabs={this.loadedTabs} onSelect={onSelect} onClose={onClose} onZoomIn={onZoomIn} onZoomOut={onZoomOut} onReady={onReady} />
, this.el)
}
return this._view.tabs
renderTabsbar () {
this.el = document.createElement('div')
this.renderComponent()
return this.el
}
}

@ -1,29 +1,23 @@
/* global Node, requestAnimationFrame */
/* global Node, requestAnimationFrame */ // eslint-disable-line
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { RemixUiTerminal } from '@remix-ui/terminal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import * as remixBleach from '../../lib/remixBleach'
const vm = require('vm')
const EventManager = require('../../lib/events')
var yo = require('yo-yo')
var javascriptserialize = require('javascript-serialize')
var jsbeautify = require('js-beautify')
var type = require('component-type')
var vm = require('vm')
var EventManager = require('../../lib/events')
const CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
const AutoCompletePopup = require('../ui/auto-complete-popup')
var CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var AutoCompletePopup = require('../ui/auto-complete-popup')
var TxLogger = require('../../app/ui/txLogger')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
const globalRegistry = require('../../global/registry')
const GistHandler = require('../../lib/gist-handler')
var csjs = require('csjs-inject')
var css = require('./styles/terminal-styles')
var KONSOLES = []
const KONSOLES = []
function register (api) { KONSOLES.push(api) }
var ghostbar = yo`<div class=${css.ghostbar} bg-secondary></div>`
const profile = {
displayName: 'Terminal',
name: 'terminal',
@ -36,75 +30,68 @@ const profile = {
class Terminal extends Plugin {
constructor (opts, api) {
super(profile)
var self = this
self.event = new EventManager()
self.blockchain = opts.blockchain
self._api = api
self._opts = opts
self.data = {
this.fileImport = new CompilerImports()
this.gistHandler = new GistHandler()
this.event = new EventManager()
this.globalRegistry = globalRegistry
this.element = document.createElement('div')
this.element.setAttribute('class', 'panel')
this.element.setAttribute('id', 'terminal-view')
this.element.setAttribute('data-id', 'terminalContainer-view')
this.eventsDecoder = this.globalRegistry.get('eventsDecoder').api
this.txListener = this.globalRegistry.get('txlistener').api
this._deps = {
fileManager: this.globalRegistry.get('filemanager').api,
editor: this.globalRegistry.get('editor').api,
compilersArtefacts: this.globalRegistry.get('compilersartefacts').api,
offsetToLineColumnConverter: this.globalRegistry.get('offsettolinecolumnconverter').api
}
this.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
this.blockchain = opts.blockchain
this.vm = vm
this._api = api
this._opts = opts
this.config = this.globalRegistry.get('config').api
this.version = packageJson.version
this.data = {
lineLength: opts.lineLength || 80, // ????
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
}
self._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
self._components = {}
self._components.cmdInterpreter = new CommandInterpreterAPI(this, null, self.blockchain)
self._components.autoCompletePopup = new AutoCompletePopup(self._opts)
self._components.autoCompletePopup.event.register('handleSelect', function (input) {
const textList = self._view.input.innerText.split(' ')
textList.pop()
textList.push(input)
self._view.input.innerText = textList
self._view.input.focus()
self.putCursor2End(self._view.input)
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {}
this._components.cmdInterpreter = new CommandInterpreterAPI(this, null, this.blockchain)
this._components.autoCompletePopup = new AutoCompletePopup(this._opts)
this._commands = {}
this.commands = {}
this._JOURNAL = []
this._jobs = []
this._INDEX = {}
this._INDEX.all = []
this._INDEX.allMain = []
this._INDEX.commands = {}
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
self._commands = {}
self.commands = {}
self._JOURNAL = []
self._jobs = []
self._INDEX = {}
self._INDEX.all = []
self._INDEX.allMain = []
self._INDEX.commands = {}
self._INDEX.commandsMain = {}
self.registerCommand('html', self._blocksRenderer('html'), { activate: true })
self.registerCommand('log', self._blocksRenderer('log'), { activate: true })
self.registerCommand('info', self._blocksRenderer('info'), { activate: true })
self.registerCommand('warn', self._blocksRenderer('warn'), { activate: true })
self.registerCommand('error', self._blocksRenderer('error'), { activate: true })
self.registerCommand('script', function execute (args, scopedCommands, append) {
var script = String(args[0])
self._shell(script, scopedCommands, function (error, output) {
if (error) scopedCommands.error(error)
else if (output) scopedCommands.log(output)
})
}, { activate: true })
function basicFilter (value, query) { try { return value.indexOf(query) !== -1 } catch (e) { return false } }
self.registerFilter('log', basicFilter)
self.registerFilter('info', basicFilter)
self.registerFilter('warn', basicFilter)
self.registerFilter('error', basicFilter)
self.registerFilter('script', basicFilter)
if (opts.shell) self._shell = opts.shell // ???
register(self)
this.logHtmlResponse = []
this.logResponse = []
}
onActivation () {
this.on('scriptRunner', 'log', (msg) => {
this.commands.log.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'info', (msg) => {
this.commands.info.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'warn', (msg) => {
this.commands.warn.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'error', (msg) => {
this.commands.error.apply(this.commands, msg.data)
})
this.renderComponent()
}
onDeactivation () {
@ -114,690 +101,43 @@ class Terminal extends Plugin {
this.off('scriptRunner', 'error')
}
log (message) {
var command = this.commands[message.type]
if (typeof command === 'function') {
if (typeof message.value === 'string' && message.type === 'html') {
var el = document.createElement('div')
el.innerHTML = remixBleach.sanitize(message.value, {
list: [
'a',
'b',
'p',
'em',
'strong',
'div',
'span',
'ul',
'li',
'ol',
'hr'
]
})
message.value = el
}
command(message.value)
};
}
logHtml (html) {
var command = this.commands.html
if (typeof command === 'function') command(html)
this.logHtmlResponse.push(html.innerText)
this.renderComponent()
this.resetLogHtml()
}
focus () {
if (this._view.input) this._view.input.focus()
resetLogHtml () {
this.logHtmlResponse = []
}
render () {
var self = this
if (self._view.el) return self._view.el
self._view.journal = yo`<div id="journal" class=${css.journal} data-id="terminalJournal"></div>`
self._view.input = yo`
<span class=${css.input} onload=${() => { this.focus() }} onpaste=${paste} onkeydown=${change}></span>
`
self._view.input.setAttribute('spellcheck', 'false')
self._view.input.setAttribute('contenteditable', 'true')
self._view.input.setAttribute('id', 'terminalCliInput')
self._view.input.setAttribute('data-id', 'terminalCliInput')
self._view.input.innerText = '\n'
self._view.cli = yo`
<div id="terminalCli" data-id="terminalCli" class="${css.cli}" onclick=${focusinput}>
<span class=${css.prompt}>${'>'}</span>
${self._view.input}
</div>
`
self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize}
class="mx-2 ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>`
self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>`
self._view.pendingTxCount = yo`<div class="mx-2" title='Pending Transactions'>0</div>`
self._view.inputSearch = yo`<input
spellcheck="false"
type="text"
class="border ${css.filter} form-control"
id="searchInput"
onkeydown=${filter}
placeholder="Search with transaction hash or address"
data-id="terminalInputSearch">
</input>`
self._view.bar = yo`
<div class="${css.bar}">
${self._view.dragbar}
<div class="${css.menu} border-top border-dark bg-light" data-id="terminalToggleMenu">
${self._view.icon}
<div class="mx-2" id="clearConsole" data-id="terminalClearConsole" onclick=${clear}>
<i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i>
</div>
${self._view.pendingTxCount}
<div class=${css.verticalLine}></div>
<div class="pt-1 h-80 mx-3 align-items-center ${css.listenOnNetwork} custom-control custom-checkbox">
<input
class="custom-control-input"
id="listenNetworkCheck"
onchange=${listenOnNetwork}
type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
>
<label
class="pt-1 form-check-label custom-control-label text-nowrap""
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
for="listenNetworkCheck"
>
listen on network
</label>
</div>
<div class=${css.search}>
<i class="fas fa-search ${css.searchIcon} bg-light" aria-hidden="true"></i>
${self._view.inputSearch}
</div>
</div>
</div>
`
self._view.term = yo`
<div class="${css.terminal_container}" tabindex="-1" data-id="terminalContainer" onscroll=${throttle(reattach, 10)} onkeydown=${focusinput}>
${self._components.autoCompletePopup.render()}
<div data-id="terminalContainerDisplay" style="
position: absolute;
height: 100%;
width: 100%;
opacity: 0.1;
z-index: -1;
"></div>
<div class=${css.terminal}>
${self._view.journal}
${self._view.cli}
</div>
</div>
`
self._view.el = yo`
<div class="${css.panel}" style="height: 180px;">
${self._view.bar}
${self._view.term}
</div>
`
setInterval(async () => {
try {
self._view.pendingTxCount.innerHTML = await self.call('udapp', 'pendingTransactionsCount')
} catch (err) {}
}, 1000)
function listenOnNetwork (ev) {
self.event.trigger('listenOnNetWork', [ev.currentTarget.checked])
}
function paste (event) {
const selection = window.getSelection()
if (!selection.rangeCount) return false
event.preventDefault()
event.stopPropagation()
var clipboard = (event.clipboardData || window.clipboardData)
var text = clipboard.getData('text/plain')
text = text.replace(/[^\x20-\xFF]/gi, '') // remove non-UTF-8 characters
var temp = document.createElement('div')
temp.innerHTML = text
var textnode = document.createTextNode(temp.textContent)
selection.getRangeAt(0).insertNode(textnode)
selection.empty()
self.scroll2bottom()
placeCaretAtEnd(event.currentTarget)
}
function placeCaretAtEnd (el) {
el.focus()
var range = document.createRange()
range.selectNodeContents(el)
range.collapse(false)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
function throttle (fn, wait) {
var time = Date.now()
return function debounce () {
if ((time + wait - Date.now()) < 0) {
fn.apply(this, arguments)
time = Date.now()
}
}
}
var css2 = csjs`
.anchor {
position : static;
border-top : 2px dotted blue;
height : 10px;
}
.overlay {
position : absolute;
width : 100%;
display : flex;
align-items : center;
justify-content : center;
bottom : 0;
right : 15px;
min-height : 20px;
}
.text {
z-index : 2;
color : black;
font-weight : bold;
pointer-events : none;
}
.background {
z-index : 1;
opacity : 0.8;
background-color : #a6aeba;
cursor : pointer;
}
.ul {
padding-left : 20px;
padding-bottom : 5px;
}
`
var text = yo`<div class="${css2.overlay} ${css2.text}"></div>`
var background = yo`<div class="${css2.overlay} ${css2.background}"></div>`
var placeholder = yo`<div class=${css2.anchor}>${background}${text}</div>`
var inserted = false
window.addEventListener('resize', function (event) {
self.event.trigger('resize', [])
self.event.trigger('resize', [])
})
function focusinput (event) {
if (
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.key === 'Down' ||
event.key === 'ArrowDown' ||
event.key === 'Up' ||
event.key === 'ArrowUp' ||
event.key === 'Left' ||
event.key === 'ArrowLeft' ||
event.key === 'Right' ||
event.key === 'ArrowRight' ||
event.key === 'Esc' ||
event.key === 'Escape'
) return
refocus()
}
function refocus () {
self._view.input.focus()
reattach({ currentTarget: self._view.term })
delete self.scroll2bottom
self.scroll2bottom()
}
function reattach (event) {
var el = event.currentTarget
var isBottomed = el.scrollHeight - el.scrollTop - el.clientHeight < 30
if (isBottomed) {
if (inserted) {
text.innerText = ''
background.onclick = undefined
if (placeholder.parentElement) self._view.journal.removeChild(placeholder)
}
inserted = false
delete self.scroll2bottom
} else {
if (!inserted) self._view.journal.appendChild(placeholder)
inserted = true
check()
if (!placeholder.nextElementSibling) {
placeholder.style.display = 'none'
} else {
placeholder.style = ''
}
self.scroll2bottom = function () {
var next = placeholder.nextElementSibling
if (next) {
placeholder.style = ''
check()
var messages = 1
while ((next = next.nextElementSibling)) messages += 1
text.innerText = `${messages} new unread log entries`
} else {
placeholder.style.display = 'none'
}
}
}
}
function check () {
var pos1 = self._view.term.offsetHeight + self._view.term.scrollTop - (self._view.el.offsetHeight * 0.15)
var pos2 = placeholder.offsetTop
if ((pos1 - pos2) > 0) {
text.style.display = 'none'
background.style.position = 'relative'
background.style.opacity = 0.3
background.style.right = 0
background.style.borderBox = 'content-box'
background.style.padding = '2px'
background.style.height = (self._view.journal.offsetHeight - (placeholder.offsetTop + placeholder.offsetHeight)) + 'px'
background.onclick = undefined
background.style.cursor = 'default'
background.style.pointerEvents = 'none'
} else {
background.style = ''
text.style = ''
background.onclick = function (event) {
placeholder.scrollIntoView()
check()
}
}
}
function hover (event) { event.currentTarget.classList.toggle(css.hover) }
function minimize (event) {
event.preventDefault()
event.stopPropagation()
if (event.button === 0) {
var classList = self._view.icon.classList
classList.toggle('fa-angle-double-down')
classList.toggle('fa-angle-double-up')
self.event.trigger('resize', [])
}
}
var filtertimeout = null
function filter (event) {
if (filtertimeout) {
clearTimeout(filtertimeout)
}
filtertimeout = setTimeout(() => {
self.updateJournal({ type: 'search', value: self._view.inputSearch.value })
self.scroll2bottom()
}, 500)
}
function clear (event) {
refocus()
self._view.journal.innerHTML = ''
}
// ----------------- resizeable ui ---------------
function mousedown (event) {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
function cancelGhostbar (event) {
if (event.keyCode === 27) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
function moveGhostbar (event) { // @NOTE HORIZONTAL ghostbar
ghostbar.style.top = self._api.getPosition(event) + 'px'
}
function removeGhostbar (event) {
if (self._view.icon.classList.contains('fa-angle-double-up')) {
self._view.icon.classList.toggle('fa-angle-double-down')
self._view.icon.classList.toggle('fa-angle-double-up')
}
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
self.event.trigger('resize', [self._api.getPosition(event)])
}
self._cmdHistory = []
self._cmdIndex = -1
self._cmdTemp = ''
var intro = yo`
<div><div> - Welcome to Remix ${packageJson.version} - </div><br>
<div>You can use this terminal to: </div>
<ul class=${css2.ul}>
<li>Check transactions details and start debugging.</li>
<li>Execute JavaScript scripts:
<br />
<i> - Input a script directly in the command line interface </i>
<br />
<i> - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface </i>
<br />
<i> - Right click on a JavaScript file in the file explorer and then click \`Run\` </i>
</li>
</ul>
<div>The following libraries are accessible:</div>
<ul class=${css2.ul}>
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.0.0</a></li>
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li>
<li><a target="_blank" href="https://www.npmjs.com/package/swarmgw">swarmgw</a> </li>
<li>remix (run remix.help() for more info)</li>
</ul>
</div>
`
self._shell('remix.help()', self.commands, () => {})
self.commands.html(intro)
self._components.txLogger = new TxLogger(this, self.blockchain)
self._components.txLogger.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await self._opts.appManager.isActive('debugger')) await self._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
return self._view.el
function wrapScript (script) {
const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix))
if (isKnownScript) return script
return `
try {
const ret = ${script};
if (ret instanceof Promise) {
ret.then((result) => { console.log(result) }).catch((error) => { console.log(error) })
} else {
console.log(ret)
}
} catch (e) {
console.log(e.message)
}
`
}
function change (event) {
if (self._components.autoCompletePopup.handleAutoComplete(
event,
self._view.input.innerText)) return
if (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n'
self.putCursor2End(self._view.input)
self.scroll2bottom()
self._components.autoCompletePopup.removeAutoComplete()
} else { // <enter>
self._cmdIndex = -1
self._cmdTemp = ''
event.preventDefault()
var script = self._view.input.innerText.trim()
self._view.input.innerText = '\n'
if (script.length) {
self._cmdHistory.unshift(script)
self.commands.script(wrapScript(script))
}
self._components.autoCompletePopup.removeAutoComplete()
}
} else if (event.which === 38) { // <arrowUp>
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else if (event.which === 40) { // <arrowDown>
if (self._cmdIndex > -1) {
self._cmdIndex--
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else {
self._cmdTemp = self._view.input.innerText
}
}
log (message) {
this.logResponse.push(message)
this.renderComponent()
this.resetLog()
}
putCursor2End (editable) {
var range = document.createRange()
range.selectNode(editable)
var child = editable
var chars
while (child) {
if (child.lastChild) child = child.lastChild
else break
if (child.nodeType === Node.TEXT_NODE) {
chars = child.textContent.length
} else {
chars = child.innerHTML.length
}
}
range.setEnd(child, chars)
var toStart = true
var toEnd = !toStart
range.collapse(toEnd)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
editable.focus()
resetLog () {
this.logResponse = []
}
updateJournal (filterEvent) {
var self = this
var commands = self.data.activeFilters.commands
var value = filterEvent.value
if (filterEvent.type === 'select') {
commands[value] = true
if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx] = item })
self._JOURNAL[item.gidx] = item
})
} else if (filterEvent.type === 'deselect') {
commands[value] = false
if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx].hide = true })
self._JOURNAL[item.gidx].hide = true
})
} else if (filterEvent.type === 'search') {
if (value !== self.data.activeFilters.input) {
var query = self.data.activeFilters.input = value
var items = self._JOURNAL
for (var gidx = 0, len = items.length; gidx < len; gidx++) {
var item = items[gidx]
if (item && self.data.filterFns[item.cmd]) {
var show = query.length ? self.data.filterFns[item.cmd](item.args, query) : true
item.hide = !show
}
}
}
}
var df = document.createDocumentFragment()
self._JOURNAL.forEach(item => {
if (item && item.el && !item.hide) df.appendChild(item.el)
})
self._view.journal.innerHTML = ''
requestAnimationFrame(function updateDOM () {
self._view.journal.appendChild(df)
})
render () {
return this.element
}
_appendItem (item) {
var self = this
var { el, gidx } = item
self._JOURNAL[gidx] = item
if (!self._jobs.length) {
requestAnimationFrame(function updateTerminal () {
self._jobs.forEach(el => self._view.journal.appendChild(el))
self.scroll2bottom()
self._jobs = []
})
}
if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
renderComponent () {
ReactDOM.render(
<RemixUiTerminal
plugin = {this}
/>,
this.element
)
}
scroll2bottom () {
var self = this
setTimeout(function () {
self._view.term.scrollTop = self._view.term.scrollHeight
}, 0)
}
_blocksRenderer (mode) {
if (mode === 'html') {
return function logger (args, scopedCommands, append) {
if (args.length) append(args[0])
}
}
mode = {
log: 'text-info',
info: 'text-info',
warn: 'text-warning',
error: 'text-danger'
}[mode] // defaults
if (mode) {
const filterUndefined = (el) => el !== undefined && el !== null
return function logger (args, scopedCommands, append) {
var types = args.filter(filterUndefined).map(type)
var values = javascriptserialize.apply(null, args.filter(filterUndefined)).map(function (val, idx) {
if (typeof args[idx] === 'string') {
const el = document.createElement('div')
el.innerHTML = args[idx].replace(/(\r\n|\n|\r)/gm, '<br>')
val = el.children.length === 0 ? el.firstChild : el
}
if (types[idx] === 'element') val = jsbeautify.html(val)
return val
})
if (values.length) {
append(yo`<span class="${mode}" >${values}</span>`)
}
}
} else {
throw new Error('mode is not supported')
}
}
_scopeCommands (append) {
var self = this
var scopedCommands = {}
Object.keys(self.commands).forEach(function makeScopedCommand (cmd) {
var command = self._commands[cmd]
scopedCommands[cmd] = function _command () {
var args = [...arguments]
command(args, scopedCommands, el => append(cmd, args, blockify(el)))
}
})
return scopedCommands
}
registerFilter (commandName, filterFn) {
this.data.filterFns[commandName] = filterFn
}
registerCommand (name, command, opts) {
var self = this
name = String(name)
if (self._commands[name]) throw new Error(`command "${name}" exists already`)
if (typeof command !== 'function') throw new Error(`invalid command: ${command}`)
self._commands[name] = command
self._INDEX.commands[name] = []
self._INDEX.commandsMain[name] = []
self.commands[name] = function _command () {
var args = [...arguments]
var steps = []
var root = { steps, cmd: name }
var ITEM = { root, cmd: name }
root.gidx = self._INDEX.allMain.push(ITEM) - 1
root.idx = self._INDEX.commandsMain[name].push(ITEM) - 1
function append (cmd, params, el) {
var item
if (cmd) { // subcommand
item = { el, cmd, root }
} else { // command
item = ITEM
item.el = el
cmd = name
}
item.gidx = self._INDEX.all.push(item) - 1
item.idx = self._INDEX.commands[cmd].push(item) - 1
item.step = steps.push(item) - 1
item.args = params
self._appendItem(item)
}
var scopedCommands = self._scopeCommands(append)
command(args, scopedCommands, el => append(null, args, blockify(el)))
}
var help = typeof command.help === 'string' ? command.help : [
'// no help available for:',
`terminal.commands.${name}(...)`
].join('\n')
self.commands[name].toString = _ => { return help }
self.commands[name].help = help
self.data.activeFilters.commands[name] = opts && opts.activate
if (opts.filterFn) {
self.registerFilter(name, opts.filterFn)
}
return self.commands[name]
}
async _shell (script, scopedCommands, done) { // default shell
if (script.indexOf('remix:') === 0) {
return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
}
var self = this
if (script.indexOf('remix.') === 0) {
// we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// for all the other case, we use the Code Executor plugin
var context = domTerminalFeatures(self, scopedCommands, self.blockchain)
try {
var cmds = vm.createContext(context)
var result = vm.runInContext(script, cmds)
return done(null, result)
} catch (error) {
return done(error.message)
}
}
try {
let result
if (script.trim().startsWith('git')) {
// result = await this.call('git', 'execute', script)
} else {
result = await this.call('scriptRunner', 'execute', script)
}
if (result) self.commands.html(yo`<pre>${result}</pre>`)
done()
} catch (error) {
done(error.message || error)
}
}
}
function domTerminalFeatures (self, scopedCommands, blockchain) {
return {
remix: self._components.cmdInterpreter
}
}
function blockify (el) { return yo`<div class="px-4 ${css.block}" data-id="block_${el.getAttribute ? el.getAttribute('id') : ''}">${el}</div>` }
module.exports = Terminal

@ -39,7 +39,11 @@ class AnalysisTab extends ViewPlugin {
}
}
onActivation () {
async onActivation () {
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
this.renderComponent()
}

@ -115,16 +115,22 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
async onActivation () {
super.onActivation()
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidity',
name: 'compileFile',
label: 'Compile',
type: [],
extension: ['.sol'],
path: [],
pattern: []
this.on('filePanel', 'workspaceInitializationCompleted', () => {
this.call('filePanel', 'registerContextMenuItem', {
id: 'solidity',
name: 'compileFile',
label: 'Compile',
type: [],
extension: ['.sol'],
path: [],
pattern: []
})
})
this.currentFile = await this.call('fileManager', 'file')
try {
this.currentFile = await this.call('fileManager', 'file')
} catch (error) {
if (error.message !== 'Error: No such file or directory No file selected') throw error
}
}
getCompilerParameters () {

@ -218,11 +218,12 @@ class ContractDropdownUI {
if (this.selectContractNames.value === '') this.enableAtAddress(false)
} else {
this.loadType = 'other'
this.createPanel.style.display = 'none'
this.orLabel.style.display = 'none'
this.compFails.style.display = 'none'
this.contractNamesContainer.style.display = 'none'
this.createPanel.style.display = 'block'
this.orLabel.style.display = 'block'
this.contractNamesContainer.style.display = 'block'
this.selectContractNames.style.display = 'block'
this.abiLabel.style.display = 'none'
if (this.selectContractNames.value === '') this.enableAtAddress(false)
}
}

@ -170,10 +170,10 @@ class SettingsUI {
onchange=${() => this.validateValue()}
>
<select name="unit" class="form-control p-1 ${css.gasNvalUnit} ${css.col2_2} custom-select" id="unit">
<option data-unit="wei">wei</option>
<option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option>
<option data-unit="ether">ether</option>
<option data-unit="wei">Wei</option>
<option data-unit="gwei">Gwei</option>
<option data-unit="finney">Finney</option>
<option data-unit="ether">Ether</option>
</select>
</div>
</div>

@ -74,6 +74,14 @@ module.exports = class TestTab extends ViewPlugin {
}
}
async onActivation () {
const isSolidityActive = await this.call('manager', 'isActive', 'solidity')
if (!isSolidityActive) {
await this.call('manager', 'activatePlugin', 'solidity')
}
this.updateRunAction()
}
onDeactivation () {
this.off('filePanel', 'newTestFileCreated')
this.off('filePanel', 'setWorkspace')
@ -215,7 +223,7 @@ module.exports = class TestTab extends ViewPlugin {
runningTests[fileName].content
)
await this.call('editor', 'discardHighlight')
await this.call('editor', 'highlight', location, fileName)
await this.call('editor', 'highlight', location, fileName, '', { focus: true })
}
}
@ -247,6 +255,14 @@ module.exports = class TestTab extends ViewPlugin {
testCallback (result, runningTests) {
this.testsOutput.hidden = false
let debugBtn = yo``
if ((result.type === 'testPass' || result.type === 'testFailure') && result.debugTxHash) {
const { web3, debugTxHash } = result
debugBtn = yo`<div id=${result.value.replaceAll(' ', '_')} class="btn border btn btn-sm ml-1" title="Start debugging" onclick=${() => this.startDebug(debugTxHash, web3)}>
<i class="fas fa-bug"></i>
</div>`
debugBtn.style.cursor = 'pointer'
}
if (result.type === 'contract') {
this.testSuite = result.value
if (this.testSuites) {
@ -268,29 +284,18 @@ module.exports = class TestTab extends ViewPlugin {
<div
id="${this.runningTestFileName}"
data-id="testTabSolidityUnitTestsOutputheader"
class="${css.testPass} ${css.testLog} bg-light mb-2 text-success border-0"
class="${css.testPass} ${css.testLog} bg-light mb-2 px-2 text-success border-0"
onclick=${() => this.discardHighlight()}
>
${result.value}
<div class="d-flex my-1 align-items-start justify-content-between">
<span style="margin-block: auto" > ${result.value}</span>
${debugBtn}
</div>
</div>
`)
} else if (result.type === 'testFailure') {
if (result.hhLogs && result.hhLogs.length) this.printHHLogs(result.hhLogs, result.value)
if (!result.assertMethod) {
let debugBtn = yo``
if (result.errMsg.includes('Transaction has been reverted by the EVM')) {
const txHash = JSON.parse(result.errMsg.replace('Transaction has been reverted by the EVM:', '')).transactionHash
const { web3 } = result
debugBtn = yo`<div
class="btn border btn btn-sm ml-1"
title="Start debugging"
onclick=${() => this.startDebug(txHash, web3)}
>
<i class="fas fa-bug"></i>
</div>`
debugBtn.style.visibility = 'visible'
debugBtn.style.cursor = 'pointer'
} else debugBtn.style.visibility = 'hidden'
this.testsOutput.appendChild(yo`
<div
class="bg-light mb-2 px-2 ${css.testLog} d-flex flex-column text-danger border-0"
@ -315,7 +320,10 @@ module.exports = class TestTab extends ViewPlugin {
id="UTContext${result.context}"
onclick=${() => this.highlightLocation(result.location, runningTests, result.filename)}
>
<span> ${result.value}</span>
<div class="d-flex my-1 align-items-start justify-content-between">
<span> ${result.value}</span>
${debugBtn}
</div>
<span class="text-dark">Error Message:</span>
<span class="pb-2 text-break">"${result.errMsg}"</span>
<span class="text-dark">Assertion:</span>
@ -499,7 +507,7 @@ module.exports = class TestTab extends ViewPlugin {
usingWorker: canUseWorker(currentVersion),
runs
}
this.testRunner.runTestSources(runningTest, compilerConfig, () => {}, () => {}, (error, result) => {
this.testRunner.runTestSources(runningTest, compilerConfig, () => {}, () => {}, null, (error, result) => {
if (error) return reject(error)
resolve(result)
}, (url, cb) => {
@ -527,17 +535,22 @@ module.exports = class TestTab extends ViewPlugin {
usingWorker: canUseWorker(currentVersion),
runs
}
const deployCb = async (file, contractAddress) => {
const compilerData = await this.call('compilerArtefacts', 'getCompilerAbstract', file)
await this.call('compilerArtefacts', 'addResolvedContract', contractAddress, compilerData)
}
this.testRunner.runTestSources(
runningTests,
compilerConfig,
(result) => this.testCallback(result, runningTests),
(_err, result, cb) => this.resultsCallback(_err, result, cb),
deployCb,
(error, result) => {
this.updateFinalResult(error, result, testFilePath)
callback(error)
}, (url, cb) => {
return this.contentImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))
}
}, { testFilePath }
)
}).catch((error) => {
if (error) return // eslint-disable-line
@ -632,7 +645,6 @@ module.exports = class TestTab extends ViewPlugin {
el.setAttribute('title', 'No solidity file selected')
} else {
el.setAttribute('title', 'The "Solidity Plugin" should be activated')
// @todo(#2747) we can activate the plugin here
}
}
if (!this.runActionElement) {

@ -21,7 +21,7 @@ const themes = [
const profile = {
name: 'theme',
events: ['themeChanged'],
methods: ['switchTheme', 'getThemes', 'currentTheme'],
methods: ['switchTheme', 'getThemes', 'currentTheme', 'fixInvert'],
version: packageJson.version,
kind: 'theme'
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -76,7 +76,6 @@ export class ExecutionContext {
if (err) name = 'Unknown'
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
else if (id === 1) name = 'Main'
else if (id === 2) name = 'Morden (deprecated)'
else if (id === 3) name = 'Ropsten'
else if (id === 4) name = 'Rinkeby'
else if (id === 5) name = 'Goerli'

@ -20,7 +20,6 @@ class VMProvider {
this.accounts = {}
this.RemixSimulatorProvider = new Provider({ fork: this.executionContext.getCurrentFork() })
this.RemixSimulatorProvider.init()
this.RemixSimulatorProvider.Accounts.resetAccounts()
this.web3 = new Web3(this.RemixSimulatorProvider)
extend(this.web3)
this.accounts = {}

@ -6,7 +6,6 @@ var EventManager = require('../lib/events')
var toolTip = require('../app/ui/tooltip')
var globalRegistry = require('../global/registry')
var SourceHighlighter = require('../app/editor/sourceHighlighter')
var GistHandler = require('./gist-handler')
class CmdInterpreterAPI {
@ -17,7 +16,6 @@ class CmdInterpreterAPI {
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.terminal = terminal
self._components.sourceHighlighter = new SourceHighlighter()
self._components.fileImport = new CompilerImports()
self._components.gistHandler = new GistHandler()
self._deps = {
@ -93,7 +91,6 @@ class CmdInterpreterAPI {
if (cb) cb()
return
}
self._components.terminal.commands.script(content)
}

@ -14,7 +14,7 @@ const requiredModules = [ // services + layout views + system views
const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

@ -13,6 +13,7 @@ export class RemixEngine extends Engine {
if (name === 'dGitProvider') return { queueTimeout: 60000 * 4 }
if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed
if (name === 'hardhat') return { queueTimeout: 60000 * 4 }
if (name === 'localPlugin') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 }
}

@ -6,7 +6,7 @@ import { CompilerClientApi } from './compiler'
const remix = new CompilerClientApi()
export const App = () => {
export const App = () => {
return (
<div>
<SolidityCompiler api={remix} />

@ -30,7 +30,6 @@ const defaultCompilerParameters = {
evmVersion: null, // compiler default
language: 'Solidity'
}
export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi {
constructor () {
super()

@ -4,7 +4,8 @@
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-unused-vars": "off",
"dot-notation": "off"
"dot-notation": "off",
"no-use-before-define": "off"
},
"ignorePatterns": ["!**/*"]
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.16",
"version": "0.5.17",
"description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -19,11 +19,11 @@
}
],
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-astwalker": "^0.0.37",
"@remix-project/remix-lib": "^0.5.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.38",
"@remix-project/remix-lib": "^0.5.8",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
@ -51,5 +51,5 @@
"typescript": "^3.7.5"
},
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -3,7 +3,8 @@
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-namespace-keyword": "off",
"no-unused-vars": "off"
"no-unused-vars": "off",
"no-use-before-define": "off"
},
"ignorePatterns": ["!**/*"]
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.37",
"version": "0.0.38",
"description": "Tool to walk through Solidity AST",
"main": "src/index.js",
"scripts": {
@ -34,10 +34,10 @@
]
},
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.8",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
@ -53,5 +53,5 @@
"tap-spec": "^5.0.0"
},
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -39,6 +39,7 @@ export function isYulAstNode (node: Record<string, unknown>): boolean {
* in each case, if the event emits false it does not descend into children.
* If no event for the current type, children are visited.
*/
// eslint-disable-next-line no-redeclare
export class AstWalker extends EventEmitter {
manageCallback (
node: AstNode,

@ -58,6 +58,7 @@ export function sourceLocationFromSrc (src: string): Location {
* Routines for retrieving solc AST object(s) using some criteria, usually
* includng "src' information.
*/
// eslint-disable-next-line no-redeclare
export class SourceMappings {
readonly source: string;
readonly lineBreaks: Array<number>;

@ -4,7 +4,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = {
name: 'compilerArtefacts',
methods: ['get', 'addResolvedContract'],
methods: ['get', 'addResolvedContract', 'getCompilerAbstract'],
events: [],
version: '0.0.1'
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.7",
"version": "0.5.8",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -18,19 +18,24 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-astwalker": "^0.0.37",
"@remix-project/remix-lib": "^0.5.7",
"@remix-project/remix-simulator": "^0.2.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.38",
"@remix-project/remix-lib": "^0.5.8",
"@remix-project/remix-simulator": "^0.2.8",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"color-support": "^1.1.3",
"commander": "^2.19.0",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
"express-ws": "^4.0.0",
"merge": "^2.1.1",
"string-similarity": "^4.0.4",
"time-stamp": "^2.2.0",
"web3": "^1.5.1"
},
"devDependencies": {
@ -62,5 +67,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -3,7 +3,8 @@
"rules": {
"standard/no-callback-literal": "off",
"no-unused-vars": "off",
"dot-notation": "off"
"dot-notation": "off",
"no-use-before-define": "off"
},
"env": {
"browser": true,

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.7",
"version": "0.5.8",
"description": "Library to various Remix tools",
"contributors": [
{
@ -14,12 +14,13 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"async": "^2.1.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^4.0.40",
"ethjs-util": "^0.1.6",
"events": "^3.0.0",
"solc": "^0.7.4",
"string-similarity": "^4.0.4",
@ -53,5 +54,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -129,11 +129,7 @@ export class Web3VmProvider {
this.storageCache[this.processingHash] = {}
if (data.to) {
try {
// dumpStorage throws error as 'Missing Node in DB'
// This can be uncommented once that error is handled
// https://github.com/ethereum/remix-project/issues/1644
// const storage = await this.vm.stateManager.dumpStorage(data.to)
const storage = {}
const storage = await this.vm.stateManager.dumpStorage(data.to)
this.storageCache[this.processingHash][tx['to']] = storage
this.lastProcessedStorageTxHash[tx['to']] = this.processingHash
} catch (e) {
@ -249,11 +245,7 @@ export class Web3VmProvider {
if (!this.storageCache[this.processingHash][this.processingAddress]) {
const account = Address.fromString(this.processingAddress)
try {
// dumpStorage throws error as 'Missing Node in DB'
// This can be uncommented once that error is handled
// https://github.com/ethereum/remix-project/issues/1644
// const storage = await this.vm.stateManager.dumpStorage(account)
const storage = {}
const storage = await this.vm.stateManager.dumpStorage(account)
this.storageCache[this.processingHash][this.processingAddress] = storage
this.lastProcessedStorageTxHash[this.processingAddress] = this.processingHash
} catch (e) {

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.7",
"version": "0.2.8",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -14,11 +14,11 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.8",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
@ -66,5 +66,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -115,8 +115,6 @@ describe('blocks', () => {
assert.deepEqual(numberTransactions, correctBlock.uncles.length)
})
})
/*
describe('eth_getStorageAt', () => {
it('should get storage at position at given address', async () => {
const abi: any = [
@ -201,30 +199,28 @@ describe('blocks', () => {
}
]
// const code = '0x608060405234801561001057600080fd5b506040516020806102018339810180604052602081101561003057600080fd5b810190808051906020019092919050505080600081905550506101a9806100586000396000f3fe60806040526004361061005c576000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd91461006157806360fe47b11461008c5780636d4ce63c146100c7578063ce01e1ec146100f2575b600080fd5b34801561006d57600080fd5b5061007661012d565b6040518082815260200191505060405180910390f35b34801561009857600080fd5b506100c5600480360360208110156100af57600080fd5b8101908080359060200190929190505050610133565b005b3480156100d357600080fd5b506100dc61013d565b6040518082815260200191505060405180910390f35b3480156100fe57600080fd5b5061012b6004803603602081101561011557600080fd5b8101908080359060200190929190505050610146565b005b60005481565b8060008190555050565b60008054905090565b80600081905550807f63a242a632efe33c0e210e04e4173612a17efa4f16aa4890bc7e46caece80de060405160405180910390a25056fea165627a7a7230582063160eb16dc361092a85ced1a773eed0b63738b83bea1e1c51cf066fa90e135d0029'
const code = '0x608060405234801561001057600080fd5b506040516020806102018339810180604052602081101561003057600080fd5b810190808051906020019092919050505080600081905550506101a9806100586000396000f3fe60806040526004361061005c576000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd91461006157806360fe47b11461008c5780636d4ce63c146100c7578063ce01e1ec146100f2575b600080fd5b34801561006d57600080fd5b5061007661012d565b6040518082815260200191505060405180910390f35b34801561009857600080fd5b506100c5600480360360208110156100af57600080fd5b8101908080359060200190929190505050610133565b005b3480156100d357600080fd5b506100dc61013d565b6040518082815260200191505060405180910390f35b3480156100fe57600080fd5b5061012b6004803603602081101561011557600080fd5b8101908080359060200190929190505050610146565b005b60005481565b8060008190555050565b60008054905090565b80600081905550807f63a242a632efe33c0e210e04e4173612a17efa4f16aa4890bc7e46caece80de060405160405180910390a25056fea165627a7a7230582063160eb16dc361092a85ced1a773eed0b63738b83bea1e1c51cf066fa90e135d0029'
// const contract = new web3.eth.Contract(abi)
// const accounts = await web3.eth.getAccounts()
const contract = new web3.eth.Contract(abi)
const accounts = await web3.eth.getAccounts()
// const contractInstance: any = await contract.deploy({ data: code, arguments: [100] }).send({ from: accounts[0], gas: 400000 })
// contractInstance.currentProvider = web3.eth.currentProvider
// contractInstance.givenProvider = web3.eth.currentProvider
const contractInstance: any = await contract.deploy({ data: code, arguments: [100] }).send({ from: accounts[0], gas: 400000 })
contractInstance.currentProvider = web3.eth.currentProvider
contractInstance.givenProvider = web3.eth.currentProvider
// await contractInstance.methods.set(100).send({ from: accounts[0].toLowerCase(), gas: 400000 })
// let storage = await web3.eth.getStorageAt(contractInstance.options.address, 0)
// assert.deepEqual(storage, '0x64')
await contractInstance.methods.set(100).send({ from: accounts[0].toLowerCase(), gas: 400000 })
let storage = await web3.eth.getStorageAt(contractInstance.options.address, 0)
assert.deepEqual(storage, '0x64')
// await contractInstance.methods.set(200).send({ from: accounts[0], gas: 400000 })
// storage = await web3.eth.getStorageAt(contractInstance.options.address, 0)
// assert.deepEqual(storage, '0x64')
await contractInstance.methods.set(200).send({ from: accounts[0], gas: 400000 })
storage = await web3.eth.getStorageAt(contractInstance.options.address, 0)
assert.deepEqual(storage, '0x64')
await contractInstance.methods.set(200).send({ from: accounts[0], gas: 400000 })
storage = await web3.eth.getStorageAt(contractInstance.options.address, 0)
assert.deepEqual(storage, '0xc8')
})
})
*/
describe('eth_call', () => {
it('should get a value', async () => {
const abi: any = [

@ -2,7 +2,8 @@
"extends": "../../.eslintrc",
"rules": {
"dot-notation": "off",
"no-unused-vars": "off"
"no-unused-vars": "off",
"no-use-before-define": "off"
},
"env": {
"browser": true,

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.4.7",
"version": "0.4.8",
"description": "Tool to load and run Solidity compiler",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -15,10 +15,10 @@
}
],
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.8",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -150,7 +150,7 @@ export interface CompilerInputOptions {
language?: Language
}
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'muirGlacier' | 'berlin' | 'london' | null
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'berlin' | 'london' | null
export type Language = 'Solidity' | 'Yul'

@ -2,7 +2,8 @@
"extends": "../../.eslintrc",
"rules": {
"dot-notation": "off",
"no-unused-vars": "off"
"no-unused-vars": "off",
"no-use-before-define": "off"
},
"env": {
"browser": true,

@ -6,7 +6,7 @@ module.exports = {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
transformIgnorePatterns: ["/node_modules/", "\\.pnp\\.[^\\\/]+$"],
transformIgnorePatterns: ["/node_modules/", "/dist/", "\\.pnp\\.[^\\\/]+$"],
rootDir: "./",
testTimeout: 40000,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'],

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.7",
"version": "0.2.8",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -35,13 +35,13 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-tests#readme",
"dependencies": {
"@ethereumjs/block": "^3.4.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.3.0",
"@ethereumjs/vm": "^5.5.0",
"@remix-project/remix-lib": "^0.5.7",
"@remix-project/remix-simulator": "^0.2.7",
"@remix-project/remix-solidity": "^0.4.7",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.8",
"@remix-project/remix-simulator": "^0.2.8",
"@remix-project/remix-solidity": "^0.4.8",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
@ -77,5 +77,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "4705cbc4d1761f75267992552da9db6cff2f3ed5"
"gitHead": "13f961dadacea374de4d79a70b1aa4acf243b18b"
}

@ -171,8 +171,9 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
* @param cb Callback
*/
export function compileContractSources (sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void {
let compiler, filepath: string
let compiler
const accounts: string[] = opts.accounts || []
const filepath = opts.testFilePath || ''
// Iterate over sources keys. Inject test libraries. Inject test library import statements.
if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
sources['tests.sol'] = { content: require('../sol/tests.sol.js') }

@ -11,7 +11,7 @@ import { compilationInterface } from './types'
* @param callback Callback
*/
export function deployAll (compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, callback) {
export function deployAll (compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, deployCb, callback) {
const compiledObject = {}
const contracts = {}
let accounts: string[] = []
@ -70,7 +70,7 @@ export function deployAll (compileResult: compilationInterface, web3: Web3, with
deployObject.send({
from: accounts[0],
gas: gas
}).on('receipt', function (receipt) {
}).on('receipt', async function (receipt) {
contractObject.options.address = receipt.contractAddress
contractObject.options.from = accounts[0]
contractObject.options.gas = 5000 * 1000
@ -79,6 +79,7 @@ export function deployAll (compileResult: compilationInterface, web3: Web3, with
contracts[contractName] = contractObject
contracts[contractName].filename = filename
if (deployCb) await deployCb(filename, receipt.contractAddress)
callback(null, { receipt: { contractAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
}).on('error', function (err) {
console.error(err)

@ -81,8 +81,8 @@ commander
const compVersion = commander.compiler
const baseURL = 'https://binaries.soliditylang.org/wasm/'
const response: AxiosResponse = await axios.get(baseURL + 'list.json')
const { releases, latestRelease } = response.data
const compString = releases[compVersion]
const { releases, latestRelease } = response.data as { releases: string[], latestRelease: string }
const compString = releases ? releases[compVersion] : null
if (!compString) {
log.error(`No compiler found in releases with version ${compVersion}`)
process.exit()

@ -61,13 +61,13 @@ export function runTestFiles (filepath: string, isDirectory: boolean, web3: Web3
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
deployAll(compilationResult, web3, false, (err, contracts) => {
deployAll(compilationResult, web3, false, null, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, (error, contracts) => {
deployAll(compilationResult, web3, true, null, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})

@ -39,7 +39,7 @@ export class UnitTestRunner {
* @param importFileCb Import file callback
* @param opts Options
*/
async runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) {
async runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, deployCb:any, finalCallback: any, importFileCb, opts: Options) {
opts = opts || {}
const sourceASTs: any = {}
const web3 = opts.web3 || await this.createWeb3Provider()
@ -53,19 +53,19 @@ export class UnitTestRunner {
})
},
(next) => {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts, event: this.event }, next)
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts, testFilePath: opts.testFilePath, event: this.event }, next)
},
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
deployAll(compilationResult, web3, false, (err, contracts) => {
deployAll(compilationResult, web3, false, deployCb, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, (error, contracts) => {
deployAll(compilationResult, web3, true, deployCb, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})

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

Loading…
Cancel
Save