// Copyright (c) 2012, 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 _ from 'underscore'; import $ from 'jquery'; import {Buffer} from 'buffer'; import {ga} from '../analytics.js'; import * as colour from '../colour.js'; import {Toggles} from '../widgets/toggles.js'; import * as Components from '../components.js'; import {LRUCache} from 'lru-cache'; import {options} from '../options.js'; import * as monaco from 'monaco-editor'; import {Alert} from '../widgets/alert.js'; import {LibsWidget} from '../widgets/libs-widget.js'; import * as codeLensHandler from '../codelens-handler.js'; import * as monacoConfig from '../monaco-config.js'; import * as TimingWidget from '../widgets/timing-info-widget.js'; import {CompilerPicker} from '../widgets/compiler-picker.js'; import {CompilerService} from '../compiler-service.js'; import {SiteSettings} from '../settings.js'; import * as LibUtils from '../lib-utils.js'; import {getAssemblyDocumentation} from '../api/api.js'; import {MonacoPane} from './pane.js'; import {CompilerInfo} from '../../types/compiler.interfaces.js'; import {MonacoPaneState} from './pane.interfaces.js'; import {Hub} from '../hub.js'; import {Container} from 'golden-layout'; import {CompilerCurrentState, CompilerState} from './compiler.interfaces.js'; import {ComponentConfig, ToolViewState} from '../components.interfaces.js'; import {LanguageLibs} from '../options.interfaces.js'; import {GccDumpFiltersState, GccDumpViewSelectedPass} from './gccdump-view.interfaces.js'; import {AssemblyInstructionInfo} from '../../lib/asm-docs/base.js'; import {PPOptions} from './pp-view.interfaces.js'; import {CompilationStatus} from '../compiler-service.interfaces.js'; import {WidgetState} from '../widgets/libs-widget.interfaces.js'; import {OptPipelineBackendOptions} from '../compilation/opt-pipeline-output.interfaces.js'; import { ActiveTools, BypassCache, CompilationRequest, CompilationRequestOptions, CompilationResult, GccDumpFlags, FiledataPair, } from '../../types/compilation/compilation.interfaces.js'; import {ResultLine} from '../../types/resultline/resultline.interfaces.js'; import * as utils from '../utils.js'; import {editor} from 'monaco-editor'; import IEditorMouseEvent = editor.IEditorMouseEvent; import {Tool, ArtifactType, Artifact} from '../../types/tool.interfaces.js'; import {assert, unwrap, unwrapString} from '../assert.js'; import {CompilerOutputOptions} from '../../types/features/filters.interfaces.js'; import {SourceAndFiles} from '../download-service.js'; import fileSaver = require('file-saver'); import {ICompilerShared} from '../compiler-shared.interfaces.js'; import {CompilerShared} from '../compiler-shared.js'; import {SentryCapture} from '../sentry.js'; import {LLVMIrBackendOptions} from '../compilation/ir.interfaces.js'; import {InstructionSet} from '../instructionsets.js'; import {escapeHTML} from '../../shared/common-utils.js'; import {CompilerVersionInfo, setCompilerVersionPopoverForPane} from '../widgets/compiler-version-info.js'; const toolIcons = require.context('../../views/resources/logos', false, /\.(png|svg)$/); type CachedOpcode = { found: boolean; data: AssemblyInstructionInfo | string; }; const OpcodeCache = new LRUCache({ maxSize: 64 * 1024, sizeCalculation: function (n) { return JSON.stringify(n).length; }, }); function patchOldFilters(filters) { if (filters === undefined) return undefined; // Filters are of the form {filter: true|false¸ ...}. In older versions, we used // to suppress the {filter:false} form. This means we can't distinguish between // "filter not on" and "filter not present". In the latter case we want to default // the filter. In the former case we want the filter off. Filters now don't suppress // but there are plenty of permalinks out there with no filters set at all. Here // we manually set any missing filters to 'false' to recover the old behaviour of // "if it's not here, it's off". ['binary', 'labels', 'directives', 'commentOnly', 'trim', 'intel', 'debugCalls'].forEach(oldFilter => { if (filters[oldFilter] === undefined) filters[oldFilter] = false; }); return filters; } const languages = options.languages; type NewToolSettings = { toolId: number; args: string[]; stdin: string; }; type LinkedCode = { range: monaco.Range; options: { isWholeLine: boolean; linesDecorationsClassName?: string; className?: string; inlineClassName?: string; }; }; type Decorations = Record; type Assembly = { labels?: any[]; source?: { line: number; column?: number; file?: string | null; mainsource?: any; }; address?: number; opcodes?: string[]; text?: string; fake?: boolean; }; // Disable max line count only for the constructor. Turns out, it needs to do quite a lot of things // eslint-disable-next-line max-statements export class Compiler extends MonacoPane { private compilerService: CompilerService; private readonly id: number; private sourceTreeId: number | null; private sourceEditorId: number | null; private originalCompilerId: string; private readonly infoByLang: Record; private deferCompiles: boolean; private needsCompile: boolean; private deviceViewOpen: boolean; private options: string; private source: string; private assembly: Assembly[]; private lastResult: CompilationResult | null; private lastTimeTaken: number; private pendingRequestSentAt: number; private pendingCMakeRequestSentAt: number; private nextRequest: CompilationRequest | null; private nextCMakeRequest: CompilationRequest | null; private readonly decorations: Decorations; private prevDecorations: string[]; private labelDefinitions: Record; private alertSystem: Alert; private awaitingInitialResults: boolean; private linkedFadeTimeoutId: NodeJS.Timeout | null; private toolsMenu: JQuery | null; private revealJumpStack: (monaco.editor.ICodeEditorViewState | null)[]; private compilerPickerElement: JQuery; private compilerPicker: CompilerPicker; private compiler: CompilerInfo | null; private recentInstructionSet: InstructionSet | null; private currentLangId: string | null; private filters: Toggles; private optButton: JQuery; private stackUsageButton: JQuery; private flagsButton?: JQuery; private ppButton: JQuery; private astButton: JQuery; private irButton: JQuery; private optPipelineButton: JQuery; private deviceButton: JQuery; private gnatDebugTreeButton: JQuery; private gnatDebugButton: JQuery; private rustMirButton: JQuery; private rustMacroExpButton: JQuery; private haskellCoreButton: JQuery; private haskellStgButton: JQuery; private haskellCmmButton: JQuery; private gccDumpButton: JQuery; private cfgButton: JQuery; private executorButton: JQuery; private libsButton: JQuery; private compileInfoLabel: JQuery; private compileClearCache: JQuery; private outputBtn: JQuery; private outputTextCount: JQuery; private outputErrorCount: JQuery; private optionsField: JQuery; private initialOptionsFieldPlacehoder: JQuery; private prependOptions: JQuery; private fullCompilerName: JQuery; private fullTimingInfo: JQuery; private compilerLicenseButton: JQuery; private filterBinaryButton: JQuery; private filterBinaryTitle: JQuery; private filterBinaryObjectButton: JQuery; private filterBinaryObjectTitle: JQuery; private filterExecuteButton: JQuery; private filterExecuteTitle: JQuery; private filterLabelsButton: JQuery; private filterLabelsTitle: JQuery; private filterDirectivesButton: JQuery; private filterDirectivesTitle: JQuery; private filterLibraryCodeButton: JQuery; private filterLibraryCodeTitle: JQuery; private filterCommentsButton: JQuery; private filterCommentsTitle: JQuery; private filterTrimButton: JQuery; private filterTrimTitle: JQuery; private filterDebugCallsButton: JQuery; private filterDebugCallsTitle: JQuery; private filterIntelButton: JQuery; private filterIntelTitle: JQuery; private filterDemangleButton: JQuery; private filterDemangleTitle: JQuery; private filterVerboseDemanglingButton: JQuery; private filterVerboseDemanglingTitle: JQuery; private noBinaryFiltersButtons: JQuery; private shortCompilerName: JQuery; private bottomBar: JQuery; private statusLabel: JQuery; private statusIcon: JQuery; private monacoPlaceholder: JQuery; private rustHirButton: JQuery; private libsWidget: LibsWidget | null; private isLabelCtxKey: monaco.editor.IContextKey; private revealJumpStackHasElementsCtxKey: monaco.editor.IContextKey; private isAsmKeywordCtxKey: monaco.editor.IContextKey; private lineHasLinkedSourceCtxKey: monaco.editor.IContextKey; private flagsViewOpen: boolean; private stackUsageViewOpen: boolean; private optViewOpen: boolean; private cfgViewOpenCount: number; private irCfgViewOpenCount: number; private wantOptInfo?: boolean; private ppViewOpen: boolean; private astViewOpen: boolean; private irViewOpen: boolean; private optPipelineViewOpenCount: number; private gccDumpViewOpen: boolean; private gccDumpPassSelected?: GccDumpViewSelectedPass; private treeDumpEnabled?: boolean; private rtlDumpEnabled?: boolean; private ipaDumpEnabled?: boolean; private dumpFlags?: GccDumpFlags; private gnatDebugTreeViewOpen: boolean; private gnatDebugViewOpen: boolean; private rustMirViewOpen: boolean; private rustMacroExpViewOpen: boolean; private rustHirViewOpen: boolean; private haskellCoreViewOpen: boolean; private haskellStgViewOpen: boolean; private haskellCmmViewOpen: boolean; private ppOptions: PPOptions; private llvmIrOptions: LLVMIrBackendOptions; private optPipelineOptions: OptPipelineBackendOptions; private isOutputOpened: boolean; private mouseMoveThrottledFunction?: ((e: monaco.editor.IEditorMouseEvent) => void) & _.Cancelable; private cursorSelectionThrottledFunction?: ((e: monaco.editor.ICursorSelectionChangedEvent) => void) & _.Cancelable; private mouseUpThrottledFunction?: ((e: monaco.editor.IEditorMouseEvent) => void) & _.Cancelable; private compilerShared: ICompilerShared; // eslint-disable-next-line max-statements constructor(hub: Hub, container: Container, state: MonacoPaneState & CompilerState) { super(hub, container, state); this.id = state.id || hub.nextCompilerId(); this.infoByLang = {}; this.deferCompiles = true; this.needsCompile = false; this.initLangAndCompiler(state); this.source = ''; this.assembly = []; this.lastResult = null; this.lastTimeTaken = 0; this.pendingRequestSentAt = 0; this.pendingCMakeRequestSentAt = 0; this.nextRequest = null; this.nextCMakeRequest = null; this.optViewOpen = false; this.cfgViewOpenCount = 0; this.irCfgViewOpenCount = 0; this.optPipelineViewOpenCount = 0; this.decorations = { labelUsages: [], }; this.prevDecorations = []; this.labelDefinitions = {}; this.alertSystem = new Alert(); this.alertSystem.prefixMessage = 'Compiler #' + this.id; this.awaitingInitialResults = false; this.linkedFadeTimeoutId = null; this.toolsMenu = null; this.revealJumpStack = []; // MonacoPane's registerButtons is not called late enough, we still need to init some buttons with new data this.initPanerButtons(); this.compilerPicker = new CompilerPicker( this.domRoot, this.hub, this.currentLangId ?? '', this.compiler?.id ?? '', this.onCompilerChange.bind(this), ); this.initLibraries(state); this.compilerShared = new CompilerShared(this.domRoot, this.onCompilerOverridesChange.bind(this)); this.compilerShared.updateState(state); // MonacoPane's registerCallbacks is not called late enough either this.initCallbacks(); // Handle initial settings this.onSettingsChange(this.settings); this.sendCompiler(); this.updateCompilerInfo(); this.updateButtons(); this.updateState(); if (this.sourceTreeId) { this.compile(); } if (!this.hub.deferred) { this.undefer(); } } override initializeStateDependentProperties(state: MonacoPaneState & CompilerState) { this.compilerService = this.hub.compilerService; this.sourceTreeId = state.tree ? state.tree : null; if (this.sourceTreeId) { this.sourceEditorId = null; } else { this.sourceEditorId = state.source || 1; } this.options = state.options || (options.compileOptions[this.currentLangId ?? ''] ?? ''); this.deviceViewOpen = !!state.deviceViewOpen; this.flagsViewOpen = state.flagsViewOpen || false; this.wantOptInfo = state.wantOptInfo; this.originalCompilerId = state.compiler; this.selection = state.selection; } override getInitialHTML() { return $('#compiler').html(); } override createEditor(editorRoot: HTMLElement) { this.editor = monaco.editor.create( editorRoot, monacoConfig.extendConfig( { readOnly: true, language: 'asm', glyphMargin: !options.embedded, guides: { bracketPairs: false, bracketPairsHorizontal: false, highlightActiveBracketPair: false, highlightActiveIndentation: false, indentation: false, }, vimInUse: false, }, this.settings, ), ); } override getPrintName() { return 'Compiler Output'; } override registerOpeningAnalyticsEvent(): void { ga.proxy('send', { hitType: 'event', eventCategory: 'OpenViewPane', eventAction: 'Compiler', }); } getEditorIdBySourcefile(sourcefile: Assembly['source']): number | null { if (this.sourceTreeId) { const tree = this.hub.getTreeById(this.sourceTreeId); if (tree) { return tree.multifileService.getEditorIdByFilename(sourcefile?.file ?? ''); } } else { if (sourcefile != null && (sourcefile.file === null || sourcefile.mainsource)) { return this.sourceEditorId; } } return null; } initLangAndCompiler(state: Pick): void { const langId = state.lang; const compilerId = state.compiler; const result = this.compilerService.processFromLangAndCompiler(langId ?? null, compilerId); this.compiler = result?.compiler ?? null; this.currentLangId = result?.langId ?? null; this.updateLibraries(); } override close(): void { codeLensHandler.unregister(this.id); this.eventHub.unsubscribe(); this.eventHub.emit('compilerClose', this.id, this.sourceTreeId ?? 0); this.editor.dispose(); this.compilerPicker.destroy(); } onCompiler(compilerId: number, compiler: unknown, options: string, editorId: number, treeId: number): void {} onCompileResult(compilerId: number, compiler: unknown, result: unknown): void {} // eslint-disable-next-line max-statements initPanerButtons(): void { const outputConfig = Components.getOutput(this.id, this.sourceEditorId ?? 0, this.sourceTreeId ?? 0); this.container.layoutManager.createDragSource(this.outputBtn, outputConfig); this.outputBtn.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(outputConfig); }); const cloneComponent = () => { const currentState: CompilerCurrentState = this.getCurrentState(); // Delete the saved id to force a new one delete currentState.id; // [flags|device]ViewOpen flags are a part of the state to prevent opening twice, // but do not pertain to the cloned compiler delete currentState.flagsViewOpen; delete currentState.deviceViewOpen; return { type: 'component', componentName: 'compiler', componentState: currentState, }; }; const createOptView = () => { return Components.getOptViewWith( this.id, this.source, this.lastResult?.optOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createStackUsageView = () => { return Components.getStackUsageViewWith( this.id, this.source, this.lastResult?.optOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createFlagsView = () => { return Components.getFlagsViewWith(this.id, this.getCompilerName(), this.optionsField.val()); }; if (this.flagsViewOpen) { createFlagsView(); } const createPpView = () => { return Components.getPpViewWith( this.id, this.source, this.lastResult?.ppOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createAstView = () => { return Components.getAstViewWith( this.id, this.source, this.lastResult?.astOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createIrView = () => { return Components.getIrViewWith( this.id, this.source, this.lastResult?.irOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createOptPipelineView = () => { const currentState = this.getCurrentState(); const langId = currentState.lang; const compilerId = currentState.compiler; return Components.getOptPipelineViewWith( this.id, langId ?? '', compilerId, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createDeviceView = () => { return Components.getDeviceViewWith( this.id, this.source, this.lastResult?.devices, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createRustMirView = () => { return Components.getRustMirViewWith( this.id, this.source, this.lastResult?.rustMirOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createRustMacroExpView = () => { return Components.getRustMacroExpViewWith( this.id, this.source, this.lastResult?.rustMacroExpOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createRustHirView = () => { return Components.getRustHirViewWith( this.id, this.source, this.lastResult?.rustHirOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createHaskellCoreView = () => { return Components.getHaskellCoreViewWith( this.id, this.source, this.lastResult?.haskellCoreOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createHaskellStgView = () => { return Components.getHaskellStgViewWith( this.id, this.source, this.lastResult?.haskellStgOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createHaskellCmmView = () => { return Components.getHaskellCmmViewWith( this.id, this.source, this.lastResult?.haskellCmmOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createGccDumpView = () => { return Components.getGccDumpViewWith( this.id, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, this.lastResult?.gccDumpOutput, ); }; const createGnatDebugTreeView = () => { return Components.getGnatDebugTreeViewWith( this.id, this.source, this.lastResult?.gnatDebugTreeOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createGnatDebugView = () => { return Components.getGnatDebugViewWith( this.id, this.source, this.lastResult?.gnatDebugOutput, this.getCompilerName(), this.sourceEditorId ?? 0, this.sourceTreeId ?? 0, ); }; const createCfgView = () => { return Components.getCfgViewWith(this.id, this.sourceEditorId ?? 0, this.sourceTreeId ?? 0); }; const createExecutor = () => { const currentState = this.getCurrentState(); const editorId = currentState.source; const treeId = currentState.tree; const langId = currentState.lang; const compilerId = currentState.compiler; const libs = this.libsWidget?.getLibsInUse().map(item => ({ name: item.libId, ver: item.versionId, })) ?? []; return Components.getExecutorWith( editorId ?? 0, langId ?? '', compilerId, libs, currentState.options, treeId ?? 0, currentState.overrides, currentState.runtimeTools, ); }; const newPaneDropdown = this.domRoot.find('.new-pane-dropdown'); const hidePaneAdder = () => { newPaneDropdown.dropdown('hide'); }; // Note that the .d.ts file lies in more than 1 way! // createDragSource returns the newly created DragSource // the second parameter can be a function that returns the config! this.container.layoutManager .createDragSource(this.domRoot.find('.btn.add-compiler'), cloneComponent as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.domRoot.find('.btn.add-compiler').on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(cloneComponent()); }); this.container.layoutManager .createDragSource(this.optButton, createOptView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.optButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createOptView()); }); this.container.layoutManager .createDragSource(this.stackUsageButton, createStackUsageView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.stackUsageButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createStackUsageView()); }); if (this.flagsButton) { const popularArgumentsMenu = this.domRoot.find('div.populararguments div.dropdown-menu'); this.container.layoutManager .createDragSource(this.flagsButton, createFlagsView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', () => popularArgumentsMenu.dropdown('hide')); this.flagsButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createFlagsView()); }); popularArgumentsMenu.append(this.flagsButton); } this.container.layoutManager .createDragSource(this.ppButton, createPpView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.ppButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createPpView()); }); this.container.layoutManager .createDragSource(this.astButton, createAstView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.astButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createAstView()); }); this.container.layoutManager .createDragSource(this.irButton, createIrView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.irButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createIrView()); }); this.container.layoutManager .createDragSource(this.optPipelineButton, createOptPipelineView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.optPipelineButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createOptPipelineView()); }); this.container.layoutManager .createDragSource(this.deviceButton, createDeviceView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.deviceButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createDeviceView()); }); this.container.layoutManager .createDragSource(this.rustMirButton, createRustMirView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.rustMirButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createRustMirView()); }); this.container.layoutManager .createDragSource(this.haskellCoreButton, createHaskellCoreView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.haskellCoreButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createHaskellCoreView()); }); this.container.layoutManager .createDragSource(this.haskellStgButton, createHaskellStgView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.haskellStgButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createHaskellStgView()); }); this.container.layoutManager .createDragSource(this.haskellCmmButton, createHaskellCmmView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.haskellCmmButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createHaskellCmmView()); }); this.container.layoutManager .createDragSource(this.rustMacroExpButton, createRustMacroExpView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.rustMacroExpButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createRustMacroExpView()); }); this.container.layoutManager .createDragSource(this.rustHirButton, createRustHirView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.rustHirButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createRustHirView()); }); this.container.layoutManager .createDragSource(this.gccDumpButton, createGccDumpView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.gccDumpButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createGccDumpView()); }); this.container.layoutManager .createDragSource(this.gnatDebugTreeButton, createGnatDebugTreeView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.gnatDebugTreeButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createGnatDebugTreeView()); }); this.container.layoutManager .createDragSource(this.gnatDebugButton, createGnatDebugView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.gnatDebugButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createGnatDebugView()); }); this.container.layoutManager .createDragSource(this.cfgButton, createCfgView as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.cfgButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createCfgView()); }); this.container.layoutManager .createDragSource(this.executorButton, createExecutor as any) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hidePaneAdder); this.executorButton.on('click', () => { const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createExecutor()); }); this.initToolButtons(); } undefer(): void { this.deferCompiles = false; if (this.needsCompile) { this.compile(); } } // Returns a label name if it can be found in the given position, otherwise // returns null. getLabelAtPosition(position: monaco.Position): any | null { const asmLine = this.assembly[position.lineNumber - 1]; // Outdated position.lineNumber can happen (Between compilations?) - Check for those and skip // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (asmLine) { const column = position.column; const labels = asmLine.labels || []; for (let i = 0; i < labels.length; ++i) { if (column >= labels[i].range.startCol && column < labels[i].range.endCol) { return labels[i]; } } } return null; } // Jumps to a label definition related to a label which was found in the // given position and highlights the given range. If no label can be found in // the given position it do nothing. jumpToLabel(position: monaco.Position): void { const label = this.getLabelAtPosition(position); if (!label) { return; } const labelDefLineNum = this.labelDefinitions[label.name]; if (!labelDefLineNum) { return; } // Highlight the new range. const endLineContent = this.editor.getModel()?.getLineContent(labelDefLineNum); this.pushRevealJump(); this.editor.setSelection( new monaco.Selection(labelDefLineNum, 0, labelDefLineNum, (endLineContent?.length ?? 0) + 1), ); // Jump to the given line. this.editor.revealLineInCenter(labelDefLineNum); } pushRevealJump(): void { this.revealJumpStack.push(this.editor.saveViewState()); this.revealJumpStackHasElementsCtxKey.set(true); } popAndRevealJump(): void { if (this.revealJumpStack.length > 0) { this.editor.restoreViewState(this.revealJumpStack.pop() ?? null); this.revealJumpStackHasElementsCtxKey.set(this.revealJumpStack.length > 0); } } override registerEditorActions(): void { this.isLabelCtxKey = this.editor.createContextKey('isLabel', true); this.revealJumpStackHasElementsCtxKey = this.editor.createContextKey('hasRevealJumpStackElements', false); this.isAsmKeywordCtxKey = this.editor.createContextKey('isAsmKeyword', true); this.lineHasLinkedSourceCtxKey = this.editor.createContextKey('lineHasLinkedSource', false); this.editor.addAction({ id: 'jumptolabel', label: 'Jump to label', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], precondition: 'isLabel', contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, run: ed => { const position = ed.getPosition(); if (position != null) { this.jumpToLabel(position); } }, }); // This returns a vscode's ContextMenuController, but that type is not exposed in Monaco const contextMenuContrib = this.editor.getContribution('editor.contrib.contextmenu'); // This is hacked this way to be able to update the precondition keys before the context menu is shown. // Right now Monaco does not expose a proper way to update those preconditions before the menu is shown, // because the editor.onContextMenu callback fires after it's been shown, so it's of little use here // The original source is src/vs/editor/contrib/contextmenu/browser/contextmenu.ts in vscode const originalOnContextMenu: ((e: IEditorMouseEvent) => void) | undefined = contextMenuContrib._onContextMenu; if (originalOnContextMenu) { contextMenuContrib._onContextMenu = (e: IEditorMouseEvent) => { if (e.target.position) { // Hiding the 'Jump to label' context menu option if no label can be found // in the clicked position. const label = this.getLabelAtPosition(e.target.position); this.isLabelCtxKey.set(label !== null); if (!this.compiler?.supportsAsmDocs) { // No need to show the "Show asm documentation" if it's just going to fail. // This is useful for things like xtensa which define an instructionSet but have no docs associated this.isAsmKeywordCtxKey.set(false); } else { const currentWord = this.editor.getModel()?.getWordAtPosition(e.target.position); if (currentWord?.word) { this.isAsmKeywordCtxKey.set( this.isWordAsmKeyword(e.target.position.lineNumber, currentWord), ); } } const prevLineNumber = e.target.position.lineNumber - 1; if (prevLineNumber >= 0 && prevLineNumber < this.assembly.length) { const lineSource = this.assembly[prevLineNumber].source; this.lineHasLinkedSourceCtxKey.set(lineSource != null && lineSource.line > 0); } // And call the original method now that we've updated the context keys originalOnContextMenu.apply(contextMenuContrib, [e]); } }; } else { // In case this ever stops working, we'll be notified SentryCapture(new Error('Context menu hack did not return valid original method')); } this.editor.addAction({ id: 'returnfromreveal', label: 'Return from reveal jump', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter], contextMenuGroupId: 'navigation', contextMenuOrder: 1.4, precondition: 'hasRevealJumpStackElements', run: () => { this.popAndRevealJump(); }, }); this.editor.addAction({ id: 'viewsource', label: 'Scroll to source', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10], keybindingContext: undefined, contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, precondition: 'lineHasLinkedSource', run: ed => { const position = ed.getPosition(); if (position != null) { const desiredLine = position.lineNumber - 1; const source = this.assembly[desiredLine].source; // The precondition ensures that this is always true, but lets not blindly belive it if (source && source.line > 0) { const editorId = this.getEditorIdBySourcefile(source); if (editorId) { // a null file means it was the user's source this.eventHub.emit('editorLinkLine', editorId, source.line, -1, -1, true); } } } }, }); this.editor.addAction({ id: 'viewasmdoc', label: 'View assembly documentation', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8], keybindingContext: undefined, precondition: 'isAsmKeyword', contextMenuGroupId: 'help', contextMenuOrder: 1.5, run: this.onAsmToolTip.bind(this), }); this.editor.addAction({ id: 'toggleColourisation', label: 'Toggle colourisation', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.F1], keybindingContext: undefined, run: () => { this.eventHub.emit('modifySettings', { colouriseAsm: !this.settings.colouriseAsm, }); }, }); this.editor.addAction({ id: 'dumpAsm', label: 'Developer: Dump asm', run: () => { // eslint-disable-next-line no-console console.log(this.assembly); }, }); } // Gets the filters that will actually be used (accounting for issues with binary // mode etc). getEffectiveFilters(): Partial { if (!this.compiler) return {}; const filters = this.filters.get(); if (filters.binaryObject && !this.compiler.supportsBinaryObject) { delete filters.binaryObject; } if (filters.binary && !this.compiler.supportsBinary) { delete filters.binary; } if (filters.execute && !this.compiler.supportsExecute) { delete filters.execute; } if (filters.libraryCode && !this.compiler.supportsLibraryCodeFilter) { delete filters.libraryCode; } if (filters.verboseDemangling && !this.compiler.supportsVerboseDemangling) { delete filters.verboseDemangling; } this.compiler.disabledFilters.forEach(filter => { if (filters[filter]) { delete filters[filter]; } }); return filters; } findTools(content: any, tools: ActiveTools[]): ActiveTools[] { if (content.componentName === 'tool') { if (content.componentState.id === this.id) { tools.push({ id: content.componentState.toolId, args: content.componentState.args, stdin: content.componentState.stdin, }); } } else if (content.content) { content.content.forEach(subcontent => { tools = this.findTools(subcontent, tools); }); } return tools; } getActiveTools(newToolSettings?: NewToolSettings): ActiveTools[] { if (!this.compiler) return []; const tools: ActiveTools[] = []; if (newToolSettings) { tools.push({ id: newToolSettings.toolId, args: newToolSettings.args, stdin: newToolSettings.stdin, }); } if (this.container.layoutManager.isInitialised) { const config = this.container.layoutManager.toConfig(); return this.findTools(config, tools); } else { return tools; } } isToolActive(activetools: ActiveTools[], toolId: number): ActiveTools | undefined { return activetools.find(tool => tool.id === toolId); } compile(bypassCache?: boolean, newTools?: NewToolSettings): void { if (this.deferCompiles) { this.needsCompile = true; return; } this.needsCompile = false; this.compileInfoLabel.text(' - Compiling...'); const options: CompilationRequestOptions = { userArguments: this.options, compilerOptions: { producePp: this.ppViewOpen ? this.ppOptions : null, produceAst: this.astViewOpen, produceGccDump: { opened: this.gccDumpViewOpen, pass: this.gccDumpPassSelected, treeDump: this.treeDumpEnabled, rtlDump: this.rtlDumpEnabled, ipaDump: this.ipaDumpEnabled, dumpFlags: this.dumpFlags, }, produceOptInfo: this.wantOptInfo ?? false, produceStackUsageInfo: this.stackUsageViewOpen, produceCfg: this.cfgViewOpenCount > 0 ? { asm: this.cfgViewOpenCount - this.irCfgViewOpenCount > 0, ir: this.irCfgViewOpenCount > 0, } : false, produceGnatDebugTree: this.gnatDebugTreeViewOpen, produceGnatDebug: this.gnatDebugViewOpen, produceIr: this.irViewOpen ? this.llvmIrOptions : null, produceOptPipeline: this.optPipelineViewOpenCount > 0 ? this.optPipelineOptions : null, produceDevice: this.deviceViewOpen, produceRustMir: this.rustMirViewOpen, produceRustMacroExp: this.rustMacroExpViewOpen, produceRustHir: this.rustHirViewOpen, produceHaskellCore: this.haskellCoreViewOpen, produceHaskellStg: this.haskellStgViewOpen, produceHaskellCmm: this.haskellCmmViewOpen, overrides: this.getCurrentState().overrides, }, filters: this.getEffectiveFilters(), tools: this.getActiveTools(newTools), libraries: this.libsWidget?.getLibsInUse().map(item => ({ id: item.libId, version: item.versionId, })) ?? [], executeParameters: { args: '', stdin: '', runtimeTools: this.getCurrentState().runtimeTools, }, }; if (this.sourceTreeId) { this.compileFromTree(options, bypassCache ?? false); } else { this.compileFromEditorSource(options, bypassCache ?? false); } } compileFromTree(options: CompilationRequestOptions, bypassCache: boolean): void { const tree = this.hub.getTreeById(this.sourceTreeId ?? 0); if (!tree) { this.sourceTreeId = null; this.compileFromEditorSource(options, bypassCache); return; } const request: CompilationRequest = { source: tree.multifileService.getMainSource(), compiler: this.compiler ? this.compiler.id : '', options: options, lang: this.currentLangId, files: tree.multifileService.getFiles(), bypassCache: BypassCache.None, }; const fetches: Promise[] = []; fetches.push( this.compilerService.expandToFiles(request.source).then((sourceAndFiles: SourceAndFiles) => { request.source = sourceAndFiles.source; request.files.push(...sourceAndFiles.files); }), ); const moreFiles: FiledataPair[] = []; for (let i = 0; i < request.files.length; i++) { const file = request.files[i]; fetches.push( this.compilerService.expandToFiles(file.contents).then((sourceAndFiles: SourceAndFiles) => { file.contents = sourceAndFiles.source; moreFiles.push(...sourceAndFiles.files); }), ); } Promise.all(fetches).then(() => { const treeState = tree.currentState(); const cmakeProject = tree.multifileService.isACMakeProject(); request.files.push(...moreFiles); if (bypassCache) request.bypassCache = BypassCache.Compilation; if (!this.compiler) { this.onCompileResponse(request, this.errorResult(''), false); } else if (cmakeProject && request.source === '') { this.onCompileResponse(request, this.errorResult(''), false); } else { if (cmakeProject) { request.options.compilerOptions.cmakeArgs = treeState.cmakeArgs; request.options.compilerOptions.customOutputFilename = treeState.customOutputFilename; this.sendCMakeCompile(request); } else { this.sendCompile(request); } } }); } compileFromEditorSource(options: CompilationRequestOptions, bypassCache: boolean) { this.compilerService.expandToFiles(this.source).then((sourceAndFiles: SourceAndFiles) => { const request: CompilationRequest = { source: sourceAndFiles.source || '', compiler: this.compiler ? this.compiler.id : '', options: options, lang: this.currentLangId, files: sourceAndFiles.files, bypassCache: BypassCache.None, }; if (bypassCache) request.bypassCache = BypassCache.Compilation; if (!this.compiler) { this.onCompileResponse(request, this.errorResult(''), false); } else { this.sendCompile(request); } }); } sendCMakeCompile(request: CompilationRequest) { if (this.pendingCMakeRequestSentAt) { // If we have a request pending, then just store this request to do once the // previous request completes. this.nextCMakeRequest = request; return; } if (this.compiler) this.eventHub.emit('compiling', this.id, this.compiler); // Display the spinner this.handleCompilationStatus({code: 4, compilerOut: 0}); this.pendingCMakeRequestSentAt = Date.now(); // After a short delay, give the user some indication that we're working on their // compilation. const progress = setTimeout(() => { this.setAssembly({asm: this.fakeAsm('')}, 0); }, 500); this.compilerService .submitCMake(request) .then((x: any) => { clearTimeout(progress); this.onCMakeResponse(request, x?.result, x?.localCacheHit ?? false); }) .catch(x => { clearTimeout(progress); let message = 'Unknown error'; if (typeof x === 'string' || x instanceof String) { message = x.toString(); } else if (x) { message = x.error || x.code || message; } this.onCMakeResponse(request, this.errorResult(''), false); }); } sendCompile(request: CompilationRequest) { const onCompilerResponse = this.onCompileResponse.bind(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; } if (this.compiler) this.eventHub.emit('compiling', this.id, this.compiler); // Display the spinner this.handleCompilationStatus({code: 4, compilerOut: 0}); this.pendingRequestSentAt = Date.now(); // After a short delay, give the user some indication that we're working on their // compilation. const progress = setTimeout(() => { this.setAssembly({asm: this.fakeAsm('')}, 0); }, 500); this.compilerService .submit(request) .then((x: any) => { clearTimeout(progress); onCompilerResponse(request, x.result, x.localCacheHit); }) .catch(e => { clearTimeout(progress); let message = 'Unknown error'; if (typeof e === 'string' || e instanceof String) { message = e.toString(); } else if (e) { message = e.error || e.code || e.message; if (e.stack) { // eslint-disable-next-line no-console console.log(e); } } onCompilerResponse(request, this.errorResult(''), false); }); } setNormalMargin(): void { this.editor.updateOptions({ lineNumbers: 'on', lineNumbersMinChars: 1, }); } setBinaryMargin(): void { this.editor.updateOptions({ lineNumbersMinChars: 6, lineNumbers: this.getBinaryForLine.bind(this), }); } getBinaryForLine(line: number): string { const obj = this.assembly[line - 1]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (obj) { return obj.address != null ? obj.address.toString(16) : ''; } else { return '???'; } } setAssembly(result: Partial, filteredCount = 0) { this.recentInstructionSet = result.instructionSet || null; const asm = result.asm || this.fakeAsm(''); this.assembly = asm; if (!this.editor.getModel()) return; const editorModel = this.editor.getModel(); if (editorModel) { if (result.languageId) { monaco.editor.setModelLanguage(editorModel, result.languageId); } else { let monacoDisassembly = 'asm'; if (this.currentLangId && this.currentLangId in languages) { // TS compiler trips if you try to fold this condition in one if const disasam = languages[this.currentLangId].monacoDisassembly; if (disasam !== null) { monacoDisassembly = disasam; } } monaco.editor.setModelLanguage(editorModel, monacoDisassembly); } } let msg = ''; if (asm.length) { msg = _.pluck(asm, 'text').join('\n'); } else if (filteredCount > 0) { msg = ''; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (asm.length === 1 && result.code !== 0 && (result.stderr || result.stdout)) { msg += '\n\n# For more information see the output window'; if (!this.isOutputOpened) { msg += '\n# To open the output window, click or drag the "Output" icon at the bottom of this window'; } } editorModel?.setValue(msg); if (!this.awaitingInitialResults) { if (this.selection) { this.editor.setSelection(this.selection); this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber); } this.awaitingInitialResults = true; } else { const visibleRanges = this.editor.getVisibleRanges(); const currentTopLine = visibleRanges.length > 0 ? visibleRanges[0].startLineNumber : 1; this.editor.revealLine(currentTopLine); } this.decorations.labelUsages = []; this.assembly.forEach((obj, line) => { if (!obj.labels || !obj.labels.length) return; obj.labels.forEach(label => { this.decorations.labelUsages.push({ range: new monaco.Range(line + 1, label.range.startCol, line + 1, label.range.endCol), options: { inlineClassName: 'asm-label-link', hoverMessage: [ { value: 'Ctrl + Left click to follow the label', }, ], }, }); }); }); this.updateDecorations(); const codeLenses: monaco.languages.CodeLens[] = []; const effectiveFilters = this.getEffectiveFilters(); if (effectiveFilters.binary || effectiveFilters.binaryObject || result.forceBinaryView) { this.setBinaryMargin(); this.assembly.forEach((obj, line) => { if (obj.opcodes) { const address = obj.address ? obj.address.toString(16) : ''; codeLenses.push({ range: { startLineNumber: line + 1, startColumn: 1, endLineNumber: line + 2, endColumn: 1, }, id: address, command: { title: obj.opcodes.join(' '), } as any, // This any cast fixes a bug }); } }); } else { this.setNormalMargin(); } if (this.settings.enableCodeLens) { if (editorModel) { codeLensHandler.registerLensesForCompiler(this.id, editorModel, codeLenses); const currentAsmLang = editorModel.getLanguageId(); codeLensHandler.registerProviderForLanguage(currentAsmLang); } } else { // Make sure the codelens is disabled codeLensHandler.unregister(this.id); } } private errorResult(text: string): CompilationResult { return {timedOut: false, asm: this.fakeAsm(text), code: -1, stdout: [], stderr: []}; } // TODO: Figure out if this is ResultLine or Assembly private fakeAsm(text: string): ResultLine[] { return [{text: text, fake: true} as ResultLine]; } doNextCompileRequest(): void { if (this.nextRequest) { const next = this.nextRequest; this.nextRequest = null; this.sendCompile(next); } } doNextCMakeRequest(): void { if (this.nextCMakeRequest) { const next = this.nextCMakeRequest; this.nextCMakeRequest = null; this.sendCMakeCompile(next); } } onCMakeResponse(request: any, result: any, cached: boolean) { result.source = this.source; this.lastResult = result; const timeTaken = Math.max(0, Date.now() - this.pendingCMakeRequestSentAt); this.lastTimeTaken = timeTaken; const wasRealReply = this.pendingCMakeRequestSentAt > 0; this.pendingCMakeRequestSentAt = 0; this.handleCompileRequestAndResult(request, result, cached, wasRealReply, timeTaken); this.doNextCMakeRequest(); } handleCompileRequestAndResult( request: any, result: any, cached: boolean, wasRealReply: boolean, timeTaken: number, ) { 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, }); // Delete trailing empty lines if (Array.isArray(result.asm)) { const indexToDiscard = _.findLastIndex(result.asm, line => { return !_.isEmpty(line.text); }); result.asm.splice(indexToDiscard + 1, result.asm.length - indexToDiscard); } this.labelDefinitions = result.labelDefinitions || {}; if (result.asm) { this.setAssembly(result, result.filteredCount || 0); } else if (result.result && result.result.asm) { this.setAssembly(result.result, result.result.filteredCount || 0); } else { result.asm = this.fakeAsm(''); this.setAssembly(result, 0); } let stdout = result.stdout || []; let stderr = result.stderr || []; let failed: boolean = result.code ? result.code !== 0 : false; if (result.buildsteps) { result.buildsteps.forEach(step => { stdout = stdout.concat(step.stdout || []); stderr = stderr.concat(step.stderr || []); failed = failed || step.code !== 0; }); } this.outputTextCount.text(stdout.length); this.outputErrorCount.text(stderr.length); if (this.isOutputOpened || (stdout.length === 0 && stderr.length === 0)) { this.outputBtn.prop('title', ''); } else { CompilerService.handleOutputButtonTitle(this.outputBtn, result); } let infoLabelText = ''; if (cached) { infoLabelText = ' - cached'; } else if (wasRealReply) { infoLabelText = ' - ' + timeTaken + 'ms'; } if (result.asmSize) { infoLabelText += ' (' + result.asmSize + 'B)'; } if (result.filteredCount && result.filteredCount > 0) { infoLabelText += ' ~' + result.filteredCount + (result.filteredCount === 1 ? ' line' : ' lines') + ' filtered'; } this.compileInfoLabel.text(infoLabelText); if (result.result) { const wasCmake = result.buildsteps && result.buildsteps.some(step => { return step.step === 'cmake'; }); this.postCompilationResult(request, result.result, wasCmake); } else { this.postCompilationResult(request, result); } if ( this.compiler?.supportsDeviceAsmView && !this.deviceViewOpen && result.devices && Object.keys(result.devices).length > 0 ) { this.deviceButton.trigger('click'); } if (this.compiler) this.eventHub.emit('compileResult', this.id, this.compiler, result, languages[this.currentLangId ?? '']); } onCompileResponse(request: any, result: any, cached: boolean): void { // Save which source produced this change. It should probably be saved earlier though result.source = this.source; this.lastResult = result; const timeTaken = Math.max(0, Date.now() - this.pendingRequestSentAt); this.lastTimeTaken = timeTaken; const wasRealReply = this.pendingRequestSentAt > 0; this.pendingRequestSentAt = 0; this.handleCompileRequestAndResult(request, result, cached, wasRealReply, timeTaken); this.doNextCompileRequest(); } postCompilationResult(request: any, result: any, wasCmake?: boolean): void { if (result.popularArguments) { this.handlePopularArgumentsResult(result.popularArguments); } else if (this.compiler) { this.compilerService .requestPopularArguments(this.compiler.id, request.options.userArguments) .then((result: any) => { if (result && result.result) { this.handlePopularArgumentsResult(result.result); } }); } this.updateButtons(); this.handleCompilationStatus(CompilerService.calculateStatusIcon(result)); const warnings = this.checkForUnwiseArguments(result.compilationOptions, wasCmake ?? false); this.setCompilationOptionsPopover( result.compilationOptions ? result.compilationOptions.join(' ') : '', warnings, ); this.checkForHints(result); this.offerFilesIfPossible(result); } offerFilesIfPossible(result: CompilationResult) { if (result.artifacts) { for (const artifact of result.artifacts) { if (artifact.type === ArtifactType.nesrom) { this.emulateNESROM(artifact.content); } else if (artifact.type === ArtifactType.bbcdiskimage) { this.emulateBbcDisk(artifact.content); } else if (artifact.type === ArtifactType.zxtape) { this.emulateSpeccyTape(artifact.content); } else if (artifact.type === ArtifactType.smsrom) { this.emulateMiracleSMS(artifact.content); } else if (artifact.type === ArtifactType.timetrace) { this.offerViewInSpeedscope(artifact); } else if (artifact.type === ArtifactType.c64prg) { this.emulateC64Prg(artifact); } else if (artifact.type === ArtifactType.heaptracktxt) { this.offerViewInSpeedscope(artifact); } } } } offerViewInSpeedscope(artifact: Artifact): void { this.alertSystem.notify( 'Click ' + 'here' + ' to view ' + artifact.title + ' in Speedscope', { group: artifact.type, collapseSimilar: false, dismissTime: 10000, onBeforeShow: function (elem) { elem.find('#download_link').on('click', () => { const tmstr = Date.now(); const live_url = 'https://static.ce-cdn.net/speedscope/index.html'; const speedscope_url = live_url + '?' + tmstr + '#customFilename=' + artifact.name + '&b64data=' + artifact.content; window.open(speedscope_url); }); }, }, ); } offerViewInPerfetto(artifact: Artifact): void { this.alertSystem.notify( 'Click ' + 'here' + ' to view ' + artifact.title + ' in Perfetto', { group: artifact.type, collapseSimilar: false, dismissTime: 10000, onBeforeShow: function (elem) { elem.find('#download_link').on('click', () => { const perfetto_url = 'https://ui.perfetto.dev'; const win = window.open(perfetto_url); if (win) { const timer = setInterval(() => win.postMessage('PING', perfetto_url), 50); const onMessageHandler = evt => { if (evt.data !== 'PONG') return; clearInterval(timer); const data = { perfetto: { buffer: Buffer.from(artifact.content, 'base64'), title: artifact.name, filename: artifact.name, }, }; win.postMessage(data, perfetto_url); }; window.addEventListener('message', onMessageHandler); } }); }, }, ); } emulateMiracleSMS(image: string): void { const dialog = $('#miracleemu'); this.alertSystem.notify( 'Click ' + 'here' + ' to emulate', { group: 'emulation', collapseSimilar: true, dismissTime: 10000, onBeforeShow: function (elem) { elem.find('#miracle_emulink').on('click', () => { dialog.modal(); const miracleMenuFrame = dialog.find('#miracleemuframe')[0]; assert(miracleMenuFrame instanceof HTMLIFrameElement); if ('contentWindow' in miracleMenuFrame) { const emuwindow = unwrap(miracleMenuFrame.contentWindow); const tmstr = Date.now(); emuwindow.location = 'https://xania.org/miracle/miracle.html?' + tmstr + '#b64sms=' + image; } }); }, }, ); } emulateSpeccyTape(image: string): void { const dialog = $('#jsspeccyemu'); this.alertSystem.notify( 'Click ' + 'here' + ' to emulate', { group: 'emulation', collapseSimilar: true, dismissTime: 10000, onBeforeShow: elem => { elem.find('#jsspeccy_emulink').on('click', () => { dialog.modal(); const speccyemuframe = dialog.find('#speccyemuframe')[0]; assert(speccyemuframe instanceof HTMLIFrameElement); if ('contentWindow' in speccyemuframe) { const emuwindow = unwrap(speccyemuframe.contentWindow); const tmstr = Date.now(); emuwindow.location = 'https://static.ce-cdn.net/jsspeccy/index.html?' + tmstr + '#b64tape=' + image; } }); }, }, ); } emulateBbcDisk(bbcdiskimage: string): void { const dialog = $('#jsbeebemu'); this.alertSystem.notify( 'Click here to emulate', { group: 'emulation', collapseSimilar: true, dismissTime: 10000, onBeforeShow: elem => { elem.find('#emulink').on('click', () => { dialog.modal(); const jsbeebemuframe = dialog.find('#jsbeebemuframe')[0]; assert(jsbeebemuframe instanceof HTMLIFrameElement); if ('contentWindow' in jsbeebemuframe) { const emuwindow = unwrap(jsbeebemuframe.contentWindow); const tmstr = Date.now(); emuwindow.location = 'https://bbc.godbolt.org/?' + tmstr + '#embed&autoboot&disc1=b64data:' + bbcdiskimage; } }); }, }, ); } emulateNESROM(nesrom: string): void { const dialog = $('#jsnesemu'); this.alertSystem.notify( 'Click here to emulate', { group: 'emulation', collapseSimilar: true, dismissTime: 10000, onBeforeShow: function (elem) { elem.find('#emulink').on('click', () => { dialog.modal(); const jsnesemuframe = dialog.find('#jsnesemuframe')[0]; assert(jsnesemuframe instanceof HTMLIFrameElement); if ('contentWindow' in jsnesemuframe) { const emuwindow = unwrap(jsnesemuframe.contentWindow); const tmstr = Date.now(); emuwindow.location = 'https://static.ce-cdn.net/jsnes-ceweb/index.html?' + tmstr + '#b64nes=' + nesrom; } }); }, }, ); } emulateC64Prg(prg: Artifact): void { this.alertSystem.notify( 'Click here to emulate', { group: 'emulation', collapseSimilar: true, dismissTime: 10000, onBeforeShow: function (elem) { elem.find('#emulink').on('click', () => { const tmstr = Date.now(); const url = 'https://static.ce-cdn.net/viciious/viciious.html?' + tmstr + '#filename=' + prg.title + '&b64c64=' + prg.content; window.open(url, '_blank'); }); }, }, ); } onEditorChange(editor: number, source: string, langId: string, compilerId?: number): void { if (this.sourceTreeId) { const tree = this.hub.getTreeById(this.sourceTreeId); if (tree) { if (tree.multifileService.isEditorPartOfProject(editor)) { if (this.settings.compileOnChange) { this.compile(); return; } } } } if ( editor === this.sourceEditorId && langId === this.currentLangId && (compilerId === undefined || compilerId === this.id) ) { this.source = source; if (this.settings.compileOnChange) { this.compile(); } } } onCompilerFlagsChange(compilerId: number, compilerFlags: string): void { if (compilerId === this.id) { this.onOptionsChange(compilerFlags); } } onToolOpened(compilerId: number, toolSettings: any): void { if (this.id === compilerId) { const toolId = toolSettings.toolId; const buttons = this.toolsMenu?.find('button'); if (buttons) $(buttons).each((idx, button) => { const toolButton = $(button); const toolName = toolButton.data('toolname'); if (toolId === toolName) { toolButton.prop('disabled', true); } }); this.compile(false, toolSettings); } } onToolClosed(compilerId: number, toolSettings: any): void { if (this.id === compilerId) { const toolId = toolSettings.toolId; const buttons = this.toolsMenu?.find('button'); if (buttons) $(buttons).each((idx, button) => { const toolButton = $(button); const toolName = toolButton.data('toolname'); if (toolId === toolName) { toolButton.prop('disabled', !this.supportsTool(toolId)); } }); } } onOutputOpened(compilerId: number): void { if (this.id === compilerId) { this.isOutputOpened = true; this.outputBtn.prop('disabled', true); this.resendResult(); } } onOutputClosed(compilerId: number): void { if (this.id === compilerId) { this.isOutputOpened = false; this.outputBtn.prop('disabled', false); } } onOptViewClosed(id: number): void { if (this.id === id) { this.wantOptInfo = false; this.optViewOpen = false; this.optButton.prop('disabled', this.optViewOpen); } } onStackUsageViewClosed(id: number): void { if (this.id === id) { this.stackUsageViewOpen = false; this.stackUsageButton.prop('disabled', this.stackUsageViewOpen); } } onFlagsViewClosed(id: number, compilerFlags: string): void { if (this.id === id) { this.flagsViewOpen = false; this.optionsField.val(compilerFlags); this.optionsField.prop('disabled', this.flagsViewOpen); this.optionsField.prop('placeholder', this.initialOptionsFieldPlacehoder); this.flagsButton?.prop('disabled', this.flagsViewOpen); this.compilerService.requestPopularArguments(this.compiler?.id ?? '', compilerFlags).then((result: any) => { if (result && result.result) { this.handlePopularArgumentsResult(result.result); } }); this.updateState(); } } onCompilerOverridesChange(): void { this.updateState(); if (this.settings.compileOnChange) { this.compile(); } } onToolSettingsChange(id: number): void { if (this.id === id) { this.compile(); } } onPpViewOpened(id: number): void { if (this.id === id) { this.ppButton.prop('disabled', true); this.ppViewOpen = true; // the pp view will request compilation once it populates its options so this.compile() is not called here } } onPpViewClosed(id: number): void { if (this.id === id) { this.ppButton.prop('disabled', false); this.ppViewOpen = false; } } onPpViewOptionsUpdated(id: number, options: PPOptions, reqCompile?: boolean): void { if (this.id === id) { this.ppOptions = options; if (reqCompile) { this.compile(); } } } onAstViewOpened(id: number): void { if (this.id === id) { this.astButton.prop('disabled', true); this.astViewOpen = true; this.compile(); } } onAstViewClosed(id: number): void { if (this.id === id) { this.astButton.prop('disabled', false); this.astViewOpen = false; } } onIrViewOpened(id: number): void { if (this.id === id) { this.irButton.prop('disabled', true); this.irViewOpen = true; this.updateDebugCallsFilter(); this.compile(); } } onIrViewClosed(id: number): void { if (this.id === id) { this.irButton.prop('disabled', false); this.irViewOpen = false; this.updateDebugCallsFilter(); } } onLLVMIrViewOptionsUpdated(id: number, options: LLVMIrBackendOptions, recompile: boolean): void { if (this.id === id) { this.llvmIrOptions = options; if (recompile) { this.compile(); } } } onOptPipelineViewOpened(id: number): void { if (this.id === id) { this.optPipelineViewOpenCount++; this.compile(); } } onOptPipelineViewClosed(id: number): void { if (this.id === id) { this.optPipelineViewOpenCount--; } } onOptPipelineViewOptionsUpdated(id: number, options: OptPipelineBackendOptions, recompile: boolean): void { if (this.id === id) { this.optPipelineOptions = options; if (recompile) { this.compile(); } } } onDeviceViewOpened(id: number): void { if (this.id === id) { this.deviceButton.prop('disabled', true); this.deviceViewOpen = true; this.updateState(); this.compile(); } } onDeviceViewClosed(id: number): void { if (this.id === id) { this.deviceButton.prop('disabled', false); this.deviceViewOpen = false; this.updateState(); } } onRustMirViewOpened(id: number): void { if (this.id === id) { this.rustMirButton.prop('disabled', true); this.rustMirViewOpen = true; this.compile(); } } onRustMirViewClosed(id: number): void { if (this.id === id) { this.rustMirButton.prop('disabled', false); this.rustMirViewOpen = false; } } onHaskellCoreViewOpened(id: number): void { if (this.id === id) { this.haskellCoreButton.prop('disabled', true); this.haskellCoreViewOpen = true; this.compile(); } } onHaskellCoreViewClosed(id: number): void { if (this.id === id) { this.haskellCoreButton.prop('disabled', false); this.haskellCoreViewOpen = false; } } onHaskellStgViewOpened(id: number): void { if (this.id === id) { this.haskellStgButton.prop('disabled', true); this.haskellStgViewOpen = true; this.compile(); } } onHaskellStgViewClosed(id: number): void { if (this.id === id) { this.haskellStgButton.prop('disabled', false); this.haskellStgViewOpen = false; } } onHaskellCmmViewOpened(id: number): void { if (this.id === id) { this.haskellCmmButton.prop('disabled', true); this.haskellCmmViewOpen = true; this.compile(); } } onHaskellCmmViewClosed(id: number): void { if (this.id === id) { this.haskellCmmButton.prop('disabled', false); this.haskellCmmViewOpen = false; } } onGnatDebugTreeViewOpened(id: number): void { if (this.id === id) { this.gnatDebugTreeButton.prop('disabled', true); this.gnatDebugTreeViewOpen = true; this.compile(); } } onGnatDebugTreeViewClosed(id: number): void { if (this.id === id) { this.gnatDebugTreeButton.prop('disabled', false); this.gnatDebugTreeViewOpen = false; } } onGnatDebugViewOpened(id: number): void { if (this.id === id) { this.gnatDebugButton.prop('disabled', true); this.gnatDebugViewOpen = true; this.compile(); } } onGnatDebugViewClosed(id: number): void { if (this.id === id) { this.gnatDebugButton.prop('disabled', false); this.gnatDebugViewOpen = false; } } onRustMacroExpViewOpened(id: number): void { if (this.id === id) { this.rustMacroExpButton.prop('disabled', true); this.rustMacroExpViewOpen = true; this.compile(); } } onRustMacroExpViewClosed(id: number): void { if (this.id === id) { this.rustMacroExpButton.prop('disabled', false); this.rustMacroExpViewOpen = false; } } onRustHirViewOpened(id: number): void { if (this.id === id) { this.rustHirButton.prop('disabled', true); this.rustHirViewOpen = true; this.compile(); } } onRustHirViewClosed(id: number): void { if (this.id === id) { this.rustHirButton.prop('disabled', false); this.rustHirViewOpen = false; } } onGccDumpUIInit(id: number): void { if (this.id === id) { this.compile(); } } onGccDumpFiltersChanged(id: number, dumpOpts: GccDumpFiltersState, reqCompile: boolean): void { if (this.id === id) { this.treeDumpEnabled = dumpOpts.treeDump; this.rtlDumpEnabled = dumpOpts.rtlDump; this.ipaDumpEnabled = dumpOpts.ipaDump; this.dumpFlags = { gimpleFe: dumpOpts.gimpleFeOption, address: dumpOpts.addressOption, slim: dumpOpts.slimOption, raw: dumpOpts.rawOption, details: dumpOpts.detailsOption, stats: dumpOpts.statsOption, blocks: dumpOpts.blocksOption, vops: dumpOpts.vopsOption, lineno: dumpOpts.linenoOption, uid: dumpOpts.uidOption, all: dumpOpts.allOption, }; if (reqCompile) { this.compile(); } } } onGccDumpPassSelected(id: number, passObject?: GccDumpViewSelectedPass, reqCompile?: boolean) { if (this.id === id) { this.gccDumpPassSelected = passObject; if (reqCompile && passObject != null) { this.compile(); } } } onGccDumpViewOpened(id: number): void { if (this.id === id) { this.gccDumpButton.prop('disabled', true); this.gccDumpViewOpen = true; } } onGccDumpViewClosed(id: number): void { if (this.id === id) { this.gccDumpButton.prop('disabled', !this.compiler?.supportsGccDump); this.gccDumpViewOpen = false; delete this.gccDumpPassSelected; delete this.treeDumpEnabled; delete this.rtlDumpEnabled; delete this.ipaDumpEnabled; delete this.dumpFlags; } } onOptViewOpened(id: number): void { if (this.id === id) { this.optViewOpen = true; this.wantOptInfo = true; this.optButton.prop('disabled', this.optViewOpen); this.compile(); } } onStackUsageViewOpened(id: number): void { if (this.id === id) { this.stackUsageViewOpen = true; this.stackUsageButton.prop('disabled', this.stackUsageViewOpen); this.compile(); } } onFlagsViewOpened(id: number): void { if (this.id === id) { this.flagsViewOpen = true; this.handlePopularArgumentsResult(null); this.optionsField.prop('disabled', this.flagsViewOpen); this.optionsField.val(''); this.optionsField.prop('placeholder', 'see detailed flags window'); this.flagsButton?.prop('disabled', this.flagsViewOpen); this.compile(); this.updateState(); } } onCfgViewOpened(id: number, isircfg: boolean): void { if (this.id === id) { this.cfgViewOpenCount++; if (isircfg) { this.irCfgViewOpenCount++; } this.compile(); } } onCfgViewClosed(id: number, isircfg: boolean): void { if (this.id === id) { this.cfgViewOpenCount--; if (isircfg) { this.irCfgViewOpenCount--; } if (this.irCfgViewOpenCount < 0) { // sometinhg has gone terribly wrong this.irCfgViewOpenCount = 0; } } } initFilterButtons(): void { this.filterBinaryObjectButton = this.domRoot.find("[data-bind='binaryObject']"); this.filterBinaryObjectTitle = this.filterBinaryObjectButton.prop('title'); this.filterBinaryButton = this.domRoot.find("[data-bind='binary']"); this.filterBinaryTitle = this.filterBinaryButton.prop('title'); this.filterExecuteButton = this.domRoot.find("[data-bind='execute']"); this.filterExecuteTitle = this.filterExecuteButton.prop('title'); this.filterLabelsButton = this.domRoot.find("[data-bind='labels']"); this.filterLabelsTitle = this.filterLabelsButton.prop('title'); this.filterDirectivesButton = this.domRoot.find("[data-bind='directives']"); this.filterDirectivesTitle = this.filterDirectivesButton.prop('title'); this.filterLibraryCodeButton = this.domRoot.find("[data-bind='libraryCode']"); this.filterLibraryCodeTitle = this.filterLibraryCodeButton.prop('title'); this.filterCommentsButton = this.domRoot.find("[data-bind='commentOnly']"); this.filterCommentsTitle = this.filterCommentsButton.prop('title'); this.filterTrimButton = this.domRoot.find("[data-bind='trim']"); this.filterTrimTitle = this.filterTrimButton.prop('title'); this.filterDebugCallsButton = this.domRoot.find("[data-bind='debugCalls']"); this.filterDebugCallsTitle = this.filterDebugCallsButton.prop('title'); this.filterIntelButton = this.domRoot.find("[data-bind='intel']"); this.filterIntelTitle = this.filterIntelButton.prop('title'); this.filterDemangleButton = this.domRoot.find("[data-bind='demangle']"); this.filterDemangleTitle = this.filterDemangleButton.prop('title'); this.filterVerboseDemanglingButton = this.domRoot.find("[data-bind='verboseDemangling']"); this.filterVerboseDemanglingTitle = this.filterVerboseDemanglingButton.prop('title'); this.noBinaryFiltersButtons = this.domRoot.find('.nonbinary'); } override registerButtons(state) { super.registerButtons(state); this.filters = new Toggles(this.domRoot.find('.filters'), patchOldFilters(state.filters)); this.optButton = this.domRoot.find('.btn.view-optimization'); this.stackUsageButton = this.domRoot.find('.btn.view-stack-usage'); this.flagsButton = this.domRoot.find('div.populararguments div.dropdown-menu button'); this.ppButton = this.domRoot.find('.btn.view-pp'); this.astButton = this.domRoot.find('.btn.view-ast'); this.irButton = this.domRoot.find('.btn.view-ir'); this.optPipelineButton = this.domRoot.find('.btn.view-opt-pipeline'); this.deviceButton = this.domRoot.find('.btn.view-device'); this.gnatDebugTreeButton = this.domRoot.find('.btn.view-gnatdebugtree'); this.gnatDebugButton = this.domRoot.find('.btn.view-gnatdebug'); this.rustMirButton = this.domRoot.find('.btn.view-rustmir'); this.rustMacroExpButton = this.domRoot.find('.btn.view-rustmacroexp'); this.rustHirButton = this.domRoot.find('.btn.view-rusthir'); this.haskellCoreButton = this.domRoot.find('.btn.view-haskellCore'); this.haskellStgButton = this.domRoot.find('.btn.view-haskellStg'); this.haskellCmmButton = this.domRoot.find('.btn.view-haskellCmm'); this.gccDumpButton = this.domRoot.find('.btn.view-gccdump'); this.cfgButton = this.domRoot.find('.btn.view-cfg'); this.executorButton = this.domRoot.find('.create-executor'); this.libsButton = this.domRoot.find('.btn.show-libs'); this.compileInfoLabel = this.domRoot.find('.compile-info'); this.compileClearCache = this.domRoot.find('.clear-cache'); this.outputBtn = this.domRoot.find('.output-btn'); this.outputTextCount = this.domRoot.find('span.text-count'); this.outputErrorCount = this.domRoot.find('span.err-count'); this.optionsField = this.domRoot.find('.options'); this.initialOptionsFieldPlacehoder = this.optionsField.prop('placeholder'); this.prependOptions = this.domRoot.find('.prepend-options'); this.fullCompilerName = this.domRoot.find('.full-compiler-name'); this.fullTimingInfo = this.domRoot.find('.full-timing-info'); this.compilerLicenseButton = this.domRoot.find('.compiler-license'); this.setCompilationOptionsPopover(this.compiler ? this.compiler.options : null, []); this.initFilterButtons(); this.filterExecuteButton.toggle(options.supportsExecute); this.filterLibraryCodeButton.toggle(options.supportsLibraryCodeFilter); this.optionsField.val(this.options); this.shortCompilerName = this.domRoot.find('.short-compiler-name'); this.compilerPickerElement = 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.monacoPlaceholder = this.domRoot.find('.monaco-placeholder'); $(this.domRoot).on('keydown', event => { if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 's') { event.preventDefault(); if (this.assembly.length > 0) { const texts = this.assembly.map(asm => asm.text ?? ''); const blob = new Blob([texts.join('\n')], {type: 'text/plain;charset=utf-8'}); const fileName = this.getLanguageName() + '-' + this.getCompilerName() + '-' + this.id + '.asm'; fileSaver.saveAs(blob, fileName); } } }); } onLibsChanged(): void { this.updateState(); this.compile(); } initLibraries(state: WidgetState): void { this.libsWidget = new LibsWidget( this.currentLangId ?? '', this.compiler, this.libsButton, state, this.onLibsChanged.bind(this), LibUtils.getSupportedLibraries( this.compiler ? this.compiler.libsArr : [], this.currentLangId ?? '', this.compiler?.remote, ), ); } updateLibraries(): void { if (this.libsWidget) { let filteredLibraries: LanguageLibs = {}; if (this.compiler) { filteredLibraries = LibUtils.getSupportedLibraries( this.compiler.libsArr, this.currentLangId ?? '', this.compiler.remote, ); } this.libsWidget.setNewLangId(this.currentLangId ?? '', this.compiler?.id ?? '', filteredLibraries); } } isSupportedTool(tool: Tool): boolean { if (this.sourceTreeId) { return tool.tool.type === 'postcompilation'; } else { return true; } } supportsTool(toolId: string): boolean { if (!this.compiler) return false; return !!Object.values(this.compiler.tools).find(tool => { return tool.tool.id === toolId && this.isSupportedTool(tool); }); } initToolButton(hideToolDropdown: () => void, button: JQuery, toolId: string): void { const createToolView: () => ComponentConfig = () => { let args = ''; let monacoStdin = false; const langTools = options.tools[this.currentLangId ?? '']; if (langTools && langTools[toolId] && langTools[toolId].tool) { if (langTools[toolId].tool.args !== undefined) { args = langTools[toolId].tool.args; } if (langTools[toolId].tool.monacoStdin !== undefined) { monacoStdin = langTools[toolId].tool.monacoStdin; } } return Components.getToolViewWith( this.id, this.getCompilerName(), this.sourceEditorId ?? 0, toolId, args, monacoStdin, this.sourceTreeId ?? 0, ); }; this.container.layoutManager .createDragSource(button, createToolView()) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ._dragListener.on('dragStart', hideToolDropdown); button.on('click', () => { button.prop('disabled', true); const insertPoint = this.hub.findParentRowOrColumn(this.container.parent) || this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(createToolView()); }); } initToolButtons(): void { this.toolsMenu = this.domRoot.find('.new-tool-dropdown'); const hideToolDropdown = () => { this.toolsMenu?.dropdown('hide'); }; this.toolsMenu.empty(); if (!this.compiler) return; const addTool = (toolName: string, title: string, toolIcon?, toolIconDark?) => { const btn = $("