Make quickfix suggestions clickable in the compiler output pane

This commit is contained in:
Paul Gey
2025-12-26 19:25:45 +01:00
parent 94622a71cd
commit 208dfa6903
5 changed files with 105 additions and 14 deletions

View File

@@ -22,6 +22,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import * as monaco from 'monaco-editor';
import {ClangirBackendOptions} from '../types/compilation/clangir.interfaces.js';
import {CompilationResult} from '../types/compilation/compilation.interfaces.js';
import {LLVMIrBackendOptions} from '../types/compilation/ir.interfaces.js';
@@ -80,6 +81,7 @@ export type EventMap = {
editorClose: (editorId: number) => void;
editorDisplayFlow: (editorId: number, flow: MessageWithLocation[]) => 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;
editorSetDecoration: (editorId: number, lineNumber: number, reveal: boolean, column?: number) => void;
executeResult: (executorId: number, compiler: any, result: any, language: Language) => void;

View File

@@ -357,6 +357,7 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
this.eventHub.on('editorSetDecoration', this.onEditorSetDecoration, this);
this.eventHub.on('editorDisplayFlow', this.onEditorDisplayFlow, 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('conformanceViewClose', this.onConformanceViewClose, 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 {
// Create something that looks like an edit operation for the whole text
const operation = {
range: this.editor.getModel()?.getFullModelRange(),
forceMoveMarkers: true,
text: newSource,
};
applyEdit(operation: monaco.editor.IIdentifiedSingleEditOperation): void {
const nullFn = () => {
return null;
};
@@ -1132,6 +1127,16 @@ export class Editor extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Edit
// @ts-expect-error: See above comment maybe
this.editor.getModel()?.pushEditOperations(viewState?.cursorState ?? null, [operation], nullFn);
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.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 {
if (Number(id) === this.id) {
if (reveal && lineNum) {

View File

@@ -24,10 +24,12 @@
import {Container} from 'golden-layout';
import $ from 'jquery';
import * as monaco from 'monaco-editor';
import _ from 'underscore';
import {escapeHTML} from '../../shared/common-utils.js';
import {CompilationResult} from '../../types/compilation/compilation.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 {Hub} from '../hub.js';
import {updateAndCalcTopBarHeight} from '../utils.js';
@@ -172,7 +174,13 @@ export class Output extends Pane<OutputState> {
if (obj.text === '') {
this.add('<br/>');
} 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);
if (lineNum && column && filename) {
elem.empty();
$('<span class="linked-compiler-output-line"></span>')
const span = $('<span class="linked-compiler-output-line"></span>')
.html(msg)
.on('click', e => {
this.emitEditorLinkLine(lineNum, column, filename, true);
@@ -288,18 +307,51 @@ export class Output extends Pane<OutputState> {
e.preventDefault();
return false;
})
.on('click', '.diagnostic-url', e => {
e.stopPropagation();
})
.on('mouseover', () => {
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 {
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() {
return `Output of ${this.compilerInfo.compilerName}`;
}

View File

@@ -705,6 +705,24 @@ kbd {
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 {
overflow: visible;
}

View File

@@ -745,4 +745,12 @@ textarea.form-control {
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']