| // Copyright (c) 2019, Compiler Explorer Authors |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above copyright |
| // notice, this list of conditions and the following disclaimer in the |
| // documentation and/or other materials provided with the distribution. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| // POSSIBILITY OF SUCH DAMAGE. |
| |
| import _ from 'underscore'; |
| import $ from 'jquery'; |
| import {ga} from '../analytics'; |
| import {Toggles} from '../widgets/toggles'; |
| import {FontScale} from '../widgets/fontscale'; |
| import {options} from '../options'; |
| import {Alert} from '../widgets/alert'; |
| import {LibsWidget} from '../widgets/libs-widget'; |
| import {Filter as AnsiToHtml} from '../ansi-to-html'; |
| import * as TimingWidget from '../widgets/timing-info-widget'; |
| import {Settings, SiteSettings} from '../settings'; |
| import * as utils from '../utils'; |
| import * as LibUtils from '../lib-utils'; |
| import {PaneRenaming} from '../widgets/pane-renaming'; |
| import {CompilerService} from '../compiler-service'; |
| import {Pane} from './pane'; |
| import {Hub} from '../hub'; |
| import {Container} from 'golden-layout'; |
| import {PaneState} from './pane.interfaces'; |
| import {ExecutorState} from './executor.interfaces'; |
| import {CompilerInfo} from '../../types/compiler.interfaces'; |
| import {Language} from '../../types/languages.interfaces'; |
| import {LanguageLibs} from '../options.interfaces'; |
| import {LLVMOptPipelineBackendOptions} from '../../types/compilation/llvm-opt-pipeline-output.interfaces'; |
| import {PPOptions} from './pp-view.interfaces'; |
| import {FiledataPair, CompilationResult} from '../../types/compilation/compilation.interfaces'; |
| import {ResultLine} from '../../types/resultline/resultline.interfaces'; |
| import {CompilationStatus as CompilerServiceCompilationStatus} from '../compiler-service.interfaces'; |
| import {CompilerPicker} from '../widgets/compiler-picker'; |
| import {GccDumpViewSelectedPass} from './gccdump-view.interfaces'; |
| import {SourceAndFiles} from '../download-service'; |
| |
| const languages = options.languages; |
| |
| type CompilationStatus = Omit<CompilerServiceCompilationStatus, 'compilerOut'> & { |
| didExecute?: boolean; |
| }; |
| |
| function makeAnsiToHtml(color?: string): AnsiToHtml { |
| return new AnsiToHtml({ |
| fg: color ? color : '#333', |
| bg: '#f5f5f5', |
| stream: true, |
| escapeXML: true, |
| }); |
| } |
| |
| type ActiveTools = { |
| id: number; |
| args: string[]; |
| stdin: string; |
| }; |
| |
| type CompilationRequestOptions = { |
| userArguments: string; |
| compilerOptions: { |
| executorRequest?: boolean; |
| skipAsm?: boolean; |
| producePp?: PPOptions | null; |
| produceAst?: boolean; |
| produceGccDump?: { |
| opened: boolean; |
| pass?: GccDumpViewSelectedPass; |
| treeDump?: boolean; |
| rtlDump?: boolean; |
| ipaDump?: boolean; |
| dumpFlags: any; |
| }; |
| produceOptInfo?: boolean; |
| produceCfg?: boolean; |
| produceGnatDebugTree?: boolean; |
| produceGnatDebug?: boolean; |
| produceIr?: boolean; |
| produceLLVMOptPipeline?: LLVMOptPipelineBackendOptions | null; |
| produceDevice?: boolean; |
| produceRustMir?: boolean; |
| produceRustMacroExp?: boolean; |
| produceRustHir?: boolean; |
| produceHaskellCore?: boolean; |
| produceHaskellStg?: boolean; |
| produceHaskellCmm?: boolean; |
| cmakeArgs?: string; |
| customOutputFilename?: string; |
| }; |
| executeParameters: { |
| args: string; |
| stdin: string; |
| }; |
| filters: Record<string, boolean>; |
| tools: ActiveTools[]; |
| libraries: CompileChildLibraries[]; |
| }; |
| |
| type CompilationRequest = { |
| source: string; |
| compiler: string; |
| options: CompilationRequestOptions; |
| lang: string | null; |
| files: FiledataPair[]; |
| bypassCache?: boolean; |
| }; |
| |
| type LangInfo = { |
| compiler: string; |
| options: string; |
| execArgs: string; |
| execStdin: string; |
| }; |
| |
| type CompileChildLibraries = { |
| id: string; |
| version: string; |
| }; |
| |
| export class Executor extends Pane<ExecutorState> { |
| private contentRoot: JQuery<HTMLElement>; |
| private readonly sourceEditorId: number | null; |
| private sourceTreeId: number | null; |
| private readonly id: number; |
| private deferCompiles: boolean; |
| private needsCompile: boolean; |
| private executionArguments: string; |
| private executionStdin: string; |
| private source: string; |
| private lastTimeTaken: number; |
| private pendingRequestSentAt: number; |
| private pendingCMakeRequestSentAt: number; |
| private nextRequest: CompilationRequest | null; |
| private nextCMakeRequest: CompilationRequest | null; |
| private options: string; |
| private lastResult: CompilationResult | null; |
| private alertSystem: Alert; |
| private readonly normalAnsiToHtml: AnsiToHtml; |
| private readonly errorAnsiToHtml: AnsiToHtml; |
| private fontScale: FontScale; |
| private compilerPicker: CompilerPicker; |
| private currentLangId: string; |
| private toggleWrapButton: Toggles; |
| private compileClearCache: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private outputContentRoot: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private executionStatusSection: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private compilerOutputSection: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private executionOutputSection: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private optionsField: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private execArgsField: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private execStdinField: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private prependOptions: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private fullCompilerName: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private fullTimingInfo: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private libsButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private compileTimeLabel: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private shortCompilerName: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private bottomBar: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private statusLabel: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private statusIcon: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]> | null; |
| private panelCompilation: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private panelArgs: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private panelStdin: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private wrapTitle: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private triggerCompilationButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private wrapButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private toggleCompilation: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private toggleArgs: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private toggleStdin: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private toggleCompilerOut: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>; |
| private libsWidget?: LibsWidget; |
| private readonly infoByLang: Record<string, LangInfo | undefined>; |
| private compiler: CompilerInfo | null; |
| |
| constructor(hub: Hub, container: Container, state: PaneState & ExecutorState) { |
| super(hub, container, state); |
| if (this.sourceTreeId) { |
| this.sourceEditorId = null; |
| } else { |
| this.sourceEditorId = state.source || 1; |
| } |
| this.id = state.id || this.hub.nextExecutorId(); |
| |
| this.contentRoot = this.domRoot.find('.content'); |
| this.infoByLang = {}; |
| this.deferCompiles = hub.deferred; |
| this.needsCompile = false; |
| this.source = ''; |
| this.lastResult = {code: -1, timedOut: false, stdout: [], stderr: []}; |
| this.lastTimeTaken = 0; |
| this.pendingRequestSentAt = 0; |
| this.pendingCMakeRequestSentAt = 0; |
| this.nextRequest = null; |
| this.nextCMakeRequest = null; |
| |
| this.alertSystem = new Alert(); |
| this.alertSystem.prefixMessage = 'Executor #' + this.id; |
| |
| this.normalAnsiToHtml = makeAnsiToHtml(); |
| this.errorAnsiToHtml = makeAnsiToHtml('red'); |
| |
| this.initButtons(state); |
| |
| this.fontScale = new FontScale(this.domRoot, state, 'pre.content'); |
| this.compilerPicker = new CompilerPicker( |
| this.domRoot, |
| this.hub, |
| this.currentLangId, |
| this.compiler ? this.compiler.id : '', |
| this.onCompilerChange.bind(this), |
| this.compilerIsVisible |
| ); |
| |
| this.initLibraries(state); |
| this.initCallbacks(); |
| // Handle initial settings |
| this.onSettingsChange(this.settings); |
| this.updateCompilerInfo(); |
| this.saveState(); |
| |
| if (this.sourceTreeId) { |
| this.compile(); |
| } |
| } |
| |
| override initializeStateDependentProperties(state: PaneState & ExecutorState) { |
| this.sourceTreeId = state.tree ?? null; |
| this.settings = Settings.getStoredSettings(); |
| this.initLangAndCompiler(state); |
| this.options = state.options || options.compileOptions[this.currentLangId]; |
| this.executionArguments = state.execArgs || ''; |
| this.executionStdin = state.execStdin || ''; |
| this.paneRenaming = new PaneRenaming(this, state); |
| } |
| |
| override getInitialHTML(): string { |
| return $('#executor').html(); |
| } |
| |
| compilerIsVisible(compiler: CompilerInfo): boolean { |
| return !!compiler.supportsExecute; |
| } |
| |
| getEditorIdByFilename(filename: string): number | null { |
| if (this.sourceTreeId) { |
| const tree = this.hub.getTreeById(this.sourceTreeId); |
| if (tree) { |
| return tree.multifileService.getEditorIdByFilename(filename); |
| } |
| } else if (this.sourceEditorId) { |
| return this.sourceEditorId; |
| } |
| return null; |
| } |
| |
| initLangAndCompiler(state: PaneState & ExecutorState): void { |
| const langId = state.lang ?? null; |
| const compilerId = state.compiler; |
| const result = this.hub.compilerService.processFromLangAndCompiler(langId, compilerId); |
| this.compiler = result?.compiler ?? null; |
| this.currentLangId = result?.langId ?? ''; |
| this.updateLibraries(); |
| } |
| |
| close(): void { |
| this.eventHub.unsubscribe(); |
| if (this.compilerPicker instanceof CompilerPicker) { |
| this.compilerPicker.close(); |
| } |
| |
| this.eventHub.emit('executorClose', this.id); |
| } |
| |
| undefer(): void { |
| this.deferCompiles = false; |
| if (this.needsCompile) this.compile(); |
| } |
| |
| override resize(): void { |
| _.defer(self => { |
| let topBarHeight = utils.updateAndCalcTopBarHeight(self.domRoot, $(self.topBar[0]), self.hideable); |
| |
| // We have some more elements that modify the topBarHeight |
| if (!self.panelCompilation.hasClass('d-none')) { |
| topBarHeight += self.panelCompilation.outerHeight(true); |
| } |
| if (!self.panelArgs.hasClass('d-none')) { |
| topBarHeight += self.panelArgs.outerHeight(true); |
| } |
| if (!self.panelStdin.hasClass('d-none')) { |
| topBarHeight += self.panelStdin.outerHeight(true); |
| } |
| |
| const bottomBarHeight = self.bottomBar.outerHeight(true); |
| self.outputContentRoot.outerHeight(self.domRoot.height() - topBarHeight - bottomBarHeight); |
| }, this); |
| } |
| |
| private errorResult(message: string): CompilationResult { |
| // @ts-expect-error: This is a valid CompilationResult |
| return {stdout: [], timedOut: false, code: -1, stderr: message}; |
| } |
| |
| compile(bypassCache?: boolean): void { |
| if (this.deferCompiles) { |
| this.needsCompile = true; |
| return; |
| } |
| this.needsCompile = false; |
| this.compileTimeLabel.text(' - Compiling...'); |
| const options: CompilationRequestOptions = { |
| userArguments: this.options, |
| executeParameters: { |
| args: this.executionArguments, |
| stdin: this.executionStdin, |
| }, |
| compilerOptions: { |
| executorRequest: true, |
| skipAsm: true, |
| }, |
| filters: {execute: true}, |
| tools: [], |
| libraries: [], |
| }; |
| |
| this.libsWidget?.getLibsInUse()?.forEach(item => { |
| options.libraries.push({ |
| id: item.libId, |
| version: item.versionId, |
| }); |
| }); |
| |
| if (this.sourceTreeId) { |
| this.compileFromTree(options, bypassCache); |
| } else { |
| this.compileFromEditorSource(options, bypassCache); |
| } |
| } |
| |
| compileFromEditorSource(options: CompilationRequestOptions, bypassCache?: boolean): void { |
| if (!this.compiler?.supportsExecute) { |
| this.alertSystem.notify('This compiler (' + this.compiler?.name + ') does not support execution', { |
| group: 'execution', |
| }); |
| return; |
| } |
| this.hub.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, |
| }; |
| if (bypassCache) request.bypassCache = true; |
| if (!this.compiler) { |
| this.onCompileResponse(request, this.errorResult('<Please select a compiler>'), false); |
| } else { |
| this.sendCompile(request); |
| } |
| }); |
| } |
| |
| compileFromTree(options: CompilationRequestOptions, bypassCache?: boolean): void { |
| const tree = this.hub.getTreeById(this.sourceTreeId ?? -1); |
| 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(), |
| }; |
| |
| const fetches: Promise<void>[] = []; |
| fetches.push( |
| this.hub.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.hub.compilerService.expandToFiles(file.contents).then((sourceAndFiles: SourceAndFiles) => { |
| file.contents = sourceAndFiles.source; |
| moreFiles.push(...sourceAndFiles.files); |
| }) |
| ); |
| } |
| request.files.push(...moreFiles); |
| |
| Promise.all(fetches).then(() => { |
| const treeState = tree.currentState(); |
| const cmakeProject = tree.multifileService.isACMakeProject(); |
| |
| if (bypassCache) request.bypassCache = true; |
| if (!this.compiler) { |
| this.onCompileResponse(request, this.errorResult('<Please select a compiler>'), false); |
| } else if (cmakeProject && request.source === '') { |
| this.onCompileResponse(request, this.errorResult('<Please supply a CMakeLists.txt>'), false); |
| } else { |
| if (cmakeProject) { |
| request.options.compilerOptions.cmakeArgs = treeState.cmakeArgs; |
| request.options.compilerOptions.customOutputFilename = treeState.customOutputFilename; |
| this.sendCMakeCompile(request); |
| } else { |
| this.sendCompile(request); |
| } |
| } |
| }); |
| } |
| |
| sendCMakeCompile(request: CompilationRequest): void { |
| const onCompilerResponse = this.onCMakeResponse.bind(this); |
| |
| 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; |
| } |
| // this.eventHub.emit('compiling', this.id, this.compiler); |
| // Display the spinner |
| this.handleCompilationStatus({code: 4}); |
| this.pendingCMakeRequestSentAt = Date.now(); |
| // After a short delay, give the user some indication that we're working on their |
| // compilation. |
| this.hub.compilerService |
| .submitCMake(request) |
| .then((x: any) => { |
| onCompilerResponse(request, x.result, x.localCacheHit); |
| }) |
| .catch(x => { |
| let message = 'Unknown error'; |
| if (_.isString(x)) { |
| message = x; |
| } else if (x) { |
| message = x.error || x.code || x.message || x; |
| } |
| onCompilerResponse(request, this.errorResult(message), false); |
| }); |
| } |
| |
| sendCompile(request: CompilationRequest): void { |
| 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; |
| } |
| // this.eventHub.emit('compiling', this.id, this.compiler); |
| // Display the spinner |
| this.handleCompilationStatus({code: 4}); |
| this.pendingRequestSentAt = Date.now(); |
| // After a short delay, give the user some indication that we're working on their |
| // compilation. |
| this.hub.compilerService |
| .submit(request) |
| .then((x: any) => { |
| onCompilerResponse(request, x.result, x.localCacheHit); |
| }) |
| .catch(x => { |
| let message = 'Unknown error'; |
| if (typeof x === 'string') { |
| message = x; |
| } else if (x) { |
| message = x.error || x.code || x.message || x; |
| } |
| onCompilerResponse(request, this.errorResult(message), false); |
| }); |
| } |
| |
| addCompilerOutputLine( |
| msg: string, |
| container: JQuery, |
| lineNum: number | undefined, |
| column: number | undefined, |
| addLineLinks: boolean, |
| filename: string | null |
| ): void { |
| const elem = $('<div/>').appendTo(container); |
| if (addLineLinks && lineNum) { |
| elem.html( |
| // @ts-expect-error: JQuery types are wrong |
| $('<span class="linked-compiler-output-line"></span>') |
| .html(msg) |
| .on('click', e => { |
| const editorId = this.getEditorIdByFilename(filename ?? ''); |
| if (editorId) { |
| this.eventHub.emit( |
| 'editorLinkLine', |
| editorId, |
| lineNum, |
| column ?? 0, |
| (column ?? 0) + 1, |
| true |
| ); |
| } |
| // do not bring user to the top of index.html |
| // http://stackoverflow.com/questions/3252730 |
| e.preventDefault(); |
| return false; |
| }) |
| .on('mouseover', () => { |
| const editorId = this.getEditorIdByFilename(filename ?? ''); |
| if (editorId) { |
| this.eventHub.emit( |
| 'editorLinkLine', |
| editorId, |
| lineNum, |
| column ?? 0, |
| (column ?? 0) + 1, |
| false |
| ); |
| } |
| }) |
| ); |
| } else { |
| elem.html(msg); |
| } |
| } |
| |
| clearPreviousOutput(): void { |
| this.executionStatusSection.empty(); |
| this.compilerOutputSection.empty(); |
| this.executionOutputSection.empty(); |
| } |
| |
| handleOutput( |
| output: ResultLine[], |
| element: JQuery<HTMLElement>, |
| ansiParser: AnsiToHtml, |
| addLineLinks: boolean |
| ): JQuery<HTMLElement> { |
| const outElem = $('<pre class="card"></pre>').appendTo(element); |
| output.forEach(obj => { |
| if (obj.text === '') { |
| this.addCompilerOutputLine('<br/>', outElem, undefined, undefined, false, null); |
| } else { |
| const lineNumber = obj.tag ? obj.tag.line : obj.line; |
| const columnNumber = obj.tag ? obj.tag.column : -1; |
| const filename = obj.tag ? obj.tag.file : false; |
| this.addCompilerOutputLine( |
| ansiParser.toHtml(obj.text), |
| outElem, |
| lineNumber, |
| columnNumber, |
| addLineLinks, |
| filename || null |
| ); |
| } |
| }); |
| return outElem; |
| } |
| |
| getBuildStdoutFromResult(result: CompilationResult): ResultLine[] { |
| let arr: ResultLine[] = []; |
| |
| if (result.buildResult) { |
| arr = arr.concat(result.buildResult.stdout); |
| } |
| |
| if (result.buildsteps) { |
| result.buildsteps.forEach(step => { |
| arr = arr.concat(step.stdout); |
| }); |
| } |
| |
| return arr; |
| } |
| |
| getBuildStderrFromResult(result: CompilationResult): ResultLine[] { |
| let arr: ResultLine[] = []; |
| |
| if (result.buildResult) { |
| arr = arr.concat(result.buildResult.stderr); |
| } |
| |
| if (result.buildsteps) { |
| result.buildsteps.forEach(step => { |
| arr = arr.concat(step.stderr); |
| }); |
| } |
| |
| return arr; |
| } |
| |
| getExecutionStdoutfromResult(result: CompilationResult): ResultLine[] { |
| if (result.execResult && result.execResult.stdout !== undefined) { |
| return result.execResult.stdout; |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
| return result.stdout || []; |
| } |
| |
| getExecutionStderrfromResult(result: CompilationResult): ResultLine[] { |
| if (result.execResult) { |
| return result.execResult.stderr as ResultLine[]; |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
| return result.stderr || []; |
| } |
| |
| onCMakeResponse(request: CompilationRequest, result: CompilationResult, cached: boolean): void { |
| 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.handleCompileRequestAndResponse(request, result, cached, wasRealReply, timeTaken); |
| |
| this.doNextCMakeRequest(); |
| } |
| |
| 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); |
| } |
| } |
| |
| handleCompileRequestAndResponse( |
| request: CompilationRequest, |
| result: CompilationResult, |
| cached: boolean, |
| wasRealReply: boolean, |
| timeTaken: number |
| ): void { |
| ga.proxy('send', { |
| hitType: 'event', |
| eventCategory: 'Compile', |
| eventAction: request.compiler, |
| eventLabel: request.options.userArguments, |
| eventValue: cached ? 1 : 0, |
| }); |
| ga.proxy('send', { |
| hitType: 'timing', |
| timingCategory: 'Compile', |
| timingVar: request.compiler, |
| timingValue: timeTaken, |
| }); |
| |
| this.clearPreviousOutput(); |
| const compileStdout = this.getBuildStdoutFromResult(result); |
| const compileStderr = this.getBuildStderrFromResult(result); |
| const execStdout = this.getExecutionStdoutfromResult(result); |
| const execStderr = this.getExecutionStderrfromResult(result); |
| |
| let buildResultCode = 0; |
| |
| if (result.buildResult) { |
| buildResultCode = result.buildResult.code; |
| } else if (result.buildsteps) { |
| result.buildsteps.forEach(step => { |
| buildResultCode = step.code; |
| }); |
| } |
| |
| if (!result.didExecute) { |
| this.executionStatusSection.append($('<div/>').text('Could not execute the program')); |
| this.executionStatusSection.append($('<div/>').text('Compiler returned: ' + buildResultCode)); |
| } |
| // reset stream styles |
| this.normalAnsiToHtml.reset(); |
| this.errorAnsiToHtml.reset(); |
| if (compileStdout.length > 0) { |
| this.compilerOutputSection.append($('<div/>').text('Compiler stdout')); |
| this.handleOutput(compileStdout, this.compilerOutputSection, this.normalAnsiToHtml, true); |
| } |
| if (compileStderr.length > 0) { |
| this.compilerOutputSection.append($('<div/>').text('Compiler stderr')); |
| this.handleOutput(compileStderr, this.compilerOutputSection, this.errorAnsiToHtml, true); |
| } |
| if (result.didExecute) { |
| const exitCode = result.execResult ? result.execResult.code : result.code; |
| this.executionOutputSection.append($('<div/>').text('Program returned: ' + exitCode)); |
| if (execStdout.length > 0) { |
| this.executionOutputSection.append($('<div/>').text('Program stdout')); |
| const outElem = this.handleOutput( |
| execStdout, |
| this.executionOutputSection, |
| this.normalAnsiToHtml, |
| false |
| ); |
| outElem.addClass('execution-stdout'); |
| } |
| if (execStderr.length > 0) { |
| this.executionOutputSection.append($('<div/>').text('Program stderr')); |
| this.handleOutput(execStderr, this.executionOutputSection, this.normalAnsiToHtml, false); |
| } |
| } |
| |
| this.handleCompilationStatus({code: 1, didExecute: result.didExecute}); |
| let timeLabelText = ''; |
| if (cached) { |
| timeLabelText = ' - cached'; |
| } else if (wasRealReply) { |
| timeLabelText = ' - ' + timeTaken + 'ms'; |
| } |
| this.compileTimeLabel.text(timeLabelText); |
| |
| this.setCompilationOptionsPopover(result.buildResult ? result.buildResult.compilationOptions.join(' ') : ''); |
| |
| if (this.currentLangId) |
| this.eventHub.emit('executeResult', this.id, this.compiler, result, languages[this.currentLangId]); |
| } |
| |
| onCompileResponse(request: CompilationRequest, result: CompilationResult, 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.handleCompileRequestAndResponse(request, result, cached, wasRealReply, timeTaken); |
| |
| this.doNextCompileRequest(); |
| } |
| |
| resendResult(): boolean { |
| if (!$.isEmptyObject(this.lastResult)) { |
| // @ts-expect-error: 'executeResult' may accept only 4 arguments |
| this.eventHub.emit('executeResult', this.id, this.compiler, this.lastResult); |
| return true; |
| } |
| return false; |
| } |
| |
| onResendExecutionResult(id: number): void { |
| if (id === this.id) { |
| this.resendResult(); |
| } |
| } |
| |
| 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) { |
| this.source = source; |
| if (this.settings.compileOnChange) { |
| this.compile(); |
| } |
| } |
| } |
| |
| initButtons(state: PaneState & ExecutorState): void { |
| this.compileClearCache = this.domRoot.find('.clear-cache'); |
| this.outputContentRoot = this.domRoot.find('pre.content'); |
| this.executionStatusSection = this.outputContentRoot.find('.execution-status'); |
| this.compilerOutputSection = this.outputContentRoot.find('.compiler-output'); |
| this.executionOutputSection = this.outputContentRoot.find('.execution-output'); |
| this.toggleWrapButton = new Toggles(this.domRoot.find('.options'), state as unknown as Record<string, boolean>); |
| |
| this.optionsField = this.domRoot.find('.compilation-options'); |
| this.execArgsField = this.domRoot.find('.execution-arguments'); |
| this.execStdinField = this.domRoot.find('.execution-stdin'); |
| this.prependOptions = this.domRoot.find('.prepend-options'); |
| this.fullCompilerName = this.domRoot.find('.full-compiler-name'); |
| this.fullTimingInfo = this.domRoot.find('.full-timing-info'); |
| this.setCompilationOptionsPopover(this.compiler?.options ?? null); |
| |
| this.compileTimeLabel = this.domRoot.find('.compile-time'); |
| this.libsButton = this.domRoot.find('.btn.show-libs'); |
| |
| // Dismiss on any click that isn't either in the opening element, inside |
| // the popover or on any alert |
| $(document).on('mouseup', e => { |
| const target = $(e.target); |
| if ( |
| !target.is(this.prependOptions) && |
| // @ts-expect-error: JQuery types are wrong |
| this.prependOptions.has(target).length === 0 && |
| target.closest('.popover').length === 0 |
| ) |
| this.prependOptions.popover('hide'); |
| |
| if ( |
| !target.is(this.fullCompilerName) && |
| // @ts-expect-error: JQuery types are wrong |
| this.fullCompilerName.has(target).length === 0 && |
| target.closest('.popover').length === 0 |
| ) |
| this.fullCompilerName.popover('hide'); |
| }); |
| |
| this.optionsField.val(this.options); |
| this.execArgsField.val(this.executionArguments); |
| this.execStdinField.val(this.executionStdin); |
| |
| this.shortCompilerName = this.domRoot.find('.short-compiler-name'); |
| this.setCompilerVersionPopover({version: '', fullVersion: ''}, ''); |
| |
| this.topBar = this.domRoot.find('.top-bar'); |
| this.bottomBar = this.domRoot.find('.bottom-bar'); |
| this.statusLabel = this.domRoot.find('.status-text'); |
| |
| this.hideable = this.domRoot.find('.hideable'); |
| this.statusIcon = this.domRoot.find('.status-icon'); |
| |
| this.panelCompilation = this.domRoot.find('.panel-compilation'); |
| this.panelArgs = this.domRoot.find('.panel-args'); |
| this.panelStdin = this.domRoot.find('.panel-stdin'); |
| |
| this.wrapButton = this.domRoot.find('.wrap-lines'); |
| this.wrapTitle = this.wrapButton.prop('title'); |
| |
| this.triggerCompilationButton = this.bottomBar.find('.trigger-compilation'); |
| |
| this.initToggleButtons(state); |
| } |
| |
| initToggleButtons(state: PaneState & ExecutorState): void { |
| this.toggleCompilation = this.domRoot.find('.toggle-compilation'); |
| this.toggleArgs = this.domRoot.find('.toggle-args'); |
| this.toggleStdin = this.domRoot.find('.toggle-stdin'); |
| this.toggleCompilerOut = this.domRoot.find('.toggle-compilerout'); |
| |
| if (!state.compilationPanelShown) { |
| this.hidePanel(this.toggleCompilation, this.panelCompilation); |
| } |
| |
| if (state.argsPanelShown) { |
| this.showPanel(this.toggleArgs, this.panelArgs); |
| } |
| |
| if (state.stdinPanelShown) { |
| this.showPanel(this.toggleStdin, this.panelStdin); |
| } |
| |
| if (!state.compilerOutShown) { |
| this.hidePanel(this.toggleCompilerOut, this.compilerOutputSection); |
| } |
| |
| if (state.wrap === true) { |
| this.contentRoot.addClass('wrap'); |
| this.wrapButton.prop('title', '[ON] ' + this.wrapTitle); |
| } else { |
| this.contentRoot.removeClass('wrap'); |
| this.wrapButton.prop('title', '[OFF] ' + this.wrapTitle); |
| } |
| } |
| |
| onLibsChanged(): void { |
| this.saveState(); |
| this.compile(); |
| } |
| |
| initLibraries(state: PaneState & ExecutorState): 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 ?? null |
| ) |
| ); |
| } |
| |
| onFontScale(): void { |
| this.saveState(); |
| } |
| |
| initListeners(): void { |
| // this.filters.on('change', _.bind(this.onFilterChange, this)); |
| this.fontScale.on('change', this.onFontScale.bind(this)); |
| this.paneRenaming.on('renamePane', this.saveState.bind(this)); |
| this.toggleWrapButton.on('change', this.onToggleWrapChange.bind(this)); |
| |
| this.container.on('destroy', this.close, this); |
| this.container.on('resize', this.resize, this); |
| this.container.on('shown', this.resize, this); |
| this.container.on('open', () => { |
| this.eventHub.emit('executorOpen', this.id, this.sourceEditorId ?? false); |
| }); |
| this.eventHub.on('editorChange', this.onEditorChange, this); |
| this.eventHub.on('editorClose', this.onEditorClose, this); |
| this.eventHub.on('settingsChange', this.onSettingsChange, this); |
| this.eventHub.on('requestCompilation', this.onRequestCompilation, this); |
| this.eventHub.on('resendExecution', this.onResendExecutionResult, this); |
| this.eventHub.on('resize', this.resize, this); |
| this.eventHub.on('findExecutors', this.sendExecutor, this); |
| this.eventHub.on('languageChange', this.onLanguageChange, this); |
| |
| this.fullTimingInfo.off('click').on('click', () => { |
| TimingWidget.displayCompilationTiming(this.lastResult, this.lastTimeTaken); |
| }); |
| } |
| |
| showPanel(button: JQuery<HTMLElement>, panel: JQuery<HTMLElement>): void { |
| panel.removeClass('d-none'); |
| button.addClass('active'); |
| this.resize(); |
| } |
| |
| hidePanel(button: JQuery<HTMLElement>, panel: JQuery<HTMLElement>): void { |
| panel.addClass('d-none'); |
| button.removeClass('active'); |
| this.resize(); |
| } |
| |
| togglePanel(button: JQuery<HTMLElement>, panel: JQuery<HTMLElement>): void { |
| if (panel.hasClass('d-none')) { |
| this.showPanel(button, panel); |
| } else { |
| this.hidePanel(button, panel); |
| } |
| this.saveState(); |
| } |
| |
| initCallbacks(): void { |
| this.initListeners(); |
| |
| const optionsChange = _.debounce(e => { |
| this.onOptionsChange($(e.target).val() as string); |
| }, 800); |
| |
| const execArgsChange = _.debounce(e => { |
| this.onExecArgsChange($(e.target).val() as string); |
| }, 800); |
| |
| const execStdinChange = _.debounce(e => { |
| this.onExecStdinChange($(e.target).val() as string); |
| }, 800); |
| |
| this.optionsField.on('change', optionsChange).on('keyup', optionsChange); |
| |
| this.execArgsField.on('change', execArgsChange).on('keyup', execArgsChange); |
| |
| this.execStdinField.on('change', execStdinChange).on('keyup', execStdinChange); |
| |
| this.compileClearCache.on('click', () => { |
| this.hub.compilerService.cache.reset(); |
| this.compile(true); |
| }); |
| |
| // Dismiss the popover on escape. |
| $(document).on('keyup.editable', e => { |
| if (e.which === 27) { |
| this.libsButton.popover('hide'); |
| } |
| }); |
| |
| this.toggleCompilation.on('click', () => { |
| this.togglePanel(this.toggleCompilation, this.panelCompilation); |
| }); |
| |
| this.toggleArgs.on('click', () => { |
| this.togglePanel(this.toggleArgs, this.panelArgs); |
| }); |
| |
| this.toggleStdin.on('click', () => { |
| this.togglePanel(this.toggleStdin, this.panelStdin); |
| }); |
| |
| this.toggleCompilerOut.on('click', () => { |
| this.togglePanel(this.toggleCompilerOut, this.compilerOutputSection); |
| }); |
| |
| this.triggerCompilationButton.on('click', () => { |
| this.compile(true); |
| }); |
| |
| // Dismiss on any click that isn't either in the opening element, inside |
| // the popover or on any alert |
| $(document).on('click', e => { |
| const elem = this.libsButton; |
| const target = $(e.target); |
| // @ts-expect-error: JQuery types are again wrong |
| if (!target.is(elem) && elem.has(target).length === 0 && target.closest('.popover').length === 0) { |
| elem.popover('hide'); |
| } |
| }); |
| |
| this.eventHub.on('initialised', this.undefer, this); |
| |
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
| if (MutationObserver !== undefined) { |
| new MutationObserver(_.bind(this.resize, this)).observe(this.execStdinField[0], { |
| attributes: true, |
| attributeFilter: ['style'], |
| }); |
| } |
| } |
| |
| shouldEmitExecutionOnFieldChange(): boolean { |
| return this.settings.executorCompileOnChange; |
| } |
| |
| onOptionsChange(options: string): void { |
| this.options = options; |
| this.saveState(); |
| if (this.shouldEmitExecutionOnFieldChange()) { |
| this.compile(); |
| } |
| } |
| |
| onExecArgsChange(args: string): void { |
| this.executionArguments = args; |
| this.saveState(); |
| if (this.shouldEmitExecutionOnFieldChange()) { |
| this.compile(); |
| } |
| } |
| |
| onExecStdinChange(newStdin: string): void { |
| this.executionStdin = newStdin; |
| this.saveState(); |
| if (this.shouldEmitExecutionOnFieldChange()) { |
| this.compile(); |
| } |
| } |
| |
| onRequestCompilation(editorId: number | boolean, treeId: number | boolean): void { |
| if (editorId === this.sourceEditorId || (treeId && treeId === this.sourceTreeId)) { |
| this.compile(); |
| } |
| } |
| |
| updateCompilerInfo(): void { |
| this.updateCompilerName(); |
| if (this.compiler) { |
| if (this.compiler.notification) { |
| this.alertSystem.notify(this.compiler.notification, { |
| group: 'compilerwarning', |
| alertClass: 'notification-info', |
| dismissTime: 5000, |
| }); |
| } |
| this.prependOptions.data('content', this.compiler.options); |
| } |
| this.sendExecutor(); |
| } |
| |
| updateCompilerUI(): void { |
| this.updateCompilerInfo(); |
| // Resize in case the new compiler name is too big |
| this.resize(); |
| } |
| |
| onCompilerChange(value: string): void { |
| this.compiler = this.hub.compilerService.findCompiler(this.currentLangId, value); |
| this.updateLibraries(); |
| this.saveState(); |
| this.compile(); |
| this.updateCompilerUI(); |
| } |
| |
| onToggleWrapChange(): void { |
| const state = this.currentState(); |
| this.contentRoot.toggleClass('wrap', state.wrap); |
| this.wrapButton.prop('title', '[' + (state.wrap ? 'ON' : 'OFF') + '] ' + this.wrapTitle); |
| this.saveState(); |
| } |
| |
| sendExecutor(): void { |
| this.eventHub.emit( |
| 'executor', |
| this.id, |
| this.compiler, |
| this.options, |
| this.sourceEditorId ?? false, |
| this.sourceTreeId ?? false |
| ); |
| } |
| |
| onEditorClose(editor: number): void { |
| if (editor === this.sourceEditorId) { |
| // We can't immediately close as an outer loop somewhere in GoldenLayout is iterating over |
| // the hierarchy. We can't modify while it's being iterated over. |
| this.close(); |
| _.defer(function (self) { |
| self.container.close(); |
| }, this); |
| } |
| } |
| |
| currentState(): ExecutorState & PaneState { |
| const state: ExecutorState & PaneState = { |
| id: this.id, |
| compilerName: '', |
| compiler: this.compiler ? this.compiler.id : '', |
| source: this.sourceEditorId ?? undefined, |
| tree: this.sourceTreeId ?? undefined, |
| options: this.options, |
| execArgs: this.executionArguments, |
| execStdin: this.executionStdin, |
| libs: this.libsWidget?.get(), |
| lang: this.currentLangId, |
| compilationPanelShown: !this.panelCompilation.hasClass('d-none'), |
| compilerOutShown: !this.compilerOutputSection.hasClass('d-none'), |
| argsPanelShown: !this.panelArgs.hasClass('d-none'), |
| stdinPanelShown: !this.panelStdin.hasClass('d-none'), |
| wrap: this.toggleWrapButton.get().wrap, |
| }; |
| |
| this.paneRenaming.addState(state); |
| this.fontScale.addState(state); |
| return state; |
| } |
| |
| saveState(): void { |
| this.container.setState(this.currentState()); |
| } |
| |
| getCompilerName(): string { |
| return this.compiler ? this.compiler.name : 'No compiler set'; |
| } |
| |
| getLanguageName(): string { |
| const lang = this.currentLangId ? (options.languages[this.currentLangId] as Language | undefined) : undefined; |
| return lang ? lang.name : '?'; |
| } |
| |
| getLinkHint(): string { |
| if (this.sourceTreeId) { |
| return 'Tree #' + this.sourceTreeId; |
| } else { |
| return 'Editor #' + this.sourceEditorId; |
| } |
| } |
| |
| override getPaneName(): string { |
| const langName = this.getLanguageName(); |
| const compName = this.getCompilerName(); |
| return 'Executor ' + compName + ' (' + langName + ', ' + this.getLinkHint() + ')'; |
| } |
| |
| override updateTitle(): void { |
| const name = this.paneName ? this.paneName : this.getPaneName(); |
| this.container.setTitle(_.escape(name)); |
| } |
| |
| updateCompilerName() { |
| this.updateTitle(); |
| const compilerName = this.getCompilerName(); |
| const compilerVersion = this.compiler?.version ?? ''; |
| const compilerFullVersion = this.compiler?.fullVersion ?? compilerVersion; |
| const compilerNotification = this.compiler?.notification ?? ''; |
| this.shortCompilerName.text(compilerName); |
| this.setCompilerVersionPopover( |
| { |
| version: compilerVersion, |
| fullVersion: compilerFullVersion, |
| }, |
| compilerNotification |
| ); |
| } |
| |
| setCompilationOptionsPopover(content: string | null) { |
| this.prependOptions.popover('dispose'); |
| this.prependOptions.popover({ |
| content: content || 'No options in use', |
| template: |
| '<div class="popover' + |
| (content ? ' compiler-options-popover' : '') + |
| '" role="tooltip"><div class="arrow"></div>' + |
| '<h3 class="popover-header"></h3><div class="popover-body"></div></div>', |
| }); |
| } |
| |
| setCompilerVersionPopover(version?: {fullVersion?: string; version: string}, notification?: string) { |
| this.fullCompilerName.popover('dispose'); |
| // `notification` contains HTML from a config file, so is 'safe'. |
| // `version` comes from compiler output, so isn't, and is escaped. |
| const bodyContent = $('<div>'); |
| const versionContent = $('<div>').html(_.escape(version?.version ?? '')); |
| bodyContent.append(versionContent); |
| if (version?.fullVersion) { |
| const hiddenSection = $('<div>'); |
| const hiddenVersionText = $('<div>').html(_.escape(version.fullVersion)).hide(); |
| const clickToExpandContent = $('<a>') |
| .attr('href', 'javascript:;') |
| .text('Toggle full version output') |
| .on('click', () => { |
| versionContent.toggle(); |
| hiddenVersionText.toggle(); |
| this.fullCompilerName.popover('update'); |
| }); |
| hiddenSection.append(hiddenVersionText).append(clickToExpandContent); |
| bodyContent.append(hiddenSection); |
| } |
| this.fullCompilerName.popover({ |
| html: true, |
| title: notification |
| ? ($.parseHTML('<span>Compiler Version: ' + notification + '</span>')[0] as any) |
| : 'Full compiler version', |
| content: bodyContent, |
| template: |
| '<div class="popover' + |
| (version ? ' compiler-options-popover' : '') + |
| '" role="tooltip">' + |
| '<div class="arrow"></div>' + |
| '<h3 class="popover-header"></h3><div class="popover-body"></div>' + |
| '</div>', |
| }); |
| } |
| |
| override onSettingsChange(newSettings: SiteSettings): void { |
| this.settings = _.clone(newSettings); |
| } |
| |
| private ariaLabel(status: CompilationStatus): string { |
| // Compiling... |
| if (status.code === 4) return 'Compiling'; |
| if (status.didExecute) { |
| return 'Program compiled & executed'; |
| } else { |
| return 'Program could not be executed'; |
| } |
| } |
| |
| private color(status: CompilationStatus) { |
| // Compiling... |
| if (status.code === 4) return '#888888'; |
| if (status.didExecute) return '#12BB12'; |
| return '#FF1212'; |
| } |
| |
| handleCompilationStatus(status: CompilationStatus): void { |
| // We want to do some custom styles for the icon, so we don't pass it here and instead do it later |
| CompilerService.handleCompilationStatus(this.statusLabel, null, {compilerOut: 0, ...status}); |
| |
| if (this.statusIcon != null) { |
| this.statusIcon |
| .removeClass() |
| .addClass('status-icon fas') |
| .css('color', this.color(status)) |
| .toggle(status.code !== 0) |
| .prop('aria-label', this.ariaLabel(status)) |
| .prop('data-status', status.code) |
| .toggleClass('fa-spinner fa-spin', status.code === 4) |
| .toggleClass('fa-times-circle', status.code !== 4 && !status.didExecute) |
| .toggleClass('fa-check-circle', status.code !== 4 && status.didExecute); |
| } |
| } |
| |
| updateLibraries(): void { |
| if (this.libsWidget) { |
| let filteredLibraries: LanguageLibs = {}; |
| if (this.compiler) { |
| filteredLibraries = LibUtils.getSupportedLibraries( |
| this.compiler.libsArr, |
| this.currentLangId || '', |
| this.compiler.remote ?? null |
| ); |
| } |
| |
| this.libsWidget.setNewLangId(this.currentLangId, this.compiler?.id ?? '', filteredLibraries); |
| } |
| } |
| |
| onLanguageChange(editorId: number | boolean, newLangId: string): void { |
| if (this.sourceEditorId === editorId && this.currentLangId) { |
| const oldLangId = this.currentLangId; |
| this.currentLangId = newLangId; |
| // Store the current selected stuff to come back to it later in the same session (Not state stored!) |
| this.infoByLang[oldLangId] = { |
| compiler: this.compiler && this.compiler.id ? this.compiler.id : options.defaultCompiler[oldLangId], |
| options: this.options, |
| execArgs: this.executionArguments, |
| execStdin: this.executionStdin, |
| }; |
| const info = this.infoByLang[this.currentLangId]; |
| this.initLangAndCompiler({compilerName: '', id: 0, lang: newLangId, compiler: info?.compiler ?? ''}); |
| this.updateCompilersSelector(info); |
| this.updateCompilerUI(); |
| this.saveState(); |
| } |
| } |
| |
| getCurrentLangCompilers(): CompilerInfo[] { |
| const allCompilers: Record<string, CompilerInfo> | undefined = this.hub.compilerService.getCompilersForLang( |
| this.currentLangId |
| ); |
| if (!allCompilers) return []; |
| |
| const hasAtLeastOneExecuteSupported = Object.values(allCompilers).some(compiler => { |
| return compiler.supportsExecute !== false; |
| }); |
| |
| if (!hasAtLeastOneExecuteSupported) { |
| this.compiler = null; |
| return []; |
| } |
| |
| return Object.values(allCompilers).filter(compiler => { |
| return ( |
| (compiler.hidden !== true && compiler.supportsExecute !== false) || |
| (this.compiler && compiler.id === this.compiler.id) |
| ); |
| }); |
| } |
| |
| updateCompilersSelector(info: LangInfo | undefined): void { |
| this.compilerPicker.update(this.currentLangId, this.compiler?.id ?? ''); |
| this.options = info?.options || ''; |
| this.optionsField.val(this.options); |
| this.executionArguments = info?.execArgs || ''; |
| this.execArgsField.val(this.executionArguments); |
| this.executionStdin = info?.execStdin || ''; |
| this.execStdinField.val(this.executionStdin); |
| } |
| |
| getDefaultPaneName(): string { |
| return ''; |
| } |
| |
| onCompileResult(compilerId: number, compiler: CompilerInfo, result: CompilationResult): void {} |
| |
| onCompiler(compilerId: number, compiler: CompilerInfo, options: string, editorId: number, treeId: number): void {} |
| |
| registerOpeningAnalyticsEvent(): void { |
| ga.proxy('send', { |
| hitType: 'event', |
| eventCategory: 'OpenViewPane', |
| eventAction: 'Executor', |
| }); |
| } |
| } |