diff --git a/static/event-map.ts b/static/event-map.ts index 2aa162562..f1ff2b22f 100644 --- a/static/event-map.ts +++ b/static/event-map.ts @@ -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; diff --git a/static/panes/editor.ts b/static/panes/editor.ts index 83fb386d2..3a760f8e2 100644 --- a/static/panes/editor.ts +++ b/static/panes/editor.ts @@ -357,6 +357,7 @@ export class Editor extends MonacoPane { return null; }; @@ -1132,6 +1127,16 @@ export class Editor extends MonacoPane { if (obj.text === '') { this.add('
'); } 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 { } } - 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 = $('
').appendTo(this.contentRoot); if (lineNum && column && filename) { elem.empty(); - $('') + const span = $('') .html(msg) .on('click', e => { this.emitEditorLinkLine(lineNum, column, filename, true); @@ -288,18 +307,51 @@ export class Output extends Pane { 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, 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($('').html(msg)); + return false; + }); + } + } + override getDefaultPaneName() { return `Output of ${this.compilerInfo.compilerName}`; } diff --git a/static/styles/explorer.scss b/static/styles/explorer.scss index 377f3d4dc..5dd44faa6 100644 --- a/static/styles/explorer.scss +++ b/static/styles/explorer.scss @@ -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; } diff --git a/static/styles/themes/dark-theme.scss b/static/styles/themes/dark-theme.scss index 30da0d16c..29636dedd 100644 --- a/static/styles/themes/dark-theme.scss +++ b/static/styles/themes/dark-theme.scss @@ -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']