parent
ec86fd504f
commit
c33a671148
@ -0,0 +1,206 @@ |
|||||||
|
var utils = require('./utils') |
||||||
|
var remix = require('ethereum-remix') |
||||||
|
var ace = require('brace') |
||||||
|
var Range = ace.acequire('ace/range').Range |
||||||
|
|
||||||
|
/* |
||||||
|
Provides source highlighting when debugging a transaction |
||||||
|
*/ |
||||||
|
function SourceHighlighter (editor, txdebugger, compilerEvent, appEvent, switchToFile) { |
||||||
|
this.switchToFile = switchToFile |
||||||
|
this.editor = editor |
||||||
|
this.txdebugger = txdebugger |
||||||
|
this.sourceMappingDecoder = new remix.util.SourceMappingDecoder() |
||||||
|
this.compilationData |
||||||
|
|
||||||
|
this.currentSourceMap |
||||||
|
this.currentLineColumnLayout // used to retrieve line/column from char/length
|
||||||
|
this.currentRange |
||||||
|
this.currentMarker |
||||||
|
this.currentExecutingAddress |
||||||
|
this.currentFile |
||||||
|
|
||||||
|
this.isDebugging = false |
||||||
|
|
||||||
|
var self = this |
||||||
|
|
||||||
|
/* hide/show marker when debugger does not have the focus */ |
||||||
|
appEvent.register('tabChanged', function (tab) { |
||||||
|
if (tab !== 'debugView') { |
||||||
|
self.editor.removeMarker(self.currentMarker) |
||||||
|
} else { |
||||||
|
if (self.currentRange) { |
||||||
|
self.currentMarker = self.editor.addMarker(self.currentRange, 'highlightcode') |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
/* update compilation data */ |
||||||
|
compilerEvent.register('compilationFinished', this, function (success, data, source) { |
||||||
|
if (!self.isDebugging) { |
||||||
|
self.reset() |
||||||
|
self.compilationData = success ? data : null |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
/* update marker, switch file if necessary */ |
||||||
|
txdebugger.codeManager.register('changed', this, function (code, address, index) { |
||||||
|
if (!this.currentExecutingAddress !== address) { |
||||||
|
// context changed, need to update srcmap
|
||||||
|
this.currentExecutingAddress = address |
||||||
|
self.loadContext(txdebugger.tx, function (error, ctrName, srcmap) { |
||||||
|
if (!error) { |
||||||
|
self.currentSourceMap = srcmap |
||||||
|
self.currentContractName = ctrName |
||||||
|
self.highlightSource(index) |
||||||
|
} |
||||||
|
}) |
||||||
|
} else { |
||||||
|
self.highlightSource(index) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
txdebugger.register('newTraceLoaded', this, function () { |
||||||
|
self.isDebugging = true |
||||||
|
}) |
||||||
|
|
||||||
|
txdebugger.register('traceUnloaded', this, function () { |
||||||
|
self.reset() |
||||||
|
self.isDebugging = false |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Load a new debugging context. A context is define by a new transaction. |
||||||
|
* it: |
||||||
|
* - builds the line/column layout from the source code. |
||||||
|
* - retrieves the source map |
||||||
|
* |
||||||
|
* @param {Integer} index - the tx which defined the new debugging context |
||||||
|
* @param {Function} callback - returns the decompressed source mapping for the given index {start, length, file, jump} |
||||||
|
*/ |
||||||
|
SourceHighlighter.prototype.loadContext = function (tx, cb) { |
||||||
|
var self = this |
||||||
|
contractName(self.currentExecutingAddress, self, function (error, ctrName) { |
||||||
|
if (!error) { |
||||||
|
var srcmap = sourceMap(isContractCreation(self.currentExecutingAddress), ctrName, self.compilationData) |
||||||
|
cb(null, ctrName, srcmap) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* remove the editor marker and init attributes |
||||||
|
*/ |
||||||
|
SourceHighlighter.prototype.reset = function () { |
||||||
|
this.currentSourceMap = null |
||||||
|
this.currentLineColumnLayout = null |
||||||
|
this.removeCurrentMarker() |
||||||
|
this.currentRange = null |
||||||
|
this.currentMarker = null |
||||||
|
this.currentExecutingAddress = null |
||||||
|
this.currentFile = null |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* remove the current highlighted statement |
||||||
|
*/ |
||||||
|
SourceHighlighter.prototype.removeCurrentMarker = function () { |
||||||
|
if (this.currentMarker) { |
||||||
|
this.editor.removeMarker(this.currentMarker) |
||||||
|
this.currentMarker = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* highlight the statement with the given @arg index |
||||||
|
* |
||||||
|
* @param {Integer} index - the index of the assembly item to be highlighted |
||||||
|
*/ |
||||||
|
SourceHighlighter.prototype.highlightSource = function (index) { |
||||||
|
var self = this |
||||||
|
this.sourceMappingDecoder.decompress(index, self.currentSourceMap, function (error, rawPos) { // retrieve the sourcemap location
|
||||||
|
if (!error) { |
||||||
|
if (self.currentFile !== rawPos.file) { // source file changed, need to update the line/column layout
|
||||||
|
var file = self.compilationData.sourceList[rawPos.file] |
||||||
|
self.sourceMappingDecoder.retrieveLineColumnLayout(self.editor.getFile(file), function (error, result) { |
||||||
|
if (!error) { |
||||||
|
self.currentLineColumnLayout = result |
||||||
|
self.currentFile = rawPos.file |
||||||
|
self.sourceMappingDecoder.getLineColumnPosition(rawPos, self.currentLineColumnLayout, function (error, pos) { |
||||||
|
if (!error) { |
||||||
|
self.highlight(pos, rawPos.file) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} else { |
||||||
|
self.sourceMappingDecoder.getLineColumnPosition(rawPos, self.currentLineColumnLayout, function (error, pos) { |
||||||
|
if (!error) { |
||||||
|
self.highlight(pos, rawPos.file) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* highlight the statement with the given @arg position |
||||||
|
* |
||||||
|
* @param {Object} position - the position to highlight { start: {line, column}, end: {line, column} } |
||||||
|
*/ |
||||||
|
SourceHighlighter.prototype.highlight = function (position, fileIndex) { |
||||||
|
var name = utils.fileNameFromKey(this.editor.getCacheFile()) // current opened tab
|
||||||
|
var source = this.compilationData.sourceList[parseInt(fileIndex)] // auto switch to that tab
|
||||||
|
this.removeCurrentMarker() |
||||||
|
if (name !== source) { |
||||||
|
this.switchToFile(source) // command the app to swicth to the curent file
|
||||||
|
} |
||||||
|
this.currentRange = new Range(position.start.line, position.start.column, position.end.line, position.end.column) |
||||||
|
this.currentMarker = this.editor.addMarker(this.currentRange, 'highlightcode') |
||||||
|
} |
||||||
|
|
||||||
|
function sourceMap (isConstructor, contractName, compilationData) { |
||||||
|
if (isConstructor) { |
||||||
|
return compilationData.contracts[contractName].srcmap |
||||||
|
} else { |
||||||
|
return srcmapRuntime(compilationData.contracts[contractName]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function contractName (executingAddress, self, cb) { |
||||||
|
if (isContractCreation(executingAddress)) { |
||||||
|
self.txdebugger.traceManager.getContractCreationCode(executingAddress, function (error, creationCode) { |
||||||
|
if (!error) { |
||||||
|
retrieveByteCode(creationCode, self.compilationData, 'bytecode', cb) |
||||||
|
} |
||||||
|
}) |
||||||
|
} else { |
||||||
|
self.txdebugger.web3().eth.getCode(executingAddress, function (error, code) { |
||||||
|
if (!error) { |
||||||
|
retrieveByteCode(code, self.compilationData, 'runtimeBytecode', cb) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function retrieveByteCode (code, compilationData, prop, cb) { |
||||||
|
for (var k in compilationData.contracts) { |
||||||
|
if (code === '0x' + compilationData.contracts[k][prop]) { |
||||||
|
cb(null, k) |
||||||
|
return |
||||||
|
} |
||||||
|
cb('unable to retrieve contract name') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function srcmapRuntime (contract) { |
||||||
|
return contract.srcmapRuntime ? contract.srcmapRuntime : contract['srcmap-runtime'] |
||||||
|
} |
||||||
|
|
||||||
|
function isContractCreation (address) { |
||||||
|
return address.indexOf('Contract Creation') !== -1 |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = SourceHighlighter |
Loading…
Reference in new issue