blob: 67fcf762eff4910fc4b616dd5e8b5ed9dbd504bb [file] [log] [blame] [raw]
// Copyright (c) 2021, 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 * as monaco from 'monaco-editor';
import {Container} from 'golden-layout';
import {MonacoPane} from './pane';
import {IrState} from './ir-view.interfaces';
import {MonacoPaneState} from './pane.interfaces';
import {ga} from '../analytics';
import {extendConfig} from '../monaco-config';
import {applyColours} from '../colour';
import {Hub} from '../hub';
export class Ir extends MonacoPane<monaco.editor.IStandaloneCodeEditor, IrState> {
linkedFadeTimeoutId = -1;
irCode: any[] = [];
colours: any[] = [];
decorations: any = {};
previousDecorations: string[] = [];
constructor(hub: Hub, container: Container, state: IrState & MonacoPaneState) {
super(hub, container, state);
if (state.irOutput) {
this.showIrResults(state.irOutput);
}
}
override getInitialHTML(): string {
return $('#ir').html();
}
override createEditor(editorRoot: HTMLElement): monaco.editor.IStandaloneCodeEditor {
return monaco.editor.create(
editorRoot,
extendConfig({
language: 'llvm-ir',
readOnly: true,
glyphMargin: true,
lineNumbersMinChars: 3,
})
);
}
override registerOpeningAnalyticsEvent(): void {
ga.proxy('send', {
hitType: 'event',
eventCategory: 'OpenViewPane',
eventAction: 'Ir',
});
}
override getDefaultPaneName(): string {
return 'LLVM IR Viewer';
}
override registerEditorActions(): void {
this.editor.addAction({
id: 'viewsource',
label: 'Scroll to source',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
run: editor => {
const position = editor.getPosition();
if (position != null) {
const desiredLine = position.lineNumber - 1;
const source = this.irCode[desiredLine].source;
if (source !== null && source.file !== null) {
this.eventHub.emit(
'editorLinkLine',
this.compilerInfo.editorId as number,
source.line,
-1,
-1,
true
);
}
}
},
});
}
override registerCallbacks(): void {
const onMouseMove = _.throttle(this.onMouseMove.bind(this), 50);
const onDidChangeCursorSelection = _.throttle(this.onDidChangeCursorSelection.bind(this), 500);
const onColoursOnCompile = this.eventHub.mediateDependentCalls(
this.onColours.bind(this),
this.onCompileResult.bind(this)
);
this.paneRenaming.on('renamePane', this.updateState.bind(this));
this.eventHub.on('compileResult', onColoursOnCompile.dependencyProxy, this);
this.eventHub.on('colours', onColoursOnCompile.dependentProxy, this);
this.eventHub.on('panesLinkLine', this.onPanesLinkLine.bind(this));
this.editor.onMouseMove(event => onMouseMove(event));
this.editor.onDidChangeCursorSelection(event => onDidChangeCursorSelection(event));
this.eventHub.emit('irViewOpened', this.compilerInfo.compilerId);
this.eventHub.emit('requestSettings');
}
override onCompileResult(compilerId: number, compiler: any, result: any): void {
if (this.compilerInfo.compilerId !== compilerId) return;
if (result.hasIrOutput) {
this.showIrResults(result.irOutput);
} else if (compiler.supportsIrView) {
this.showIrResults([{text: '<No output>'}]);
}
}
override onCompiler(compilerId: number, compiler: any, options: unknown, editorId: number, treeId: number): void {
if (this.compilerInfo.compilerId !== compilerId) return;
this.compilerInfo.compilerName = compiler ? compiler.name : '';
this.compilerInfo.editorId = editorId;
this.compilerInfo.treeId = treeId;
this.updateTitle();
if (compiler && !compiler.supportsIrView) {
this.editor.setValue('<LLVM IR output is not supported for this compiler>');
}
}
showIrResults(result: any[]): void {
this.irCode = result;
this.editor.getModel()?.setValue(result.length ? _.pluck(result, 'text').join('\n') : '<No LLVM IR generated>');
if (!this.isAwaitingInitialResults) {
if (this.selection) {
this.editor.setSelection(this.selection);
this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber);
}
this.isAwaitingInitialResults = true;
}
}
onColours(compilerId: number, colours: any, scheme: any): void {
if (compilerId !== this.compilerInfo.compilerId) return;
const irColours: Record<number, number> = {};
for (const [index, code] of this.irCode.entries()) {
if (
code.source &&
code.source.file === null &&
code.source.line > 0 &&
colours[code.source.line - 1] !== undefined
) {
irColours[index] = colours[code.source.line - 1];
}
}
this.colours = applyColours(this.editor, irColours, scheme, this.colours);
}
onMouseMove(e: monaco.editor.IEditorMouseEvent): void {
if (e.target.position === null) return;
if (this.settings.hoverShowSource === true) {
this.clearLinkedLines();
if (e.target.position.lineNumber - 1 in this.irCode) {
const hoverCode = this.irCode[e.target.position.lineNumber - 1];
let sourceLine = -1;
let sourceColumnBegin = -1;
let sourceColumnEnd = -1;
if (hoverCode.source && !hoverCode.source.file) {
sourceLine = hoverCode.source.line;
if (hoverCode.source.column) {
sourceColumnBegin = hoverCode.source.column;
sourceColumnEnd = sourceColumnBegin;
}
}
this.eventHub.emit(
'editorLinkLine',
this.compilerInfo.editorId as number,
sourceLine,
sourceColumnBegin,
sourceColumnEnd,
false
);
this.eventHub.emit(
'panesLinkLine',
this.compilerInfo.compilerId,
sourceLine,
sourceColumnBegin,
sourceColumnEnd,
false,
this.getPaneName()
);
}
}
}
onPanesLinkLine(
compilerId: number,
lineNumber: number,
columnBegin: number,
columnEnd: number,
revealLinesInEditor: boolean,
sender: string
): void {
if (compilerId !== this.compilerInfo.compilerId) return;
const lineNumbers: number[] = [];
const directlyLinkedLineNumbers: number[] = [];
const isSignalFromAnotherPane = sender !== this.getPaneName();
for (const [index, irLine] of this.irCode.entries()) {
if (irLine.source && irLine.source.file === null && irLine.source.line === lineNumber) {
const line = index + 1;
const currentColumn = irLine.source.column;
lineNumbers.push(line);
if (
isSignalFromAnotherPane &&
currentColumn &&
columnBegin <= currentColumn &&
currentColumn <= columnEnd
) {
directlyLinkedLineNumbers.push(line);
}
}
}
if (revealLinesInEditor && lineNumbers[0]) {
// Just make sure that the mapped line is in view!
this.editor.revealLinesInCenter(lineNumbers[0], lineNumbers[0]);
}
const lineClassName = isSignalFromAnotherPane ? 'linked-code-decoration-line' : '';
const linkedLineDecorations: monaco.editor.IModelDeltaDecoration[] = lineNumbers.map(line => ({
range: new monaco.Range(line, 1, line, 1),
options: {
isWholeLine: true,
linesDecorationsClassName: 'linked-code-decoration-margin',
className: lineClassName,
},
}));
const directlyLinkedLineDecorations: monaco.editor.IModelDeltaDecoration[] = directlyLinkedLineNumbers.map(
line => ({
range: new monaco.Range(line, 1, line, 1),
options: {
isWholeLine: true,
inlineClassName: 'linked-code-decoration-column',
},
})
);
this.decorations.linkedCode = [...linkedLineDecorations, ...directlyLinkedLineDecorations];
if (this.linkedFadeTimeoutId !== -1) {
clearTimeout(this.linkedFadeTimeoutId);
}
// @ts-expect-error mismatched type on setTimeout, assumes NodeJS.Timeout
this.linkedFadeTimeoutId = setTimeout(() => {
this.clearLinkedLines();
this.linkedFadeTimeoutId = -1;
}, 5000);
this.updateDecorations();
}
updateDecorations(): void {
this.previousDecorations = this.editor.deltaDecorations(
this.previousDecorations,
_.flatten(_.values(this.decorations))
);
}
clearLinkedLines(): void {
this.decorations.linkedCode = [];
this.updateDecorations();
}
/** LLVM IR View proxies some things in the standard callbacks */
override registerStandardCallbacks(): void {
// TODO(jeremy-rifkin) While I'm here, this needs to be refactored to take advantage of base class logic
// Other panes probably need to be changed too
this.fontScale.on('change', this.updateState.bind(this));
this.container.on('destroy', this.close.bind(this));
this.container.on('resize', this.resize.bind(this));
this.eventHub.on('compiler', this.onCompiler.bind(this));
this.eventHub.on('compilerClose', this.onCompilerClose.bind(this));
this.eventHub.on('settingsChange', this.onSettingsChange.bind(this));
this.eventHub.on('shown', this.resize.bind(this));
this.eventHub.on('resize', this.resize.bind(this));
}
override close(): void {
this.eventHub.unsubscribe();
this.eventHub.emit('irViewClosed', this.compilerInfo.compilerId);
this.editor.dispose();
}
}