diff options
author | Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> | 2022-04-12 18:18:07 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-13 00:18:07 +0200 |
commit | c4415586aae5639efe448b3e1381b0cd91413519 (patch) | |
tree | 952bed500345e7906159ce5c25a817f46f03ceb7 | |
parent | d0c0791bd0c31361c237b98f56a804971dab4397 (diff) | |
download | compiler-explorer-gh-2318.tar.gz compiler-explorer-gh-2318.zip |
Output pane ts conversion (#3495)gh-2318
* Converted output pane to ts
* Updated copyright year
* Remove jquery import
* Quick refactor
-rw-r--r-- | static/panes/output.interfaces.ts | 27 | ||||
-rw-r--r-- | static/panes/output.js | 280 | ||||
-rw-r--r-- | static/panes/output.ts | 275 | ||||
-rw-r--r-- | static/widgets/fontscale.ts | 6 |
4 files changed, 305 insertions, 283 deletions
diff --git a/static/panes/output.interfaces.ts b/static/panes/output.interfaces.ts new file mode 100644 index 000000000..a1fa98132 --- /dev/null +++ b/static/panes/output.interfaces.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2022, 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. + +export interface OutputState { + wrap: boolean; +} diff --git a/static/panes/output.js b/static/panes/output.js deleted file mode 100644 index 279689d0a..000000000 --- a/static/panes/output.js +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2016, 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('underscore'); -var $ = require('jquery'); -var FontScale = require('../widgets/fontscale').FontScale; -var AnsiToHtml = require('../ansi-to-html').Filter; -var Toggles = require('../widgets/toggles').Toggles; -var ga = require('../analytics').ga; -var PaneRenaming = require('../widgets/pane-renaming').PaneRenaming; - -function makeAnsiToHtml(color) { - return new AnsiToHtml({ - fg: color ? color : '#333', - bg: '#f5f5f5', - stream: true, - escapeXML: true, - }); -} - -function Output(hub, container, state) { - this.container = container; - this.compilerId = state.compiler; - this.editorId = state.editor; - this.treeId = state.tree; - this.hub = hub; - this.eventHub = hub.createEventHub(); - this.domRoot = container.getElement(); - this.domRoot.html($('#compiler-output').html()); - this.contentRoot = this.domRoot.find('.content'); - this.optionsToolbar = this.domRoot.find('.options-toolbar'); - this.compilerName = ''; - this.fontScale = new FontScale(this.domRoot, state, '.content'); - this.fontScale.on('change', _.bind(function () { - this.saveState(); - }, this)); - this.normalAnsiToHtml = makeAnsiToHtml(); - this.errorAnsiToHtml = makeAnsiToHtml('red'); - - this.paneRenaming = new PaneRenaming(this, state); - - this.initButtons(); - this.initCallbacks(state); - - this.onOptionsChange(); - ga.proxy('send', { - hitType: 'event', - eventCategory: 'OpenViewPane', - eventAction: 'Output', - }); -} - -Output.prototype.initCallbacks = function (state) { - this.options = new Toggles(this.domRoot.find('.options'), state); - this.options.on('change', _.bind(this.onOptionsChange, this)); - - this.paneRenaming.on('renamePane', this.saveState.bind(this)); - - this.container.on('resize', this.resize, this); - this.container.on('shown', this.resize, this); - this.container.on('destroy', this.close, this); - - this.eventHub.on('compiling', this.onCompiling, this); - this.eventHub.on('compileResult', this.onCompileResult, this); - this.eventHub.on('compilerClose', this.onCompilerClose, this); - this.eventHub.emit('outputOpened', this.compilerId); -}; - -Output.prototype.getEffectiveOptions = function () { - return this.options.get(); -}; - -Output.prototype.resize = function () { - this.contentRoot.height(this.domRoot.height() - this.optionsToolbar.height() - 5); -}; - -Output.prototype.onOptionsChange = function () { - var options = this.getEffectiveOptions(); - this.contentRoot.toggleClass('wrap', options.wrap); - this.wrapButton.prop('title', '[' + (options.wrap ? 'ON' : 'OFF') + '] ' + this.wrapTitle); - - this.saveState(); -}; - -Output.prototype.initButtons = function () { - this.wrapButton = this.domRoot.find('.wrap-lines'); - this.wrapTitle = this.wrapButton.prop('title'); -}; - -Output.prototype.currentState = function () { - var options = this.getEffectiveOptions(); - var state = { - compiler: this.compilerId, - editor: this.editorId, - tree: this.treeId, - wrap: options.wrap, - }; - this.paneRenaming.addState(state); - this.fontScale.addState(state); - return state; -}; - -Output.prototype.saveState = function () { - this.container.setState(this.currentState()); -}; - -Output.prototype.addOutputLines = function (result) { - _.each((result.stdout || []).concat(result.stderr || []), function (obj) { - var lineNumber = obj.tag ? obj.tag.line : obj.line; - var columnNumber = obj.tag ? obj.tag.column : -1; - if (obj.text === '') { - this.add('<br/>'); - } else { - this.add(this.normalAnsiToHtml.toHtml(obj.text), lineNumber, columnNumber, obj.tag ? obj.tag.file : false); - } - }, this); -}; - -Output.prototype.onCompiling = function (compilerId) { - if (this.compilerId === compilerId) { - this.setCompileStatus(true); - } -}; - -Output.prototype.onCompileResult = function (id, compiler, result) { - if (id !== this.compilerId) return; - if (compiler) this.compilerName = compiler.name; - - this.contentRoot.empty(); - - if (result.buildsteps) { - _.each(result.buildsteps, _.bind(function (step) { - this.add('Step ' + step.step + ' returned: ' + step.code); - this.addOutputLines(step); - }, this)); - } else { - this.addOutputLines(result); - if (!result.execResult) { - this.add('Compiler returned: ' + result.code); - } else { - this.add('ASM generation compiler returned: ' + result.code); - this.addOutputLines(result.execResult.buildResult); - this.add('Execution build compiler returned: ' + result.execResult.buildResult.code); - } - } - - if (result.execResult && (result.execResult.didExecute || result.didExecute)) { - this.add('Program returned: ' + result.execResult.code); - if (result.execResult.stderr.length || result.execResult.stdout.length) { - _.each(result.execResult.stderr, function (obj) { - // Conserve empty lines as they are discarded by ansiToHtml - if (obj.text === '') { - this.programOutput('<br/>'); - } else { - this.programOutput(this.errorAnsiToHtml.toHtml(obj.text), 'red'); - } - }, this); - - _.each(result.execResult.stdout, function (obj) { - // Conserve empty lines as they are discarded by ansiToHtml - if (obj.text === '') { - this.programOutput('<br/>'); - } else { - this.programOutput(this.normalAnsiToHtml.toHtml(obj.text)); - } - }, this); - } - } - this.setCompileStatus(false); - this.updateTitle(); -}; - -Output.prototype.programOutput = function (msg, color) { - var elem = $('<div/>').appendTo(this.contentRoot) - .html(msg) - .addClass('program-exec-output'); - - if (color) - elem.css('color', color); -}; - -Output.prototype.getEditorIdByFilename = function (filename) { - var tree = this.hub.getTreeById(this.treeId); - if (tree) { - return tree.multifileService.getEditorIdByFilename(filename); - } - return false; -}; - -Output.prototype.emitEditorLinkLine = function (lineNum, column, filename, goto) { - if (this.editorId) { - this.eventHub.emit('editorLinkLine', this.editorId, lineNum, column, column + 1, goto); - } else if (filename) { - var editorId = this.getEditorIdByFilename(filename); - if (editorId) { - this.eventHub.emit('editorLinkLine', editorId, lineNum, column, column + 1, goto); - } - } -}; - -Output.prototype.add = function (msg, lineNum, column, filename) { - var elem = $('<div/>').appendTo(this.contentRoot); - if (lineNum) { - elem.html( - $('<span class="linked-compiler-output-line"></span>') - .html(msg) - .on('click', _.bind(function (e) { - this.emitEditorLinkLine(lineNum, column, filename, 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.emitEditorLinkLine(lineNum, column, filename, false); - }, this)) - ); - } else { - elem.html(msg); - } -}; - -Output.prototype.getPaneName = function () { - var name = 'Output'; - if (this.compilerName) name += ' of ' + this.compilerName; - name += ' (Compiler #' + this.compilerId + ')'; - return name; -}; - -Output.prototype.updateTitle = function () { - var name = this.paneName ? this.paneName : this.getPaneName(); - this.container.setTitle(_.escape(name)); -}; - -Output.prototype.onCompilerClose = function (id) { - if (id === this.compilerId) { - // 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); - } -}; - -Output.prototype.close = function () { - this.eventHub.emit('outputClosed', this.compilerId); - this.eventHub.unsubscribe(); -}; - -Output.prototype.setCompileStatus = function (isCompiling) { - this.contentRoot.toggleClass('compiling', isCompiling); -}; - -module.exports = { - Output: Output, -}; diff --git a/static/panes/output.ts b/static/panes/output.ts new file mode 100644 index 000000000..f82bbf932 --- /dev/null +++ b/static/panes/output.ts @@ -0,0 +1,275 @@ +// Copyright (c) 2022, 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. + +import { Toggles } from '../widgets/toggles'; +import _ from 'underscore'; +import { Pane } from './pane'; +import { ga } from '../analytics'; +import { Container } from 'golden-layout'; +import { PaneState } from './pane.interfaces'; +import { Hub } from '../hub'; +import * as AnsiToHtml from '../ansi-to-html'; +import { OutputState } from './output.interfaces'; +import { FontScale } from '../widgets/fontscale'; + +function makeAnsiToHtml(color?) { + return new AnsiToHtml.Filter({ + fg: color ? color : '#333', + bg: '#f5f5f5', + stream: true, + escapeXML: true, + }); +} + +export class Output extends Pane<OutputState> { + hub: Hub; + contentRoot: JQuery<HTMLElement>; + optionsToolbar: JQuery<HTMLElement>; + fontScale: FontScale; + wrapButton: JQuery<HTMLElement>; + normalAnsiToHtml: AnsiToHtml.Filter; + errorAnsiToHtml: AnsiToHtml.Filter; + wrapTitle: string; + options: Toggles; + constructor(hub: Hub, container: Container, state: OutputState & PaneState) { + // canonicalize state + if((state as any).compiler) state.id = (state as any).compiler; + if((state as any).editor) state.editorid = (state as any).editor; + if((state as any).tree) state.treeid = (state as any).tree; + super(hub, container, state); + this.hub = hub; + this.contentRoot = this.domRoot.find('.content'); + this.optionsToolbar = this.domRoot.find('.options-toolbar'); + this.fontScale = new FontScale(this.domRoot, state, '.content'); + this.fontScale.on('change', this.updateState.bind(this)); + this.normalAnsiToHtml = makeAnsiToHtml(); + this.errorAnsiToHtml = makeAnsiToHtml('red'); + this.eventHub.emit('outputOpened', this.compilerInfo.compilerId); + this.onOptionsChange(); + } + + override getInitialHTML(): string { + return $('#compiler-output').html(); + } + + override registerOpeningAnalyticsEvent() { + ga.proxy('send', { + hitType: 'event', + eventCategory: 'OpenViewPane', + eventAction: 'Output', + }); + } + + override registerButtons(state: OutputState & PaneState) { + this.wrapButton = this.domRoot.find('.wrap-lines'); + this.wrapTitle = this.wrapButton.prop('title'); + // TODO: Would be nice to be able to get rid of this cast + this.options = new Toggles(this.domRoot.find('.options'), state as unknown as Record<string, boolean>); + } + + override registerCallbacks() { + this.options.on('change', this.onOptionsChange.bind(this)); + this.eventHub.on('compiling', this.onCompiling, this); + } + + onOptionsChange() { + const options = this.getEffectiveOptions(); + this.contentRoot.toggleClass('wrap', options.wrap); + this.wrapButton.prop('title', '[' + (options.wrap ? 'ON' : 'OFF') + '] ' + this.wrapTitle); + this.updateState(); + } + + getEffectiveOptions() { + return this.options.get(); + } + + override resize() { + const rootHeight = this.domRoot.height(); + const toolbarHeight = this.optionsToolbar.height(); + if(rootHeight && toolbarHeight) { + this.contentRoot.height(rootHeight - toolbarHeight - 5); + } + } + + override getCurrentState() { + const parent = super.getCurrentState(); + const options = this.getEffectiveOptions(); + const state = { + wrap: options.wrap, + ...parent, + }; + this.fontScale.addState(state); + return state as any; + } + + addOutputLines(result) { + const stdout = result.stdout || []; + const stderr = result.stderr || []; + for(const obj of stdout.concat(stderr)) { + const lineNumber = obj.tag ? obj.tag.line : obj.line; + const columnNumber = obj.tag ? obj.tag.column : -1; + if (obj.text === '') { + this.add('<br/>'); + } else { + this.add(this.normalAnsiToHtml.toHtml(obj.text), + lineNumber, columnNumber, obj.tag ? obj.tag.file : false); + } + } + } + + onCompiling(compilerId: number) { + if (this.compilerInfo.compilerId === compilerId) { + this.setCompileStatus(true); + } + } + + override onCompileResult(compilerId: number, compiler: any, result: any) { + if (compilerId !== this.compilerInfo.compilerId) return; + if (compiler) this.compilerInfo.compilerName = compiler.name; + + this.contentRoot.empty(); + + if (result.buildsteps) { + for(const step of result.buildsteps) { + this.add('Step ' + step.step + ' returned: ' + step.code); + this.addOutputLines(step); + } + } else { + this.addOutputLines(result); + if (!result.execResult) { + this.add('Compiler returned: ' + result.code); + } else { + this.add('ASM generation compiler returned: ' + result.code); + this.addOutputLines(result.execResult.buildResult); + this.add('Execution build compiler returned: ' + result.execResult.buildResult.code); + } + } + + if (result.execResult && (result.execResult.didExecute || result.didExecute)) { + this.add('Program returned: ' + result.execResult.code); + if (result.execResult.stderr.length || result.execResult.stdout.length) { + for(const obj of result.execResult.stderr) { + // Conserve empty lines as they are discarded by ansiToHtml + if (obj.text === '') { + this.programOutput('<br/>'); + } else { + this.programOutput(this.errorAnsiToHtml.toHtml(obj.text), 'red'); + } + } + + for(const obj of result.execResult.stdout) { + // Conserve empty lines as they are discarded by ansiToHtml + if (obj.text === '') { + this.programOutput('<br/>'); + } else { + this.programOutput(this.normalAnsiToHtml.toHtml(obj.text)); + } + } + } + } + this.setCompileStatus(false); + this.updateTitle(); + } + + override onCompiler(compilerId: number, compiler: unknown, options: unknown, editorId: number, + treeId: number) {} + + programOutput(msg: string, color?: string) { + const elem = $('<div/>').appendTo(this.contentRoot) + .html(msg) + .addClass('program-exec-output'); + + if (color) + elem.css('color', color); + } + + getEditorIdByFilename(filename) { + if(this.compilerInfo.treeId) { + const tree = this.hub.getTreeById(this.compilerInfo.treeId); + if (tree) { + return tree.multifileService.getEditorIdByFilename(filename); + } + return false; + } + } + + emitEditorLinkLine(lineNum, column, filename, goto) { + if (this.compilerInfo.editorId) { + this.eventHub.emit('editorLinkLine', this.compilerInfo.editorId, lineNum, column, column + 1, goto); + } else if (filename) { + const editorId = this.getEditorIdByFilename(filename); + if (editorId) { + this.eventHub.emit('editorLinkLine', editorId, lineNum, column, column + 1, goto); + } + } + } + + add(msg: string, lineNum?: number, column?: number, filename?: string) { + const elem = $('<div/>').appendTo(this.contentRoot); + if (lineNum) { + elem.html( + $('<span class="linked-compiler-output-line"></span>') + .html(msg) + .on('click', (e) => { + this.emitEditorLinkLine(lineNum, column, filename, true); + // do not bring user to the top of index.html + // http://stackoverflow.com/questions/3252730 + e.preventDefault(); + return false; + }) + .on('mouseover', () => { + this.emitEditorLinkLine(lineNum, column, filename, false); + }) as any // TODO + ); + } else { + elem.html(msg); + } + } + + override getDefaultPaneName() { + return `Output of ${this.compilerInfo.compilerName}`; + } + + override getPaneTag() { + return `(Compiler #${this.compilerInfo.compilerId})`; + } + + override onCompilerClose(id) { + if (id === this.compilerInfo.compilerId) { + // 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(() => { this.container.close(); }); + } + } + + override close() { + this.eventHub.emit('outputClosed', this.compilerInfo.compilerId); + this.eventHub.unsubscribe(); + } + + setCompileStatus(isCompiling) { + this.contentRoot.toggleClass('compiling', isCompiling); + } +} diff --git a/static/widgets/fontscale.ts b/static/widgets/fontscale.ts index 21dbdcf12..4543f0001 100644 --- a/static/widgets/fontscale.ts +++ b/static/widgets/fontscale.ts @@ -79,10 +79,10 @@ export class FontScale extends EventEmitter.EventEmitter { private domRoot: JQuery; public scale: number; private readonly usePxUnits: boolean; - private fontSelectorOrEditor: JQuery | IEditor; + private fontSelectorOrEditor: JQuery | string | IEditor; private isFontOfStr: boolean; - constructor(domRoot: JQuery, state: FontScaleState & any, fontSelectorOrEditor: JQuery | IEditor) { + constructor(domRoot: JQuery, state: FontScaleState & any, fontSelectorOrEditor: JQuery | string | IEditor) { super(); this.domRoot = domRoot; // Old scale went from 0.3 to 3. New one uses 8 up to 30, so we can convert the old ones to the new format @@ -125,7 +125,7 @@ export class FontScale extends EventEmitter.EventEmitter { this.apply(); } - setTarget(target: JQuery | IEditor) { + setTarget(target: JQuery | string | IEditor) { this.fontSelectorOrEditor = target; this.isFontOfStr = typeof (this.fontSelectorOrEditor) === 'string'; } |