mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
Make quickfix suggestions clickable in the compiler output pane
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
import {ClangirBackendOptions} from '../types/compilation/clangir.interfaces.js';
|
import {ClangirBackendOptions} from '../types/compilation/clangir.interfaces.js';
|
||||||
import {CompilationResult} from '../types/compilation/compilation.interfaces.js';
|
import {CompilationResult} from '../types/compilation/compilation.interfaces.js';
|
||||||
import {LLVMIrBackendOptions} from '../types/compilation/ir.interfaces.js';
|
import {LLVMIrBackendOptions} from '../types/compilation/ir.interfaces.js';
|
||||||
@@ -80,6 +81,7 @@ export type EventMap = {
|
|||||||
editorClose: (editorId: number) => void;
|
editorClose: (editorId: number) => void;
|
||||||
editorDisplayFlow: (editorId: number, flow: MessageWithLocation[]) => void;
|
editorDisplayFlow: (editorId: number, flow: MessageWithLocation[]) => void;
|
||||||
editorLinkLine: (editorId: number, lineNumber: number, colBegin: number, colEnd: number, reveal: boolean) => void;
|
editorLinkLine: (editorId: number, lineNumber: number, colBegin: number, colEnd: number, reveal: boolean) => void;
|
||||||
|
editorApplyQuickfix: (editorId: number, range: monaco.IRange, text: string | null) => void;
|
||||||
editorOpen: (editorId: number) => void;
|
editorOpen: (editorId: number) => void;
|
||||||
editorSetDecoration: (editorId: number, lineNumber: number, reveal: boolean, column?: number) => void;
|
editorSetDecoration: (editorId: number, lineNumber: number, reveal: boolean, column?: number) => void;
|
||||||
executeResult: (executorId: number, compiler: any, result: any, language: Language) => void;
|
executeResult: (executorId: number, compiler: any, result: any, language: Language) => void;
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
|
|||||||
this.eventHub.on('editorSetDecoration', this.onEditorSetDecoration, this);
|
this.eventHub.on('editorSetDecoration', this.onEditorSetDecoration, this);
|
||||||
this.eventHub.on('editorDisplayFlow', this.onEditorDisplayFlow, this);
|
this.eventHub.on('editorDisplayFlow', this.onEditorDisplayFlow, this);
|
||||||
this.eventHub.on('editorLinkLine', this.onEditorLinkLine, this);
|
this.eventHub.on('editorLinkLine', this.onEditorLinkLine, this);
|
||||||
|
this.eventHub.on('editorApplyQuickfix', this.onEditorApplyQuickfix, this);
|
||||||
this.eventHub.on('conformanceViewOpen', this.onConformanceViewOpen, this);
|
this.eventHub.on('conformanceViewOpen', this.onConformanceViewOpen, this);
|
||||||
this.eventHub.on('conformanceViewClose', this.onConformanceViewClose, this);
|
this.eventHub.on('conformanceViewClose', this.onConformanceViewClose, this);
|
||||||
this.eventHub.on('newSource', this.onNewSource, this);
|
this.eventHub.on('newSource', this.onNewSource, this);
|
||||||
@@ -1114,13 +1115,7 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSource(newSource: string): void {
|
applyEdit(operation: monaco.editor.IIdentifiedSingleEditOperation): void {
|
||||||
// Create something that looks like an edit operation for the whole text
|
|
||||||
const operation = {
|
|
||||||
range: this.editor.getModel()?.getFullModelRange(),
|
|
||||||
forceMoveMarkers: true,
|
|
||||||
text: newSource,
|
|
||||||
};
|
|
||||||
const nullFn = () => {
|
const nullFn = () => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -1132,6 +1127,16 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
|
|||||||
// @ts-expect-error: See above comment maybe
|
// @ts-expect-error: See above comment maybe
|
||||||
this.editor.getModel()?.pushEditOperations(viewState?.cursorState ?? null, [operation], nullFn);
|
this.editor.getModel()?.pushEditOperations(viewState?.cursorState ?? null, [operation], nullFn);
|
||||||
this.numberUsedLines();
|
this.numberUsedLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSource(newSource: string): void {
|
||||||
|
// Create something that looks like an edit operation for the whole text
|
||||||
|
const operation = {
|
||||||
|
range: this.editor.getModel()!.getFullModelRange(),
|
||||||
|
forceMoveMarkers: true,
|
||||||
|
text: newSource,
|
||||||
|
};
|
||||||
|
this.applyEdit(operation);
|
||||||
|
|
||||||
if (!this.awaitingInitialResults) {
|
if (!this.awaitingInitialResults) {
|
||||||
if (this.selection) {
|
if (this.selection) {
|
||||||
@@ -1755,6 +1760,12 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEditorApplyQuickfix(editorId: number, range: monaco.IRange, text: string | null): void {
|
||||||
|
if (editorId === this.id) {
|
||||||
|
this.applyEdit({forceMoveMarkers: true, range, text});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEditorSetDecoration(id: number, lineNum: number, reveal: boolean, column?: number): void {
|
onEditorSetDecoration(id: number, lineNum: number, reveal: boolean, column?: number): void {
|
||||||
if (Number(id) === this.id) {
|
if (Number(id) === this.id) {
|
||||||
if (reveal && lineNum) {
|
if (reveal && lineNum) {
|
||||||
|
|||||||
@@ -24,10 +24,12 @@
|
|||||||
|
|
||||||
import {Container} from 'golden-layout';
|
import {Container} from 'golden-layout';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import {escapeHTML} from '../../shared/common-utils.js';
|
import {escapeHTML} from '../../shared/common-utils.js';
|
||||||
import {CompilationResult} from '../../types/compilation/compilation.interfaces.js';
|
import {CompilationResult} from '../../types/compilation/compilation.interfaces.js';
|
||||||
import {CompilerInfo} from '../../types/compiler.interfaces.js';
|
import {CompilerInfo} from '../../types/compiler.interfaces.js';
|
||||||
|
import {Fix} from '../../types/resultline/resultline.interfaces.js';
|
||||||
import * as AnsiToHtml from '../ansi-to-html.js';
|
import * as AnsiToHtml from '../ansi-to-html.js';
|
||||||
import {Hub} from '../hub.js';
|
import {Hub} from '../hub.js';
|
||||||
import {updateAndCalcTopBarHeight} from '../utils.js';
|
import {updateAndCalcTopBarHeight} from '../utils.js';
|
||||||
@@ -172,7 +174,13 @@ export class Output extends Pane<OutputState> {
|
|||||||
if (obj.text === '') {
|
if (obj.text === '') {
|
||||||
this.add('<br/>');
|
this.add('<br/>');
|
||||||
} else {
|
} else {
|
||||||
this.add(this.normalAnsiToHtml.toHtml(obj.text), lineNumber, columnNumber, obj.tag?.file);
|
this.add(
|
||||||
|
this.normalAnsiToHtml.toHtml(obj.text),
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
obj.tag?.file,
|
||||||
|
obj.tag?.fixes,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,11 +283,22 @@ export class Output extends Pane<OutputState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(msg: string, lineNum?: number, column?: number, filename?: string) {
|
emitEditorApplyQuickfix(filename: string, range: monaco.IRange, text: string | null): void {
|
||||||
|
if (this.compilerInfo.editorId) {
|
||||||
|
this.eventHub.emit('editorApplyQuickfix', this.compilerInfo.editorId, range, text);
|
||||||
|
} else if (filename) {
|
||||||
|
const editorId = this.getEditorIdByFilename(filename);
|
||||||
|
if (editorId) {
|
||||||
|
this.eventHub.emit('editorApplyQuickfix', editorId, range, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(msg: string, lineNum?: number, column?: number, filename?: string, fixes?: Fix[]) {
|
||||||
const elem = $('<div/>').appendTo(this.contentRoot);
|
const elem = $('<div/>').appendTo(this.contentRoot);
|
||||||
if (lineNum && column && filename) {
|
if (lineNum && column && filename) {
|
||||||
elem.empty();
|
elem.empty();
|
||||||
$('<span class="linked-compiler-output-line"></span>')
|
const span = $('<span class="linked-compiler-output-line"></span>')
|
||||||
.html(msg)
|
.html(msg)
|
||||||
.on('click', e => {
|
.on('click', e => {
|
||||||
this.emitEditorLinkLine(lineNum, column, filename, true);
|
this.emitEditorLinkLine(lineNum, column, filename, true);
|
||||||
@@ -288,18 +307,51 @@ export class Output extends Pane<OutputState> {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
.on('click', '.diagnostic-url', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
})
|
|
||||||
.on('mouseover', () => {
|
.on('mouseover', () => {
|
||||||
this.emitEditorLinkLine(lineNum, column, filename, false);
|
this.emitEditorLinkLine(lineNum, column, filename, false);
|
||||||
})
|
})
|
||||||
.appendTo(elem);
|
.on('click', '.diagnostic-url', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fixes) {
|
||||||
|
this.addQuickfixHandlers(span, msg, fixes, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.appendTo(elem);
|
||||||
} else {
|
} else {
|
||||||
elem.html(msg);
|
elem.html(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addQuickfixHandlers(span: JQuery<HTMLElement>, msg: string, fixes: Fix[], filename: string): void {
|
||||||
|
if (fixes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.attr('title', fixes[0].title).addClass('quickfix-action');
|
||||||
|
for (const fix of fixes) {
|
||||||
|
span.on('click', e => {
|
||||||
|
for (const {text, line, endline, column, endcolumn} of fix.edits) {
|
||||||
|
if (line && endline && column && endcolumn) {
|
||||||
|
this.emitEditorApplyQuickfix(
|
||||||
|
filename,
|
||||||
|
{
|
||||||
|
startLineNumber: line,
|
||||||
|
startColumn: column,
|
||||||
|
endLineNumber: endline,
|
||||||
|
endColumn: endcolumn,
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(e.delegateTarget).replaceWith($('<span></span>').html(msg));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override getDefaultPaneName() {
|
override getDefaultPaneName() {
|
||||||
return `Output of ${this.compilerInfo.compilerName}`;
|
return `Output of ${this.compilerInfo.compilerName}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -705,6 +705,24 @@ kbd {
|
|||||||
color: var(--terminal-bright-blue) !important;
|
color: var(--terminal-bright-blue) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quickfix-action:hover {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickfix-action::after {
|
||||||
|
font-style: normal;
|
||||||
|
content: '💡';
|
||||||
|
border: 1px solid var(--terminal-bright-blue);
|
||||||
|
margin-left: 3em;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickfix-action:hover:after {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
.lm_content {
|
.lm_content {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -745,4 +745,12 @@ textarea.form-control {
|
|||||||
color: #ffda6a !important;
|
color: #ffda6a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quickfix-action::after {
|
||||||
|
background: color.adjust(#333, $lightness: 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickfix-action:hover:after {
|
||||||
|
background: color.adjust(#333, $lightness: 25%);
|
||||||
|
}
|
||||||
|
|
||||||
} // End html[data-theme='dark']
|
} // End html[data-theme='dark']
|
||||||
|
|||||||
Reference in New Issue
Block a user