Commit f16b2eb2 authored by Waqar Ahmed's avatar Waqar Ahmed Committed by Christoph Cullmann
Browse files

Add a frontend for compiler-explorer

This change introduces a new plugin: Compiler Explorer. The main purpose
of this plugin is to allow you to view the generated code of any file
in your project easily. You can do that in Compiler-Explorer but it
takes a lot more effort and you have to copy over flags etc.

Besides the plugin itself, there is a major change packed in i.e., the
ability to allow *any* QWidget in Kate's tabs.
parent a169c5e8
Pipeline #87434 passed with stage
in 3 minutes and 30 seconds
......@@ -4,6 +4,7 @@ find_package(KF5TextEditor ${KF5_DEP_VERSION} QUIET REQUIRED)
ecm_optional_add_subdirectory(backtracebrowser)
ecm_optional_add_subdirectory(close-except-like) # Close all documents except this one (or similar).
ecm_optional_add_subdirectory(colorpicker) # Inline color preview/picker
ecm_optional_add_subdirectory(compiler-explorer)
ecm_optional_add_subdirectory(externaltools)
ecm_optional_add_subdirectory(filebrowser)
ecm_optional_add_subdirectory(filetree)
......
#include "AsmView.h"
#include <AsmViewModel.h>
#include <QApplication>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QMenu>
#include <QPainter>
#include <QStyledItemDelegate>
#include <KLocalizedString>
#include <KSyntaxHighlighting/Theme>
#include <KTextEditor/Editor>
#include <QDebug>
#include <kfts_fuzzy_match.h>
class LineNumberDelegate : public QStyledItemDelegate
{
public:
LineNumberDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
auto updateColors = [this] {
auto e = KTextEditor::Editor::instance();
auto theme = e->theme();
lineNumColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::LineNumbers));
borderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::Separator));
currentLineColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::CurrentLineNumber));
iconBorderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::IconBorder));
};
updateColors();
connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
}
void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
{
QStyleOptionViewItem option = opt;
initStyleOption(&option, index);
painter->save();
QString text = option.text;
option.text = QString();
auto iconBorder = option.rect;
QColor textColor = lineNumColor;
// paint background
if (option.state & QStyle::State_Selected) {
textColor = currentLineColor;
painter->fillRect(option.rect, option.palette.highlight());
} else {
painter->fillRect(iconBorder, iconBorderColor);
}
option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
iconBorder.setRight(iconBorder.right() - 1); // leave space for separator
painter->setPen(borderColor);
painter->drawLine(iconBorder.topRight(), iconBorder.bottomRight());
auto textRect = option.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
textRect.setRight(textRect.right() - 5); // 4 px padding
painter->setFont(index.data(Qt::FontRole).value<QFont>());
painter->setPen(textColor);
painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, text);
painter->restore();
}
private:
QColor currentLineColor;
QColor borderColor;
QColor lineNumColor;
QColor iconBorderColor;
};
class CodeDelegate : public QStyledItemDelegate
{
public:
CodeDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
auto updateColors = [this] {
auto e = KTextEditor::Editor::instance();
auto theme = e->theme();
normalColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Normal));
keywordColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Keyword));
funcColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Function));
stringColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::String));
};
updateColors();
connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
}
void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
{
QStyleOptionViewItem option = opt;
initStyleOption(&option, index);
painter->save();
QString text = option.text;
option.text = QString();
QColor textColor = normalColor;
option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
const AsmViewModel *m = static_cast<const AsmViewModel *>(index.model());
if (m->hasError()) {
drawTextWithErrors(painter, option, text);
painter->restore();
return;
}
QVector<QTextLayout::FormatRange> fmts;
// is a label?
if (!text.isEmpty() && !text.at(0).isSpace()) {
QTextCharFormat f;
f.setForeground(funcColor);
int colon = findColon(text);
if (colon > -1) {
fmts.append({0, colon + 1, f});
}
} else {
int i = firstNotSpace(text);
int nextSpace = text.indexOf(QLatin1Char(' '), i + 1);
// If there is no space then this is the only word on the line
// e.g "ret"
if (nextSpace == -1) {
nextSpace = text.length();
}
QTextCharFormat f;
if (i >= 0 && nextSpace > i) {
f.setForeground(keywordColor);
fmts.append({i, nextSpace - i, f});
i = nextSpace + 1;
}
const auto [strOpen, strClose] = getStringPos(text, i);
if (strOpen >= 0) {
f = QTextCharFormat();
f.setForeground(stringColor);
fmts.append({strOpen, strClose - strOpen, f});
// move forward
i = strClose;
}
auto labels = this->rowLabels(index);
if (!labels.isEmpty()) {
f = QTextCharFormat();
f.setForeground(funcColor);
f.setUnderlineStyle(QTextCharFormat::SingleUnderline);
for (const auto &label : labels) {
fmts.append({label.col, label.len, f});
}
}
}
kfts::paintItemViewText(painter, text, option, fmts);
painter->restore();
}
static int firstNotSpace(const QString &text)
{
for (int i = 0; i < text.size(); ++i) {
if (!text.at(i).isSpace()) {
return i;
}
}
return 0;
}
static std::pair<int, int> getStringPos(const QString &text, int from)
{
int open = text.indexOf(QLatin1Char('"'), from);
if (open == -1)
return {-1, -1};
int close = text.indexOf(QLatin1Char('"'), open + 1);
if (close == -1)
return {-1, -1};
return {open, close + 1}; // +1 because we include the quote as well
}
static int findColon(const QString &text, int from = 0)
{
int colon = text.indexOf(QLatin1Char(':'), from);
if (colon == -1) {
return -1;
}
if (colon + 1 >= text.length()) {
return colon;
}
if (text.at(colon + 1) != QLatin1Char(':')) {
return colon;
}
colon += 2;
auto isLabelEnd = [text](int &i) {
if (text.at(i) == QLatin1Char(':')) {
// reached end, good enough to be a label
if (i + 1 >= text.length()) {
return true;
}
if (text.at(i + 1) != QLatin1Char(':')) {
return true;
}
i++;
}
return false;
};
for (int i = colon; i < text.length(); i++) {
if (isLabelEnd(i)) {
return i;
}
}
return -1;
}
static QVector<LabelInRow> rowLabels(const QModelIndex &index)
{
return index.data(AsmViewModel::RowLabels).value<QVector<LabelInRow>>();
}
void drawTextWithErrors(QPainter *p, const QStyleOptionViewItem &option, const QString &text) const
{
QVector<QTextLayout::FormatRange> fmts;
int errIdx = text.indexOf(QLatin1String("error:"));
if (errIdx != -1) {
QTextCharFormat f;
f.setForeground(keywordColor);
fmts.append({errIdx, 5, f});
}
kfts::paintItemViewText(p, text, option, fmts);
}
private:
QColor keywordColor;
QColor funcColor;
QColor normalColor;
QColor stringColor;
};
AsmView::AsmView(QWidget *parent)
: QTreeView(parent)
{
setUniformRowHeights(true);
setRootIsDecorated(false);
setHeaderHidden(true);
setSelectionMode(QAbstractItemView::ContiguousSelection);
setItemDelegateForColumn(0, new LineNumberDelegate(this));
setItemDelegateForColumn(1, new CodeDelegate(this));
auto updateColors = [this] {
auto e = KTextEditor::Editor::instance();
auto theme = e->theme();
auto palette = this->palette();
QColor c = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::CurrentLine));
palette.setColor(QPalette::Highlight, c);
c = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Normal));
palette.setColor(QPalette::Text, c);
c = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor));
palette.setColor(QPalette::Base, c);
setPalette(palette);
};
updateColors();
connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
}
void AsmView::contextMenuEvent(QContextMenuEvent *e)
{
QPoint p = e->pos();
QMenu menu(this);
menu.addAction(i18n("Scroll to source"), this, [this, p] {
auto model = static_cast<AsmViewModel *>(this->model());
int line = model->sourceLineForAsmLine(indexAt(p));
Q_EMIT scrollToLineRequested(line);
});
QModelIndex index = indexAt(e->pos());
if (index.isValid()) {
auto labels = index.data(AsmViewModel::RowLabels).value<QVector<LabelInRow>>();
if (!labels.isEmpty()) {
menu.addAction(i18n("Jump to label"), this, [this, index] {
auto model = static_cast<AsmViewModel *>(this->model());
const auto labels = index.data(AsmViewModel::RowLabels).value<QVector<LabelInRow>>();
if (labels.isEmpty()) {
return;
}
const QString asmLine = index.data().toString();
auto labelInRow = labels.first();
QString label = asmLine.mid(labelInRow.col, labelInRow.len);
int line = model->asmLineForLabel(label);
if (line != -1) {
auto labelIdx = model->index(line - 1, 1);
scrollTo(labelIdx, ScrollHint::PositionAtCenter);
if (selectionModel()) {
selectionModel()->select(labelIdx, QItemSelectionModel::ClearAndSelect);
}
}
});
}
}
if (!selectedIndexes().isEmpty()) {
menu.addAction(i18n("Copy"), this, [this] {
const auto selected = selectedIndexes();
QString text;
for (const auto idx : selected) {
if (idx.column() == AsmViewModel::Column_LineNo)
continue;
text += idx.data().toString() + QStringLiteral("\n");
}
qApp->clipboard()->setText(text);
});
}
menu.addAction(i18n("Select All"), this, [this] {
if (auto sm = selectionModel()) {
QItemSelection sel;
auto start = model()->index(0, 0);
auto end = model()->index(model()->rowCount() - 1, model()->columnCount() - 1);
sel.select(start, end);
sm->select(sel, QItemSelectionModel::ClearAndSelect);
}
});
menu.exec(mapToGlobal(p));
}
#ifndef KATE_CE_ASM_VIEW_H
#define KATE_CE_ASM_VIEW_H
#include <QTreeView>
class AsmView : public QTreeView
{
Q_OBJECT
public:
explicit AsmView(QWidget *parent);
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
Q_SIGNALS:
void scrollToLineRequested(int line);
};
#endif // KATE_CE_ASM_VIEW_H
#include "AsmViewModel.h"
#include <QColor>
#include <QDebug>
#include <QFont>
#include <QFontDatabase>
AsmViewModel::AsmViewModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
QVariant AsmViewModel::data(const QModelIndex &index, int role) const
{
const int row = index.row();
switch (role) {
case Qt::DisplayRole:
if (index.column() == Column_LineNo)
return QString::number(row + 1);
else if (index.column() == Column_Text)
return m_rows.at(row).text;
else
Q_UNREACHABLE();
case Qt::FontRole:
return m_font;
case Qt::BackgroundRole:
if (index.column() == Column_Text) {
if (m_hoveredLine != -1) {
const auto &sourcePos = m_rows.at(row).source;
if (sourcePos.file.isEmpty() && sourcePos.line == m_hoveredLine + 1) {
return QColor(17, 90, 130); // Dark bluish, probably need better logic and colors
}
}
}
break;
case Roles::RowLabels:
if (index.column() == Column_Text) {
return QVariant::fromValue(m_rows.at(row).labels);
}
}
return {};
}
void AsmViewModel::setDataFromCE(std::vector<AsmRow> text, QHash<SourcePos, CodeGenLines> sourceToAsm, QHash<QString, int> labelToAsmLines)
{
beginResetModel();
m_rows = std::move(text);
endResetModel();
m_sourceToAsm = std::move(sourceToAsm);
m_labelToAsmLine = std::move(labelToAsmLines);
}
void AsmViewModel::clear()
{
beginResetModel();
m_rows.clear();
endResetModel();
m_sourceToAsm.clear();
}
void AsmViewModel::highlightLinkedAsm(int line)
{
m_hoveredLine = line;
}
#ifndef KATE_ASM_VIEW_MODEL_H
#define KATE_ASM_VIEW_MODEL_H
#include <QAbstractTableModel>
#include <QFont>
/**
* Corresponding source code location
*/
struct SourcePos {
QString file;
int line;
int col;
};
inline uint qHash(const SourcePos &key, uint seed = 0)
{
return qHash(key.line /* + key.col*/, seed) ^ qHash(key.file, seed);
}
inline bool operator==(const SourcePos &l, const SourcePos &r)
{
return r.file == l.file && r.line == l.line;
}
struct LabelInRow {
/**
* position of label in text
*/
int col = 0;
int len = 0;
};
Q_DECLARE_METATYPE(QVector<LabelInRow>)
struct AsmRow {
QVector<LabelInRow> labels;
// Corresponding source location
// for this asm row
SourcePos source;
QString text;
};
class AsmViewModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit AsmViewModel(QObject *parent);
enum Columns {
Column_LineNo = 0,
Column_Text = 1,
Column_COUNT,
};
enum Roles {
RowLabels = Qt::UserRole + 1,
};
int rowCount(const QModelIndex &p) const override
{
return p.isValid() ? 0 : m_rows.size();
}
int columnCount(const QModelIndex &) const override
{
return Column_COUNT;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
using CodeGenLines = std::vector<int>;
void setDataFromCE(std::vector<AsmRow> text, QHash<SourcePos, CodeGenLines> sourceToAsm, QHash<QString, int> labelToAsmLines);
void clear();
CodeGenLines asmForSourcePos(const SourcePos &p) const
{
return m_sourceToAsm.value(p);
}
void setFont(const QFont &f)
{
m_font = f;
}
QFont font() const
{
return m_font;
}
int sourceLineForAsmLine(const QModelIndex &asmLineIndex)
{
if (!asmLineIndex.isValid()) {
return -1;
}
int row = asmLineIndex.row();
return m_rows.at(row).source.line;
}
void highlightLinkedAsm(int line);
int asmLineForLabel(const QString &label) const
{
return m_labelToAsmLine.value(label, -1);
}
bool hasError() const
{
return m_hasError;
}
void setHasError(bool err)
{
m_hasError = err;
}
private:
std::vector<AsmRow> m_rows;
QHash<SourcePos, CodeGenLines> m_sourceToAsm;
QHash<QString, int> m_labelToAsmLine;
QFont m_font;
int m_hoveredLine = -1;
bool m_hasError = false;
};
#endif
add_library(compilerexplorer MODULE "")
target_compile_definitions(compilerexplorer PRIVATE TRANSLATION_DOMAIN="compilerexplorer")
target_link_libraries(compilerexplorer PRIVATE KF5::I18n KF5::TextEditor)
target_sources(
compilerexplorer
PRIVATE
ce_plugin.cpp
ce_service.cpp
ce_widget.cpp
AsmView.cpp
AsmViewModel.cpp
compiledbreader.cpp
)
target_include_directories(
compilerexplorer
PUBLIC