File indexing completed on 2024-04-28 04:37:21

0001 /*
0002     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "problemmodel.h"
0008 #include <QThread>
0009 #include <QIcon>
0010 #include <KLocalizedString>
0011 #include <interfaces/icore.h>
0012 #include <interfaces/idocument.h>
0013 #include <interfaces/idocumentcontroller.h>
0014 #include <interfaces/iprojectcontroller.h>
0015 #include <interfaces/ilanguagecontroller.h>
0016 #include <interfaces/icompletionsettings.h>
0017 #include <interfaces/iassistant.h>
0018 #include <language/editor/documentrange.h>
0019 
0020 #include <shell/problem.h>
0021 #include <shell/problemstore.h>
0022 #include <shell/problemstorenode.h>
0023 #include <shell/filteredproblemstore.h>
0024 #include <shell/problemconstants.h>
0025 #include <shell/watcheddocumentset.h>
0026 
0027 namespace KDevelop
0028 {
0029 
0030 class ProblemModelPrivate
0031 {
0032 public:
0033     explicit ProblemModelPrivate(KDevelop::ProblemStore *store)
0034         : m_problems(store)
0035         , m_features(KDevelop::ProblemModel::NoFeatures)
0036         , m_fullUpdateTooltip(i18nc("@info:tooltip", "Re-parse all watched documents"))
0037         , m_isPlaceholderShown(false)
0038     {
0039     }
0040 
0041     KDevelop::IProblem::Ptr createPlaceholdreProblem() const
0042     {
0043         Q_ASSERT(!m_placeholderText.isEmpty());
0044 
0045         KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(m_placeholderSource));
0046         problem->setDescription(m_placeholderText);
0047         problem->setFinalLocation(m_placeholderLocation);
0048         problem->setSeverity(KDevelop::IProblem::Hint);
0049 
0050         return problem;
0051     }
0052 
0053     QScopedPointer<KDevelop::ProblemStore> m_problems;
0054     KDevelop::ProblemModel::Features m_features;
0055     QString m_fullUpdateTooltip;
0056     QString m_placeholderText;
0057     QString m_placeholderSource;
0058     KDevelop::DocumentRange m_placeholderLocation;
0059     bool m_isPlaceholderShown;
0060 };
0061 
0062 
0063 ProblemModel::ProblemModel(QObject * parent, ProblemStore *store)
0064   : QAbstractItemModel(parent)
0065   , d_ptr(new ProblemModelPrivate(store))
0066 {
0067     Q_D(ProblemModel);
0068 
0069     if (!d->m_problems) {
0070         d->m_problems.reset(new FilteredProblemStore());
0071         d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter;
0072     }
0073 
0074     setScope(CurrentDocument);
0075 
0076     connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument);
0077     connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemModel::closedDocument);
0078     connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this,
0079             &ProblemModel::documentUrlChanged);
0080     /// CompletionSettings include a list of todo markers we care for, so need to update
0081     connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate);
0082 
0083     if (ICore::self()->documentController()->activeDocument()) {
0084         setCurrentDocument(ICore::self()->documentController()->activeDocument());
0085     }
0086 
0087     connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild);
0088     connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild);
0089 
0090     connect(d->m_problems.data(), &ProblemStore::problemsChanged, this, &ProblemModel::problemsChanged);
0091 }
0092 
0093 ProblemModel::~ ProblemModel()
0094 {
0095 }
0096 
0097 int ProblemModel::rowCount(const QModelIndex& parent) const
0098 {
0099     Q_D(const ProblemModel);
0100 
0101     if (!parent.isValid()) {
0102         return d->m_problems->count();
0103     } else {
0104         return d->m_problems->count(reinterpret_cast<ProblemStoreNode*>(parent.internalPointer()));
0105     }
0106 }
0107 
0108 static QString displayUrl(const QUrl &url, const QUrl &base)
0109 {
0110     if (base.isParentOf(url)) {
0111         return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length());
0112     } else {
0113         return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain);
0114     }
0115 }
0116 
0117 QVariant ProblemModel::data(const QModelIndex& index, int role) const
0118 {
0119     Q_D(const ProblemModel);
0120 
0121     if (!index.isValid())
0122         return QVariant();
0123 
0124     QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename);
0125     IProblem::Ptr p = problemForIndex(index);
0126     if (!p.constData()) {
0127         if (role == Qt::DisplayRole && index.column() == Error) {
0128             auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer());
0129             if (node) {
0130                 return node->label();
0131             }
0132         }
0133         return {};
0134     }
0135 
0136     if (role == SeverityRole) {
0137         return p->severity();
0138     } else if (role == ProblemRole) {
0139         return QVariant::fromValue(p);
0140     }
0141 
0142     switch (role) {
0143     case Qt::DisplayRole:
0144         switch (index.column()) {
0145         case Source:
0146             return p->sourceString();
0147         case Error:
0148             return p->description();
0149         case File:
0150             return displayUrl(p->finalLocation().document.toUrl().adjusted(QUrl::NormalizePathSegments), baseDirectory);
0151         case Line:
0152             if (p->finalLocation().isValid()) {
0153                 return QString::number(p->finalLocation().start().line() + 1);
0154             }
0155             break;
0156         case Column:
0157             if (p->finalLocation().isValid()) {
0158                 return QString::number(p->finalLocation().start().column() + 1);
0159             }
0160             break;
0161         }
0162         break;
0163 
0164     case Qt::DecorationRole:
0165         if (index.column() == Error) {
0166             return IProblem::iconForSeverity(p->severity());
0167         }
0168         break;
0169     case Qt::ToolTipRole:
0170         return p->explanation();
0171 
0172     default:
0173         break;
0174     }
0175 
0176     return {};
0177 }
0178 
0179 QModelIndex ProblemModel::parent(const QModelIndex& index) const
0180 {
0181     auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer());
0182     if (!node) {
0183         return {};
0184     }
0185 
0186     ProblemStoreNode *parent = node->parent();
0187     if (!parent || parent->isRoot()) {
0188         return {};
0189     }
0190 
0191     int idx = parent->index();
0192     return createIndex(idx, 0, parent);
0193 }
0194 
0195 QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const
0196 {
0197     Q_D(const ProblemModel);
0198 
0199     if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) {
0200         return QModelIndex();
0201     }
0202 
0203     auto *parentNode = reinterpret_cast<ProblemStoreNode*>(parent.internalPointer());
0204     const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode);
0205     return createIndex(row, column, (void*)node);
0206 }
0207 
0208 int ProblemModel::columnCount(const QModelIndex& parent) const
0209 {
0210     Q_UNUSED(parent)
0211     return LastColumn;
0212 }
0213 
0214 IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const
0215 {
0216     auto *node = reinterpret_cast<ProblemStoreNode*>(index.internalPointer());
0217     if (!node) {
0218         return {};
0219     } else {
0220         return node->problem();
0221     }
0222 }
0223 
0224 ProblemModel::Features ProblemModel::features() const
0225 {
0226     Q_D(const ProblemModel);
0227 
0228     return d->m_features;
0229 }
0230 
0231 void ProblemModel::setFeatures(Features features)
0232 {
0233     Q_D(ProblemModel);
0234 
0235     d->m_features = features;
0236 }
0237 
0238 QString ProblemModel::fullUpdateTooltip() const
0239 {
0240     Q_D(const ProblemModel);
0241 
0242     return d->m_fullUpdateTooltip;
0243 }
0244 
0245 void ProblemModel::setFullUpdateTooltip(const QString& tooltip)
0246 {
0247     Q_D(ProblemModel);
0248 
0249     if (d->m_fullUpdateTooltip == tooltip) {
0250         return;
0251     }
0252 
0253     d->m_fullUpdateTooltip = tooltip;
0254     emit fullUpdateTooltipChanged();
0255 }
0256 
0257 void ProblemModel::setPlaceholderText(const QString& text, const KDevelop::DocumentRange& location, const QString& source)
0258 {
0259     Q_D(ProblemModel);
0260 
0261     d->m_placeholderText = text;
0262     d->m_placeholderLocation = location;
0263     d->m_placeholderSource = source;
0264 
0265     if (d->m_isPlaceholderShown || d->m_problems->count() == 0) {
0266         // clearing will show/update the new placeholder
0267         clearProblems();
0268     }
0269 }
0270 
0271 void ProblemModel::addProblem(const IProblem::Ptr &problem)
0272 {
0273     Q_D(ProblemModel);
0274 
0275     if (d->m_isPlaceholderShown) {
0276         setProblems({ problem });
0277     } else {
0278         int c = d->m_problems->count();
0279         beginInsertRows(QModelIndex(), c, c);
0280         d->m_problems->addProblem(problem);
0281         endInsertRows();
0282     }
0283 }
0284 
0285 void ProblemModel::setProblems(const QVector<IProblem::Ptr> &problems)
0286 {
0287     Q_D(ProblemModel);
0288 
0289     beginResetModel();
0290     if (problems.isEmpty() && !d->m_placeholderText.isEmpty()) {
0291         d->m_problems->setProblems({ d->createPlaceholdreProblem() });
0292         d->m_isPlaceholderShown = true;
0293     } else {
0294         d->m_problems->setProblems(problems);
0295         d->m_isPlaceholderShown = false;
0296     }
0297     endResetModel();
0298 }
0299 
0300 void ProblemModel::clearProblems()
0301 {
0302     setProblems({});
0303 }
0304 
0305 QVector<IProblem::Ptr> ProblemModel::problems(const KDevelop::IndexedString& document) const
0306 {
0307     Q_D(const ProblemModel);
0308 
0309     return d->m_problems->problems(document);
0310 }
0311 
0312 QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const
0313 {
0314     Q_UNUSED(orientation);
0315 
0316     if (role != Qt::DisplayRole)
0317         return {};
0318 
0319     switch (section) {
0320         case Source:
0321             return i18nc("@title:column source of problem", "Source");
0322         case Error:
0323             return i18nc("@title:column problem description", "Problem");
0324         case File:
0325             return i18nc("@title:column file where problem was found", "File");
0326         case Line:
0327             return i18nc("@title:column line number with problem", "Line");
0328         case Column:
0329             return i18nc("@title:column column number with problem", "Column");
0330     }
0331 
0332     return {};
0333 }
0334 
0335 void ProblemModel::setCurrentDocument(IDocument* document)
0336 {
0337     Q_D(ProblemModel);
0338 
0339     Q_ASSERT(thread() == QThread::currentThread());
0340 
0341     QUrl currentDocument = document->url();
0342     /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
0343     d->m_problems->setCurrentDocument(IndexedString(currentDocument));
0344 }
0345 
0346 void ProblemModel::closedDocument(IDocument* document)
0347 {
0348     Q_D(ProblemModel);
0349 
0350     if (IndexedString(document->url()) == d->m_problems->currentDocument())
0351     {   // reset current document
0352         d->m_problems->setCurrentDocument(IndexedString());
0353     }
0354 }
0355 
0356 void ProblemModel::documentUrlChanged(IDocument* document, const QUrl& previousUrl)
0357 {
0358     Q_D(ProblemModel);
0359 
0360     Q_ASSERT(thread() == QThread::currentThread());
0361 
0362     const auto currentDocument = d->m_problems->currentDocument();
0363     // If currentDocument.isEmpty(), the renamed document must have been closed already in
0364     // DocumentControllerPrivate::changeDocumentUrl() because of a conflict with another open modified document at its
0365     // new URL; another document at document->url() should be active now. So set the active document's URL as current.
0366     if (currentDocument.isEmpty() || currentDocument == IndexedString{previousUrl}) {
0367         setCurrentDocument(document);
0368     }
0369 }
0370 
0371 void ProblemModel::onBeginRebuild()
0372 {
0373     beginResetModel();
0374 }
0375 
0376 void ProblemModel::onEndRebuild()
0377 {
0378     endResetModel();
0379 }
0380 
0381 void ProblemModel::setShowImports(bool showImports)
0382 {
0383     Q_D(ProblemModel);
0384 
0385     Q_ASSERT(thread() == QThread::currentThread());
0386 
0387     d->m_problems->setShowImports(showImports);
0388 }
0389 
0390 bool ProblemModel::showImports() const
0391 {
0392     Q_D(const ProblemModel);
0393 
0394     return d->m_problems->showImports();
0395 }
0396 
0397 void ProblemModel::setScope(ProblemScope scope)
0398 {
0399     Q_D(ProblemModel);
0400 
0401     Q_ASSERT(thread() == QThread::currentThread());
0402 
0403     if (!features().testFlag(ScopeFilter))
0404         scope = ProblemScope::BypassScopeFilter;
0405 
0406     /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
0407     d->m_problems->setScope(scope);
0408 }
0409 
0410 void ProblemModel::setPathForDocumentsInPathScope(const QString& path)
0411 {
0412     Q_D(ProblemModel);
0413 
0414     d->m_problems->setPathForDocumentsInPathScope(path);
0415 }
0416 
0417 void ProblemModel::setSeverity(int severity)
0418 {
0419     switch (severity)
0420     {
0421         case KDevelop::IProblem::Error:
0422             setSeverities(KDevelop::IProblem::Error);
0423             break;
0424         case KDevelop::IProblem::Warning:
0425             setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning);
0426             break;
0427         case KDevelop::IProblem::Hint:
0428             setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint);
0429             break;
0430     }
0431 }
0432 
0433 void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities)
0434 {
0435     Q_D(ProblemModel);
0436 
0437     Q_ASSERT(thread() == QThread::currentThread());
0438     /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
0439     d->m_problems->setSeverities(severities);
0440 }
0441 
0442 void ProblemModel::setGrouping(int grouping)
0443 {
0444     Q_D(ProblemModel);
0445 
0446     /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
0447     d->m_problems->setGrouping(grouping);
0448 }
0449 
0450 ProblemStore* ProblemModel::store() const
0451 {
0452     Q_D(const ProblemModel);
0453 
0454     return d->m_problems.data();
0455 }
0456 
0457 }
0458 
0459 #include "moc_problemmodel.cpp"