File indexing completed on 2024-05-12 04:38:07

0001 /*
0002     SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "colorcache.h"
0008 
0009 #include "configurablecolors.h"
0010 #include "codehighlighting.h"
0011 
0012 #include <KColorScheme>
0013 
0014 #include "../../interfaces/icore.h"
0015 #include "../../interfaces/ilanguagecontroller.h"
0016 #include "../../interfaces/icompletionsettings.h"
0017 #include "../../interfaces/idocument.h"
0018 #include "../../interfaces/idocumentcontroller.h"
0019 #include "../interfaces/ilanguagesupport.h"
0020 #include "../duchain/duchain.h"
0021 #include "../duchain/duchainlock.h"
0022 #include <debug.h>
0023 #include "widgetcolorizer.h"
0024 
0025 #include <KTextEditor/Document>
0026 #include <KTextEditor/View>
0027 #include <KTextEditor/ConfigInterface>
0028 #include <KSyntaxHighlighting/Definition>
0029 #include <KSyntaxHighlighting/Format>
0030 
0031 #define ifDebug(x)
0032 
0033 namespace KDevelop {
0034 ColorCache* ColorCache::m_self = nullptr;
0035 
0036 CodeHighlightingType highlightingTypeFromName(const QString& name)
0037 {
0038     if (name == QLatin1String("Class")) {
0039         return CodeHighlightingType::Class;
0040     } else if (name == QLatin1String("Local Member Variable")) {
0041         return CodeHighlightingType::LocalClassMember;
0042     } else if (name == QLatin1String("Local Member Function")) {
0043         return CodeHighlightingType::LocalMemberFunction;
0044     } else if (name == QLatin1String("Inherited Member Variable")) {
0045         return CodeHighlightingType::InheritedClassMember;
0046     } else if (name == QLatin1String("Inherited Member Function")) {
0047         return CodeHighlightingType::InheritedMemberFunction;
0048     } else if (name == QLatin1String("Function")) {
0049         return CodeHighlightingType::Function;
0050     } else if (name == QLatin1String("Function Argument")) {
0051         return CodeHighlightingType::FunctionVariable;
0052     } else if (name == QLatin1String("Type Alias")) {
0053         return CodeHighlightingType::TypeAlias;
0054     } else if (name == QLatin1String("Forward Declaration")) {
0055         return CodeHighlightingType::ForwardDeclaration;
0056     } else if (name == QLatin1String("Namespace")) {
0057         return CodeHighlightingType::Namespace;
0058     } else if (name == QLatin1String("Local Variable")) {
0059         return CodeHighlightingType::LocalVariable;
0060     } else if (name == QLatin1String("Global Variable")) {
0061         return CodeHighlightingType::GlobalVariable;
0062     } else if (name == QLatin1String("Member Variable")) {
0063         return CodeHighlightingType::MemberVariable;
0064     } else if (name == QLatin1String("Namespace Variable")) {
0065         return CodeHighlightingType::NamespaceVariable;
0066     } else if (name == QLatin1String("Enumeration")) {
0067         return CodeHighlightingType::Enum;
0068     } else if (name == QLatin1String("Enumerator")) {
0069         return CodeHighlightingType::Enumerator;
0070     } else if (name == QLatin1String("Macro")) {
0071         return CodeHighlightingType::Macro;
0072     } else if (name == QLatin1String("Macro Function")) {
0073         return CodeHighlightingType::MacroFunctionLike;
0074     }
0075     return CodeHighlightingType::Error;
0076 }
0077 
0078 ColorCache::ColorCache(QObject* parent)
0079     : QObject(parent)
0080     , m_defaultColors(new ConfigurableHighlightingColors)
0081     , m_validColorCount(0)
0082     , m_colorOffset(0)
0083     , m_localColorRatio(0)
0084     , m_globalColorRatio(0)
0085     , m_globalColorSource(ICompletionSettings::GlobalColorSource::AutoGenerated)
0086     , m_boldDeclarations(true)
0087 {
0088     Q_ASSERT(m_self == nullptr);
0089 
0090     updateColorsFromScheme(); // default / fallback
0091     updateColorsFromSettings();
0092 
0093     connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged,
0094             this, &ColorCache::updateColorsFromSettings, Qt::QueuedConnection);
0095 
0096     connect(ICore::self()->documentController(), &IDocumentController::documentActivated,
0097             this, &ColorCache::slotDocumentActivated);
0098 
0099     bool hadDoc = tryActiveDocument();
0100 
0101     updateInternal();
0102 
0103     m_self = this;
0104 
0105     if (!hadDoc) {
0106         // try to update later on again
0107         QMetaObject::invokeMethod(this, "tryActiveDocument", Qt::QueuedConnection);
0108     }
0109 }
0110 
0111 bool ColorCache::tryActiveDocument()
0112 {
0113     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0114     if (view) {
0115         updateColorsFromView(view);
0116         return true;
0117     }
0118     return false;
0119 }
0120 
0121 ColorCache::~ColorCache()
0122 {
0123     m_self = nullptr;
0124     delete m_defaultColors;
0125     m_defaultColors = nullptr;
0126 }
0127 
0128 ColorCache* ColorCache::self()
0129 {
0130     if (!m_self) {
0131         m_self = new ColorCache;
0132     }
0133     return m_self;
0134 }
0135 
0136 void ColorCache::generateColors()
0137 {
0138     // Primary colors taken from: http://colorbrewer2.org/?type=qualitative&scheme=Paired&n=12
0139     const QColor colors[] = {
0140         {"#b15928"}, {"#ff7f00"}, {"#b2df8a"}, {"#33a02c"}, {"#a6cee3"},
0141         {"#1f78b4"}, {"#6a3d9a"}, {"#cab2d6"}, {"#e31a1c"}, {"#fb9a99"}
0142     };
0143     const int colorCount = std::extent<decltype(colors)>::value;
0144 
0145     // Supplementary colors generated by: http://tools.medialab.sciences-po.fr/iwanthue/
0146     const QColor supplementaryColors[] = {
0147         {"#D33B67"}, {"#5EC764"}, {"#6CC82D"}, {"#995729"}, {"#FB4D84"},
0148         {"#4B8828"}, {"#D847D0"}, {"#B56AC5"}, {"#E96F0C"}, {"#DC7161"},
0149         {"#4D7279"}, {"#01AAF1"}, {"#D2A237"}, {"#F08CA5"}, {"#C83E93"},
0150         {"#5D7DF7"}, {"#EFBB51"}, {"#108BBB"}, {"#5C84B8"}, {"#02F8BC"},
0151         {"#A5A9F7"}, {"#F28E64"}, {"#A461E6"}, {"#6372D3"}
0152     };
0153     const int supplementaryColorCount = std::extent<decltype(supplementaryColors)>::value;
0154 
0155     m_colors.clear();
0156     m_colors.reserve(colorCount + supplementaryColorCount);
0157 
0158     for (const auto& color: colors) {
0159         m_colors.append(blendLocalColor(color));
0160     }
0161 
0162     m_primaryColorCount = m_colors.count();
0163 
0164     for (const auto& color: supplementaryColors) {
0165         m_colors.append(blendLocalColor(color));
0166     }
0167 
0168     m_validColorCount = m_colors.count();
0169 }
0170 
0171 void ColorCache::slotDocumentActivated()
0172 {
0173     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0174     ifDebug(qCDebug(LANGUAGE) << "doc activated:" << doc; )
0175     if (view) {
0176         updateColorsFromView(view);
0177     }
0178 }
0179 
0180 void ColorCache::slotViewSettingsChanged()
0181 {
0182     auto* view = qobject_cast<KTextEditor::View*>(sender());
0183     Q_ASSERT(view);
0184 
0185     ifDebug(qCDebug(LANGUAGE) << "settings changed" << view; )
0186     updateColorsFromView(view);
0187 }
0188 
0189 void ColorCache::updateColorsFromView(KTextEditor::View* view)
0190 {
0191     if (!view) {
0192         // yeah, the HighlightInterface methods returning an Attribute
0193         // require a View... kill me for that mess
0194         return;
0195     }
0196 
0197     QColor foreground(QColor::Invalid);
0198     QColor background(QColor::Invalid);
0199 
0200     KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal);
0201     foreground = style->foreground().color();
0202     if (style->hasProperty(QTextFormat::BackgroundBrush)) {
0203         background = style->background().color();
0204     }
0205 
0206     // FIXME: this is in kateview
0207 //     qCDebug(LANGUAGE) << "got foreground:" << foreground.name() << "old is:" << m_foregroundColor.name();
0208 //NOTE: this slot is defined in KatePart > 4.4, see ApiDocs of the ConfigInterface
0209 
0210     // the signal is not defined in ConfigInterface, but according to the docs it should be
0211     // can't use new signal slot syntax here, since ConfigInterface is not a QObject
0212     if (KTextEditor::View* view = m_view.data()) {
0213         Q_ASSERT(qobject_cast<KTextEditor::ConfigInterface*>(view));
0214         // we only listen to a single view, i.e. the active one
0215         disconnect(view, &KTextEditor::View::configChanged, this, &ColorCache::slotViewSettingsChanged);
0216     }
0217     Q_ASSERT(qobject_cast<KTextEditor::ConfigInterface*>(view));
0218     connect(view, &KTextEditor::View::configChanged, this, &ColorCache::slotViewSettingsChanged);
0219     m_view = view;
0220 
0221     bool anyAttrChanged = false;
0222     if (!foreground.isValid()) {
0223         // fallback to colorscheme variant
0224         ifDebug(qCDebug(LANGUAGE) << "updating from scheme"; )
0225         updateColorsFromScheme();
0226     } else if (m_foregroundColor != foreground || m_backgroundColor != background) {
0227         m_foregroundColor = foreground;
0228         m_backgroundColor = background;
0229         m_defaultColors->reset(this, view);
0230         anyAttrChanged = true;
0231     }
0232 
0233     anyAttrChanged |= updateColorsFromTheme(view->theme());
0234 
0235     if (anyAttrChanged) {
0236         ifDebug(qCDebug(LANGUAGE) << "updating from document"; )
0237         update();
0238     }
0239 }
0240 
0241 bool ColorCache::updateColorsFromTheme(const KSyntaxHighlighting::Theme& theme)
0242 {
0243     if (m_globalColorSource != ICompletionSettings::GlobalColorSource::FromTheme)
0244         return false;
0245 
0246     // from ktexteditor/src/syntax/kateextendedattribute.h
0247     static const int SelectedBackground = QTextFormat::UserProperty + 2;
0248 
0249     const auto schemeDefinition = m_schemeRepo.definitionForName(QStringLiteral("Semantic Colors"));
0250     const auto formats = schemeDefinition.formats();
0251 
0252     bool anyAttrChanged = false;
0253     for (const auto& format : formats) {
0254         const auto type = highlightingTypeFromName(format.name());
0255         if (type == CodeHighlightingType::Error) {
0256             continue;
0257         }
0258         const auto attr = m_defaultColors->attribute(type);
0259         Q_ASSERT(attr);
0260 
0261         auto forwardProperty = [&](auto formatGetter, auto attrProperty, auto attrSetter) {
0262             auto formatProperty = (format.*formatGetter)(theme);
0263             if (attrProperty != formatProperty) {
0264                 (attr.data()->*attrSetter)(formatProperty);
0265                 anyAttrChanged = true;
0266             }
0267         };
0268 
0269         using namespace KSyntaxHighlighting;
0270         using namespace KTextEditor;
0271         forwardProperty(&Format::isBold, attr->fontBold(), &Attribute::setFontBold);
0272         forwardProperty(&Format::isItalic, attr->fontItalic(), &Attribute::setFontItalic);
0273         forwardProperty(&Format::isUnderline, attr->fontUnderline(), &Attribute::setFontUnderline);
0274         forwardProperty(&Format::isStrikeThrough, attr->fontStrikeOut(), &Attribute::setFontStrikeOut);
0275         forwardProperty(&Format::textColor, attr->foreground().color(), &Attribute::setForeground);
0276         forwardProperty(&Format::selectedTextColor, attr->selectedForeground().color(),
0277                         &Attribute::setSelectedForeground);
0278 
0279         if (format.hasBackgroundColor(theme)) {
0280             forwardProperty(&Format::backgroundColor, attr->background().color(), &Attribute::setBackground);
0281         } else if (type == CodeHighlightingType::HighlightUses) {
0282             auto background = QColor::fromRgb(theme.editorColor(KSyntaxHighlighting::Theme::SearchHighlight));
0283             if (attr->background().color() != background) {
0284                 attr->setBackground(background);
0285                 anyAttrChanged = true;
0286             }
0287         } else if (attr->background() != QBrush()) {
0288             attr->setBackground(QBrush());
0289             anyAttrChanged = true;
0290         }
0291 
0292         // from KSyntaxHighlighting::Format::isDefaultTextStyle
0293         if (format.selectedBackgroundColor(theme).rgba() != theme.selectedBackgroundColor(KSyntaxHighlighting::Theme::Normal)) {
0294             forwardProperty(&Format::selectedBackgroundColor, attr->selectedBackground().color(),
0295                             &Attribute::setSelectedBackground);
0296         } else if (attr->hasProperty(SelectedBackground)) {
0297             attr->clearProperty(SelectedBackground);
0298             anyAttrChanged = true;
0299         }
0300     }
0301     return anyAttrChanged;
0302 }
0303 
0304 void ColorCache::updateColorsFromScheme()
0305 {
0306     KColorScheme scheme(QPalette::Normal, KColorScheme::View);
0307 
0308     QColor foreground = scheme.foreground(KColorScheme::NormalText).color();
0309     QColor background = scheme.background(KColorScheme::NormalBackground).color();
0310 
0311     if (foreground != m_foregroundColor || background != m_backgroundColor) {
0312         m_foregroundColor = foreground;
0313         m_backgroundColor = background;
0314         update();
0315     }
0316 }
0317 
0318 void ColorCache::updateColorsFromSettings()
0319 {
0320     auto settings = ICore::self()->languageController()->completionSettings();
0321 
0322     const auto globalColorSource = settings->globalColorSource();
0323     const auto globalColorSourceChanged = globalColorSource != m_globalColorSource;
0324     m_globalColorSource = globalColorSource;
0325 
0326     const auto globalRatio = settings->globalColorizationLevel();
0327     const auto globalRatioChanged = globalRatio != m_globalColorRatio;
0328     m_globalColorRatio = globalRatio;
0329 
0330     const auto localRatio = settings->localColorizationLevel();
0331     const auto localRatioChanged = localRatio != m_localColorRatio;
0332     m_localColorRatio = localRatio;
0333 
0334     const auto boldDeclartions = settings->boldDeclarations();
0335     const auto boldDeclarationsChanged = boldDeclartions != m_boldDeclarations;
0336     m_boldDeclarations = boldDeclartions;
0337 
0338     if (m_view && (globalRatioChanged || globalColorSourceChanged)) {
0339         updateDefaultColorsFromSource();
0340     }
0341 
0342     if (globalColorSourceChanged || globalRatioChanged || localRatioChanged || boldDeclarationsChanged) {
0343         update();
0344     }
0345 }
0346 
0347 void ColorCache::updateDefaultColorsFromSource()
0348 {
0349     switch (m_globalColorSource) {
0350     case ICompletionSettings::GlobalColorSource::AutoGenerated:
0351         m_defaultColors->reset(this, m_view.data());
0352         break;
0353     case ICompletionSettings::GlobalColorSource::FromTheme:
0354         updateColorsFromTheme(m_view->theme());
0355         break;
0356     }
0357 }
0358 
0359 void ColorCache::update()
0360 {
0361     if (!m_self) {
0362         ifDebug(qCDebug(LANGUAGE) << "not updating - still initializating"; )
0363         // don't update on startup, updateInternal is called directly there
0364         return;
0365     }
0366 
0367     QMetaObject::invokeMethod(this, "updateInternal", Qt::QueuedConnection);
0368 }
0369 
0370 void ColorCache::updateInternal()
0371 {
0372     ifDebug(qCDebug(LANGUAGE) << "update internal" << m_self; )
0373     generateColors();
0374 
0375     if (!m_self) {
0376         // don't do anything else fancy on startup
0377         return;
0378     }
0379 
0380     emit colorsGotChanged();
0381 
0382     // rehighlight open documents
0383     if (!ICore::self() || ICore::self()->shuttingDown()) {
0384         return;
0385     }
0386     const auto documents = ICore::self()->documentController()->openDocuments();
0387     for (IDocument* doc : documents) {
0388         const auto languages = ICore::self()->languageController()->languagesForUrl(doc->url());
0389         for (const auto lang : languages) {
0390             ReferencedTopDUContext top;
0391             {
0392                 DUChainReadLocker lock;
0393                 top = lang->standardContext(doc->url());
0394             }
0395 
0396             if (top) {
0397                 if (ICodeHighlighting* highlighting = lang->codeHighlighting()) {
0398                     highlighting->highlightDUChain(top);
0399                 }
0400             }
0401         }
0402     }
0403 }
0404 
0405 QColor ColorCache::blend(QColor color, uchar ratio) const
0406 {
0407     Q_ASSERT(m_backgroundColor.isValid());
0408     Q_ASSERT(m_foregroundColor.isValid());
0409     return WidgetColorizer::blendForeground(color, float( ratio ) / float( 0xff ), m_foregroundColor,
0410                                             m_backgroundColor);
0411 }
0412 
0413 QColor ColorCache::blendBackground(QColor color, uchar ratio) const
0414 {
0415     return WidgetColorizer::blendBackground(color, float( ratio ) / float( 0xff ), m_foregroundColor,
0416                                             m_backgroundColor);
0417 }
0418 
0419 QColor ColorCache::blendGlobalColor(QColor color) const
0420 {
0421     return blend(color, m_globalColorRatio);
0422 }
0423 
0424 QColor ColorCache::blendLocalColor(QColor color) const
0425 {
0426     return blend(color, m_localColorRatio);
0427 }
0428 
0429 ConfigurableHighlightingColors* ColorCache::defaultColors() const
0430 {
0431     return m_defaultColors;
0432 }
0433 
0434 QColor ColorCache::generatedColor(uint num) const
0435 {
0436     return num > ( uint )m_colors.size() ? foregroundColor() : m_colors[num];
0437 }
0438 
0439 uint ColorCache::validColorCount() const
0440 {
0441     return m_validColorCount;
0442 }
0443 
0444 uint ColorCache::primaryColorCount() const
0445 {
0446     return m_primaryColorCount;
0447 }
0448 
0449 QColor ColorCache::foregroundColor() const
0450 {
0451     return m_foregroundColor;
0452 }
0453 }
0454 
0455 #include "moc_colorcache.cpp"