File indexing completed on 2024-05-05 16:46:08
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"