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"