From e4909784105f64f9855f49477b93c56c70d3cf46 Mon Sep 17 00:00:00 2001 From: Markus <28785953+MarkusJx@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:07:04 +0200 Subject: [PATCH] Convert device-view.js to typescript (#4105) --- cypress/integration/frontend-testing.js | 3 +- cypress/integration/functionality.js | 1 - cypress/plugins/index.js | 6 +- cypress/support/index.js | 2 +- static/panes/device-view.interfaces.ts | 36 ++ static/panes/device-view.js | 436 ------------------------ static/panes/device-view.ts | 358 +++++++++++++++++++ static/panes/editor.js | 2 +- static/sharing.ts | 1 - 9 files changed, 400 insertions(+), 445 deletions(-) create mode 100644 static/panes/device-view.interfaces.ts delete mode 100644 static/panes/device-view.js create mode 100644 static/panes/device-view.ts diff --git a/cypress/integration/frontend-testing.js b/cypress/integration/frontend-testing.js index 45830749f..3377762b3 100644 --- a/cypress/integration/frontend-testing.js +++ b/cypress/integration/frontend-testing.js @@ -97,7 +97,7 @@ describe('Known good state test', () => { }, }, ); - }) + }); afterEach('Ensure no output in console', () => { cy.window().then(win => { @@ -115,5 +115,4 @@ describe('Known good state test', () => { cy.get('span.lm_title:visible').contains('Output'); cy.get('span.lm_title:visible').contains('Conformance'); }); - }); diff --git a/cypress/integration/functionality.js b/cypress/integration/functionality.js index db378f75d..cd237b719 100644 --- a/cypress/integration/functionality.js +++ b/cypress/integration/functionality.js @@ -1,6 +1,5 @@ import {runFrontendTest} from '../support/utils'; - describe('Motd testing', () => { before(() => { cy.visit('/'); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 59b2bab6e..5bef9009c 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -17,6 +17,6 @@ */ // eslint-disable-next-line no-unused-vars module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +}; diff --git a/cypress/support/index.js b/cypress/support/index.js index 185d654ec..3d469a6b6 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -14,4 +14,4 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; diff --git a/static/panes/device-view.interfaces.ts b/static/panes/device-view.interfaces.ts new file mode 100644 index 000000000..22d1a2c01 --- /dev/null +++ b/static/panes/device-view.interfaces.ts @@ -0,0 +1,36 @@ +// 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 type DeviceAsmState = { + device?: string; + irOutput?: DeviceAsmCode[]; +}; + +export type DeviceAsmCode = { + source?: { + file?: string; + line: number; + }; + text: string; +}; diff --git a/static/panes/device-view.js b/static/panes/device-view.js deleted file mode 100644 index 270ff96a5..000000000 --- a/static/panes/device-view.js +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (c) 2020, 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 FontScale = require('../widgets/fontscale').FontScale; -var monaco = require('monaco-editor'); -var _ = require('underscore'); -var $ = require('jquery'); -var colour = require('../colour'); -var ga = require('../analytics').ga; -var monacoConfig = require('../monaco-config'); -var PaneRenaming = require('../widgets/pane-renaming').PaneRenaming; - -var TomSelect = require('tom-select'); - -function DeviceAsm(hub, container, state) { - this.container = container; - this.eventHub = hub.createEventHub(); - this.domRoot = container.getElement(); - this.domRoot.html($('#device').html()); - - this.decorations = {}; - this.prevDecorations = []; - var root = this.domRoot.find('.monaco-placeholder'); - - this.deviceEditor = monaco.editor.create( - root[0], - monacoConfig.extendConfig({ - language: 'asm', - readOnly: true, - glyphMargin: true, - lineNumbersMinChars: 3, - }) - ); - - this._compilerId = state.id; - this._compilerName = state.compilerName; - this._editorId = state.editorid; - this._treeId = state.treeid; - - this.awaitingInitialResults = false; - this.selection = state.selection; - this.selectedDevice = state.device || ''; - this.devices = null; - - this.settings = {}; - - this.colours = []; - this.deviceCode = []; - this.lastColours = []; - this.lastColourScheme = {}; - - var changeDeviceEl = this.domRoot[0].querySelector('.change-device'); - this.selectize = new TomSelect(changeDeviceEl, { - sortField: 'name', - valueField: 'name', - labelField: 'name', - searchField: ['name'], - options: [], - items: [], - dropdownParent: 'body', - plugins: ['input_autogrow'], - }); - - this.paneRenaming = new PaneRenaming(this, state); - - this.initButtons(state); - this.initCallbacks(); - this.initEditorActions(); - - if (state && state.irOutput) { - this.showDeviceAsmResults(state.irOutput); - } - - ga.proxy('send', { - hitType: 'event', - eventCategory: 'OpenViewPane', - eventAction: 'DeviceAsm', - }); -} - -DeviceAsm.prototype.initEditorActions = function () { - this.deviceEditor.addAction({ - id: 'viewsource', - label: 'Scroll to source', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10], - keybindingContext: null, - contextMenuGroupId: 'navigation', - contextMenuOrder: 1.5, - run: _.bind(function (ed) { - var position = ed.getPosition(); - if (position != null) { - var desiredLine = position.lineNumber - 1; - var source = this.deviceCode[desiredLine].source; - if (source !== null && source.file === null) { - // a null file means it was the user's source - this.eventHub.emit('editorLinkLine', this._editorId, source.line, -1, true); - } - } - }, this), - }); -}; - -DeviceAsm.prototype.initButtons = function (state) { - this.fontScale = new FontScale(this.domRoot, state, this.deviceEditor); - - this.topBar = this.domRoot.find('.top-bar'); -}; - -DeviceAsm.prototype.initCallbacks = function () { - this.linkedFadeTimeoutId = -1; - this.mouseMoveThrottledFunction = _.throttle(_.bind(this.onMouseMove, this), 50); - this.deviceEditor.onMouseMove( - _.bind(function (e) { - this.mouseMoveThrottledFunction(e); - }, this) - ); - - this.cursorSelectionThrottledFunction = _.throttle(_.bind(this.onDidChangeCursorSelection, this), 500); - this.deviceEditor.onDidChangeCursorSelection( - _.bind(function (e) { - this.cursorSelectionThrottledFunction(e); - }, this) - ); - - this.fontScale.on('change', _.bind(this.updateState, this)); - this.selectize.on('change', _.bind(this.onDeviceSelect, this)); - this.paneRenaming.on('renamePane', this.updateState.bind(this)); - - this.container.on('destroy', this.close, this); - - this.eventHub.on('compileResult', this.onCompileResponse, this); - this.eventHub.on('compiler', this.onCompiler, this); - this.eventHub.on('colours', this.onColours, this); - this.eventHub.on('panesLinkLine', this.onPanesLinkLine, this); - this.eventHub.on('compilerClose', this.onCompilerClose, this); - this.eventHub.on('settingsChange', this.onSettingsChange, this); - this.eventHub.emit('deviceViewOpened', this._compilerId); - this.eventHub.emit('requestSettings'); - - this.container.on('resize', this.resize, this); - this.container.on('shown', this.resize, this); -}; - -// TODO: de-dupe with compiler etc -DeviceAsm.prototype.resize = function () { - var topBarHeight = this.topBar.outerHeight(true); - this.deviceEditor.layout({ - width: this.domRoot.width(), - height: this.domRoot.height() - topBarHeight, - }); -}; - -DeviceAsm.prototype.onCompileResponse = function (id, compiler, result) { - if (this._compilerId !== id) return; - this.devices = result.devices; - var deviceNames = []; - if (!this.devices) { - this.showDeviceAsmResults([{text: ''}]); - } else { - deviceNames = Object.keys(this.devices); - } - - this.makeDeviceSelector(deviceNames); - this.updateDeviceAsm(); - - // Why call this explicitly instead of just listening to the "colours" event? - // Because the recolouring happens before this editors value is set using "showDeviceAsmResults". - this.onColours(this._compilerId, this.lastColours, this.lastColourScheme); -}; - -DeviceAsm.prototype.makeDeviceSelector = function (deviceNames) { - var selectize = this.selectize; - - _.each( - selectize.options, - function (p) { - if (deviceNames.indexOf(p.name) === -1) { - selectize.removeOption(p.name); - } - }, - this - ); - - _.each( - deviceNames, - function (p) { - selectize.addOption({name: p}); - }, - this - ); - - if (!this.selectedDevice && deviceNames.length > 0) { - this.selectedDevice = deviceNames[0]; - selectize.setValue(this.selectedDevice, true); - } else if (this.selectedDevice && deviceNames.indexOf(this.selectedDevice) === -1) { - selectize.clear(true); - this.showDeviceAsmResults([{text: ''}]); - } else { - selectize.setValue(this.selectedDevice, true); - this.updateDeviceAsm(); - } -}; - -DeviceAsm.prototype.onDeviceSelect = function () { - this.selectedDevice = this.selectize.getValue(); - this.updateState(); - this.updateDeviceAsm(); -}; - -DeviceAsm.prototype.updateDeviceAsm = function () { - if (this.selectedDevice && this.devices[this.selectedDevice]) { - var languageId = this.devices[this.selectedDevice].languageId; - this.showDeviceAsmResults(this.devices[this.selectedDevice].asm, languageId); - } else this.showDeviceAsmResults([{text: ''}]); -}; - -DeviceAsm.prototype.getPaneTag = function () { - if (this._editorId) { - return this._compilerName + ' (Editor #' + this._editorId + ', Compiler #' + this._compilerId + ')'; - } else { - return this._compilerName + ' (Tree #' + this._treeId + ', Compiler #' + this._compilerId + ')'; - } -}; - -DeviceAsm.prototype.getDefaultPaneName = function () { - return 'Device Viewer'; -}; - -DeviceAsm.prototype.getPaneName = function () { - return this.paneName ? this.paneName : this.getDefaultPaneName() + ' ' + this.getPaneTag(); -}; - -DeviceAsm.prototype.updateTitle = function () { - this.container.setTitle(_.escape(this.getPaneName())); -}; - -DeviceAsm.prototype.showDeviceAsmResults = function (deviceCode, languageId) { - if (!this.deviceEditor) return; - this.deviceCode = deviceCode; - - if (!languageId) { - languageId = 'asm'; - } - - monaco.editor.setModelLanguage(this.deviceEditor.getModel(), languageId); - - this.deviceEditor - .getModel() - .setValue(deviceCode.length ? _.pluck(deviceCode, 'text').join('\n') : ''); - - if (!this.awaitingInitialResults) { - if (this.selection) { - this.deviceEditor.setSelection(this.selection); - this.deviceEditor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber); - } - this.awaitingInitialResults = true; - } -}; - -DeviceAsm.prototype.onCompiler = function (id, compiler, options, editorId, treeId) { - if (id === this._compilerId) { - this._compilerName = compiler ? compiler.name : ''; - this._editorId = editorId; - this._treeId = treeId; - this.updateTitle(); - if (compiler && !compiler.supportsDeviceAsmView) { - this.deviceEditor.setValue(''); - } - } -}; - -DeviceAsm.prototype.onColours = function (id, colours, scheme) { - this.lastColours = colours; - this.lastColourScheme = scheme; - - if (id === this._compilerId) { - var irColours = {}; - _.each(this.deviceCode, function (x, index) { - if (x.source && x.source.file === null && x.source.line > 0 && colours[x.source.line - 1] !== undefined) { - irColours[index] = colours[x.source.line - 1]; - } - }); - this.colours = colour.applyColours(this.deviceEditor, irColours, scheme, this.colours); - } -}; - -DeviceAsm.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. - _.defer(function (self) { - self.container.close(); - }, this); - } -}; - -DeviceAsm.prototype.updateState = function () { - this.container.setState(this.currentState()); -}; - -DeviceAsm.prototype.currentState = function () { - var state = { - id: this._compilerId, - editorid: this._editorId, - treeid: this._treeId, - selection: this.selection, - device: this.selectedDevice, - }; - this.paneRenaming.addState(state); - this.fontScale.addState(state); - return state; -}; - -DeviceAsm.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); - } -}; - -DeviceAsm.prototype.onSettingsChange = function (newSettings) { - this.settings = newSettings; - this.deviceEditor.updateOptions({ - contextmenu: newSettings.useCustomContextMenu, - minimap: { - enabled: newSettings.showMinimap, - }, - fontFamily: newSettings.editorsFFont, - fontLigatures: newSettings.editorsFLigatures, - }); -}; - -DeviceAsm.prototype.onMouseMove = function (e) { - if (e === null || e.target === null || e.target.position === null) return; - if (this.settings.hoverShowSource === true && this.deviceCode) { - this.clearLinkedLines(); - var hoverCode = this.deviceCode[e.target.position.lineNumber - 1]; - if (hoverCode) { - // We check that we actually have something to show at this point! - var sourceLine = hoverCode.source && !hoverCode.source.file ? hoverCode.source.line : -1; - this.eventHub.emit('editorLinkLine', this._editorId, sourceLine, -1, false); - this.eventHub.emit('panesLinkLine', this._compilerId, sourceLine, false, this.getPaneName()); - } - } -}; - -DeviceAsm.prototype.onDidChangeCursorSelection = function (e) { - if (this.awaitingInitialResults) { - this.selection = e.selection; - this.updateState(); - } -}; - -DeviceAsm.prototype.updateDecorations = function () { - this.prevDecorations = this.deviceEditor.deltaDecorations( - this.prevDecorations, - _.flatten(_.values(this.decorations)) - ); -}; - -DeviceAsm.prototype.clearLinkedLines = function () { - this.decorations.linkedCode = []; - this.updateDecorations(); -}; - -DeviceAsm.prototype.onPanesLinkLine = function (compilerId, lineNumber, revealLine, sender) { - if (Number(compilerId) === this._compilerId) { - var lineNums = []; - _.each(this.deviceCode, function (irLine, i) { - if (irLine.source && irLine.source.file === null && irLine.source.line === lineNumber) { - var line = i + 1; - lineNums.push(line); - } - }); - if (revealLine && lineNums[0]) this.deviceEditor.revealLineInCenter(lineNums[0]); - var lineClass = sender !== this.getPaneName() ? 'linked-code-decoration-line' : ''; - this.decorations.linkedCode = _.map(lineNums, function (line) { - return { - range: new monaco.Range(line, 1, line, 1), - options: { - isWholeLine: true, - linesDecorationsClassName: 'linked-code-decoration-margin', - className: lineClass, - }, - }; - }); - if (this.linkedFadeTimeoutId !== -1) { - clearTimeout(this.linkedFadeTimeoutId); - } - this.linkedFadeTimeoutId = setTimeout( - _.bind(function () { - this.clearLinkedLines(); - this.linkedFadeTimeoutId = -1; - }, this), - 5000 - ); - this.updateDecorations(); - } -}; - -DeviceAsm.prototype.close = function () { - this.eventHub.unsubscribe(); - this.eventHub.emit('deviceViewClosed', this._compilerId); - this.deviceEditor.dispose(); -}; - -module.exports = { - DeviceAsm: DeviceAsm, -}; diff --git a/static/panes/device-view.ts b/static/panes/device-view.ts new file mode 100644 index 000000000..a54e76165 --- /dev/null +++ b/static/panes/device-view.ts @@ -0,0 +1,358 @@ +// 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 * as monaco from 'monaco-editor'; +import _ from 'underscore'; +import * as colour from '../colour'; +import {ga} from '../analytics'; +import * as monacoConfig from '../monaco-config'; +import TomSelect from 'tom-select'; +import GoldenLayout from 'golden-layout'; +import {Hub} from '../hub'; +import {MonacoPane} from './pane'; +import {DeviceAsmCode, DeviceAsmState} from './device-view.interfaces'; +import {MonacoPaneState} from './pane.interfaces'; +import {CompilerInfo} from '../../types/compiler.interfaces'; +import {CompilationResult} from '../../types/compilation/compilation.interfaces'; + +type DeviceType = { + languageId: string; + asm: DeviceAsmCode[]; +}; + +export class DeviceAsm extends MonacoPane { + private decorations: Record<'linkedCode', monaco.editor.IModelDeltaDecoration[]>; + private prevDecorations: string[]; + private selectedDevice: string; + private devices: Record | null; + private colours: string[]; + private deviceCode: DeviceAsmCode[]; + private lastColours: Record; + private lastColourScheme: string; + private readonly selectize: TomSelect; + private linkedFadeTimeoutId: NodeJS.Timeout | null; + + public constructor(hub: Hub, container: GoldenLayout.Container, state: DeviceAsmState & MonacoPaneState) { + super(hub, container, state); + + this.prevDecorations = []; + + this.selection = state.selection; + this.selectedDevice = state.device || ''; + this.devices = null; + + this.colours = []; + this.deviceCode = []; + this.lastColours = []; + this.lastColourScheme = ''; + + const changeDeviceEl = this.domRoot[0].querySelector('.change-device') as HTMLInputElement; + this.selectize = new TomSelect(changeDeviceEl, { + sortField: 'name', + valueField: 'name', + labelField: 'name', + searchField: ['name'], + options: [], + items: [], + dropdownParent: 'body', + plugins: ['input_autogrow'], + }); + + if (state.irOutput) { + this.showDeviceAsmResults(state.irOutput); + } + } + + override getInitialHTML(): string { + return $('#device').html(); + } + + override createEditor(editorRoot: HTMLElement): monaco.editor.IStandaloneCodeEditor { + return monaco.editor.create( + editorRoot, + monacoConfig.extendConfig({ + language: 'asm', + readOnly: true, + glyphMargin: true, + lineNumbersMinChars: 3, + }) + ); + } + + override registerOpeningAnalyticsEvent(): void { + ga.proxy('send', { + hitType: 'event', + eventCategory: 'OpenViewPane', + eventAction: 'DeviceAsm', + }); + } + + override registerEditorActions(): void { + this.editor.addAction({ + id: 'viewsource', + label: 'Scroll to source', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10], + keybindingContext: undefined, + contextMenuGroupId: 'navigation', + contextMenuOrder: 1.5, + run: ed => { + const position = ed.getPosition(); + if (position != null) { + const desiredLine = position.lineNumber - 1; + const source = this.deviceCode[desiredLine].source; + if (source && source.file == null && this.compilerInfo.editorId != null) { + // a null file means it was the user's source + this.eventHub.emit('editorLinkLine', this.compilerInfo.editorId, source.line, -1, -1, true); + } + } + }, + }); + } + + override registerCallbacks(): void { + this.linkedFadeTimeoutId = null; + const mouseMoveThrottledFunction = _.throttle(this.onMouseMove.bind(this), 50); + this.editor.onMouseMove(e => mouseMoveThrottledFunction(e)); + + const cursorSelectionThrottledFunction = _.throttle(this.onDidChangeCursorSelection.bind(this), 500); + this.editor.onDidChangeCursorSelection(e => cursorSelectionThrottledFunction(e)); + + this.selectize.on('change', this.onDeviceSelect.bind(this)); + + this.eventHub.on('colours', this.onColours, this); + this.eventHub.on('panesLinkLine', this.onPanesLinkLine, this); + this.eventHub.emit('deviceViewOpened', this.compilerInfo.compilerId); + this.eventHub.emit('requestSettings'); + + this.container.on('shown', this.resize, this); + } + + override onCompileResult(id: number, compiler: CompilerInfo, result: CompilationResult): void { + if (this.compilerInfo.compilerId !== id) return; + // @ts-expect-error: CompilationResult does not have the 'devices' type + this.devices = result.devices; + let deviceNames: string[] = []; + if (!this.devices) { + this.showDeviceAsmResults([{text: ''}]); + } else { + deviceNames = Object.keys(this.devices); + } + + this.makeDeviceSelector(deviceNames); + this.updateDeviceAsm(); + + // Why call this explicitly instead of just listening to the "colours" event? + // Because the recolouring happens before this editors value is set using "showDeviceAsmResults". + this.onColours(this.compilerInfo.compilerId, this.lastColours, this.lastColourScheme); + } + + makeDeviceSelector(deviceNames: string[]): void { + const selectize = this.selectize; + + for (const key in selectize.options) { + if (deviceNames.includes(selectize.options[key].name)) { + selectize.removeOption(selectize.options[key].name); + } + } + + deviceNames.forEach(deviceName => { + selectize.addOption({name: deviceName}); + }); + + if (!this.selectedDevice && deviceNames.length > 0) { + this.selectedDevice = deviceNames[0]; + selectize.setValue(this.selectedDevice, true); + } else if (this.selectedDevice && deviceNames.indexOf(this.selectedDevice) === -1) { + selectize.clear(true); + this.showDeviceAsmResults([{text: ''}]); + } else { + selectize.setValue(this.selectedDevice, true); + this.updateDeviceAsm(); + } + } + + onDeviceSelect(): void { + this.selectedDevice = this.selectize.getValue() as string; + this.updateState(); + this.updateDeviceAsm(); + } + + updateDeviceAsm(): void { + if (this.selectedDevice && this.devices != null && this.selectedDevice in this.devices) { + const languageId = this.devices[this.selectedDevice].languageId; + this.showDeviceAsmResults(this.devices[this.selectedDevice].asm, languageId); + } else { + this.showDeviceAsmResults([{text: ``}]); + } + } + + override getDefaultPaneName(): string { + return 'Device Viewer'; + } + + showDeviceAsmResults(deviceCode: DeviceAsmCode[], languageId?: string): void { + this.deviceCode = deviceCode; + + if (!languageId) { + languageId = 'asm'; + } + + const model = this.editor.getModel(); + if (model) { + monaco.editor.setModelLanguage(model, languageId); + model.setValue(deviceCode.length ? deviceCode.map(d => d.text).join('\n') : ''); + } + + if (!this.isAwaitingInitialResults) { + if (this.selection) { + this.editor.setSelection(this.selection); + this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber); + } + this.isAwaitingInitialResults = true; + } + } + + override onCompiler( + id: number, + compiler: CompilerInfo | undefined, + options: string, + editorId: number, + treeId: number + ): void { + if (id === this.compilerInfo.compilerId) { + this.compilerInfo.compilerName = compiler ? compiler.name : ''; + this.compilerInfo.editorId = editorId; + this.compilerInfo.treeId = treeId; + this.updateTitle(); + // @ts-expect-error: CompilerInfo does not have the 'supportsDeviceAsmView' type + if (compiler && !compiler.supportsDeviceAsmView) { + this.editor.setValue(''); + } + } + } + + onColours(id: number, colours: Record, scheme: string): void { + this.lastColours = colours; + this.lastColourScheme = scheme; + + if (id === this.compilerInfo.compilerId) { + const irColours = {}; + this.deviceCode.forEach((x: DeviceAsmCode, index: number) => { + if (x.source && x.source.file == null && x.source.line > 0 && colours[x.source.line - 1]) { + irColours[index] = colours[x.source.line - 1]; + } + }); + this.colours = colour.applyColours(this.editor, irColours, scheme, this.colours); + } + } + + override onCompilerClose(id: number): void { + 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(); + }); + } + } + + onMouseMove(e: monaco.editor.IEditorMouseEvent): void { + if (e.target.position === null) return; + if (this.settings.hoverShowSource) { + this.clearLinkedLines(); + const hoverCode = this.deviceCode[e.target.position.lineNumber - 1]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (hoverCode && this.compilerInfo.editorId != null) { + // We check that we actually have something to show at this point! + const sourceLine = hoverCode.source && !hoverCode.source.file ? hoverCode.source.line : -1; + this.eventHub.emit('editorLinkLine', this.compilerInfo.editorId, sourceLine, -1, 0, false); + this.eventHub.emit( + 'panesLinkLine', + this.compilerInfo.compilerId, + sourceLine, + -1, + 0, + false, + this.getPaneName() + ); + } + } + } + + updateDecorations(): void { + this.prevDecorations = this.editor.deltaDecorations( + this.prevDecorations, + Object.values(this.decorations).flatMap(x => x) + ); + } + + clearLinkedLines(): void { + this.decorations.linkedCode = []; + this.updateDecorations(); + } + + onPanesLinkLine( + compilerId: number, + lineNumber: number, + _colBegin: number, + _colEnd: number, + revealLine: boolean, + sender: string + ): void { + if (Number(compilerId) === this.compilerInfo.compilerId) { + const lineNums: number[] = []; + this.deviceCode.forEach((irLine: DeviceAsmCode, i: number) => { + if (irLine.source && irLine.source.file == null && irLine.source.line === lineNumber) { + const line = i + 1; + lineNums.push(line); + } + }); + if (revealLine && lineNums[0]) this.editor.revealLineInCenter(lineNums[0]); + const lineClass = sender !== this.getPaneName() ? 'linked-code-decoration-line' : ''; + this.decorations.linkedCode = lineNums.map(line => ({ + range: new monaco.Range(line, 1, line, 1), + options: { + isWholeLine: true, + linesDecorationsClassName: 'linked-code-decoration-margin', + className: lineClass, + }, + })); + if (this.linkedFadeTimeoutId !== null) { + clearTimeout(this.linkedFadeTimeoutId); + } + this.linkedFadeTimeoutId = setTimeout(() => { + this.clearLinkedLines(); + this.linkedFadeTimeoutId = null; + }, 5000); + this.updateDecorations(); + } + } + + override close(): void { + this.eventHub.unsubscribe(); + this.eventHub.emit('deviceViewClosed', this.compilerInfo.compilerId); + this.editor.dispose(); + } +} diff --git a/static/panes/editor.js b/static/panes/editor.js index 1a76a7434..bc9e1d858 100644 --- a/static/panes/editor.js +++ b/static/panes/editor.js @@ -1522,7 +1522,7 @@ Editor.prototype.onExecuteResponse = function (executorId, compiler, result) { if (result.buildResult) { output = output.concat(this.getAllOutputAndErrors(result.buildResult, compiler.name, 'Executor ' + executorId)); } - this.setDecorationTags(this.collectOutputWidgets(output).widgets, 'Executor '+ executorId); + this.setDecorationTags(this.collectOutputWidgets(output).widgets, 'Executor ' + executorId); this.numberUsedLines(); }; diff --git a/static/sharing.ts b/static/sharing.ts index c51ad6fbf..95266e178 100644 --- a/static/sharing.ts +++ b/static/sharing.ts @@ -282,7 +282,6 @@ export class Sharing { } }); }); - } private copyLinkTypeToClipboard(type: LinkType): void {