File indexing completed on 2024-04-28 05:48:20
0001 #include "ce_widget.h" 0002 #include "AsmView.h" 0003 #include "AsmViewModel.h" 0004 #include "ce_plugin.h" 0005 #include "ce_service.h" 0006 #include "compiledbreader.h" 0007 #include "ktexteditor_utils.h" 0008 0009 #include <QComboBox> 0010 #include <QEvent> 0011 #include <QHBoxLayout> 0012 #include <QHoverEvent> 0013 #include <QInputDialog> 0014 #include <QJsonArray> 0015 #include <QJsonDocument> 0016 #include <QJsonObject> 0017 #include <QLineEdit> 0018 #include <QMenu> 0019 #include <QPushButton> 0020 #include <QSplitter> 0021 #include <QToolButton> 0022 #include <QTreeView> 0023 0024 #include <KConfigGroup> 0025 #include <KLocalizedString> 0026 #include <KMessageBox> 0027 #include <KSharedConfig> 0028 #include <KTextEditor/MainWindow> 0029 #include <KXMLGUIFactory> 0030 #include <kwidgetsaddons_version.h> 0031 0032 enum CE_Options { 0033 CE_Option_FilterLabel = 1, 0034 CE_Option_IntelAsm, 0035 CE_Option_FilterUnusedLibFuncs, 0036 CE_Option_FilterComments, 0037 CE_Option_Demangle, 0038 }; 0039 0040 static bool readConfigForCEOption(CE_Options o) 0041 { 0042 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("kate_compilerexplorer")); 0043 switch (o) { 0044 case CE_Option_FilterLabel: 0045 return cg.readEntry("FilterUnusedLabels", true); 0046 case CE_Option_IntelAsm: 0047 return cg.readEntry("UseIntelAsmSyntax", true); 0048 case CE_Option_FilterUnusedLibFuncs: 0049 return cg.readEntry("OptionFilterLibFuncs", true); 0050 case CE_Option_FilterComments: 0051 return cg.readEntry("OptionFilterComments", true); 0052 case CE_Option_Demangle: 0053 return cg.readEntry("OptionDemangle", true); 0054 default: 0055 Q_UNREACHABLE(); 0056 } 0057 } 0058 0059 static void writeConfigForCEOption(CE_Options o, bool value) 0060 { 0061 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("kate_compilerexplorer")); 0062 switch (o) { 0063 case CE_Option_FilterLabel: 0064 return cg.writeEntry("FilterUnusedLabels", value); 0065 case CE_Option_IntelAsm: 0066 return cg.writeEntry("UseIntelAsmSyntax", value); 0067 case CE_Option_FilterUnusedLibFuncs: 0068 return cg.writeEntry("OptionFilterLibFuncs", value); 0069 case CE_Option_FilterComments: 0070 return cg.writeEntry("OptionFilterComments", value); 0071 case CE_Option_Demangle: 0072 return cg.writeEntry("OptionDemangle", value); 0073 default: 0074 Q_UNREACHABLE(); 0075 } 0076 } 0077 0078 CEWidget::CEWidget(CEPluginView *pluginView, KTextEditor::MainWindow *mainWindow) 0079 : QWidget() // The widget will be passed on to kate, no need for a parent 0080 , m_pluginView(pluginView) 0081 , m_mainWindow(mainWindow) 0082 , m_asmView(new AsmView(this)) 0083 , m_model(new AsmViewModel(this)) 0084 , m_lineEdit(new QLineEdit(this)) 0085 , m_languagesCombo(new QComboBox(this)) 0086 , m_compilerCombo(new QComboBox(this)) 0087 , m_optsCombo(new QToolButton(this)) 0088 , m_compileButton(new QPushButton(this)) 0089 { 0090 doc = m_mainWindow->activeView()->document(); 0091 0092 setWindowTitle(QStringLiteral("Compiler Explorer - ") + doc->documentName()); 0093 0094 auto mainLayout = new QVBoxLayout; 0095 setLayout(mainLayout); 0096 0097 createTopBar(mainLayout); 0098 createMainViews(mainLayout); 0099 0100 connect(m_compileButton, &QPushButton::clicked, this, &CEWidget::doCompile); 0101 connect(CompilerExplorerSvc::instance(), &CompilerExplorerSvc::asmResult, this, &CEWidget::processAndShowAsm); 0102 0103 connect(this, &CEWidget::lineHovered, m_model, &AsmViewModel::highlightLinkedAsm); 0104 connect(m_asmView, &AsmView::scrollToLineRequested, this, [this](int line) { 0105 m_textEditor->setCursorPosition({line, 0}); 0106 }); 0107 0108 QString file = doc->url().toLocalFile(); 0109 QString compilecmds = CompileDBReader::locateCompileCommands(m_mainWindow, file); 0110 QString args = CompileDBReader::filteredArgsForFile(compilecmds, file); 0111 m_lineEdit->setText(args); 0112 0113 warnIfBadArgs(args.split(QLatin1Char(' '))); 0114 0115 setFocusPolicy(Qt::StrongFocus); 0116 } 0117 0118 CEWidget::~CEWidget() 0119 { 0120 removeViewAsActiveXMLGuiClient(); 0121 } 0122 0123 bool CEWidget::shouldClose() 0124 { 0125 int ret = 0126 KMessageBox::warningTwoActions(this, i18n("Do you really want to close %1?", windowTitle()), {}, KStandardGuiItem::close(), KStandardGuiItem::cancel()); 0127 return ret == KMessageBox::PrimaryAction; 0128 } 0129 0130 void CEWidget::removeViewAsActiveXMLGuiClient() 0131 { 0132 if (m_textEditor) { 0133 m_mainWindow->guiFactory()->removeClient(m_textEditor); 0134 } 0135 } 0136 0137 bool CEWidget::eventFilter(QObject *o, QEvent *e) 0138 { 0139 // We live in a stacked widget in kateviewspace 0140 // use hide/show to figure out when we are not active 0141 if (e->type() == QEvent::Show) { 0142 if (m_textEditor) { 0143 m_mainWindow->guiFactory()->addClient(m_textEditor); 0144 } 0145 return QWidget::eventFilter(o, e); 0146 } else if (e->type() == QEvent::Hide) { 0147 removeViewAsActiveXMLGuiClient(); 0148 return QWidget::eventFilter(o, e); 0149 } 0150 0151 if (o != m_textEditor) { 0152 return QWidget::eventFilter(o, e); 0153 } 0154 0155 if (e->type() != QEvent::HoverMove) { 0156 return QWidget::eventFilter(o, e); 0157 } 0158 0159 auto event = static_cast<QHoverEvent *>(e); 0160 auto cursor = m_textEditor->coordinatesToCursor(event->position().toPoint()); 0161 Q_EMIT lineHovered(cursor.line()); // Can be invalid, that is okay 0162 m_asmView->viewport()->update(); 0163 0164 return QWidget::eventFilter(o, e); 0165 } 0166 0167 void CEWidget::createTopBar(QVBoxLayout *mainLayout) 0168 { 0169 QHBoxLayout *topBarLayout = new QHBoxLayout; 0170 mainLayout->addLayout(topBarLayout); 0171 0172 topBarLayout->addWidget(m_languagesCombo); 0173 topBarLayout->addWidget(m_compilerCombo); 0174 topBarLayout->addWidget(m_optsCombo); 0175 topBarLayout->addWidget(m_lineEdit); 0176 topBarLayout->addWidget(m_compileButton); 0177 0178 auto svc = CompilerExplorerSvc::instance(); 0179 0180 connect(svc, &CompilerExplorerSvc::languages, this, &CEWidget::setAvailableLanguages); 0181 svc->sendRequest(CompilerExplorer::Languages); 0182 0183 connect(svc, &CompilerExplorerSvc::compilers, this, &CEWidget::setAvailableCompilers); 0184 svc->sendRequest(CompilerExplorer::Compilers); 0185 0186 m_compileButton->setText(i18n("Compile")); 0187 0188 initOptionsComboBox(); 0189 } 0190 0191 void CEWidget::setAvailableLanguages(const QByteArray &data) 0192 { 0193 if (!doc) { 0194 return; 0195 } 0196 0197 const QJsonArray langs = QJsonDocument::fromJson(data).array(); 0198 0199 // We use the highlightingMode to set the current active language, 0200 // but this needs some sort of mapping to CE's lang names 0201 auto currentFileLang = doc->highlightingMode(); 0202 QString activeLang; 0203 0204 m_languagesCombo->clear(); 0205 0206 for (const auto &langJV : langs) { 0207 const auto lang = langJV.toObject(); 0208 const auto id = lang.value(QStringLiteral("id")).toString(); 0209 const auto name = lang.value(QStringLiteral("name")).toString(); 0210 0211 if (name == currentFileLang) { 0212 activeLang = name; 0213 } 0214 0215 m_languagesCombo->addItem(name, id); 0216 } 0217 m_languagesCombo->setCurrentText(activeLang); 0218 m_languagesCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); 0219 0220 connect(m_languagesCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { 0221 QString id = m_languagesCombo->itemData(index).toString(); 0222 repopulateCompilersCombo(id); 0223 }); 0224 } 0225 0226 void CEWidget::setAvailableCompilers(const QByteArray &data) 0227 { 0228 if (!doc) { 0229 return; 0230 } 0231 0232 const QJsonArray json = QJsonDocument::fromJson(data).array(); 0233 0234 m_langToCompiler.clear(); 0235 0236 for (const auto &value : json) { 0237 const auto compilerName = value[QStringLiteral("name")].toString(); 0238 const auto lang = value[QStringLiteral("lang")].toString(); 0239 const auto id = value[QStringLiteral("id")]; 0240 0241 Compiler compiler{compilerName, id}; 0242 m_langToCompiler.push_back({lang, compiler}); 0243 } 0244 0245 repopulateCompilersCombo(doc->highlightingMode().toLower()); 0246 m_compilerCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); 0247 } 0248 0249 void CEWidget::repopulateCompilersCombo(const QString &lang) 0250 { 0251 auto currentFileLang = lang; 0252 0253 auto compilersForLang = compilersForLanguage(currentFileLang); 0254 if (compilersForLang.empty()) { 0255 compilersForLang = m_langToCompiler; 0256 } 0257 0258 m_compilerCombo->clear(); 0259 0260 for (const auto &[lang, compiler] : compilersForLang) { 0261 m_compilerCombo->addItem(compiler.name, compiler.id); 0262 } 0263 0264 m_compilerCombo->setCurrentIndex(0); 0265 } 0266 0267 std::vector<CEWidget::CompilerLangPair> CEWidget::compilersForLanguage(const QString &lang) const 0268 { 0269 std::vector<CompilerLangPair> compilersForLang; 0270 for (const auto &pair : m_langToCompiler) { 0271 if (pair.first == lang) { 0272 compilersForLang.push_back(pair); 0273 } 0274 } 0275 return compilersForLang; 0276 } 0277 0278 void CEWidget::initOptionsComboBox() 0279 { 0280 QMenu *menu = new QMenu(this); 0281 m_optsCombo->setMenu(menu); 0282 m_optsCombo->setToolButtonStyle(Qt::ToolButtonTextOnly); 0283 m_optsCombo->setText(i18n("Options")); 0284 m_optsCombo->setPopupMode(QToolButton::InstantPopup); 0285 m_optsCombo->setArrowType(Qt::DownArrow); 0286 0287 auto checkableAction = [this](const QString &name, CE_Options o) { 0288 QAction *action = new QAction(name, this); 0289 action->setCheckable(true); 0290 action->setChecked(readConfigForCEOption(o)); 0291 action->setData((int)o); 0292 0293 connect(action, &QAction::toggled, this, [o](bool v) { 0294 writeConfigForCEOption(o, v); 0295 }); 0296 0297 return action; 0298 }; 0299 0300 menu->addAction(checkableAction(i18n("Demangle Identifiers"), CE_Option_Demangle)); 0301 menu->addAction(checkableAction(i18n("Filter Library Functions"), CE_Option_FilterUnusedLibFuncs)); 0302 menu->addAction(checkableAction(i18n("Filter Unused Labels"), CE_Option_FilterLabel)); 0303 menu->addAction(checkableAction(i18n("Filter Comments"), CE_Option_FilterComments)); 0304 menu->addAction(checkableAction(i18n("Intel Syntax"), CE_Option_IntelAsm)); 0305 0306 menu->addAction(i18n("Change Url..."), this, [this] { 0307 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("kate_compilerexplorer")); 0308 QString url = cg.readEntry("kate_compilerexplorer_url", QStringLiteral("http://localhost:10240")); 0309 0310 bool ok = false; 0311 QString newUrl = QInputDialog::getText(this, 0312 i18n("Enter Url"), 0313 i18n("Enter Url to CompilerExplorer instance. For e.g., http://localhost:10240"), 0314 QLineEdit::Normal, 0315 url, 0316 &ok); 0317 0318 if (ok && !newUrl.isEmpty()) { 0319 CompilerExplorerSvc::instance()->changeUrl(newUrl); 0320 cg.writeEntry("kate_compilerexplorer_url", newUrl); 0321 } 0322 }); 0323 } 0324 0325 void CEWidget::createMainViews(QVBoxLayout *mainLayout) 0326 { 0327 if (!doc) { 0328 return; 0329 } 0330 0331 QSplitter *splitter = new QSplitter(this); 0332 0333 m_textEditor = doc->createView(this, m_mainWindow); 0334 0335 m_asmView->setModel(m_model); 0336 0337 addExtraActionstoTextEditor(); 0338 m_textEditor->installEventFilter(this); 0339 m_textEditor->focusProxy()->installEventFilter(this); 0340 0341 splitter->addWidget(m_textEditor); 0342 splitter->addWidget(m_asmView); 0343 splitter->setSizes({INT_MAX, INT_MAX}); 0344 0345 mainLayout->addWidget(splitter); 0346 } 0347 0348 QString CEWidget::currentCompiler() const 0349 { 0350 return m_compilerCombo->currentData().toString(); 0351 } 0352 0353 bool CEWidget::compilationFailed(const QJsonObject &obj) 0354 { 0355 int colWidth = m_asmView->columnWidth(AsmViewModel::Column_Text); 0356 0357 QFontMetrics fm(m_asmView->font()); 0358 int avgCharWidth = fm.averageCharWidth(); 0359 0360 int maxChars = colWidth / avgCharWidth; 0361 0362 auto code = obj.value(QStringLiteral("code")); 0363 if (!code.isUndefined()) { 0364 int compilerReturnCode = code.toInt(); 0365 if (compilerReturnCode != 0) { 0366 const auto stderror = obj.value(QStringLiteral("stderr")).toArray(); 0367 std::vector<AsmRow> rows; 0368 0369 // strip any escape sequences 0370 static const QRegularExpression re(QStringLiteral("[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]")); 0371 0372 AsmRow r; 0373 r.text = i18n("Compiler returned: %1", compilerReturnCode); 0374 rows.push_back(r); 0375 0376 for (const auto &err : stderror) { 0377 QString text = err.toObject().value(QStringLiteral("text")).toString(); 0378 text.replace(re, QLatin1String("")); 0379 0380 QStringList lines; 0381 lines.reserve(text.size() / maxChars); 0382 for (int i = 0; i < text.size(); i += maxChars) { 0383 lines << text.mid(i, maxChars); 0384 } 0385 0386 for (const auto &line : std::as_const(lines)) { 0387 AsmRow r; 0388 r.text = line; 0389 rows.push_back(r); 0390 } 0391 } 0392 0393 m_model->setDataFromCE(std::move(rows), {}, {}); 0394 return true; 0395 } 0396 } 0397 return false; 0398 } 0399 0400 void CEWidget::processAndShowAsm(const QByteArray &data) 0401 { 0402 m_model->clear(); 0403 0404 std::vector<AsmRow> rows; 0405 QHash<SourcePos, AsmViewModel::CodeGenLines> sourceToAsm; 0406 0407 const auto json = QJsonDocument::fromJson(data); 0408 0409 const auto mainObj = json.object(); 0410 0411 if (compilationFailed(mainObj)) { 0412 m_model->setHasError(true); 0413 return; 0414 } 0415 m_model->setHasError(false); 0416 0417 // printf("%s\n", json.toJson().constData()); 0418 const QJsonArray assembly = mainObj.value(QStringLiteral("asm")).toArray(); 0419 rows.reserve(assembly.size()); 0420 0421 int currentAsmLine = 0; 0422 0423 // Process the assembly 0424 for (const auto &line : assembly) { 0425 AsmRow row; 0426 0427 auto labels = line[QStringLiteral("labels")].toArray(); 0428 if (!labels.empty()) { 0429 for (const auto &label : labels) { 0430 LabelInRow l; 0431 auto rangeJV = label.toObject().value(QStringLiteral("range")); 0432 0433 const auto range = rangeJV.toObject(); 0434 int startCol = range.value(QStringLiteral("startCol")).toInt() - 1; 0435 int endCol = range.value(QStringLiteral("endCol")).toInt(); 0436 l.col = startCol; 0437 l.len = endCol - startCol; 0438 row.labels.push_back(l); 0439 } 0440 } 0441 0442 const auto source = line[QStringLiteral("source")].toObject(); 0443 QString file = source.value(QStringLiteral("file")).toString(); 0444 int srcLine = source.value(QStringLiteral("line")).toInt(); 0445 int srcCol = source.value(QStringLiteral("column")).toInt(); 0446 0447 row.source.file = file.isEmpty() ? QString() : file; // can be empty 0448 row.source.line = srcLine; 0449 row.source.col = srcCol; 0450 0451 row.text = line[QStringLiteral("text")].toString(); 0452 0453 rows.push_back(row); 0454 sourceToAsm[row.source].push_back(currentAsmLine); 0455 0456 currentAsmLine++; 0457 } 0458 0459 const QJsonObject labelDefinitions = mainObj.value(QStringLiteral("labelDefinitions")).toObject(); 0460 // Label => Line Number 0461 QHash<QString, int> labelDefs; 0462 auto it = labelDefinitions.constBegin(); 0463 auto end = labelDefinitions.constEnd(); 0464 for (; it != end; ++it) { 0465 labelDefs.insert(it.key(), it.value().toInt()); 0466 } 0467 0468 m_model->setDataFromCE(std::move(rows), std::move(sourceToAsm), std::move(labelDefs)); 0469 m_asmView->resizeColumnToContents(0); 0470 } 0471 0472 void CEWidget::doCompile() 0473 { 0474 m_model->clear(); 0475 0476 if (!doc) { 0477 return; 0478 } 0479 0480 const QString text = doc->text(); 0481 if (text.isEmpty()) { 0482 return; 0483 } 0484 0485 const auto actions = m_optsCombo->menu()->actions(); 0486 0487 bool demangle = false; 0488 bool intel = false; 0489 bool labels = false; 0490 bool comments = false; 0491 bool libfuncs = false; 0492 for (auto action : actions) { 0493 bool isChecked = action->isChecked(); 0494 if (action->data().toInt() == CE_Option_Demangle) 0495 demangle = isChecked; 0496 else if (action->data().toInt() == CE_Option_FilterComments) 0497 comments = isChecked; 0498 else if (action->data().toInt() == CE_Option_FilterLabel) 0499 labels = isChecked; 0500 else if (action->data().toInt() == CE_Option_FilterUnusedLibFuncs) 0501 libfuncs = isChecked; 0502 else if (action->data().toInt() == CE_Option_IntelAsm) 0503 intel = isChecked; 0504 } 0505 0506 QString args2 = m_lineEdit->text().trimmed(); 0507 0508 const auto json = CompilerExplorerSvc::getCompilationOptions(text, args2, intel, demangle, labels, comments, libfuncs); 0509 const auto compiler = currentCompiler(); 0510 const QString endpoint = QStringLiteral("compiler/") + compiler + QStringLiteral("/compile"); 0511 0512 CompilerExplorerSvc::instance()->compileRequest(endpoint, json.toJson()); 0513 } 0514 0515 void CEWidget::addExtraActionstoTextEditor() 0516 { 0517 Q_ASSERT(m_textEditor); 0518 0519 auto m = m_textEditor->defaultContextMenu(); 0520 0521 QMenu *menu = new QMenu(this); 0522 menu->addAction(i18n("Reveal linked code"), this, [this] { 0523 auto line = m_textEditor->cursorPosition().line(); 0524 SourcePos p{QString(), line + 1, 0}; 0525 AsmViewModel::CodeGenLines asmLines = m_model->asmForSourcePos(p); 0526 // qDebug() << "Linked code for: " << line; 0527 if (!asmLines.empty()) { 0528 auto index = m_model->index(asmLines.front(), 0); 0529 m_asmView->scrollTo(index, QAbstractItemView::PositionAtCenter); 0530 0531 // Highlight the linked lines 0532 Q_EMIT lineHovered(line); 0533 m_asmView->viewport()->update(); 0534 // qDebug() << "Scrolling to: " << index.data() << asmLines.size() << asmLines.front(); 0535 } 0536 }); 0537 menu->addActions(m->actions()); 0538 m_textEditor->setContextMenu(menu); 0539 } 0540 0541 void CEWidget::sendMessage(const QString &plainText, bool warn) 0542 { 0543 // use generic output view 0544 Utils::showMessage(plainText, {}, i18n("CompilerExplorer"), warn ? MessageType::Error : MessageType::Info); 0545 } 0546 0547 void CEWidget::warnIfBadArgs(const QStringList &args) 0548 { 0549 QStringList warnableArgs = {QStringLiteral("flto"), QStringLiteral("fsanitize")}; 0550 QStringList found; 0551 for (const auto &a : args) { 0552 for (const auto &w : warnableArgs) { 0553 if (a.contains(w)) { 0554 warnableArgs.removeOne(w); 0555 found.append(w); 0556 } 0557 } 0558 } 0559 0560 QString msg = i18n("'%1' compiler flags were found. Output may not be useful.", found.join(QLatin1String(", "))); 0561 sendMessage(msg, true); 0562 } 0563 0564 #include "moc_ce_widget.cpp"