| // Copyright (c) 2019, Compiler Explorer Authors |
| // 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 $ = require('jquery'); |
| var _ = require('underscore'); |
| var ga = require('../analytics'); |
| var Toggles = require('../toggles'); |
| var FontScale = require('../fontscale'); |
| var options = require('../options'); |
| var Alert = require('../alert'); |
| var local = require('../local'); |
| var Sentry = require('@sentry/browser'); |
| var Libraries = require('../libs-widget'); |
| var AnsiToHtml = require('../ansi-to-html'); |
| require('../modes/asm-mode'); |
| require('../modes/ptx-mode'); |
| |
| require('selectize'); |
| |
| |
| var languages = options.languages; |
| |
| function makeAnsiToHtml(color) { |
| return new AnsiToHtml({ |
| fg: color ? color : '#333', |
| bg: '#f5f5f5', |
| stream: true, |
| escapeXML: true |
| }); |
| } |
| |
| |
| function Executor(hub, container, state) { |
| this.container = container; |
| this.hub = hub; |
| this.eventHub = hub.createEventHub(); |
| this.compilerService = hub.compilerService; |
| this.domRoot = container.getElement(); |
| this.domRoot.html($('#executor').html()); |
| this.sourceEditorId = state.source || 1; |
| this.settings = JSON.parse(local.get('settings', '{}')); |
| this.initLang(state); |
| this.initCompiler(state); |
| this.infoByLang = {}; |
| this.deferCompiles = hub.deferred; |
| this.needsCompile = false; |
| this.options = state.options || options.compileOptions[this.currentLangId]; |
| this.executionArguments = state.execArgs || ''; |
| this.executionStdin = state.execStdin || ''; |
| this.source = ''; |
| this.lastResult = {}; |
| this.pendingRequestSentAt = 0; |
| this.nextRequest = null; |
| |
| this.alertSystem = new Alert(); |
| this.alertSystem.prefixMessage = "Executor #" + this.id + ": "; |
| |
| this.normalAnsiToHtml = makeAnsiToHtml(); |
| this.errorAnsiToHtml = makeAnsiToHtml('red'); |
| |
| this.initButtons(state); |
| |
| this.fontScale = new FontScale(this.domRoot, state, 'pre.content'); |
| |
| this.compilerPicker.selectize({ |
| sortField: [ |
| {field: '$order'}, |
| {field: '$score'}, |
| {field: 'name'} |
| ], |
| valueField: 'id', |
| labelField: 'name', |
| searchField: ['name'], |
| optgroupField: 'group', |
| optgroups: this.getGroupsInUse(), |
| lockOptgroupOrder: true, |
| options: _.map(this.getCurrentLangCompilers(), _.identity), |
| items: this.compiler ? [this.compiler.id] : [], |
| dropdownParent: 'body', |
| closeAfterSelect: true |
| }).on('change', _.bind(function (e) { |
| var val = $(e.target).val(); |
| if (val) { |
| ga.proxy('send', { |
| hitType: 'event', |
| eventCategory: 'SelectCompiler', |
| eventAction: val |
| }); |
| this.onCompilerChange(val); |
| } |
| }, this)); |
| |
| this.compilerSelecrizer = this.compilerPicker[0].selectize; |
| |
| this.initLibraries(state); |
| |
| |
| this.initCallbacks(); |
| // Handle initial settings |
| this.onSettingsChange(this.settings); |
| this.updateCompilerInfo(); |
| this.saveState(); |
| ga.proxy('send', { |
| hitType: 'event', |
| eventCategory: 'OpenViewPane', |
| eventAction: 'Executor' |
| }); |
| } |
| |
| Executor.prototype.getGroupsInUse = function () { |
| return _.chain(this.getCurrentLangCompilers()) |
| .map() |
| .uniq(false, function (compiler) { |
| return compiler.group; |
| }) |
| .map(function (compiler) { |
| return {value: compiler.group, label: compiler.groupName || compiler.group}; |
| }) |
| .sortBy('label') |
| .value(); |
| }; |
| |
| Executor.prototype.close = function () { |
| this.eventHub.unsubscribe(); |
| this.eventHub.emit('executorClose', this.id); |
| }; |
| |
| Executor.prototype.undefer = function () { |
| this.deferCompiles = false; |
| if (this.needsCompile) this.compile(); |
| }; |
| |
| Executor.prototype.updateAndCalcTopBarHeight = function () { |
| // If we save vertical space by hiding stuff that's OK to hide |
| // when thin, then hide that stuff. |
| this.hideable.show(); |
| var topBarHeightMax = this.topBar.outerHeight(true); |
| this.hideable.hide(); |
| var topBarHeightMin = this.topBar.outerHeight(true); |
| var topBarHeight = topBarHeightMin; |
| if (topBarHeightMin === topBarHeightMax) { |
| this.hideable.show(); |
| } |
| |
| if (!this.panelCompilation.hasClass('d-none')) { |
| topBarHeight += this.panelCompilation.outerHeight(true); |
| } |
| if (!this.panelArgs.hasClass('d-none')) { |
| topBarHeight += this.panelArgs.outerHeight(true); |
| } |
| if (!this.panelStdin.hasClass('d-none')) { |
| topBarHeight += this.panelStdin.outerHeight(true); |
| } |
| |
| return topBarHeight; |
| }; |
| |
| Executor.prototype.resize = function () { |
| var topBarHeight = this.updateAndCalcTopBarHeight(); |
| var bottomBarHeight = this.bottomBar.outerHeight(true); |
| this.outputContentRoot.outerHeight(this.domRoot.height() - topBarHeight - bottomBarHeight); |
| |
| }; |
| |
| Executor.prototype.initLang = function (state) { |
| // If we don't have a language, but a compiler, find the corresponding language. |
| this.currentLangId = state.lang; |
| if (!this.currentLangId && state.compiler) { |
| this.currentLangId = this.langOfCompiler(state.compiler); |
| } |
| if (!this.currentLangId && languages[this.settings.defaultLanguage]) { |
| this.currentLangId = languages[this.settings.defaultLanguage].id; |
| } |
| if (!this.currentLangId) { |
| this.currentLangId = _.keys(languages)[0]; |
| } |
| }; |
| |
| Executor.prototype.initCompiler = function (state) { |
| this.originalCompilerId = state.compiler; |
| if (state.compiler) |
| this.compiler = this.findCompiler(this.currentLangId, state.compiler); |
| else |
| this.compiler = this.findCompiler(this.currentLangId, options.defaultCompiler[this.currentLangId]); |
| if (!this.compiler) { |
| var compilers = this.compilerService.compilersByLang[this.currentLangId]; |
| if (compilers) this.compiler = _.values(compilers)[0]; |
| } |
| }; |
| |
| function errorResult(message) { |
| return {code: -1, stderr: message}; |
| } |
| |
| Executor.prototype.compile = function (bypassCache) { |
| if (this.deferCompiles) { |
| this.needsCompile = true; |
| return; |
| } |
| this.needsCompile = false; |
| this.compileTimeLabel.text(' - Compiling...'); |
| var options = { |
| userArguments: this.options, |
| executeParameters: { |
| args: this.executionArguments, |
| stdin: this.executionStdin |
| }, |
| compilerOptions: { |
| executorRequest: true |
| }, |
| filters: {execute: true}, |
| tools: [] |
| }; |
| var includeFlag = this.compiler ? this.compiler.includeFlag : '-I'; |
| _.each(this.libsWidget.getLibsInUse(), function (item) { |
| _.each(item.path, function (path) { |
| options.userArguments += ' ' + includeFlag + path; |
| }); |
| }); |
| this.compilerService.expand(this.source).then(_.bind(function (expanded) { |
| var request = { |
| source: expanded || '', |
| compiler: this.compiler ? this.compiler.id : '', |
| options: options, |
| lang: this.currentLangId |
| }; |
| if (bypassCache) request.bypassCache = true; |
| if (!this.compiler) { |
| this.onCompileResponse(request, errorResult('<Please select a compiler>'), false); |
| } else { |
| this.sendCompile(request); |
| } |
| }, this)); |
| }; |
| |
| Executor.prototype.sendCompile = function (request) { |
| var onCompilerResponse = _.bind(this.onCompileResponse, this); |
| |
| if (this.pendingRequestSentAt) { |
| // If we have a request pending, then just store this request to do once the |
| // previous request completes. |
| this.nextRequest = request; |
| return; |
| } |
| // this.eventHub.emit('compiling', this.id, this.compiler); |
| // Display the spinner |
| this.handleCompilationStatus({code: 4}); |
| this.pendingRequestSentAt = Date.now(); |
| // After a short delay, give the user some indication that we're working on their |
| // compilation. |
| this.compilerService.submit(request) |
| .then(function (x) { |
| onCompilerResponse(request, x.result, x.localCacheHit); |
| }) |
| .catch(function (x) { |
| var message = "Unknown error"; |
| if (_.isString(x)) { |
| message = x; |
| } else if (x) { |
| message = x.error || x.code; |
| } |
| onCompilerResponse(request, errorResult(message), false); |
| }); |
| }; |
| |
| Executor.prototype.addCompilerOutputLine = function (msg, container, lineNum, column) { |
| var elem = $('<p></p>').appendTo(container); |
| if (lineNum) { |
| elem.html( |
| $('<span class="linked-compiler-output-line"></span>') |
| .html(msg) |
| .click(_.bind(function (e) { |
| this.eventHub.emit('editorLinkLine', this.sourceEditorId, lineNum, column, true); |
| // do not bring user to the top of index.html |
| // http://stackoverflow.com/questions/3252730 |
| e.preventDefault(); |
| return false; |
| }, this)) |
| .on('mouseover', _.bind(function () { |
| this.eventHub.emit('editorLinkLine', this.sourceEditorId, lineNum, column, false); |
| }, this)) |
| ); |
| } else { |
| elem.html(msg); |
| } |
| }; |
| |
| Executor.prototype.clearPreviousOutput = function () { |
| this.executionStatusSection.empty(); |
| this.compilerOutputSection.empty(); |
| this.executionOutputSection.empty(); |
| }; |
| |
| Executor.prototype.onCompileResponse = function (request, result, cached) { |
| // Save which source produced this change. It should probably be saved earlier though |
| result.source = this.source; |
| this.lastResult = result; |
| var timeTaken = Math.max(0, Date.now() - this.pendingRequestSentAt); |
| var wasRealReply = this.pendingRequestSentAt > 0; |
| this.pendingRequestSentAt = 0; |
| ga.proxy('send', { |
| hitType: 'event', |
| eventCategory: 'Compile', |
| eventAction: request.compiler, |
| eventLabel: request.options.userArguments, |
| eventValue: cached ? 1 : 0 |
| }); |
| ga.proxy('send', { |
| hitType: 'timing', |
| timingCategory: 'Compile', |
| timingVar: request.compiler, |
| timingValue: timeTaken |
| }); |
| |
| this.clearPreviousOutput(); |
| var compileStdout = result.buildResult.stdout || []; |
| var compileStderr = result.buildResult.stderr || []; |
| var execStdout = result.stdout || []; |
| var execStderr = result.stderr || []; |
| if (!result.didExecute) { |
| this.executionStatusSection.append($('<p></p>').text('Could not execute the program')); |
| this.executionStatusSection.append($('<p></p>').text('Compiler returned: ' + result.buildResult.code)); |
| } |
| if (compileStdout.length > 0) { |
| this.compilerOutputSection.append($('<p></p>').text('Compiler stdout')); |
| var outElem = $('<pre class="card"></pre>').appendTo(this.compilerOutputSection); |
| _.each(compileStdout, function (obj) { |
| if (obj.test === "") { |
| this.addCompilerOutputLine("<br/>", outElem); |
| } else { |
| var lineNumber = obj.tag ? obj.tag.line : obj.line; |
| var columnNumber = obj.tag ? obj.tag.column : -1; |
| this.addCompilerOutputLine(this.normalAnsiToHtml.toHtml(obj.text), outElem, lineNumber, columnNumber); |
| } |
| }, this); |
| } |
| if (compileStderr.length > 0) { |
| this.compilerOutputSection.append($('<p></p>').text('Compiler stderr')); |
| var errElem = $('<pre class="card"></pre>').appendTo(this.compilerOutputSection); |
| _.each(compileStderr, function (obj) { |
| if (obj.test === "") { |
| this.addCompilerOutputLine("<br/>", errElem); |
| } else { |
| var lineNumber = obj.tag ? obj.tag.line : obj.line; |
| var columnNumber = obj.tag ? obj.tag.column : -1; |
| this.addCompilerOutputLine(this.errorAnsiToHtml.toHtml(obj.text), errElem, lineNumber, columnNumber); |
| } |
| }, this); |
| } |
| if (result.didExecute) { |
| this.executionOutputSection.append($('<p></p>').text('Program returned: ' + result.code)); |
| if (execStdout.length > 0) { |
| this.outputContentRoot.append($('<p></p>').text('Program stdout')); |
| $('<pre class="card execution-stdout"></pre>') |
| .text(_.pluck(execStdout, 'text').join('\n')) |
| .appendTo(this.executionOutputSection); |
| } |
| if (execStderr.length > 0) { |
| this.executionOutputSection.append($('<p></p>').text('Program stderr')); |
| $('<pre class="card"></pre>') |
| .text(_.pluck(execStderr, 'text').join('\n')) |
| .css({color: 'red'}) |
| .appendTo(this.executionOutputSection); |
| } |
| } |
| |
| |
| this.handleCompilationStatus({code: 1, didExecute: result.didExecute}); |
| var timeLabelText = ''; |
| if (cached) { |
| timeLabelText = ' - cached'; |
| } else if (wasRealReply) { |
| timeLabelText = ' - ' + timeTaken + 'ms'; |
| } |
| this.compileTimeLabel.text(timeLabelText); |
| |
| this.setCompilationOptionsPopover(result.compilationOptions ? result.compilationOptions.join(' ') : ''); |
| |
| this.eventHub.emit('compileResult', this.id, this.compiler, result.buildResult, languages[this.currentLangId]); |
| |
| if (this.nextRequest) { |
| var next = this.nextRequest; |
| this.nextRequest = null; |
| this.sendCompile(next); |
| } |
| }; |
| |
| |
| Executor.prototype.onEditorChange = function (editor, source, langId, compilerId) { |
| if (editor === this.sourceEditorId && langId === this.currentLangId && |
| (compilerId === undefined || compilerId === this.id)) { |
| this.source = source; |
| if (this.settings.compileOnChange) { |
| this.compile(); |
| } |
| } |
| }; |
| |
| Executor.prototype.initButtons = function (state) { |
| this.filters = new Toggles(this.domRoot.find('.filters'), state.filters); |
| |
| this.compileClearCache = this.domRoot.find('.clear-cache'); |
| this.outputContentRoot = this.domRoot.find('pre.content'); |
| this.executionStatusSection = this.outputContentRoot.find('.execution-status'); |
| this.compilerOutputSection = this.outputContentRoot.find('.compiler-output'); |
| this.executionOutputSection = this.outputContentRoot.find('.execution-output'); |
| |
| this.optionsField = this.domRoot.find('.compilation-options'); |
| this.execArgsField = this.domRoot.find('.execution-arguments'); |
| this.execStdinField = this.domRoot.find('.execution-stdin'); |
| this.prependOptions = this.domRoot.find('.prepend-options'); |
| this.fullCompilerName = this.domRoot.find('.full-compiler-name'); |
| this.setCompilationOptionsPopover(this.compiler ? this.compiler.options : null); |
| |
| this.compileTimeLabel = this.domRoot.find('.compile-time'); |
| this.libsButton = this.domRoot.find('.btn.show-libs'); |
| |
| // Dismiss on any click that isn't either in the opening element, inside |
| // the popover or on any alert |
| $(document).on('mouseup', _.bind(function (e) { |
| var target = $(e.target); |
| if (!target.is(this.prependOptions) && this.prependOptions.has(target).length === 0 && |
| target.closest('.popover').length === 0) |
| this.prependOptions.popover("hide"); |
| |
| if (!target.is(this.fullCompilerName) && this.fullCompilerName.has(target).length === 0 && |
| target.closest('.popover').length === 0) |
| this.fullCompilerName.popover("hide"); |
| }, this)); |
| |
| this.optionsField.val(this.options); |
| this.execArgsField.val(this.executionArguments); |
| this.execStdinField.val(this.executionStdin); |
| |
| this.shortCompilerName = this.domRoot.find('.short-compiler-name'); |
| this.compilerPicker = this.domRoot.find('.compiler-picker'); |
| this.setCompilerVersionPopover(''); |
| |
| this.topBar = this.domRoot.find('.top-bar'); |
| this.bottomBar = this.domRoot.find('.bottom-bar'); |
| this.statusLabel = this.domRoot.find('.status-text'); |
| |
| this.hideable = this.domRoot.find('.hideable'); |
| this.statusIcon = this.domRoot.find('.status-icon'); |
| |
| this.panelCompilation = this.domRoot.find('.panel-compilation'); |
| this.panelArgs = this.domRoot.find('.panel-args'); |
| this.panelStdin = this.domRoot.find('.panel-stdin'); |
| |
| this.initToggleButtons(state); |
| }; |
| |
| Executor.prototype.initToggleButtons = function (state) { |
| this.toggleCompilation = this.domRoot.find('.toggle-compilation'); |
| this.toggleArgs = this.domRoot.find('.toggle-args'); |
| this.toggleStdin = this.domRoot.find('.toggle-stdin'); |
| this.toggleCompilerOut = this.domRoot.find('.toggle-compilerout'); |
| |
| if (state.compilationPanelShown === false) { |
| this.hidePanel(this.toggleCompilation, this.panelCompilation); |
| } |
| |
| if (state.argsPanelShown === true) { |
| this.showPanel(this.toggleArgs, this.panelArgs); |
| } |
| |
| if (state.stdinPanelShown === true) { |
| this.showPanel(this.toggleStdin, this.panelStdin); |
| } |
| |
| if (state.compilerOutShown === false) { |
| this.hidePanel(this.toggleCompilerOut, this.compilerOutputSection); |
| } |
| }; |
| |
| Executor.prototype.onLibsChanged = function () { |
| this.saveState(); |
| this.compile(); |
| }; |
| |
| Executor.prototype.initLibraries = function (state) { |
| this.libsWidget = new Libraries.Widget(this.currentLangId, this.libsButton, |
| state, _.bind(this.onLibsChanged, this)); |
| }; |
| |
| Executor.prototype.onFontScale = function () { |
| this.saveState(); |
| }; |
| |
| Executor.prototype.initListeners = function () { |
| // this.filters.on('change', _.bind(this.onFilterChange, this)); |
| this.fontScale.on('change', _.bind(this.onFontScale, this)); |
| |
| this.container.on('destroy', this.close, this); |
| this.container.on('resize', this.resize, this); |
| this.container.on('shown', this.resize, this); |
| this.container.on('open', function () { |
| this.eventHub.emit('compilerOpen', this.id, this.sourceEditorId); |
| }, this); |
| this.eventHub.on('editorChange', this.onEditorChange, this); |
| this.eventHub.on('editorClose', this.onEditorClose, this); |
| this.eventHub.on('settingsChange', this.onSettingsChange, this); |
| this.eventHub.on('requestCompilation', this.onRequestCompilation, this); |
| this.eventHub.on('resize', this.resize, this); |
| |
| this.eventHub.on('languageChange', this.onLanguageChange, this); |
| }; |
| |
| Executor.prototype.showPanel = function (button, panel) { |
| panel.removeClass('d-none'); |
| button.addClass('active'); |
| this.resize(); |
| }; |
| |
| Executor.prototype.hidePanel = function (button, panel) { |
| panel.addClass('d-none'); |
| button.removeClass('active'); |
| this.resize(); |
| }; |
| |
| Executor.prototype.togglePanel = function (button, panel) { |
| if (panel.hasClass('d-none')) { |
| this.showPanel(button, panel); |
| } else { |
| this.hidePanel(button, panel); |
| } |
| this.saveState(); |
| }; |
| |
| Executor.prototype.initCallbacks = function () { |
| this.initListeners(); |
| |
| var optionsChange = _.debounce(_.bind(function (e) { |
| this.onOptionsChange($(e.target).val()); |
| }, this), 800); |
| |
| var execArgsChange = _.debounce(_.bind(function (e) { |
| this.onExecArgsChange($(e.target).val()); |
| }, this), 800); |
| |
| var execStdinChange = _.debounce(_.bind(function (e) { |
| this.onExecStdinChange($(e.target).val()); |
| }, this), 800); |
| |
| this.optionsField |
| .on('change', optionsChange) |
| .on('keyup', optionsChange); |
| |
| this.execArgsField |
| .on('change', execArgsChange) |
| .on('keyup', execArgsChange); |
| |
| this.execStdinField |
| .on('change', execStdinChange) |
| .on('keyup', execStdinChange); |
| |
| this.compileClearCache.on('click', _.bind(function () { |
| this.compilerService.cache.reset(); |
| this.compile(true); |
| }, this)); |
| |
| // Dismiss the popover on escape. |
| $(document).on('keyup.editable', _.bind(function (e) { |
| if (e.which === 27) { |
| this.libsButton.popover('hide'); |
| } |
| }, this)); |
| |
| this.toggleCompilation.on('click', _.bind(function () { |
| this.togglePanel(this.toggleCompilation, this.panelCompilation); |
| }, this)); |
| |
| this.toggleArgs.on('click', _.bind(function () { |
| this.togglePanel(this.toggleArgs, this.panelArgs); |
| }, this)); |
| |
| this.toggleStdin.on('click', _.bind(function () { |
| this.togglePanel(this.toggleStdin, this.panelStdin); |
| }, this)); |
| |
| this.toggleCompilerOut.on('click', _.bind(function () { |
| this.togglePanel(this.toggleCompilerOut, this.compilerOutputSection); |
| }, this)); |
| |
| // Dismiss on any click that isn't either in the opening element, inside |
| // the popover or on any alert |
| $(document).on('click', _.bind(function (e) { |
| var elem = this.libsButton; |
| var target = $(e.target); |
| if (!target.is(elem) && elem.has(target).length === 0 && target.closest('.popover').length === 0) { |
| elem.popover('hide'); |
| } |
| }, this)); |
| |
| this.eventHub.on('initialised', this.undefer, this); |
| }; |
| |
| Executor.prototype.onOptionsChange = function (options) { |
| this.options = options; |
| this.saveState(); |
| this.compile(); |
| }; |
| |
| Executor.prototype.onExecArgsChange = function (args) { |
| this.executionArguments = args; |
| this.saveState(); |
| this.compile(); |
| }; |
| |
| Executor.prototype.onExecStdinChange = function (newStdin) { |
| this.executionStdin = newStdin; |
| this.saveState(); |
| this.compile(); |
| }; |
| |
| Executor.prototype.onRequestCompilation = function (editorId) { |
| if (editorId === this.sourceEditorId) { |
| this.compile(); |
| } |
| }; |
| |
| Executor.prototype.updateCompilerInfo = function () { |
| this.updateCompilerName(); |
| if (this.compiler) { |
| if (this.compiler.notification) { |
| this.alertSystem.notify(this.compiler.notification, { |
| group: 'compilerwarning', |
| alertClass: 'notification-info', |
| dismissTime: 5000 |
| }); |
| } |
| this.prependOptions.data('content', this.compiler.options); |
| } |
| }; |
| |
| Executor.prototype.updateCompilerUI = function () { |
| this.updateCompilerInfo(); |
| // Resize in case the new compiler name is too big |
| this.resize(); |
| }; |
| |
| Executor.prototype.onCompilerChange = function (value) { |
| this.compiler = this.compilerService.findCompiler(this.currentLangId, value); |
| this.saveState(); |
| this.compile(); |
| this.updateCompilerUI(); |
| //this.sendCompiler(); |
| }; |
| |
| Executor.prototype.onEditorClose = function (editor) { |
| if (editor === this.sourceEditorId) { |
| // We can't immediately close as an outer loop somewhere in GoldenLayout is iterating over |
| // the hierarchy. We can't modify while it's being iterated over. |
| this.close(); |
| _.defer(function (self) { |
| self.container.close(); |
| }, this); |
| } |
| }; |
| |
| Executor.prototype.currentState = function () { |
| var state = { |
| compiler: this.compiler ? this.compiler.id : '', |
| source: this.sourceEditorId, |
| options: this.options, |
| execArgs: this.executionArguments, |
| execStdin: this.executionStdin, |
| libs: this.libsWidget.get(), |
| lang: this.currentLangId, |
| compilationPanelShown: !this.panelCompilation.hasClass('d-none'), |
| compilerOutShown: !this.compilerOutputSection.hasClass('d-none'), |
| argsPanelShown: !this.panelArgs.hasClass('d-none'), |
| stdinPanelShown: !this.panelStdin.hasClass('d-none') |
| }; |
| this.fontScale.addState(state); |
| return state; |
| }; |
| |
| Executor.prototype.saveState = function () { |
| this.container.setState(this.currentState()); |
| }; |
| |
| Executor.prototype.getCompilerName = function () { |
| return this.compiler ? this.compiler.name : 'No compiler set'; |
| }; |
| |
| |
| Executor.prototype.getLanguageName = function () { |
| var lang = options.languages[this.currentLangId]; |
| return lang ? lang.name : '?'; |
| }; |
| |
| Executor.prototype.getPaneName = function () { |
| var langName = this.getLanguageName(); |
| var compName = this.getCompilerName(); |
| return compName + ' Executor (Editor #' + this.sourceEditorId + ') ' + langName; |
| }; |
| |
| Executor.prototype.updateCompilerName = function () { |
| var compilerName = this.getCompilerName(); |
| var compilerVersion = this.compiler ? this.compiler.version : ''; |
| this.container.setTitle(this.getPaneName()); |
| this.shortCompilerName.text(compilerName); |
| this.setCompilerVersionPopover(compilerVersion); |
| }; |
| |
| Executor.prototype.setCompilationOptionsPopover = function (content) { |
| this.prependOptions.popover('dispose'); |
| this.prependOptions.popover({ |
| content: content || 'No options in use', |
| template: '<div class="popover' + |
| (content ? ' compiler-options-popover' : '') + |
| '" role="tooltip"><div class="arrow"></div>' + |
| '<h3 class="popover-header"></h3><div class="popover-body"></div></div>' |
| }); |
| }; |
| |
| Executor.prototype.setCompilerVersionPopover = function (version) { |
| this.fullCompilerName.popover('dispose'); |
| this.fullCompilerName.popover({ |
| content: version || '', |
| template: '<div class="popover' + |
| (version ? ' compiler-options-popover' : '') + |
| '" role="tooltip"><div class="arrow"></div>' + |
| '<h3 class="popover-header"></h3><div class="popover-body"></div></div>' |
| }); |
| }; |
| |
| Executor.prototype.onSettingsChange = function (newSettings) { |
| this.settings = _.clone(newSettings); |
| }; |
| |
| Executor.prototype.handleCompilationStatus = function (status) { |
| if (!this.statusLabel || !this.statusIcon) return; |
| |
| function ariaLabel() { |
| // Compiling... |
| if (status.code === 4) return "Compiling"; |
| if (status.didExecute) { |
| return "Program compiled & executed"; |
| } else { |
| return "Program could not be executed"; |
| } |
| } |
| |
| function color() { |
| // Compiling... |
| if (status.code === 4) return "black"; |
| if (status.didExecute) return "#12BB12"; |
| return "#FF1212"; |
| } |
| |
| this.statusIcon |
| .removeClass() |
| .addClass('status-icon fas') |
| .css('color', color()) |
| .toggle(status.code !== 0) |
| .prop('aria-label', ariaLabel()) |
| .prop('data-status', status.code) |
| .toggleClass('fa-spinner', status.code === 4) |
| .toggleClass('fa-times-circle', status.code !== 4 && !status.didExecute) |
| .toggleClass('fa-check-circle', status.code !== 4 && status.didExecute); |
| }; |
| |
| Executor.prototype.onLanguageChange = function (editorId, newLangId) { |
| if (this.sourceEditorId === editorId) { |
| var oldLangId = this.currentLangId; |
| this.currentLangId = newLangId; |
| // Store the current selected stuff to come back to it later in the same session (Not state stored!) |
| this.infoByLang[oldLangId] = { |
| compiler: this.compiler && this.compiler.id ? this.compiler.id : options.defaultCompiler[oldLangId], |
| options: this.options, |
| execArgs: this.executionArguments, |
| execStdin: this.executionStdin |
| }; |
| this.libsWidget.setNewLangId(newLangId); |
| this.updateCompilersSelector(); |
| this.updateCompilerUI(); |
| //this.sendCompiler(); |
| this.saveState(); |
| } |
| }; |
| |
| Executor.prototype.getCurrentLangCompilers = function () { |
| return this.compilerService.getCompilersForLang(this.currentLangId); |
| }; |
| |
| Executor.prototype.updateCompilersSelector = function () { |
| this.compilerSelecrizer.clearOptions(true); |
| this.compilerSelecrizer.clearOptionGroups(); |
| _.each(this.getGroupsInUse(), function (group) { |
| this.compilerSelecrizer.addOptionGroup(group.value, {label: group.label}); |
| }, this); |
| this.compilerSelecrizer.load(_.bind(function (callback) { |
| callback(_.map(this.getCurrentLangCompilers(), _.identity)); |
| }, this)); |
| var defaultOrFirst = _.bind(function () { |
| // If the default is a valid compiler, return it |
| var defaultCompiler = options.defaultCompiler[this.currentLangId]; |
| if (defaultCompiler) return defaultCompiler; |
| // Else try to find the first one for this language |
| var value = _.find(options.compilers, _.bind(function (compiler) { |
| return compiler.lang === this.currentLangId; |
| }, this)); |
| |
| // Return the first, or an empty string if none found (Should prob report this one...) |
| return value && value.id ? value.id : ""; |
| }, this); |
| var info = this.infoByLang[this.currentLangId] || {}; |
| this.compiler = this.findCompiler(this.currentLangId, info.compiler || defaultOrFirst()); |
| this.compilerSelecrizer.setValue([this.compiler ? this.compiler.id : null], true); |
| this.options = info.options || ''; |
| this.optionsField.val(this.options); |
| this.executionArguments = info.execArgs || ''; |
| this.execArgsField.val(this.executionArguments); |
| this.executionStdin = info.execStdin || ''; |
| this.execStdinField.val(this.executionStdin); |
| }; |
| |
| Executor.prototype.findCompiler = function (langId, compilerId) { |
| return this.compilerService.findCompiler(langId, compilerId); |
| }; |
| |
| Executor.prototype.langOfCompiler = function (compilerId) { |
| var compiler = _.find(options.compilers, function (compiler) { |
| return compiler.id === compilerId || compiler.alias === compilerId; |
| }); |
| if (!compiler) { |
| Sentry.captureMessage('Unable to find compiler id "' + compilerId + '"'); |
| compiler = options.compilers[0]; |
| } |
| return compiler.lang; |
| }; |
| |
| module.exports = { |
| Executor: Executor |
| }; |