File indexing completed on 2024-04-28 05:48:20

0001 #include "AsmView.h"
0002 #include <AsmViewModel.h>
0003 
0004 #include <QApplication>
0005 #include <QClipboard>
0006 #include <QContextMenuEvent>
0007 #include <QMenu>
0008 #include <QPainter>
0009 #include <QStyledItemDelegate>
0010 
0011 #include <KLocalizedString>
0012 #include <KSyntaxHighlighting/Theme>
0013 #include <KTextEditor/Editor>
0014 
0015 #include <QDebug>
0016 
0017 #include <drawing_utils.h>
0018 #include <ktexteditor_utils.h>
0019 
0020 class LineNumberDelegate : public QStyledItemDelegate
0021 {
0022 public:
0023     explicit LineNumberDelegate(QObject *parent)
0024         : QStyledItemDelegate(parent)
0025     {
0026         auto updateColors = [this] {
0027             auto e = KTextEditor::Editor::instance();
0028             auto theme = e->theme();
0029             lineNumColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::LineNumbers));
0030             borderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::Separator));
0031             currentLineColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::CurrentLineNumber));
0032             iconBorderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::IconBorder));
0033         };
0034         updateColors();
0035         connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
0036     }
0037 
0038     void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
0039     {
0040         QStyleOptionViewItem option = opt;
0041         initStyleOption(&option, index);
0042 
0043         painter->save();
0044 
0045         QString text = option.text;
0046         option.text = QString();
0047 
0048         auto iconBorder = option.rect;
0049 
0050         QColor textColor = lineNumColor;
0051         // paint background
0052         if (option.state & QStyle::State_Selected) {
0053             textColor = currentLineColor;
0054             painter->fillRect(option.rect, option.palette.highlight());
0055         } else {
0056             painter->fillRect(iconBorder, iconBorderColor);
0057         }
0058 
0059         option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
0060 
0061         iconBorder.setRight(iconBorder.right() - 1); // leave space for separator
0062 
0063         painter->setPen(borderColor);
0064         painter->drawLine(iconBorder.topRight(), iconBorder.bottomRight());
0065 
0066         auto textRect = option.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
0067         textRect.setRight(textRect.right() - 5); // 4 px padding
0068         painter->setFont(index.data(Qt::FontRole).value<QFont>());
0069         painter->setPen(textColor);
0070         painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, text);
0071 
0072         painter->restore();
0073     }
0074 
0075 private:
0076     QColor currentLineColor;
0077     QColor borderColor;
0078     QColor lineNumColor;
0079     QColor iconBorderColor;
0080 };
0081 
0082 class CodeDelegate : public QStyledItemDelegate
0083 {
0084 public:
0085     explicit CodeDelegate(QObject *parent)
0086         : QStyledItemDelegate(parent)
0087     {
0088         auto updateColors = [this] {
0089             auto e = KTextEditor::Editor::instance();
0090             auto theme = e->theme();
0091 
0092             normalColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Normal));
0093             keywordColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Keyword));
0094             funcColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Function));
0095             stringColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::String));
0096         };
0097         updateColors();
0098         connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
0099     }
0100 
0101     void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
0102     {
0103         QStyleOptionViewItem option = opt;
0104         initStyleOption(&option, index);
0105 
0106         painter->save();
0107 
0108         QString text = option.text;
0109         option.text = QString();
0110 
0111         option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
0112 
0113         const AsmViewModel *m = static_cast<const AsmViewModel *>(index.model());
0114         if (m->hasError()) {
0115             drawTextWithErrors(painter, option, text);
0116             painter->restore();
0117             return;
0118         }
0119 
0120         QList<QTextLayout::FormatRange> fmts;
0121 
0122         // is a label?
0123         if (!text.isEmpty() && !text.at(0).isSpace()) {
0124             QTextCharFormat f;
0125             f.setForeground(funcColor);
0126             int colon = findColon(text);
0127             if (colon > -1) {
0128                 fmts.append({0, colon + 1, f});
0129             }
0130 
0131         } else {
0132             int i = firstNotSpace(text);
0133             int nextSpace = text.indexOf(QLatin1Char(' '), i + 1);
0134 
0135             // If there is no space then this is the only word on the line
0136             // e.g "ret"
0137             if (nextSpace == -1) {
0138                 nextSpace = text.length();
0139             }
0140 
0141             QTextCharFormat f;
0142 
0143             if (i >= 0 && nextSpace > i) {
0144                 f.setForeground(keywordColor);
0145                 fmts.append({i, nextSpace - i, f});
0146 
0147                 i = nextSpace + 1;
0148             }
0149 
0150             const auto [strOpen, strClose] = getStringPos(text, i);
0151             if (strOpen >= 0) {
0152                 f = QTextCharFormat();
0153                 f.setForeground(stringColor);
0154                 fmts.append({strOpen, strClose - strOpen, f});
0155 
0156                 // move forward
0157                 i = strClose;
0158             }
0159 
0160             auto labels = this->rowLabels(index);
0161             if (!labels.isEmpty()) {
0162                 f = QTextCharFormat();
0163                 f.setForeground(funcColor);
0164                 f.setUnderlineStyle(QTextCharFormat::SingleUnderline);
0165                 for (const auto &label : labels) {
0166                     fmts.append({label.col, label.len, f});
0167                 }
0168             }
0169         }
0170 
0171         Utils::paintItemViewText(painter, text, option, fmts);
0172 
0173         painter->restore();
0174     }
0175 
0176     static int firstNotSpace(const QString &text)
0177     {
0178         for (int i = 0; i < text.size(); ++i) {
0179             if (!text.at(i).isSpace()) {
0180                 return i;
0181             }
0182         }
0183         return 0;
0184     }
0185 
0186     static std::pair<int, int> getStringPos(const QString &text, int from)
0187     {
0188         int open = text.indexOf(QLatin1Char('"'), from);
0189         if (open == -1)
0190             return {-1, -1};
0191         int close = text.indexOf(QLatin1Char('"'), open + 1);
0192         if (close == -1)
0193             return {-1, -1};
0194         return {open, close + 1}; // +1 because we include the quote as well
0195     }
0196 
0197     static int findColon(const QString &text, int from = 0)
0198     {
0199         int colon = text.indexOf(QLatin1Char(':'), from);
0200         if (colon == -1) {
0201             return -1;
0202         }
0203 
0204         if (colon + 1 >= text.length()) {
0205             return colon;
0206         }
0207 
0208         if (text.at(colon + 1) != QLatin1Char(':')) {
0209             return colon;
0210         }
0211 
0212         colon += 2;
0213 
0214         auto isLabelEnd = [text](int &i) {
0215             if (text.at(i) == QLatin1Char(':')) {
0216                 // reached end, good enough to be a label
0217                 if (i + 1 >= text.length()) {
0218                     return true;
0219                 }
0220                 if (text.at(i + 1) != QLatin1Char(':')) {
0221                     return true;
0222                 }
0223                 i++;
0224             }
0225             return false;
0226         };
0227 
0228         for (int i = colon; i < text.length(); i++) {
0229             if (isLabelEnd(i)) {
0230                 return i;
0231             }
0232         }
0233 
0234         return -1;
0235     }
0236 
0237     static QList<LabelInRow> rowLabels(const QModelIndex &index)
0238     {
0239         return index.data(AsmViewModel::RowLabels).value<QList<LabelInRow>>();
0240     }
0241 
0242     void drawTextWithErrors(QPainter *p, const QStyleOptionViewItem &option, const QString &text) const
0243     {
0244         QList<QTextLayout::FormatRange> fmts;
0245 
0246         int errIdx = text.indexOf(QLatin1String("error:"));
0247         if (errIdx != -1) {
0248             QTextCharFormat f;
0249             f.setForeground(keywordColor);
0250             fmts.append({errIdx, 5, f});
0251         }
0252 
0253         Utils::paintItemViewText(p, text, option, fmts);
0254     }
0255 
0256 private:
0257     QColor keywordColor;
0258     QColor funcColor;
0259     QColor normalColor;
0260     QColor stringColor;
0261 };
0262 
0263 AsmView::AsmView(QWidget *parent)
0264     : QTreeView(parent)
0265 {
0266     setUniformRowHeights(true);
0267     setRootIsDecorated(false);
0268     setHeaderHidden(true);
0269     setSelectionMode(QAbstractItemView::ContiguousSelection);
0270 
0271     setItemDelegateForColumn(0, new LineNumberDelegate(this));
0272     setItemDelegateForColumn(1, new CodeDelegate(this));
0273 
0274     auto updateColors = [this] {
0275         auto e = KTextEditor::Editor::instance();
0276         auto theme = e->theme();
0277         auto palette = this->palette();
0278         QColor c = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::CurrentLine));
0279         palette.setColor(QPalette::Highlight, c);
0280         c = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Normal));
0281         palette.setColor(QPalette::Text, c);
0282         c = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor));
0283         palette.setColor(QPalette::Base, c);
0284         setPalette(palette);
0285 
0286         auto model = static_cast<AsmViewModel *>(this->model());
0287         if (!model) {
0288             qWarning() << Q_FUNC_INFO << "Unexpected null model!";
0289             return;
0290         }
0291         model->setFont(Utils::editorFont());
0292     };
0293     QMetaObject::invokeMethod(this, updateColors, Qt::QueuedConnection);
0294     connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
0295 }
0296 
0297 void AsmView::contextMenuEvent(QContextMenuEvent *e)
0298 {
0299     QPoint p = e->pos();
0300 
0301     QMenu menu(this);
0302     menu.addAction(i18n("Scroll to source"), this, [this, p] {
0303         auto model = static_cast<AsmViewModel *>(this->model());
0304         int line = model->sourceLineForAsmLine(indexAt(p));
0305         Q_EMIT scrollToLineRequested(line);
0306     });
0307 
0308     QModelIndex index = indexAt(e->pos());
0309     if (index.isValid()) {
0310         auto labels = index.data(AsmViewModel::RowLabels).value<QList<LabelInRow>>();
0311         if (!labels.isEmpty()) {
0312             menu.addAction(i18n("Jump to label"), this, [this, index] {
0313                 auto model = static_cast<AsmViewModel *>(this->model());
0314 
0315                 const auto labels = index.data(AsmViewModel::RowLabels).value<QList<LabelInRow>>();
0316                 if (labels.isEmpty()) {
0317                     return;
0318                 }
0319 
0320                 const QString asmLine = index.data().toString();
0321 
0322                 auto labelInRow = labels.first();
0323                 QString label = asmLine.mid(labelInRow.col, labelInRow.len);
0324                 int line = model->asmLineForLabel(label);
0325 
0326                 if (line != -1) {
0327                     auto labelIdx = model->index(line - 1, 1);
0328                     scrollTo(labelIdx, ScrollHint::PositionAtCenter);
0329                     if (selectionModel()) {
0330                         selectionModel()->select(labelIdx, QItemSelectionModel::ClearAndSelect);
0331                     }
0332                 }
0333             });
0334         }
0335     }
0336 
0337     if (!selectedIndexes().isEmpty()) {
0338         menu.addAction(i18n("Copy"), this, [this] {
0339             const auto selected = selectedIndexes();
0340             QString text;
0341             for (const auto idx : selected) {
0342                 if (idx.column() == AsmViewModel::Column_LineNo)
0343                     continue;
0344                 text += idx.data().toString() + QStringLiteral("\n");
0345             }
0346             qApp->clipboard()->setText(text);
0347         });
0348     }
0349 
0350     menu.addAction(i18n("Select All"), this, [this] {
0351         if (auto sm = selectionModel()) {
0352             QItemSelection sel;
0353             auto start = model()->index(0, 0);
0354             auto end = model()->index(model()->rowCount() - 1, model()->columnCount() - 1);
0355             sel.select(start, end);
0356             sm->select(sel, QItemSelectionModel::ClearAndSelect);
0357         }
0358     });
0359 
0360     menu.exec(mapToGlobal(p));
0361 }
0362 
0363 #include "moc_AsmView.cpp"