remix-ide
Version:
Minimalistic browser-based Solidity IDE
293 lines (281 loc) • 12.1 kB
JavaScript
var yo = require('yo-yo')
var async = require('async')
var remixLib = require('remix-lib')
var EventManager = require('../lib/events')
var CompilerImport = require('../app/compiler/compiler-imports')
var executionContext = require('../execution-context')
var toolTip = require('../app/ui/tooltip')
var globalRegistry = require('../global/registry')
var SourceHighlighter = require('../app/editor/sourceHighlighter')
var RemixDebug = require('remix-debug').EthDebugger
var TreeView = require('../app/ui/TreeView') // TODO setup a direct reference to the UI components
var solidityTypeFormatter = require('../app/debugger/debuggerUI/vmDebugger/utils/SolidityTypeFormatter')
class CmdInterpreterAPI {
constructor (terminal, localRegistry) {
const self = this
self.event = new EventManager()
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.terminal = terminal
self._components.sourceHighlighter = new SourceHighlighter()
self._components.fileImport = new CompilerImport()
self._deps = {
app: self._components.registry.get('app').api,
fileManager: self._components.registry.get('filemanager').api,
editor: self._components.registry.get('editor').api,
compilersArtefacts: self._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
}
self.commandHelp = {
'remix.call(message: {name, key, payload})': 'Call a registered plugins',
'remix.getFile(path)': 'Returns the content of the file located at the given path',
'remix.setFile(path, content)': 'set the content of the file located at the given path',
'remix.debug(hash)': 'Start debugging a transaction.',
'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.setproviderurl(url)': 'Change the current provider to Web3 provider and set the url endpoint.',
'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',
'remix.debugHelp()': 'Display help message for debugging'
}
}
call (message) {
return this._components.terminal.externalApi.request(message)
}
log () { arguments[0] != null ? this._components.terminal.commands.html(arguments[0]) : this._components.terminal.commands.html(arguments[1]) }
highlight (rawLocation) {
var self = this
if (!rawLocation) {
self._components.sourceHighlighter.currentSourceLocation(null)
return
}
var lineColumnPos = self._deps.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file,
self._deps.compilersArtefacts['__last'].getSourceCode().sources,
self._deps.compilersArtefacts['__last'].getAsts())
self._components.sourceHighlighter.currentSourceLocation(lineColumnPos, rawLocation)
}
debug (hash, cb) {
var self = this
delete self.d
executionContext.web3().eth.getTransaction(hash, (error, tx) => {
if (error) return cb(error)
var debugSession = new RemixDebug({
compilationResult: () => {
return self._deps.compilersArtefacts['__last'].getData()
}
})
debugSession.addProvider('web3', executionContext.web3())
debugSession.switchProvider('web3')
debugSession.debug(tx)
self.d = debugSession
this._components.terminal.commands.log('A new debugging session is available at remix.d')
if (cb) cb(null, debugSession)
// helpers
self.d.highlight = (address, vmtraceIndex) => {
if (!address) return self.highlight()
self.d.sourceLocationFromVMTraceIndex(address, vmtraceIndex, (error, rawLocation) => {
if (!error && rawLocation) {
self.highlight(rawLocation)
}
})
}
self.d.stateAt = (vmTraceIndex) => {
self.d.extractStateAt(vmTraceIndex, (error, state) => {
if (error) return self.log(error)
self.d.decodeStateAt(vmTraceIndex, state, (error, state) => {
if (error) return this._components.terminal.commands.html(error)
var treeView = new TreeView({
json: true,
formatSelf: solidityTypeFormatter.formatSelf,
extractData: solidityTypeFormatter.extractData
})
self.log('State at ' + vmTraceIndex)
self._components.terminal.commands.html(treeView.render(state, true))
})
})
}
self.d.localsAt = (contractAddress, vmTraceIndex) => {
debugSession.sourceLocationFromVMTraceIndex(contractAddress, vmTraceIndex, (error, location) => {
if (error) return self.log(error)
debugSession.decodeLocalsAt(23, location, (error, locals) => {
if (error) return this._components.terminal.commands.html(error)
var treeView = new TreeView({
json: true,
formatSelf: solidityTypeFormatter.formatSelf,
extractData: solidityTypeFormatter.extractData
})
self.log('Locals at ' + vmTraceIndex)
self._components.terminal.commands.html(treeView.render(locals, true))
})
})
}
self.d.goTo = (row) => {
if (self._deps.editor.current()) {
var breakPoint = new remixLib.code.BreakpointManager(self.d, (sourceLocation) => {
return self._deps.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file,
self._deps.compilersArtefacts['__last'].getSourceCode().sources,
self._deps.compilersArtefacts['__last'].getAsts())
})
breakPoint.event.register('breakpointHit', (sourceLocation, currentStep) => {
self.log(null, 'step index ' + currentStep)
self.highlight(sourceLocation)
self.d.stateAt(currentStep)
self.d.traceManager.getCurrentCalledAddressAt(currentStep, (error, address) => {
if (error) return self.log(address)
self.d.localsAt(address, currentStep)
})
})
breakPoint.event.register('NoBreakpointHit', () => { self.log('line ' + row + ' is not part of the current execution') })
breakPoint.add({fileName: self._deps.editor.current(), row: row - 1})
breakPoint.jumpNextBreakpoint(0, true)
}
}
})
}
loadgist (id, cb) {
const self = this
self._deps.app.loadFromGist({gist: id})
if (cb) cb()
}
loadurl (url, cb) {
const self = this
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
(err, content, cleanUrl, type, url) => {
if (err) {
toolTip(`Unable to load ${url}: ${err}`)
if (cb) cb(err)
} else {
self._deps.fileManager.setFile(type + '/' + cleanUrl, content)
try {
content = JSON.parse(content)
async.eachOfSeries(content.sources, (value, file, callbackSource) => {
var url = value.urls[0] // @TODO retrieve all other contents ?
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) {
toolTip(`Cannot retrieve the content of ${url}: ${error}`)
return callbackSource(`Cannot retrieve the content of ${url}: ${error}`)
} else {
try {
await self._deps.fileManager.setFile(type + '/' + cleanUrl, content)
callbackSource()
} catch (e) {
callbackSource(e.message)
}
}
})
}, (error) => {
if (cb) cb(error)
})
} catch (e) {}
if (cb) cb()
}
})
}
setproviderurl (url, cb) {
executionContext.setProviderFromEndpoint(url, 'web3', (error) => {
if (error) toolTip(error)
if (cb) cb()
})
}
exeCurrent (cb) {
return this.execute(undefined, cb)
}
getFile (path, cb) {
var provider = this._deps.fileManager.fileProviderOf(path)
if (provider) {
provider.get(path, cb)
} else {
cb('file not found')
}
}
setFile (path, content, cb) {
cb = cb || function () {}
var provider = this._deps.fileManager.fileProviderOf(path)
if (provider) {
provider.set(path, content, (error) => {
if (error) return cb(error)
this._deps.fileManager.syncEditor(path)
cb()
})
} else {
cb('file not found')
}
}
execute (file, cb) {
const self = this
function _execute (content, cb) {
if (!content) {
toolTip('no content to execute')
if (cb) cb()
return
}
self._components.terminal.commands.script(content)
}
if (typeof file === 'undefined') {
var content = self._deps.editor.currentContent()
_execute(content, cb)
return
}
var provider = self._deps.fileManager.fileProviderOf(file)
if (!provider) {
toolTip(`provider for path ${file} not found`)
if (cb) cb()
return
}
provider.get(file, (error, content) => {
if (error) {
toolTip(error)
if (cb) cb()
return
}
_execute(content, cb)
})
}
help (cb) {
const self = this
var help = yo`<div></div>`
for (var k in self.commandHelp) {
help.appendChild(yo`<div>${k}: ${self.commandHelp[k]}</div>`)
help.appendChild(yo`<br>`)
}
self._components.terminal.commands.html(help)
if (cb) cb()
return ''
}
debugHelp (cb) {
const self = this
var help = yo`<div>Here are some examples of scripts that can be run (using remix.exeCurrent() or directly from the console)</div>`
help.appendChild(yo`<br>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>remix.debug('0x3c247ac268afb9a9c183feb9d4e83df51efbc8a2f4624c740789b788dac43029', function (error, debugSession) {
remix.log = function () { arguments[0] != null ? console.log(arguments[0]) : console.log(arguments[1]) }
remix.d.traceManager.getLength(remix.log)
remix.storageView = remix.d.storageViewAt(97, '0x692a70d2e424a56d2c6c27aa97d1a86395877b3a')
console.log('storage at 97 :')
remix.storageView.storageRange(remix.log)
})<br/></div>`)
help.appendChild(yo`<div>remix.log = function () { arguments[0] != null ? console.log(arguments[0]) : console.log(arguments[1]) }
remix.d.extractStateAt(2, function (error, state) {
remix.d.decodeStateAt(97, state, remix.log)
})<br/></div>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>remix.highlight(contractAddress, vmTraceIndex)</div>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>remix.goTo(row) (this log the index in the vm trace, state and local variables)</div>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>remix.stateAt(vmTraceIndex)</div>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>remix.localsAt(vmTraceIndex)</div>`)
help.appendChild(yo`<br>`)
help.appendChild(yo`<div>Please see <a href="https://www.npmjs.com/package/remix-debug" target="_blank">https://www.npmjs.com/package/remix-debug</a> for more informations</div>`)
self._components.terminal.commands.html(help)
if (cb) cb()
return ''
}
}
module.exports = CmdInterpreterAPI