From c33a671148d797b2d5fff7c104eade9494b9dd24 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 16 Aug 2016 16:19:19 +0200 Subject: [PATCH 1/3] add sourceHighlighter --- assets/css/browser-solidity.css | 6 + src/app.js | 17 ++- src/app/debugger.js | 3 +- src/app/editor.js | 8 ++ src/app/sourceHighlighter.js | 206 ++++++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 src/app/sourceHighlighter.js diff --git a/assets/css/browser-solidity.css b/assets/css/browser-solidity.css index c70b1c996c..03a196f7eb 100644 --- a/assets/css/browser-solidity.css +++ b/assets/css/browser-solidity.css @@ -434,3 +434,9 @@ input[readonly] { input[type="file"] { display: none; } + +.highlightcode { + position:absolute; + z-index:20; + background-color:#F4B9B7; +} diff --git a/src/app.js b/src/app.js index 27a6aa69df..9e534b62eb 100644 --- a/src/app.js +++ b/src/app.js @@ -18,6 +18,7 @@ var UniversalDApp = require('./universal-dapp.js'); var Debugger = require('./app/debugger'); var FormalVerification = require('./app/formalVerification'); var EventManager = require('./lib/eventManager'); +var SourceHighlighter = require('./app/sourceHighlighter'); // The event listener needs to be registered as early as possible, because the // parent will send the message upon the "load" event. @@ -233,10 +234,15 @@ var run = function () { } return false; }); + + function swicthToFile (file) { + editor.setCacheFile(utils.fileKey(file)); + updateFiles(); + } function showFileHandler (ev) { ev.preventDefault(); - editor.setCacheFile(utils.fileKey($(this).find('.name').text())); + swicthToFile($(this).find('.name').text()) updateFiles(); return false; } @@ -421,22 +427,21 @@ var run = function () { transactionDebugger.switchProvider('VM'); transactionDebugger.addProvider('INTERNAL', executionContext.web3()); transactionDebugger.addProvider('EXTERNAL', executionContext.web3()); - transactionDebugger.onDebugRequested = function () { - selectTab($('ul#options li.debugView')); - }; var udapp = new UniversalDApp(executionContext, { removable: false, removable_instances: true }, transactionDebugger); - udapp.event.register('debugRequested', this, function (data) { - transactionDebugger.debug(data); + udapp.event.register('debugRequested', this, function (txResult) { + transactionDebugger.debug(txResult); + selectTab($('ul#options li.debugView')); }); var compiler = new Compiler(editor, queryParams, handleGithubCall, updateFiles); var formalVerification = new FormalVerification($('#verificationView'), compiler.event); var renderer = new Renderer(editor, executionContext.web3(), updateFiles, udapp, executionContext, formalVerification.event, compiler.event); // eslint-disable-line + var sourceHighlighter = new SourceHighlighter(editor, transactionDebugger.debugger, compiler.event, this.event, swicthToFile) // eslint-disable-line executionContext.event.register('contextChanged', this, function (context) { compiler.compile(); diff --git a/src/app/debugger.js b/src/app/debugger.js index e7e6ac0d16..098d9a7a51 100644 --- a/src/app/debugger.js +++ b/src/app/debugger.js @@ -15,8 +15,7 @@ function Debugger (id, executionContextEvent) { } Debugger.prototype.debug = function (receipt) { - if (this.onDebugRequested) this.onDebugRequested(); - var self = this; + var self = this this.debugger.web3().eth.getTransaction(receipt.transactionHash, function (error, tx) { if (!error) { self.debugger.debug(tx); diff --git a/src/app/editor.js b/src/app/editor.js index ee03bb0320..ea2f884209 100644 --- a/src/app/editor.js +++ b/src/app/editor.js @@ -15,6 +15,14 @@ function Editor (loadingFromGist, storage) { setupStuff(getFiles()); + this.addMarker = function (range, cssClass) { + return editor.session.addMarker(range, cssClass) + } + + this.removeMarker = function (markerId) { + editor.session.removeMarker(markerId) + } + this.newFile = function () { var untitledCount = ''; while (storage.exists(SOL_CACHE_UNTITLED + untitledCount)) { diff --git a/src/app/sourceHighlighter.js b/src/app/sourceHighlighter.js new file mode 100644 index 0000000000..95ef564e1d --- /dev/null +++ b/src/app/sourceHighlighter.js @@ -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 From e7f051263543542f66518b05c0feeabbda49061b Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 26 Aug 2016 10:27:09 +0200 Subject: [PATCH 2/3] delete sourcehightlighter.js --- src/app.js | 44 +++++--- src/app/compiler-worker.js | 2 +- src/app/compiler.js | 24 ++-- src/app/debugger.js | 108 ++++++++++++++++-- src/app/execution-context.js | 34 ++++-- src/app/sourceHighlighter.js | 206 ----------------------------------- 6 files changed, 170 insertions(+), 248 deletions(-) delete mode 100644 src/app/sourceHighlighter.js diff --git a/src/app.js b/src/app.js index 9e534b62eb..c4e3a36110 100644 --- a/src/app.js +++ b/src/app.js @@ -18,8 +18,6 @@ var UniversalDApp = require('./universal-dapp.js'); var Debugger = require('./app/debugger'); var FormalVerification = require('./app/formalVerification'); var EventManager = require('./lib/eventManager'); -var SourceHighlighter = require('./app/sourceHighlighter'); - // The event listener needs to be registered as early as possible, because the // parent will send the message upon the "load" event. var filesToLoad = null; @@ -234,7 +232,7 @@ var run = function () { } return false; }); - + function swicthToFile (file) { editor.setCacheFile(utils.fileKey(file)); updateFiles(); @@ -242,8 +240,7 @@ var run = function () { function showFileHandler (ev) { ev.preventDefault(); - swicthToFile($(this).find('.name').text()) - updateFiles(); + swicthToFile($(this).find('.name').text()); return false; } @@ -422,11 +419,14 @@ var run = function () { } var executionContext = new ExecutionContext(); - var transactionDebugger = new Debugger('#debugger', executionContext.event); - transactionDebugger.addProvider('VM', executionContext.vm()); + var compiler = new Compiler(editor, queryParams, handleGithubCall, updateFiles); + var formalVerification = new FormalVerification($('#verificationView'), compiler.event); + + var transactionDebugger = new Debugger('#debugger', editor, compiler, executionContext.event, swicthToFile); + transactionDebugger.addProvider('vm', executionContext.vm()); transactionDebugger.switchProvider('VM'); - transactionDebugger.addProvider('INTERNAL', executionContext.web3()); - transactionDebugger.addProvider('EXTERNAL', executionContext.web3()); + transactionDebugger.addProvider('injected', executionContext.web3()); + transactionDebugger.addProvider('web3', executionContext.web3()); var udapp = new UniversalDApp(executionContext, { removable: false, @@ -434,14 +434,10 @@ var run = function () { }, transactionDebugger); udapp.event.register('debugRequested', this, function (txResult) { - transactionDebugger.debug(txResult); - selectTab($('ul#options li.debugView')); + startdebugging(txResult.transactionHash); }); - var compiler = new Compiler(editor, queryParams, handleGithubCall, updateFiles); - var formalVerification = new FormalVerification($('#verificationView'), compiler.event); var renderer = new Renderer(editor, executionContext.web3(), updateFiles, udapp, executionContext, formalVerification.event, compiler.event); // eslint-disable-line - var sourceHighlighter = new SourceHighlighter(editor, transactionDebugger.debugger, compiler.event, this.event, swicthToFile) // eslint-disable-line executionContext.event.register('contextChanged', this, function (context) { compiler.compile(); @@ -451,8 +447,9 @@ var run = function () { compiler.compile(); }); - executionContext.event.register('compilerLoaded', this, function (context) { + compiler.event.register('compilerLoaded', this, function (context) { compiler.compile(); + initWithQueryParams(); }); compiler.event.register('compilerLoaded', this, function (version) { @@ -460,6 +457,23 @@ var run = function () { compiler.compile(); }); + function initWithQueryParams () { + if (queryParams.get().endpointurl) { + executionContext.setEndPointUrl(queryParams.get().endpointurl); + } + if (queryParams.get().context) { + executionContext.setContext(queryParams.get().context); + } + if (queryParams.get().debugtx) { + startdebugging(queryParams.get().debugtx); + } + } + + function startdebugging (txHash) { + transactionDebugger.debug(txHash); + selectTab($('ul#options li.debugView')); + } + function setVersionText (text) { $('#version').text(text); } diff --git a/src/app/compiler-worker.js b/src/app/compiler-worker.js index ecf549f2d4..424796bfb9 100644 --- a/src/app/compiler-worker.js +++ b/src/app/compiler-worker.js @@ -34,7 +34,7 @@ module.exports = function (self) { break; case 'compile': missingInputs.length = 0; - self.postMessage({cmd: 'compiled', data: compileJSON(data.source, data.optimize), missingInputs: missingInputs}); + self.postMessage({cmd: 'compiled', data: compileJSON(data.source, data.optimize), missingInputs: missingInputs, source: data.source}); break; } }, false); diff --git a/src/app/compiler.js b/src/app/compiler.js index 172950df11..fc2b043f31 100644 --- a/src/app/compiler.js +++ b/src/app/compiler.js @@ -16,7 +16,7 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { var compileJSON; var compilerAcceptsMultipleFiles; - + var previousInput = ''; var cachedRemoteFiles = {}; @@ -60,7 +60,7 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { }); }; this.compile = compile; - + function setCompileJSON (_compileJSON) { compileJSON = _compileJSON; } @@ -91,24 +91,28 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { result = { error: 'Uncaught JavaScript exception:\n' + exception }; } - compilationFinished(result, missingInputs); + compilationFinished(result, missingInputs, source); }; onCompilerLoaded(compiler.version()); } } - function compilationFinished (data, missingInputs) { + this.lastCompilationResult = { + data: null, + source: null + } + function compilationFinished (data, missingInputs, source) { var noFatalErrors = true; // ie warnings are ok if (data['error'] !== undefined) { - self.event.trigger('compilationFinished', [false, [data['error']], editor.getValue()]); + self.event.trigger('compilationFinished', [false, [data['error']], source]); if (utils.errortype(data['error']) !== 'warning') { noFatalErrors = false; } } if (data['errors'] !== undefined) { - self.event.trigger('compilationFinished', [false, data['errors'], editor.getValue()]); + self.event.trigger('compilationFinished', [false, data['errors'], source]); data['errors'].forEach(function (err) { if (utils.errortype(err) !== 'warning') { noFatalErrors = false; @@ -119,7 +123,11 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { if (missingInputs !== undefined && missingInputs.length > 0) { compile(missingInputs); } else if (noFatalErrors) { - self.event.trigger('compilationFinished', [true, data, editor.getValue()]); + self.lastCompilationResult = { + data: data, + source: source + } + self.event.trigger('compilationFinished', [true, data, source]); } } @@ -170,7 +178,7 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { } catch (exception) { result = { 'error': 'Invalid JSON output from the compiler: ' + exception }; } - compilationFinished(result, data.missingInputs); + compilationFinished(result, data.missingInputs, data.source); break; } }); diff --git a/src/app/debugger.js b/src/app/debugger.js index 098d9a7a51..eba1d7e571 100644 --- a/src/app/debugger.js +++ b/src/app/debugger.js @@ -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; diff --git a/src/app/execution-context.js b/src/app/execution-context.js index cc6d218128..9f4a8a808b 100644 --- a/src/app/execution-context.js +++ b/src/app/execution-context.js @@ -21,7 +21,6 @@ vm.stateManager.checkpoint(); /* trigger contextChanged, web3EndpointChanged */ - function ExecutionContext () { var self = this; this.event = new EventManager(); @@ -39,6 +38,16 @@ function ExecutionContext () { return vm; }; + this.setEndPointUrl = function (url) { + $web3endpoint.val(url); + }; + + this.setContext = function (context) { + executionContext = context; + executionContextChange(context); + setExecutionContextRadio() + }; + var $injectedToggle = $('#injected-mode'); var $vmToggle = $('#vm-mode'); var $web3Toggle = $('#web3-mode'); @@ -50,9 +59,9 @@ function ExecutionContext () { setExecutionContextRadio(); - $injectedToggle.on('change', executionContextChange); - $vmToggle.on('change', executionContextChange); - $web3Toggle.on('change', executionContextChange); + $injectedToggle.on('change', executionContextUIChange); + $vmToggle.on('change', executionContextUIChange); + $web3Toggle.on('change', executionContextUIChange); $web3endpoint.on('change', function () { setProviderFromEndpoint(); if (executionContext === 'web3') { @@ -60,20 +69,23 @@ function ExecutionContext () { } }); - function executionContextChange (ev) { - if (ev.target.value === 'web3' && !confirm('Are you sure you want to connect to a local ethereum node?')) { + function executionContextUIChange (ev) { + executionContextChange(ev.target.value); + } + + function executionContextChange (context) { + if (context === 'web3' && !confirm('Are you sure you want to connect to a local ethereum node?')) { setExecutionContextRadio(); - } else if (ev.target.value === 'injected' && injectedProvider === undefined) { + } else if (context === 'injected' && injectedProvider === undefined) { setExecutionContextRadio(); } else { - executionContext = ev.target.value; - if (executionContext === 'web3') { + if (context === 'web3') { setProviderFromEndpoint(); self.event.trigger('contextChanged', ['web3']); - } else if (executionContext === 'injected') { + } else if (context === 'injected') { web3.setProvider(injectedProvider); self.event.trigger('contextChanged', ['injected']); - } else if (executionContext === 'vm') { + } else if (context === 'vm') { vm.stateManager.revert(function () { vm.stateManager.checkpoint(); }); diff --git a/src/app/sourceHighlighter.js b/src/app/sourceHighlighter.js deleted file mode 100644 index 95ef564e1d..0000000000 --- a/src/app/sourceHighlighter.js +++ /dev/null @@ -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 From ed4c94cd1ab59acf44f67cbc6ec9326c7505c1fc Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 29 Aug 2016 10:23:24 +0200 Subject: [PATCH 3/3] readd format assembly text --- package.json | 2 +- src/app.js | 5 +---- src/app/compiler.js | 16 +++++++-------- src/app/debugger.js | 40 +++++++++++++++++++----------------- src/app/editor.js | 8 ++++---- src/app/execution-context.js | 2 +- src/app/renderer.js | 19 +++++++++++++---- 7 files changed, 51 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index b213e952b1..7ef80b54d6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "browserify": "^13.0.0", "csslint": "^1.0.2", "es6-shim": "^0.35.1", - "ethereum-remix": "0.0.2-alpha.0.0.7", + "ethereum-remix": "0.0.2-alpha.0.0.8", "ethereumjs-abi": "^0.6.4", "ethereumjs-block": "^1.2.2", "ethereumjs-tx": "^1.1.1", diff --git a/src/app.js b/src/app.js index c4e3a36110..1032f761d0 100644 --- a/src/app.js +++ b/src/app.js @@ -109,9 +109,6 @@ var run = function () { el.parent().find('li').removeClass('active'); $('#optionViews').attr('class', '').addClass(cls); el.addClass('active'); - } else { - el.removeClass('active'); - $('#optionViews').removeClass(cls); } self.event.trigger('tabChanged', [cls]); }; @@ -424,7 +421,7 @@ var run = function () { var transactionDebugger = new Debugger('#debugger', editor, compiler, executionContext.event, swicthToFile); transactionDebugger.addProvider('vm', executionContext.vm()); - transactionDebugger.switchProvider('VM'); + transactionDebugger.switchProvider('vm'); transactionDebugger.addProvider('injected', executionContext.web3()); transactionDebugger.addProvider('web3', executionContext.web3()); diff --git a/src/app/compiler.js b/src/app/compiler.js index fc2b043f31..f3a06af4a0 100644 --- a/src/app/compiler.js +++ b/src/app/compiler.js @@ -16,7 +16,7 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { var compileJSON; var compilerAcceptsMultipleFiles; - + var previousInput = ''; var cachedRemoteFiles = {}; @@ -60,7 +60,7 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { }); }; this.compile = compile; - + function setCompileJSON (_compileJSON) { compileJSON = _compileJSON; } @@ -101,18 +101,16 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { this.lastCompilationResult = { data: null, source: null - } + }; function compilationFinished (data, missingInputs, source) { var noFatalErrors = true; // ie warnings are ok if (data['error'] !== undefined) { - self.event.trigger('compilationFinished', [false, [data['error']], source]); if (utils.errortype(data['error']) !== 'warning') { noFatalErrors = false; } } if (data['errors'] !== undefined) { - self.event.trigger('compilationFinished', [false, data['errors'], source]); data['errors'].forEach(function (err) { if (utils.errortype(err) !== 'warning') { noFatalErrors = false; @@ -120,13 +118,15 @@ function Compiler (editor, queryParams, handleGithubCall, updateFiles) { }); } - if (missingInputs !== undefined && missingInputs.length > 0) { + if (!noFatalErrors) { + self.event.trigger('compilationFinished', [false, data, source]); + } else if (missingInputs !== undefined && missingInputs.length > 0) { compile(missingInputs); - } else if (noFatalErrors) { + } else { self.lastCompilationResult = { data: data, source: source - } + }; self.event.trigger('compilationFinished', [true, data, source]); } } diff --git a/src/app/debugger.js b/src/app/debugger.js index eba1d7e571..398bff35dc 100644 --- a/src/app/debugger.js +++ b/src/app/debugger.js @@ -9,12 +9,12 @@ var Range = ace.acequire('ace/range').Range; 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.sourceMappingDecoder = new remix.util.SourceMappingDecoder(); this.el.appendChild(this.debugger.render()); this.editor = editor; this.switchToFile = switchToFile; this.compiler = compiler; - this.cache = new Cache() + this.cache = new Cache(); var self = this; executionContextEvent.register('contextChanged', this, function (context) { @@ -23,33 +23,36 @@ function Debugger (id, editor, compiler, executionContextEvent, switchToFile) { this.lastCompilationResult = null; this.debugger.register('newTraceLoaded', this, function () { - self.cache.clear() + self.cache.clear(); self.lastCompilationResult = self.compiler.lastCompilationResult; }); this.debugger.register('traceUnloaded', this, function () { - self.lastCompilationResult = null; - self.removeCurrentMarker() - self.cache.clear() + 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() + 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])) + if (self.lastCompilationResult) { + 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); + } else { + self.removeCurrentMarker(); } - var lineColumnPos = self.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, self.cache.lineBreakPositionsByContent[address]) - self.highlight(lineColumnPos, rawLocation); - } - }); + }); + } }); } @@ -120,13 +123,12 @@ Debugger.prototype.removeCurrentMarker = function () { } }; - function Cache () { - this.contentLineBreakPosition = {} + this.contentLineBreakPosition = {}; } Cache.prototype.clear = function () { - this.lineBreakPositionsByContent = {} -} + this.lineBreakPositionsByContent = {}; +}; module.exports = Debugger; diff --git a/src/app/editor.js b/src/app/editor.js index ea2f884209..0d06fcc1e9 100644 --- a/src/app/editor.js +++ b/src/app/editor.js @@ -16,12 +16,12 @@ function Editor (loadingFromGist, storage) { setupStuff(getFiles()); this.addMarker = function (range, cssClass) { - return editor.session.addMarker(range, cssClass) - } + return editor.session.addMarker(range, cssClass); + }; this.removeMarker = function (markerId) { - editor.session.removeMarker(markerId) - } + editor.session.removeMarker(markerId); + }; this.newFile = function () { var untitledCount = ''; diff --git a/src/app/execution-context.js b/src/app/execution-context.js index 9f4a8a808b..9a2caae02d 100644 --- a/src/app/execution-context.js +++ b/src/app/execution-context.js @@ -45,7 +45,7 @@ function ExecutionContext () { this.setContext = function (context) { executionContext = context; executionContextChange(context); - setExecutionContextRadio() + setExecutionContextRadio(); }; var $injectedToggle = $('#injected-mode'); diff --git a/src/app/renderer.js b/src/app/renderer.js index 5dd9158acb..2f9b04225c 100644 --- a/src/app/renderer.js +++ b/src/app/renderer.js @@ -20,9 +20,14 @@ function Renderer (editor, web3, updateFiles, udapp, executionContext, formalVer if (success) { self.contracts(data, source); } else { - data.forEach(function (err) { - self.error(err); - }); + if (data['error']) { + self.error(data['error']); + } + if (data['errors']) { + data['errors'].forEach(function (err) { + self.error(err); + }); + } } }); } @@ -93,11 +98,17 @@ Renderer.prototype.contracts = function (data, source) { $contractOutput.append(uiHelper.textRow('Web3 deploy', uiHelper.gethDeploy(contractName.toLowerCase(), contract['interface'], contract.bytecode), 'deploy')); $contractOutput.append(uiHelper.textRow('uDApp', combined(contractName, contract['interface'], contract.bytecode), 'deploy')); } - return $contractOutput.append(uiHelper.getDetails(contract, source, contractName)); + var ctrSource = getSource(contractName, source, data); + return $contractOutput.append(uiHelper.getDetails(contract, ctrSource, contractName)); }; // // var self = this; + var getSource = function (contractName, source, data) { + var currentFile = utils.fileNameFromKey(self.editor.getCacheFile()); + return source.sources[currentFile]; + }; + var getAddress = function () { return $('#txorigin').val(); }; var getValue = function () {