File indexing completed on 2024-05-05 04:40:13

0001 /*
0002     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008-2009 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "problemhighlighter.h"
0009 
0010 #include <serialization/indexedstring.h>
0011 #include <language/duchain/navigation/abstractnavigationwidget.h>
0012 #include <interfaces/icore.h>
0013 #include <interfaces/ilanguagecontroller.h>
0014 #include <interfaces/icompletionsettings.h>
0015 #include <language/duchain/duchainlock.h>
0016 #include <language/duchain/duchainutils.h>
0017 #include <language/duchain/topducontext.h>
0018 #include <language/duchain/navigation/problemnavigationcontext.h>
0019 #include <language/editor/documentrange.h>
0020 
0021 #include <shell/problem.h>
0022 
0023 #include <KTextEditor/Document>
0024 #include <KTextEditor/MarkInterface>
0025 #include <KTextEditor/View>
0026 #include <KTextEditor/MovingInterface>
0027 #include <KColorScheme>
0028 
0029 using namespace KTextEditor;
0030 using namespace KDevelop;
0031 
0032 namespace
0033 {
0034 
0035 QColor colorForSeverity(IProblem::Severity severity)
0036 {
0037     KColorScheme scheme(QPalette::Active);
0038     switch (severity) {
0039     case IProblem::Error:
0040         return scheme.foreground(KColorScheme::NegativeText).color();
0041     case IProblem::Warning:
0042         return scheme.foreground(KColorScheme::NeutralText).color();
0043     case IProblem::Hint:
0044     default:
0045         return scheme.foreground(KColorScheme::PositiveText).color();
0046     }
0047 }
0048 }
0049 
0050 ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document)
0051     : m_document(document)
0052 {
0053     Q_ASSERT(m_document);
0054 
0055     connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this,
0056             &ProblemHighlighter::settingsChanged);
0057     connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems);
0058     if (qobject_cast<MovingInterface*>(m_document)) {
0059         // can't use new signal/slot syntax here, MovingInterface is not a QObject
0060         connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this,
0061                 SLOT(clearProblems()));
0062     }
0063     connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this,
0064             SLOT(aboutToRemoveText(KTextEditor::Range)));
0065 }
0066 
0067 void ProblemHighlighter::settingsChanged()
0068 {
0069     // Re-highlight
0070     setProblems(m_problems);
0071 }
0072 
0073 ProblemHighlighter::~ProblemHighlighter()
0074 {
0075     if (m_topHLRanges.isEmpty() || !m_document)
0076         return;
0077 
0078     qDeleteAll(m_topHLRanges);
0079 }
0080 
0081 void ProblemHighlighter::setProblems(const QVector<IProblem::Ptr>& problems)
0082 {
0083     if (!m_document)
0084         return;
0085 
0086     if (m_problems == problems)
0087         return;
0088 
0089     const bool hadProblems = !m_problems.isEmpty();
0090     m_problems = problems;
0091 
0092     qDeleteAll(m_topHLRanges);
0093     m_topHLRanges.clear();
0094 
0095     IndexedString url(m_document->url());
0096 
0097     /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar
0098     ///      but having no background.
0099     ///      also make it nicer together with other plugins, this would currently fail with
0100     ///      this method...
0101     const uint errorMarkType = KTextEditor::MarkInterface::Error;
0102     const uint warningMarkType = KTextEditor::MarkInterface::Warning;
0103     auto* markIface = qobject_cast<KTextEditor::MarkInterface*>(m_document.data());
0104     if (markIface && hadProblems) {
0105         // clear previously added marks
0106         const auto oldMarks = markIface->marks();
0107         for (KTextEditor::Mark* mark : oldMarks) {
0108             if (mark->type & (errorMarkType | warningMarkType)) {
0109                 markIface->removeMark(mark->line, errorMarkType | warningMarkType);
0110             }
0111         }
0112     }
0113 
0114     if (problems.isEmpty()) {
0115         return;
0116     }
0117 
0118     DUChainReadLocker lock;
0119 
0120     TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url());
0121 
0122     auto* iface = qobject_cast<KTextEditor::MovingInterface*>(m_document.data());
0123     Q_ASSERT(iface);
0124 
0125     for (const IProblem::Ptr& problem : problems) {
0126         if (problem->finalLocation().document != url || !problem->finalLocation().isValid())
0127             continue;
0128 
0129         KTextEditor::Range range;
0130         if (top)
0131             range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation()));
0132         else
0133             range = problem->finalLocation();
0134 
0135         // Fix problem's location range if necessary
0136         if (problem->finalLocationMode() != IProblem::Range && range.onSingleLine()) {
0137             int line = range.start().line();
0138             const QString lineString = m_document->line(line);
0139 
0140             int startColumn = 0;
0141             int endColumn = lineString.length();
0142 
0143             // If the line contains only space-characters then
0144             // we will highlight it "as is", without trimming.
0145             if (problem->finalLocationMode() == IProblem::TrimmedLine && !lineString.trimmed().isEmpty()) {
0146                 while (lineString.at(startColumn++).isSpace()) {}
0147                 --startColumn;
0148 
0149                 while (lineString.at(--endColumn).isSpace()) {}
0150                 ++endColumn;
0151             }
0152 
0153             range.setStart(Cursor(line, startColumn));
0154             range.setEnd(Cursor(line, endColumn));
0155 
0156             problem->setFinalLocation(DocumentRange(problem->finalLocation().document, range));
0157             problem->setFinalLocationMode(IProblem::Range);
0158         }
0159 
0160         if (range.end().line() >= m_document->lines())
0161             range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1));
0162 
0163         if (range.isEmpty()) {
0164             range.setEnd(range.end() + KTextEditor::Cursor(0, 1));
0165         }
0166 
0167         KTextEditor::MovingRange* problemRange = iface->newMovingRange(range);
0168         m_topHLRanges.append(problemRange);
0169 
0170         if (problem->source() != IProblem::ToDo
0171             && (problem->severity() != IProblem::Hint
0172                 || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) {
0173             KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute());
0174             attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline);
0175             attribute->setUnderlineColor(colorForSeverity(problem->severity()));
0176             problemRange->setAttribute(attribute);
0177         }
0178 
0179         if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) {
0180             uint mark;
0181             if (problem->severity() == IProblem::Error) {
0182                 mark = errorMarkType;
0183             } else if (problem->severity() == IProblem::Warning) {
0184                 mark = warningMarkType;
0185             } else {
0186                 continue;
0187             }
0188             markIface->addMark(problem->finalLocation().start().line(), mark);
0189         }
0190     }
0191 }
0192 
0193 void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range)
0194 {
0195     if (range.onSingleLine()) { // no need to optimize this
0196         return;
0197     }
0198 
0199     QList<MovingRange*>::iterator it = m_topHLRanges.begin();
0200     while (it != m_topHLRanges.end()) {
0201         if (range.contains((*it)->toRange())) {
0202             delete (*it);
0203             it = m_topHLRanges.erase(it);
0204         } else {
0205             ++it;
0206         }
0207     }
0208 }
0209 
0210 void ProblemHighlighter::clearProblems()
0211 {
0212     setProblems({});
0213 }
0214 
0215 #include "moc_problemhighlighter.cpp"