File indexing completed on 2024-05-05 05:51:20

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2008-2014 Dominik Haumann <dhaumann kde org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 // BEGIN Includes
0008 #include "katebacktracebrowser.h"
0009 
0010 #include "btparser.h"
0011 
0012 #include <KConfigGroup>
0013 #include <KLineEdit>
0014 #include <KLocalizedString> // i18n
0015 #include <KPluginFactory>
0016 #include <KSharedConfig>
0017 
0018 #include <KTextEditor/Cursor>
0019 #include <KTextEditor/View>
0020 
0021 #include <QClipboard>
0022 #include <QDialogButtonBox>
0023 #include <QDir>
0024 #include <QFile>
0025 #include <QFileDialog>
0026 #include <QFileInfo>
0027 #include <QStandardPaths>
0028 #include <QTreeWidget>
0029 #include <QUrl>
0030 // END Includes
0031 
0032 K_PLUGIN_FACTORY_WITH_JSON(KateBtBrowserFactory, "katebacktracebrowserplugin.json", registerPlugin<KateBtBrowserPlugin>();)
0033 
0034 KateBtBrowserPlugin *KateBtBrowserPlugin::s_self = nullptr;
0035 static QStringList fileExtensions = QStringList() << QStringLiteral("*.cpp") << QStringLiteral("*.cxx") << QStringLiteral("*.c") << QStringLiteral("*.cc")
0036                                                   << QStringLiteral("*.h") << QStringLiteral("*.hpp") << QStringLiteral("*.hxx") << QStringLiteral("*.moc");
0037 
0038 KateBtBrowserPlugin::KateBtBrowserPlugin(QObject *parent, const QVariantList &)
0039     : KTextEditor::Plugin(parent)
0040     , indexer(&db)
0041 {
0042     s_self = this;
0043     db.loadFromFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser/backtracedatabase.db"));
0044 }
0045 
0046 KateBtBrowserPlugin::~KateBtBrowserPlugin()
0047 {
0048     if (indexer.isRunning()) {
0049         indexer.cancel();
0050         indexer.wait();
0051     }
0052 
0053     const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser");
0054     QDir().mkpath(path);
0055     db.saveToFile(path + QStringLiteral("/backtracedatabase.db"));
0056 
0057     s_self = nullptr;
0058 }
0059 
0060 KateBtBrowserPlugin &KateBtBrowserPlugin::self()
0061 {
0062     return *s_self;
0063 }
0064 
0065 QObject *KateBtBrowserPlugin::createView(KTextEditor::MainWindow *mainWindow)
0066 {
0067     KateBtBrowserPluginView *view = new KateBtBrowserPluginView(this, mainWindow);
0068     return view;
0069 }
0070 
0071 KateBtDatabase &KateBtBrowserPlugin::database()
0072 {
0073     return db;
0074 }
0075 
0076 BtFileIndexer &KateBtBrowserPlugin::fileIndexer()
0077 {
0078     return indexer;
0079 }
0080 
0081 void KateBtBrowserPlugin::startIndexer()
0082 {
0083     if (indexer.isRunning()) {
0084         indexer.cancel();
0085         indexer.wait();
0086     }
0087     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("backtracebrowser"));
0088     indexer.setSearchPaths(cg.readEntry("search-folders", QStringList()));
0089     indexer.setFilter(cg.readEntry("file-extensions", fileExtensions));
0090     indexer.start();
0091     Q_EMIT newStatus(i18n("Indexing files..."));
0092 }
0093 
0094 int KateBtBrowserPlugin::configPages() const
0095 {
0096     return 1;
0097 }
0098 
0099 KTextEditor::ConfigPage *KateBtBrowserPlugin::configPage(int number, QWidget *parent)
0100 {
0101     if (number == 0) {
0102         return new KateBtConfigWidget(parent);
0103     }
0104 
0105     return nullptr;
0106 }
0107 
0108 KateBtBrowserPluginView::KateBtBrowserPluginView(KateBtBrowserPlugin *plugin, KTextEditor::MainWindow *mainWindow)
0109     : QObject(mainWindow)
0110 {
0111     // init console
0112     QWidget *toolview = mainWindow->createToolView(plugin,
0113                                                    QStringLiteral("kate_private_plugin_katebacktracebrowserplugin"),
0114                                                    KTextEditor::MainWindow::Bottom,
0115                                                    QIcon::fromTheme(QStringLiteral("tools-report-bug")),
0116                                                    i18n("Backtrace"));
0117     m_widget = new KateBtBrowserWidget(mainWindow, toolview);
0118 
0119     connect(plugin, &KateBtBrowserPlugin::newStatus, m_widget, &KateBtBrowserWidget::setStatus);
0120 }
0121 
0122 KateBtBrowserPluginView::~KateBtBrowserPluginView()
0123 {
0124     // cleanup, kill toolview + widget
0125     auto toolview = m_widget->parent();
0126     delete m_widget;
0127     delete toolview;
0128 }
0129 
0130 KateBtBrowserWidget::KateBtBrowserWidget(KTextEditor::MainWindow *mainwindow, QWidget *parent)
0131     : QWidget(parent)
0132     , mw(mainwindow)
0133 {
0134     setupUi(this);
0135 
0136     timer.setSingleShot(true);
0137     connect(&timer, &QTimer::timeout, this, &KateBtBrowserWidget::clearStatus);
0138     connect(btnBacktrace, &QPushButton::clicked, this, &KateBtBrowserWidget::loadFile);
0139     connect(btnClipboard, &QPushButton::clicked, this, &KateBtBrowserWidget::loadClipboard);
0140     connect(btnConfigure, &QPushButton::clicked, this, &KateBtBrowserWidget::configure);
0141     connect(lstBacktrace, &QTreeWidget::itemActivated, this, &KateBtBrowserWidget::itemActivated);
0142 }
0143 
0144 KateBtBrowserWidget::~KateBtBrowserWidget()
0145 {
0146 }
0147 
0148 void KateBtBrowserWidget::loadFile()
0149 {
0150     QString url = QFileDialog::getOpenFileName(mw->window(), i18n("Load Backtrace"));
0151     QFile f(url);
0152     if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
0153         QString str = QString::fromUtf8(f.readAll());
0154         loadBacktrace(str);
0155     }
0156 }
0157 
0158 void KateBtBrowserWidget::loadClipboard()
0159 {
0160     QString bt = QApplication::clipboard()->text();
0161     loadBacktrace(bt);
0162 }
0163 
0164 void KateBtBrowserWidget::loadBacktrace(const QString &bt)
0165 {
0166     const QList<BtInfo> infos = KateBtParser::parseBacktrace(bt);
0167 
0168     lstBacktrace->clear();
0169     for (const BtInfo &info : infos) {
0170         QTreeWidgetItem *it = new QTreeWidgetItem(lstBacktrace);
0171         it->setData(0, Qt::DisplayRole, QString::number(info.step));
0172         it->setData(0, Qt::ToolTipRole, QString::number(info.step));
0173         QFileInfo fi(info.filename);
0174         it->setData(1, Qt::DisplayRole, fi.fileName());
0175         it->setData(1, Qt::ToolTipRole, info.filename);
0176 
0177         if (info.type == BtInfo::Source) {
0178             it->setData(2, Qt::DisplayRole, QString::number(info.line));
0179             it->setData(2, Qt::ToolTipRole, QString::number(info.line));
0180             it->setData(2, Qt::UserRole, QVariant(info.line));
0181         }
0182         it->setData(3, Qt::DisplayRole, info.function);
0183         it->setData(3, Qt::ToolTipRole, info.function);
0184 
0185         lstBacktrace->addTopLevelItem(it);
0186     }
0187     lstBacktrace->resizeColumnToContents(0);
0188     lstBacktrace->resizeColumnToContents(1);
0189     lstBacktrace->resizeColumnToContents(2);
0190 
0191     if (lstBacktrace->topLevelItemCount()) {
0192         setStatus(i18n("Loading backtrace succeeded"));
0193     } else {
0194         setStatus(i18n("Loading backtrace failed"));
0195     }
0196 }
0197 
0198 void KateBtBrowserWidget::configure()
0199 {
0200     KateBtConfigDialog dlg(mw->window());
0201     dlg.exec();
0202 }
0203 
0204 void KateBtBrowserWidget::itemActivated(QTreeWidgetItem *item, int column)
0205 {
0206     Q_UNUSED(column);
0207 
0208     QVariant variant = item->data(2, Qt::UserRole);
0209     if (variant.isValid()) {
0210         int line = variant.toInt();
0211         QString file = QDir::fromNativeSeparators(item->data(1, Qt::ToolTipRole).toString());
0212         file = QDir::cleanPath(file);
0213 
0214         QString path = file;
0215         // if not absolute path + exists, try to find with index
0216         if (!QFile::exists(path)) {
0217             // try to match the backtrace forms ".*/foo/bar.txt" and "foo/bar.txt"
0218             static const QRegularExpression rx1(QStringLiteral("/([^/]+)/([^/]+)$"));
0219             QRegularExpressionMatch match = rx1.match(file);
0220             if (match.hasMatch()) {
0221                 file = match.captured(1) + QLatin1Char('/') + match.captured(2);
0222             } else {
0223                 static const QRegularExpression rx2(QStringLiteral("([^/]+)/([^/]+)$"));
0224                 if (rx2.match(file).hasMatch()) {
0225                     // file is of correct form
0226                 } else {
0227                     qDebug() << "file patter did not match:" << file;
0228                     setStatus(i18n("File not found: %1", file));
0229                     return;
0230                 }
0231             }
0232             path = KateBtBrowserPlugin::self().database().value(file);
0233         }
0234 
0235         if (!path.isEmpty() && QFile::exists(path)) {
0236             KTextEditor::View *kv = mw->openUrl(QUrl::fromLocalFile(path));
0237             kv->setCursorPosition(KTextEditor::Cursor(line - 1, 0));
0238             kv->setFocus();
0239             setStatus(i18n("Opened file: %1", file));
0240         }
0241     } else {
0242         setStatus(i18n("No debugging information available"));
0243     }
0244 }
0245 
0246 void KateBtBrowserWidget::setStatus(const QString &status)
0247 {
0248     lblStatus->setText(status);
0249     timer.start(10 * 1000);
0250 }
0251 
0252 void KateBtBrowserWidget::clearStatus()
0253 {
0254     lblStatus->setText(QString());
0255 }
0256 
0257 KateBtConfigWidget::KateBtConfigWidget(QWidget *parent)
0258     : KTextEditor::ConfigPage(parent)
0259 {
0260     setupUi(this);
0261     edtUrl->setMode(KFile::Directory);
0262     edtUrl->setUrl(QUrl(QDir().absolutePath()));
0263 
0264     reset();
0265 
0266     connect(btnAdd, &QPushButton::clicked, this, &KateBtConfigWidget::add);
0267     connect(btnRemove, &QPushButton::clicked, this, &KateBtConfigWidget::remove);
0268     connect(edtExtensions, &QLineEdit::textChanged, this, &KateBtConfigWidget::textChanged);
0269 
0270     m_changed = false;
0271 }
0272 
0273 KateBtConfigWidget::~KateBtConfigWidget()
0274 {
0275 }
0276 
0277 QString KateBtConfigWidget::name() const
0278 {
0279     return i18n("Backtrace");
0280 }
0281 
0282 QString KateBtConfigWidget::fullName() const
0283 {
0284     return i18n("Backtrace Settings");
0285 }
0286 
0287 QIcon KateBtConfigWidget::icon() const
0288 {
0289     return QIcon::fromTheme(QStringLiteral("tools-report-bug"));
0290 }
0291 
0292 void KateBtConfigWidget::apply()
0293 {
0294     if (m_changed) {
0295         QStringList sl;
0296         for (int i = 0; i < lstFolders->count(); ++i) {
0297             sl << lstFolders->item(i)->data(Qt::DisplayRole).toString();
0298         }
0299         KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("backtracebrowser"));
0300         cg.writeEntry("search-folders", sl);
0301 
0302         QString filter = edtExtensions->text();
0303         filter.replace(QLatin1Char(','), QLatin1Char(' ')).replace(QLatin1Char(';'), QLatin1Char(' '));
0304         cg.writeEntry("file-extensions", filter.split(QLatin1Char(' '), Qt::SkipEmptyParts));
0305 
0306         KateBtBrowserPlugin::self().startIndexer();
0307         m_changed = false;
0308     }
0309 }
0310 
0311 void KateBtConfigWidget::reset()
0312 {
0313     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("backtracebrowser"));
0314     lstFolders->clear();
0315     lstFolders->addItems(cg.readEntry("search-folders", QStringList()));
0316     edtExtensions->setText(cg.readEntry("file-extensions", fileExtensions).join(QLatin1Char(' ')));
0317 }
0318 
0319 void KateBtConfigWidget::defaults()
0320 {
0321     lstFolders->clear();
0322     edtExtensions->setText(fileExtensions.join(QLatin1Char(' ')));
0323 
0324     m_changed = true;
0325 }
0326 
0327 void KateBtConfigWidget::add()
0328 {
0329     QDir url(edtUrl->lineEdit()->text());
0330     if (url.exists()) {
0331         if (lstFolders->findItems(url.absolutePath(), Qt::MatchExactly).empty()) {
0332             lstFolders->addItem(url.absolutePath());
0333             Q_EMIT changed();
0334             m_changed = true;
0335         }
0336     }
0337 }
0338 
0339 void KateBtConfigWidget::remove()
0340 {
0341     QListWidgetItem *item = lstFolders->currentItem();
0342     if (item) {
0343         delete item;
0344         Q_EMIT changed();
0345         m_changed = true;
0346     }
0347 }
0348 
0349 void KateBtConfigWidget::textChanged()
0350 {
0351     Q_EMIT changed();
0352     m_changed = true;
0353 }
0354 
0355 KateBtConfigDialog::KateBtConfigDialog(QWidget *parent)
0356     : QDialog(parent)
0357 {
0358     setWindowTitle(i18n("Backtrace Browser Settings"));
0359 
0360     m_configWidget = new KateBtConfigWidget(this);
0361 
0362     QVBoxLayout *layout = new QVBoxLayout(this);
0363 
0364     QDialogButtonBox *box = new QDialogButtonBox(this);
0365     box->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0366 
0367     layout->addWidget(m_configWidget);
0368     layout->addWidget(box);
0369 
0370     connect(this, &KateBtConfigDialog::accepted, m_configWidget, &KateBtConfigWidget::apply);
0371     connect(box, &QDialogButtonBox::accepted, this, &KateBtConfigDialog::accept);
0372     connect(box, &QDialogButtonBox::rejected, this, &KateBtConfigDialog::reject);
0373 }
0374 
0375 KateBtConfigDialog::~KateBtConfigDialog()
0376 {
0377 }
0378 
0379 #include "katebacktracebrowser.moc"
0380 #include "moc_katebacktracebrowser.cpp"
0381 
0382 // kate: space-indent on; indent-width 4; replace-tabs on;