aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/compilerexplorer
diff options
context:
space:
mode:
authorMarcus Tillmanns <marcus.tillmanns@qt.io>2023-12-14 09:35:42 +0100
committerMarcus Tillmanns <marcus.tillmanns@qt.io>2023-12-14 10:14:02 +0000
commit3425959e21ac959b414390bccb36fb928e5953e0 (patch)
tree3331cdb5908b93e360060bb9c42e097cc5c2d20d /src/plugins/compilerexplorer
parent7b1213d9f8cec3d8a58c3f163c4ea3241b578095 (diff)
CompilerExplorer: Add assembly => source links
When hovering over the assembly, the matching source lines are highlighted. Links inside the assembly are now clickable. Change-Id: I22479a2e1badcfd95e0f341b2556fc93c8469625 Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/plugins/compilerexplorer')
-rw-r--r--src/plugins/compilerexplorer/api/compile.h64
-rw-r--r--src/plugins/compilerexplorer/compilerexplorereditor.cpp246
-rw-r--r--src/plugins/compilerexplorer/compilerexplorereditor.h38
3 files changed, 296 insertions, 52 deletions
diff --git a/src/plugins/compilerexplorer/api/compile.h b/src/plugins/compilerexplorer/api/compile.h
index 618af39a246..0f0493739d2 100644
--- a/src/plugins/compilerexplorer/api/compile.h
+++ b/src/plugins/compilerexplorer/api/compile.h
@@ -226,7 +226,7 @@ struct CompilerResult
struct CompileResult : CompilerResult
{
- struct Asm
+ struct AssemblyLine
{
// A part of the asm that is a (hyper)link to a label (the name references labelDefinitions)
struct Label
@@ -246,34 +246,68 @@ struct CompileResult : CompilerResult
label.range.endCol = object["range"]["endCol"].toInt();
return label;
}
+
+ bool operator==(const Label &other) const
+ {
+ return name == other.name && range.startCol == other.range.startCol
+ && range.endCol == other.range.endCol;
+ }
+ bool operator!=(const Label &other) const { return !(*this == other); }
};
QList<Label> labels;
// The part of the source that generated this asm
- struct
+ struct Source
{
- int column;
+ std::optional<int> column;
QString file;
int line;
- } source;
+ bool operator==(const Source &other) const
+ {
+ return column == other.column && file == other.file && line == other.line;
+ }
+ bool operator!=(const Source &other) const { return !(*this == other); }
+ };
+
+ std::optional<Source> source;
QString text;
QStringList opcodes;
- static Asm fromJson(const QJsonObject &object)
+ static AssemblyLine fromJson(const QJsonObject &object)
{
- Asm asm_;
- asm_.text = object["text"].toString();
+ AssemblyLine line;
+ line.text = object["text"].toString();
auto opcodes = object["opcodes"].toArray();
for (const auto &opcode : opcodes)
- asm_.opcodes.append(opcode.toString());
- asm_.source.column = object["source"]["column"].toInt();
- asm_.source.file = object["source"]["file"].toString();
- asm_.source.line = object["source"]["line"].toInt();
+ line.opcodes.append(opcode.toString());
+
+ auto itSource = object.find("source");
+ if (itSource != object.end() && !itSource->isNull()) {
+ std::optional<int> columnOpt;
+ auto source = itSource->toObject();
+
+ auto itColumn = source.find("column");
+ if (itColumn != source.end() && !itColumn->isNull())
+ columnOpt = itColumn->toInt();
+
+ line.source = Source{
+ columnOpt,
+ source["file"].toString(),
+ source["line"].toInt(),
+ };
+ }
+
for (const auto &label : object["labels"].toArray()) {
- asm_.labels.append(Label::fromJson(label.toObject()));
+ line.labels.append(Label::fromJson(label.toObject()));
}
- return asm_;
+ return line;
+ }
+
+ bool operator==(const AssemblyLine &other) const
+ {
+ return source == other.source && text == other.text && opcodes == other.opcodes;
}
+ bool operator!=(const AssemblyLine &other) const { return !(*this == other); }
};
struct ExecResult
@@ -304,7 +338,7 @@ struct CompileResult : CompilerResult
};
QMap<QString, int> labelDefinitions;
- QList<Asm> assemblyLines;
+ QList<AssemblyLine> assemblyLines;
std::optional<ExecResult> execResult;
@@ -327,7 +361,7 @@ struct CompileResult : CompilerResult
if (object.contains("asm")) {
for (const auto &asmLine : object["asm"].toArray())
- result.assemblyLines.append(Asm::fromJson(asmLine.toObject()));
+ result.assemblyLines.append(AssemblyLine::fromJson(asmLine.toObject()));
}
if (object.contains("execResult"))
diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.cpp b/src/plugins/compilerexplorer/compilerexplorereditor.cpp
index dea0c439df9..4c0af4f73ea 100644
--- a/src/plugins/compilerexplorer/compilerexplorereditor.cpp
+++ b/src/plugins/compilerexplorer/compilerexplorereditor.cpp
@@ -15,9 +15,11 @@
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/texteditoractionhandler.h>
+#include <texteditor/texteditorsettings.h>
#include <texteditor/textmark.h>
#include <projectexplorer/projectexplorerconstants.h>
@@ -55,6 +57,13 @@ using namespace Utils;
namespace CompilerExplorer {
+enum {
+ LinkProperty = QTextFormat::UserProperty + 10,
+};
+
+constexpr char AsmEditorLinks[] = "AsmEditor.Links";
+constexpr char SourceEditorHoverLine[] = "SourceEditor.HoveredLine";
+
CodeEditorWidget::CodeEditorWidget(const std::shared_ptr<SourceSettings> &settings,
QUndoStack *undoStack)
: m_settings(settings)
@@ -264,6 +273,57 @@ QString SourceEditorWidget::sourceCode()
return {};
}
+void SourceEditorWidget::markSourceLocation(
+ const std::optional<Api::CompileResult::AssemblyLine> &assemblyLine)
+{
+ if (!assemblyLine || !assemblyLine->source) {
+ m_codeEditor->setExtraSelections(SourceEditorHoverLine, {});
+ return;
+ }
+
+ auto source = *assemblyLine->source;
+
+ // If this is a location in a different file we cannot highlight it
+ if (!source.file.isEmpty()) {
+ m_codeEditor->setExtraSelections(SourceEditorHoverLine, {});
+ return;
+ }
+
+ // Lines are 1-based, so if we get 0 it means we don't have a valid location
+ if (source.line == 0) {
+ m_codeEditor->setExtraSelections(SourceEditorHoverLine, {});
+ return;
+ }
+
+ QList<QTextEdit::ExtraSelection> selections;
+
+ const TextEditor::FontSettings fs = TextEditor::TextEditorSettings::fontSettings();
+ QTextCharFormat background = fs.toTextCharFormat(TextEditor::C_CURRENT_LINE);
+ QTextCharFormat column = fs.toTextCharFormat(TextEditor::C_OCCURRENCES);
+
+ QTextBlock block = m_codeEditor->textDocument()->document()->findBlockByLineNumber(source.line
+ - 1);
+
+ QTextEdit::ExtraSelection selection;
+ selection.cursor = QTextCursor(m_codeEditor->textDocument()->document());
+ selection.cursor.setPosition(block.position());
+ selection.cursor.setPosition(qMax(block.position(), block.position() + block.length() - 1),
+ QTextCursor::KeepAnchor);
+ selection.cursor.setKeepPositionOnInsert(true);
+ selection.format = background;
+ selection.format.setProperty(QTextFormat::FullWidthSelection, true);
+ selections.append(selection);
+
+ if (source.column) {
+ selection.cursor.setPosition(block.position() + *source.column - 1);
+ selection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+ selection.format = column;
+ selections.append(selection);
+ }
+
+ m_codeEditor->setExtraSelections(SourceEditorHoverLine, selections);
+}
+
CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings,
QUndoStack *undoStack)
@@ -286,8 +346,14 @@ CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSett
auto toolBar = new StyledBar;
m_asmEditor = new AsmEditorWidget(undoStack);
- m_asmDocument = QSharedPointer<TextDocument>(new TextDocument);
+ m_asmDocument = QSharedPointer<AsmDocument>(new AsmDocument);
m_asmEditor->setTextDocument(m_asmDocument);
+
+ connect(m_asmEditor,
+ &AsmEditorWidget::hoveredLineChanged,
+ this,
+ &CompilerWidget::hoveredLineChanged);
+
QTC_ASSERT_EXPECTED(m_asmEditor->configureGenericHighlighter("Intel x86 (NASM)"),
m_asmEditor->configureGenericHighlighter(
Utils::mimeTypeForName("text/x-asm")));
@@ -464,28 +530,9 @@ void CompilerWidget::doCompile()
m_resultTerminal->writeToTerminal((out + "\r\n").toUtf8(), false);
}
}
- qDeleteAll(m_marks);
- m_marks.clear();
-
- QString asmText;
- for (auto l : r.assemblyLines)
- asmText += l.text + "\n";
-
- m_asmDocument->setPlainText(asmText);
-
- int i = 0;
- for (auto l : r.assemblyLines) {
- i++;
- if (l.opcodes.empty())
- continue;
-
- auto mark = new TextMark(m_asmDocument.get(),
- i,
- TextMarkCategory{Tr::tr("Bytes"), "Bytes"});
- m_asmDocument->addMark(mark);
- mark->setLineAnnotation(l.opcodes.join(' '));
- m_marks.append(mark);
- }
+
+ const QList<QTextEdit::ExtraSelection> links = m_asmDocument->setCompileResult(r);
+ m_asmEditor->setExtraSelections(AsmEditorLinks, links);
} catch (const std::exception &e) {
Core::MessageManager::writeDisrupting(
Tr::tr("Failed to compile: \"%1\".").arg(QString::fromUtf8(e.what())));
@@ -541,9 +588,9 @@ void EditorWidget::focusInEvent(QFocusEvent *event)
FancyMainWindow::focusInEvent(event);
}
-void EditorWidget::addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
- const std::shared_ptr<CompilerSettings> &compilerSettings,
- int idx)
+CompilerWidget *EditorWidget::addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
+ const std::shared_ptr<CompilerSettings> &compilerSettings,
+ int idx)
{
auto compiler = new CompilerWidget(sourceSettings, compilerSettings, m_undoStack);
compiler->setWindowTitle("Compiler #" + QString::number(idx));
@@ -563,6 +610,8 @@ void EditorWidget::addCompiler(const std::shared_ptr<SourceSettings> &sourceSett
connect(compiler, &CompilerWidget::gotFocus, this, [this] {
m_actionHandler.updateCurrentEditor();
});
+
+ return compiler;
}
QVariantMap EditorWidget::windowStateCallback()
@@ -608,15 +657,27 @@ void EditorWidget::addSourceEditor(const std::shared_ptr<SourceSettings> &source
addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
sourceSettings->compilers.forEachItem<CompilerSettings>(
- [this, sourceSettings](const std::shared_ptr<CompilerSettings> &compilerSettings, int idx) {
- addCompiler(sourceSettings, compilerSettings, idx + 1);
+ [this,
+ sourceEditor,
+ sourceSettings](const std::shared_ptr<CompilerSettings> &compilerSettings, int idx) {
+ auto compilerWidget = addCompiler(sourceSettings, compilerSettings, idx + 1);
+ connect(compilerWidget,
+ &CompilerWidget::hoveredLineChanged,
+ sourceEditor,
+ &SourceEditorWidget::markSourceLocation);
});
sourceSettings->compilers.setItemAddedCallback<CompilerSettings>(
- [this, sourceSettings](const std::shared_ptr<CompilerSettings> &compilerSettings) {
- addCompiler(sourceSettings->shared_from_this(),
- compilerSettings,
- sourceSettings->compilers.size());
+ [this, sourceEditor, sourceSettings](
+ const std::shared_ptr<CompilerSettings> &compilerSettings) {
+ auto compilerWidget = addCompiler(sourceSettings->shared_from_this(),
+ compilerSettings,
+ sourceSettings->compilers.size());
+
+ connect(compilerWidget,
+ &CompilerWidget::hoveredLineChanged,
+ sourceEditor,
+ &SourceEditorWidget::markSourceLocation);
});
sourceSettings->compilers.setItemRemovedCallback<CompilerSettings>(
@@ -885,8 +946,129 @@ EditorFactory::EditorFactory()
setEditorCreator([this]() { return new Editor(m_actionHandler); });
}
+QList<QTextEdit::ExtraSelection> AsmDocument::setCompileResult(
+ const Api::CompileResult &compileResult)
+{
+ m_assemblyLines = compileResult.assemblyLines;
+
+ document()->clear();
+ qDeleteAll(m_marks);
+ m_marks.clear();
+
+ QString asmText;
+ QTextCursor cursor(document());
+
+ QTextCharFormat linkFormat = TextEditor::TextEditorSettings::fontSettings().toTextCharFormat(
+ TextEditor::C_LINK);
+
+ QList<QTextEdit::ExtraSelection> links;
+
+ auto labelRow = [&labels = std::as_const(compileResult.labelDefinitions)](
+ const QString &labelName) -> std::optional<int> {
+ auto it = labels.find(labelName);
+ if (it != labels.end())
+ return *it;
+ return std::nullopt;
+ };
+
+ setPlainText(
+ Utils::transform(m_assemblyLines, [](const auto &line) { return line.text; }).join('\n'));
+
+ int currentLine = 0;
+ for (auto l : m_assemblyLines) {
+ currentLine++;
+
+ auto createLabelLink = [currentLine, &linkFormat, &cursor, labelRow](
+ const Api::CompileResult::AssemblyLine::Label &label) {
+ QTextEdit::ExtraSelection selection;
+ selection.cursor = cursor;
+ QTextBlock block = cursor.document()->findBlockByLineNumber(currentLine - 1);
+ selection.cursor.setPosition(block.position() + label.range.startCol - 1);
+ selection.cursor.setPosition(block.position() + label.range.endCol - 1,
+ QTextCursor::KeepAnchor);
+ selection.cursor.setKeepPositionOnInsert(true);
+ selection.format = linkFormat;
+
+ if (auto lRow = labelRow(label.name))
+ selection.format.setProperty(LinkProperty, *lRow);
+
+ return selection;
+ };
+
+ links.append(Utils::transform(l.labels, createLabelLink));
+
+ if (!l.opcodes.empty()) {
+ auto mark = new TextMark(this, currentLine, TextMarkCategory{Tr::tr("Bytes"), "Bytes"});
+ addMark(mark);
+ mark->setLineAnnotation(l.opcodes.join(' '));
+ m_marks.append(mark);
+ }
+ }
+
+ emit contentsChanged();
+
+ return links;
+}
+
AsmEditorWidget::AsmEditorWidget(QUndoStack *stack)
: m_undoStack(stack)
{}
+void AsmEditorWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ const QTextCursor cursor = cursorForPosition(event->pos());
+
+ int line = cursor.block().blockNumber();
+ auto document = static_cast<AsmDocument *>(textDocument());
+
+ std::optional<Api::CompileResult::AssemblyLine> newLine;
+ if (line < document->asmLines().size())
+ newLine = document->asmLines()[line];
+
+ if (m_currentlyHoveredLine != newLine) {
+ m_currentlyHoveredLine = newLine;
+ emit hoveredLineChanged(newLine);
+ }
+
+ TextEditorWidget::mouseMoveEvent(event);
+}
+
+void AsmEditorWidget::leaveEvent(QEvent *event)
+{
+ if (m_currentlyHoveredLine) {
+ m_currentlyHoveredLine = std::nullopt;
+ emit hoveredLineChanged(std::nullopt);
+ }
+
+ TextEditorWidget::leaveEvent(event);
+}
+
+void AsmEditorWidget::findLinkAt(const QTextCursor &cursor,
+ const Utils::LinkHandler &processLinkCallback,
+ bool,
+ bool)
+{
+ QList<QTextEdit::ExtraSelection> links = this->extraSelections(AsmEditorLinks);
+
+ auto contains = [cursor](const QTextEdit::ExtraSelection &selection) {
+ if (selection.format.hasProperty(LinkProperty)
+ && selection.cursor.selectionStart() <= cursor.position()
+ && selection.cursor.selectionEnd() >= cursor.position()) {
+ return true;
+ }
+ return false;
+ };
+
+ if (std::optional<QTextEdit::ExtraSelection> selection = Utils::findOr(links,
+ std::nullopt,
+ contains)) {
+ const int row = selection->format.property(LinkProperty).toInt();
+ Link link{{}, row, 0};
+ link.linkTextStart = selection->cursor.selectionStart();
+ link.linkTextEnd = selection->cursor.selectionEnd();
+
+ processLinkCallback(link);
+ }
+}
+
} // namespace CompilerExplorer
diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.h b/src/plugins/compilerexplorer/compilerexplorereditor.h
index 978bda4164f..b7ce26a5c39 100644
--- a/src/plugins/compilerexplorer/compilerexplorereditor.h
+++ b/src/plugins/compilerexplorer/compilerexplorereditor.h
@@ -30,6 +30,7 @@ namespace CompilerExplorer {
class JsonSettingsDocument;
class SourceEditorWidget;
+class AsmDocument;
class CodeEditorWidget : public TextEditor::TextEditorWidget
{
@@ -56,6 +57,19 @@ private:
QUndoStack *m_undoStack;
};
+class AsmDocument : public TextEditor::TextDocument
+{
+public:
+ using TextEditor::TextDocument::TextDocument;
+
+ QList<QTextEdit::ExtraSelection> setCompileResult(const Api::CompileResult &compileResult);
+ QList<Api::CompileResult::AssemblyLine> &asmLines() { return m_assemblyLines; }
+
+private:
+ QList<Api::CompileResult::AssemblyLine> m_assemblyLines;
+ QList<TextEditor::TextMark *> m_marks;
+};
+
class AsmEditorWidget : public TextEditor::TextEditorWidget
{
Q_OBJECT
@@ -69,14 +83,25 @@ public:
emit gotFocus();
}
+ void findLinkAt(const QTextCursor &,
+ const Utils::LinkHandler &processLinkCallback,
+ bool resolveTarget = true,
+ bool inNextSplit = false) override;
+
void undo() override { m_undoStack->undo(); }
void redo() override { m_undoStack->redo(); }
+protected:
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void leaveEvent(QEvent *event) override;
+
signals:
void gotFocus();
+ void hoveredLineChanged(const std::optional<Api::CompileResult::AssemblyLine> &assemblyLine);
private:
QUndoStack *m_undoStack;
+ std::optional<Api::CompileResult::AssemblyLine> m_currentlyHoveredLine;
};
class JsonSettingsDocument : public Core::IDocument
@@ -128,6 +153,9 @@ public:
TextEditor::TextEditorWidget *textEditor() { return m_codeEditor; }
+public slots:
+ void markSourceLocation(const std::optional<Api::CompileResult::AssemblyLine> &assemblyLine);
+
signals:
void sourceCodeChanged();
void remove();
@@ -163,19 +191,19 @@ private:
signals:
void remove();
void gotFocus();
+ void hoveredLineChanged(const std::optional<Api::CompileResult::AssemblyLine> &assemblyLine);
private:
AsmEditorWidget *m_asmEditor{nullptr};
Core::SearchableTerminal *m_resultTerminal{nullptr};
SpinnerSolution::Spinner *m_spinner{nullptr};
- QSharedPointer<TextEditor::TextDocument> m_asmDocument;
+ QSharedPointer<AsmDocument> m_asmDocument;
std::unique_ptr<QFutureWatcher<Api::CompileResult>> m_compileWatcher;
QString m_source;
QTimer *m_delayTimer{nullptr};
- QList<TextEditor::TextMark *> m_marks;
};
class HelperWidget : public QWidget
@@ -213,9 +241,9 @@ protected:
void setupHelpWidget();
QWidget *createHelpWidget() const;
- void addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
- const std::shared_ptr<CompilerSettings> &compilerSettings,
- int idx);
+ CompilerWidget *addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
+ const std::shared_ptr<CompilerSettings> &compilerSettings,
+ int idx);
void addSourceEditor(const std::shared_ptr<SourceSettings> &sourceSettings);
void removeSourceEditor(const std::shared_ptr<SourceSettings> &sourceSettings);