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

0001 /*
0002     SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "probleminlinenoteprovider.h"
0008 
0009 #include <interfaces/icore.h>
0010 #include <interfaces/ilanguagecontroller.h>
0011 #include <language/editor/documentrange.h>
0012 
0013 #include <KColorScheme>
0014 #include <KTextEditor/Document>
0015 #include <KTextEditor/InlineNoteInterface>
0016 #include <KTextEditor/View>
0017 
0018 #include <QIcon>
0019 #include <QPainter>
0020 #include <QFontMetrics>
0021 #include <QStyle>
0022 
0023 using namespace KDevelop;
0024 
0025 static constexpr int marginColumns = 2;
0026 
0027 struct SeverityColors
0028 {
0029     QColor foreground;
0030     QColor background;
0031 };
0032 
0033 static SeverityColors severityColors(IProblem::Severity severity)
0034 {
0035     const KColorScheme::ForegroundRole foregroundRole =
0036         (severity == IProblem::Error) ?   KColorScheme::NegativeText :
0037         (severity == IProblem::Warning) ? KColorScheme::NeutralText :
0038         /* IProblem::Hint, default */     KColorScheme::PositiveText;
0039     const KColorScheme::BackgroundRole backgroundRole =
0040         (severity == IProblem::Error) ?   KColorScheme::NegativeBackground :
0041         (severity == IProblem::Warning) ? KColorScheme::NeutralBackground :
0042         /* IProblem::Hint, default */     KColorScheme::PositiveBackground;
0043 
0044     KColorScheme scheme(QPalette::Active);
0045     return {
0046         scheme.foreground(foregroundRole).color(),
0047         scheme.background(backgroundRole).color()
0048     };
0049 }
0050 
0051 ProblemInlineNoteProvider::ProblemInlineNoteProvider(KTextEditor::Document* document)
0052     : m_document{document}
0053 {
0054     auto registerProvider = [this] (KTextEditor::Document*, KTextEditor::View* view) {
0055         auto* inlineNoteIface = qobject_cast<KTextEditor::InlineNoteInterface*>(view);
0056         if(inlineNoteIface) {
0057             inlineNoteIface->registerInlineNoteProvider(this);
0058         }
0059     };
0060     for (auto* view : m_document->views()) {
0061        registerProvider(m_document, view);
0062     }
0063     connect(m_document, &KTextEditor::Document::viewCreated, this,  registerProvider);
0064     connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged,
0065             this, &ProblemInlineNoteProvider::completionSettingsChanged);
0066 }
0067 
0068 ProblemInlineNoteProvider::~ProblemInlineNoteProvider()
0069 {
0070     if (!m_document) {
0071         return;
0072     }
0073     for (auto* view : m_document->views()) {
0074         auto* inlineNoteIface = qobject_cast<KTextEditor::InlineNoteInterface*>(view);
0075         if(inlineNoteIface) {
0076             inlineNoteIface->unregisterInlineNoteProvider(this);
0077         }
0078     }
0079 }
0080 
0081 void ProblemInlineNoteProvider::completionSettingsChanged()
0082 {
0083     if (m_currentLevel == ICore::self()->languageController()->completionSettings()->problemInlineNotesLevel()) {
0084         return;
0085     }
0086     setProblems(m_problems);
0087 }
0088 
0089 void ProblemInlineNoteProvider::setProblems(const QVector<IProblem::Ptr>& problems)
0090 {
0091     if (!m_document) {
0092         return;
0093     }
0094     m_problemForLine.clear();
0095     m_problems = problems;
0096     if (problems.isEmpty()) {
0097         emit inlineNotesReset();
0098         return;
0099     }
0100     m_currentLevel = ICore::self()->languageController()->completionSettings()->problemInlineNotesLevel() ;
0101     if (m_currentLevel == ICompletionSettings::NoProblemsInlineNotesLevel) {
0102         return;
0103     }
0104     for (const IProblem::Ptr& problem : problems) {
0105         if (problem->finalLocation().document.toUrl() != m_document->url() || !problem->finalLocation().isValid()) {
0106             continue;
0107         }
0108         switch (problem->severity()) {
0109         case IProblem::NoSeverity:
0110         case IProblem::Hint:
0111             if (m_currentLevel != ICompletionSettings::AllProblemsInlineNotesLevel) {
0112                 continue;
0113             }
0114             break;
0115         case IProblem::Warning: 
0116             if (m_currentLevel == ICompletionSettings::ErrorsProblemInlineNotesLevel) {
0117                 continue;
0118             }
0119             break;
0120         case IProblem::Error:
0121             break;
0122         }
0123         const int line = problem->finalLocation().start().line();
0124         // Only render the problem with the highest severity in each line.
0125         if (m_problemForLine.contains(line)) {
0126             const IProblem::Ptr currentProblem = m_problemForLine.value(line);
0127             if (problem->severity() == currentProblem->severity()) {
0128                 if (problem->finalLocation().start().column() < currentProblem->finalLocation().start().column()) {
0129                     m_problemForLine[line] = problem;
0130                 }
0131             // No Severity has the lowest value
0132             } else if (problem->severity() < currentProblem->severity() && problem->severity() != IProblem::NoSeverity) {
0133                 m_problemForLine[line] = problem;
0134             } else if (currentProblem->severity() == IProblem::NoSeverity)  {
0135                 m_problemForLine[line] = problem;
0136             }
0137         } else {
0138              m_problemForLine[line] = problem;
0139         }
0140     }
0141     emit inlineNotesReset();
0142 }
0143 
0144 QVector<int> ProblemInlineNoteProvider::inlineNotes(int line) const
0145 {
0146     return m_problemForLine.contains(line) ? QVector<int>(1, m_document->endOfLine(line).column() + marginColumns)
0147                                            : QVector<int>();
0148 }
0149 
0150 // matching logic of KateIconBorder's icon height calculation for a line
0151 static constexpr int iconTopBottomMargin = 1;
0152 
0153 static int iconHeight(const KTextEditor::InlineNote& note)
0154 {
0155     QFontMetrics fontMetrics(note.font());
0156 
0157     return qMin(fontMetrics.height(), note.lineHeight()) - 2 * iconTopBottomMargin;
0158 }
0159 
0160 static constexpr int noteBorderWidth = 2;
0161 
0162 struct InlineNoteLayout
0163 {
0164     int iconSize;
0165     int iconX;
0166     int descriptionX;
0167     int rightMargin;
0168 };
0169 
0170 
0171 // Design of note:
0172 // following basically the message widget design, but reduced to only a colored border on the left side
0173 //
0174 // |_O_description_
0175 // <vertical borderline><left margin><icon><spacing><description text><right margin>
0176 static void doInlineNoteLayout(const KTextEditor::InlineNote& note,
0177                                InlineNoteLayout* layout)
0178 {
0179     const auto* view = note.view();
0180     const auto* style = view->style();
0181     const int leftMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr, view);
0182     layout->rightMargin = style->pixelMetric(QStyle::PM_LayoutRightMargin, nullptr, view);
0183     const int noteSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, nullptr, view);
0184 
0185     layout->iconSize = iconHeight(note);
0186 
0187     layout->iconX = noteBorderWidth + leftMargin;
0188     layout->descriptionX = layout->iconX + layout->iconSize + noteSpacing;
0189 }
0190 
0191 QSize ProblemInlineNoteProvider::inlineNoteSize(const KTextEditor::InlineNote& note) const
0192 {
0193     InlineNoteLayout layout;
0194     doInlineNoteLayout(note, &layout);
0195 
0196     const auto prob = m_problemForLine[note.position().line()];
0197     QFont font = note.font();
0198     font.setItalic(true);
0199     const QFontMetrics metric(font);
0200     const QRect boundingRect = metric.boundingRect(prob->description());
0201     return {layout.descriptionX + boundingRect.width() + layout.rightMargin, note.lineHeight()};
0202 }
0203 
0204 void ProblemInlineNoteProvider::paintInlineNote(const KTextEditor::InlineNote& note, QPainter& painter) const
0205 {
0206     InlineNoteLayout layout;
0207     doInlineNoteLayout(note, &layout);
0208 
0209     const int line = note.position().line();
0210     const auto prob = m_problemForLine[note.position().line()];
0211     QFont font = note.font();
0212     font.setItalic(true);
0213     painter.setFont(font);
0214     const KTextEditor::View* view = note.view();
0215     // NOTE cursorToCoordinate is relative to (0,0) of the view widget so we have to subtract the x
0216     // value of the start of the line from it. However it returns also -1 for cursors that have no
0217     // actual text so we subtract the width of the margin column(s) from the available width
0218     const int viewWidth = view->textAreaRect().width();
0219     const int textAreaStart = view->cursorToCoordinate(KTextEditor::Cursor(line, 0)).x();
0220     const int marginWidth = view->cursorToCoordinate(KTextEditor::Cursor(line, marginColumns)).x() - textAreaStart;
0221     const int nonTextSize = layout.descriptionX + layout.rightMargin;
0222     const int lineEnd = view->cursorToCoordinate(KTextEditor::Cursor(line, note.position().column() - marginColumns)).x() - textAreaStart;
0223     const int availableTextWidth = viewWidth - marginWidth - lineEnd - nonTextSize;
0224     QString text = painter.fontMetrics().elidedText(prob->description(), Qt::ElideRight, availableTextWidth);
0225     QIcon icon = IProblem::iconForSeverity(prob->severity());
0226     // QFontMetrics doesn't provide results that are good enough for painting so we use QPainter::boundingRect here
0227     QRect boundingRect = painter.boundingRect(QRect(layout.descriptionX, 0, 0, 0), Qt::AlignLeft, text);
0228     const auto colors = severityColors(prob->severity());
0229     // background
0230     painter.setBrush(colors.background);
0231     painter.setPen(Qt::NoPen);
0232     painter.drawRect(boundingRect.adjusted(-(layout.descriptionX), 0, layout.rightMargin, 0));
0233     // borderline
0234     painter.setBrush(colors.foreground);
0235     painter.drawRect(QRect(0, 0, noteBorderWidth, note.lineHeight()));
0236     // icpn
0237     icon.paint(&painter, layout.iconX, iconTopBottomMargin, layout.iconSize, layout.iconSize, Qt::AlignCenter);
0238     // text
0239     painter.setPen(colors.foreground);
0240     painter.drawText(boundingRect, Qt::AlignLeft, text);
0241 }
0242 
0243 #include "moc_probleminlinenoteprovider.cpp"