|  | // Copyright (c) 2017, Matt Godbolt | 
|  | // All rights reserved. | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | //     * Redistributions of source code must retain the above copyright notice, | 
|  | //       this list of conditions and the following disclaimer. | 
|  | //     * Redistributions in binary form must reproduce the above copyright | 
|  | //       notice, this list of conditions and the following disclaimer in the | 
|  | //       documentation and/or other materials provided with the distribution. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | 
|  | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
|  | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
|  | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
|  | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | // POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | 'use strict'; | 
|  |  | 
|  | var FontScale = require('../fontscale'); | 
|  | var monaco = require('monaco-editor'); | 
|  | var _ = require('underscore'); | 
|  | var $ = require('jquery'); | 
|  | var ga = require('../analytics'); | 
|  |  | 
|  | require('../modes/asm-mode'); | 
|  | require('selectize'); | 
|  |  | 
|  | // note that these variables are saved to state, so don't change, only add to it | 
|  | var | 
|  | DiffType_ASM = 0, | 
|  | DiffType_CompilerStdOut = 1, | 
|  | DiffType_CompilerStdErr = 2, | 
|  | DiffType_ExecStdOut = 3, | 
|  | DiffType_ExecStdErr = 4; | 
|  |  | 
|  | function State(id, model, difftype) { | 
|  | this.id = id; | 
|  | this.model = model; | 
|  | this.compiler = null; | 
|  | this.result = null; | 
|  | this.difftype = difftype; | 
|  | } | 
|  |  | 
|  | State.prototype.update = function (id, compiler, result) { | 
|  | if (this.id !== id) return false; | 
|  | this.compiler = compiler; | 
|  | this.result = result; | 
|  | this.refresh(); | 
|  |  | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | State.prototype.refresh = function () { | 
|  | var output = []; | 
|  | if (this.result) { | 
|  | switch (this.difftype) { | 
|  | case DiffType_ASM: | 
|  | output = this.result.asm || []; | 
|  | break; | 
|  | case DiffType_CompilerStdOut: | 
|  | output = this.result.stdout || []; | 
|  | break; | 
|  | case DiffType_CompilerStdErr: | 
|  | output = this.result.stderr || []; | 
|  | break; | 
|  | case DiffType_ExecStdOut: | 
|  | if (this.result.execResult) | 
|  | output = this.result.execResult.stdout || []; | 
|  | break; | 
|  | case DiffType_ExecStdErr: | 
|  | if (this.result.execResult) | 
|  | output = this.result.execResult.stderr || []; | 
|  | break; | 
|  | } | 
|  | } | 
|  | this.model.setValue(_.pluck(output, 'text').join('\n')); | 
|  | }; | 
|  |  | 
|  | function getItemDisplayTitle(item) { | 
|  | if (typeof item.id === 'string') { | 
|  | var p = item.id.indexOf('_exec'); | 
|  | if (p !== -1) { | 
|  | return 'Executor #' + item.id.substr(0, p); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 'Compiler #' + item.id; | 
|  | } | 
|  |  | 
|  | function Diff(hub, container, state) { | 
|  | this.container = container; | 
|  | this.eventHub = hub.createEventHub(); | 
|  | this.domRoot = container.getElement(); | 
|  | this.domRoot.html($('#diff').html()); | 
|  | this.compilers = {}; | 
|  | var root = this.domRoot.find('.monaco-placeholder'); | 
|  |  | 
|  | this.outputEditor = monaco.editor.createDiffEditor(root[0], { | 
|  | fontFamily: 'Consolas, "Liberation Mono", Courier, monospace', | 
|  | scrollBeyondLastLine: true, | 
|  | readOnly: true, | 
|  | language: 'asm', | 
|  | }); | 
|  |  | 
|  | this.lhs = new State(state.lhs, monaco.editor.createModel('', 'asm'), state.lhsdifftype || DiffType_ASM); | 
|  | this.rhs = new State(state.rhs, monaco.editor.createModel('', 'asm'), state.rhsdifftype || DiffType_ASM); | 
|  | this.outputEditor.setModel({ original: this.lhs.model, modified: this.rhs.model }); | 
|  |  | 
|  | var selectizeType = this.domRoot.find('.difftype-picker').selectize({ | 
|  | sortField: 'name', | 
|  | valueField: 'id', | 
|  | labelField: 'name', | 
|  | searchField: ['name'], | 
|  | options: [ | 
|  | { id: DiffType_ASM, name: 'Assembly' }, | 
|  | { id: DiffType_CompilerStdOut, name: 'Compiler stdout' }, | 
|  | { id: DiffType_CompilerStdErr, name: 'Compiler stderr' }, | 
|  | { id: DiffType_ExecStdOut, name: 'Execution stdout' }, | 
|  | { id: DiffType_ExecStdErr, name: 'Execution stderr' }, | 
|  | ], | 
|  | items: [], | 
|  | render: { | 
|  | option: function (item, escape) { | 
|  | return '<div>' + escape(item.name) + '</div>'; | 
|  | }, | 
|  | }, | 
|  | dropdownParent: 'body', | 
|  | }).on('change', _.bind(function (e) { | 
|  | var target = $(e.target); | 
|  | if (target.hasClass('lhsdifftype')) { | 
|  | this.lhs.difftype = parseInt(target.val()); | 
|  | this.lhs.refresh(); | 
|  | } else { | 
|  | this.rhs.difftype = parseInt(target.val()); | 
|  | this.rhs.refresh(); | 
|  | } | 
|  | this.updateState(); | 
|  | }, this)); | 
|  |  | 
|  | var selectize = this.domRoot.find('.diff-picker').selectize({ | 
|  | sortField: 'name', | 
|  | valueField: 'id', | 
|  | labelField: 'name', | 
|  | searchField: ['name'], | 
|  | options: [], | 
|  | items: [], | 
|  | render: { | 
|  | option: function (item, escape) { | 
|  | return '<div>' + | 
|  | '<span class="compiler">' + escape(item.compiler.name) + '</span>' + | 
|  | '<span class="options">' + escape(item.options) + '</span>' + | 
|  | '<ul class="meta">' + | 
|  | '<li class="editor">Editor #' + escape(item.editorId) + '</li>' + | 
|  | '<li class="compilerId">' + escape(getItemDisplayTitle(item)) + '</li>' + | 
|  | '</ul></div>'; | 
|  | }, | 
|  | }, | 
|  | dropdownParent: 'body', | 
|  | }).on('change', _.bind(function (e) { | 
|  | var target = $(e.target); | 
|  | var compiler = this.compilers[target.val()]; | 
|  | if (!compiler) return; | 
|  | if (target.hasClass('lhs')) { | 
|  | this.lhs.compiler = compiler; | 
|  | this.lhs.id = compiler.id; | 
|  | } else { | 
|  | this.rhs.compiler = compiler; | 
|  | this.rhs.id = compiler.id; | 
|  | } | 
|  | this.onDiffSelect(compiler.id); | 
|  | }, this)); | 
|  |  | 
|  | this.selectize = { | 
|  | lhs: selectize[0].selectize, rhs: selectize[1].selectize, | 
|  | lhsdifftype: selectizeType[0].selectize, rhsdifftype: selectizeType[1].selectize, | 
|  | }; | 
|  |  | 
|  | this.initButtons(state); | 
|  | this.initCallbacks(); | 
|  |  | 
|  | this.updateCompilerNames(); | 
|  | this.updateCompilers(); | 
|  | ga.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'OpenViewPane', | 
|  | eventAction: 'Diff', | 
|  | }); | 
|  | } | 
|  |  | 
|  | // TODO: de-dupe with compiler etc | 
|  | Diff.prototype.resize = function () { | 
|  | var topBarHeight = this.topBar.outerHeight(true); | 
|  | this.outputEditor.layout({ | 
|  | width: this.domRoot.width(), | 
|  | height: this.domRoot.height() - topBarHeight, | 
|  | }); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onDiffSelect = function (id) { | 
|  | this.requestResendResult(id); | 
|  | this.updateCompilerNames(); | 
|  | this.updateState(); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onCompileResult = function (id, compiler, result) { | 
|  | // both sides must be updated, don't be tempted to rewrite this as | 
|  | // var changes = lhs.update() || rhs.update(); | 
|  | var lhsChanged = this.lhs.update(id, compiler, result); | 
|  | var rhsChanged = this.rhs.update(id, compiler, result); | 
|  | if (lhsChanged || rhsChanged) { | 
|  | this.updateCompilerNames(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onExecuteResult = function (id, compiler, result) { | 
|  | var compileResult = _.assign({}, result.buildResult); | 
|  | compileResult.execResult = { | 
|  | code: result.code, | 
|  | stdout: result.stdout, | 
|  | stderr: result.stderr, | 
|  | }; | 
|  |  | 
|  | this.onCompileResult(id + '_exec', compiler, compileResult); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.initButtons = function (state) { | 
|  | this.fontScale = new FontScale(this.domRoot, state, this.outputEditor); | 
|  |  | 
|  | this.topBar = this.domRoot.find('.top-bar'); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.initCallbacks = function () { | 
|  | this.fontScale.on('change', _.bind(this.updateState, this)); | 
|  |  | 
|  | this.eventHub.on('compileResult', this.onCompileResult, this); | 
|  | this.eventHub.on('executeResult', this.onExecuteResult, this); | 
|  | this.eventHub.on('compiler', this.onCompiler, this); | 
|  | this.eventHub.on('compilerClose', this.onCompilerClose, this); | 
|  | this.eventHub.on('executor', this.onExecutor, this); | 
|  | this.eventHub.on('executorClose', this.onExecutorClose, this); | 
|  | this.eventHub.on('settingsChange', this.onSettingsChange, this); | 
|  | this.eventHub.on('themeChange', this.onThemeChange, this); | 
|  | this.container.on('destroy', function () { | 
|  | this.eventHub.unsubscribe(); | 
|  | this.outputEditor.dispose(); | 
|  | }, this); | 
|  | this.container.on('resize', this.resize, this); | 
|  | this.container.on('shown', this.resize, this); | 
|  |  | 
|  | this.requestResendResult(this.lhs.id); | 
|  | this.requestResendResult(this.rhs.id); | 
|  |  | 
|  | this.eventHub.emit('findCompilers'); | 
|  | this.eventHub.emit('findExecutors'); | 
|  |  | 
|  | this.eventHub.emit('requestTheme'); | 
|  | this.eventHub.emit('requestSettings'); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.requestResendResult = function (id) { | 
|  | if (typeof id === 'string') { | 
|  | var p = id.indexOf('_exec'); | 
|  | if (p !== -1) { | 
|  | var execId = parseInt(id.substr(0, p)); | 
|  | this.eventHub.emit('resendExecution', execId); | 
|  | } | 
|  | } else { | 
|  | this.eventHub.emit('resendCompilation', id); | 
|  | } | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onCompiler = function (id, compiler, options, editorId) { | 
|  | if (!compiler) return; | 
|  | options = options || ''; | 
|  | var name = compiler.name + ' ' + options; | 
|  | // TODO: selectize doesn't play nicely with CSS tricks for truncation; this is the best I can do | 
|  | // There's a plugin at: http://www.benbybenjacobs.com/blog/2014/04/09/no-wrap-plugin-for-selectize-dot-js | 
|  | // but it doesn't look easy to integrate. | 
|  | var maxLength = 30; | 
|  | if (name.length > maxLength - 3) name = name.substr(0, maxLength - 3) + '...'; | 
|  | this.compilers[id] = { | 
|  | id: id, | 
|  | name: name, | 
|  | options: options, | 
|  | editorId: editorId, | 
|  | compiler: compiler, | 
|  | }; | 
|  | if (!this.lhs.id) { | 
|  | this.lhs.compiler = this.compilers[id]; | 
|  | this.lhs.id = id; | 
|  | this.onDiffSelect(id); | 
|  | } else if (!this.rhs.id) { | 
|  | this.rhs.compiler = this.compilers[id]; | 
|  | this.rhs.id = id; | 
|  | this.onDiffSelect(id); | 
|  | } | 
|  | this.updateCompilers(); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onExecutor = function (id, compiler, options, editorId) { | 
|  | this.onCompiler(id + '_exec', compiler, options, editorId); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onCompilerClose = function (id) { | 
|  | delete this.compilers[id]; | 
|  | this.updateCompilers(); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onExecutorClose = function (id) { | 
|  | this.onCompilerClose(id + '_exec'); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.updateCompilerNames = function () { | 
|  | var name = 'Diff'; | 
|  | if (this.lhs.compiler && this.rhs.compiler) | 
|  | name += ' ' + this.lhs.compiler.name + ' vs ' + this.rhs.compiler.name; | 
|  | this.container.setTitle(name); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.updateCompilersFor = function (selectize, id) { | 
|  | selectize.clearOptions(); | 
|  | _.each(this.compilers, function (compiler) { | 
|  | selectize.addOption(compiler); | 
|  | }, this); | 
|  | if (this.compilers[id]) { | 
|  | selectize.setValue(id); | 
|  | } | 
|  | }; | 
|  |  | 
|  | Diff.prototype.updateCompilers = function () { | 
|  | this.updateCompilersFor(this.selectize.lhs, this.lhs.id); | 
|  | this.updateCompilersFor(this.selectize.rhs, this.rhs.id); | 
|  |  | 
|  | this.selectize.lhsdifftype.setValue(this.lhs.difftype || DiffType_ASM); | 
|  | this.selectize.rhsdifftype.setValue(this.rhs.difftype || DiffType_ASM); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.updateState = function () { | 
|  | var state = { | 
|  | lhs: this.lhs.id, | 
|  | rhs: this.rhs.id, | 
|  | lhsdifftype: this.lhs.difftype, | 
|  | rhsdifftype: this.rhs.difftype, | 
|  | }; | 
|  | this.fontScale.addState(state); | 
|  | this.container.setState(state); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onThemeChange = function (newTheme) { | 
|  | if (this.outputEditor) | 
|  | this.outputEditor.updateOptions({ theme: newTheme.monaco }); | 
|  | }; | 
|  |  | 
|  | Diff.prototype.onSettingsChange = function (newSettings) { | 
|  | this.outputEditor.updateOptions({ | 
|  | minimap: { | 
|  | enabled: newSettings.showMinimap, | 
|  | }, | 
|  | fontFamily: newSettings.editorsFFont, | 
|  | fontLigatures: newSettings.editorsFLigatures, | 
|  | }); | 
|  | }; | 
|  |  | 
|  | module.exports = { | 
|  | Diff: Diff, | 
|  | getComponent: function (lhs, rhs) { | 
|  | return { | 
|  | type: 'component', | 
|  | componentName: 'diff', | 
|  | componentState: { lhs: lhs, rhs: rhs }, | 
|  | }; | 
|  | }, | 
|  | }; |