parent
c33a671148
commit
e7f0512635
@ -1,38 +1,132 @@ |
||||
var remix = require('ethereum-remix'); |
||||
var utils = require('./utils'); |
||||
var ace = require('brace'); |
||||
var Range = ace.acequire('ace/range').Range; |
||||
|
||||
function Debugger (id, executionContextEvent) { |
||||
/** |
||||
* Manage remix and source highlighting |
||||
*/ |
||||
function Debugger (id, editor, compiler, executionContextEvent, switchToFile) { |
||||
this.el = document.querySelector(id); |
||||
this.debugger = new remix.ui.Debugger(); |
||||
this.sourceMappingDecoder = new remix.util.SourceMappingDecoder() |
||||
this.el.appendChild(this.debugger.render()); |
||||
this.editor = editor; |
||||
this.switchToFile = switchToFile; |
||||
this.compiler = compiler; |
||||
this.cache = new Cache() |
||||
|
||||
var self = this; |
||||
executionContextEvent.register('contextChanged', this, function (context) { |
||||
context = context === 'vm' ? 'VM' : context; |
||||
context = context === 'injected' ? 'EXTERNAL' : context; |
||||
context = context === 'web3' ? 'INTERNAL' : context; |
||||
self.switchProvider(context); |
||||
}); |
||||
|
||||
this.lastCompilationResult = null; |
||||
this.debugger.register('newTraceLoaded', this, function () { |
||||
self.cache.clear() |
||||
self.lastCompilationResult = self.compiler.lastCompilationResult; |
||||
}); |
||||
|
||||
this.debugger.register('traceUnloaded', this, function () { |
||||
self.lastCompilationResult = null; |
||||
self.removeCurrentMarker() |
||||
self.cache.clear() |
||||
}); |
||||
|
||||
this.editor.onChangeSetup(function () { |
||||
if (arguments.length > 0) { // if arguments.length === 0 this is a session change, we don't want to stop debugging in that case
|
||||
self.debugger.unLoad() |
||||
} |
||||
}); |
||||
|
||||
// register selected code item, highlight the corresponding source location
|
||||
this.debugger.codeManager.register('changed', this, function (code, address, index) { |
||||
this.debugger.sourceLocationTracker.getSourceLocation(address, index, self.lastCompilationResult.data.contracts, function (error, rawLocation) { |
||||
if (!error) { |
||||
if (!self.cache.lineBreakPositionsByContent[address]) { |
||||
self.cache.lineBreakPositionsByContent[address] = self.sourceMappingDecoder.getLinebreakPositions(self.editor.getFile(self.lastCompilationResult.data.sourceList[rawLocation.file]))
|
||||
} |
||||
var lineColumnPos = self.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, self.cache.lineBreakPositionsByContent[address]) |
||||
self.highlight(lineColumnPos, rawLocation); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
Debugger.prototype.debug = function (receipt) { |
||||
var self = this |
||||
this.debugger.web3().eth.getTransaction(receipt.transactionHash, function (error, tx) { |
||||
/** |
||||
* Start debugging using Remix |
||||
* |
||||
* @param {String} txHash - hash of the transaction |
||||
*/ |
||||
Debugger.prototype.debug = function (txHash) { |
||||
var self = this; |
||||
this.debugger.web3().eth.getTransaction(txHash, function (error, tx) { |
||||
if (!error) { |
||||
self.debugger.debug(tx); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
/** |
||||
* highlight the given @arg lineColumnPos |
||||
* |
||||
* @param {Object} lineColumnPos - position of the source code to hightlight {start: {line, column}, end: {line, column}} |
||||
* @param {Object} rawLocation - raw position of the source code to hightlight {start, length, file, jump} |
||||
*/ |
||||
Debugger.prototype.highlight = function (lineColumnPos, rawLocation) { |
||||
var name = utils.fileNameFromKey(this.editor.getCacheFile()); // current opened tab
|
||||
var source = this.lastCompilationResult.data.sourceList[rawLocation.file]; // auto switch to that tab
|
||||
this.removeCurrentMarker(); |
||||
if (name !== source) { |
||||
this.switchToFile(source); // command the app to swicth to the next file
|
||||
} |
||||
this.currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column); |
||||
this.currentMarker = this.editor.addMarker(this.currentRange, 'highlightcode'); |
||||
}; |
||||
|
||||
/** |
||||
* add a new web3 provider to remix |
||||
* |
||||
* @param {String} type - type/name of the provider to add |
||||
* @param {Object} obj - provider |
||||
*/ |
||||
Debugger.prototype.addProvider = function (type, obj) { |
||||
this.debugger.addProvider(type, obj); |
||||
}; |
||||
|
||||
/** |
||||
* switch the provider |
||||
* |
||||
* @param {String} type - type/name of the provider to use |
||||
*/ |
||||
Debugger.prototype.switchProvider = function (type) { |
||||
this.debugger.switchProvider(type); |
||||
}; |
||||
|
||||
/** |
||||
* get the current provider |
||||
*/ |
||||
Debugger.prototype.web3 = function (type) { |
||||
return this.debugger.web3(); |
||||
}; |
||||
|
||||
/** |
||||
* unhighlight the current highlighted statement |
||||
*/ |
||||
Debugger.prototype.removeCurrentMarker = function () { |
||||
if (this.currentMarker) { |
||||
this.editor.removeMarker(this.currentMarker); |
||||
this.currentMarker = null; |
||||
} |
||||
}; |
||||
|
||||
|
||||
function Cache () { |
||||
this.contentLineBreakPosition = {} |
||||
} |
||||
|
||||
Cache.prototype.clear = function () { |
||||
this.lineBreakPositionsByContent = {} |
||||
} |
||||
|
||||
module.exports = Debugger; |
||||
|
@ -1,206 +0,0 @@ |
||||
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