kde-extraapps/kdevplatform/language/highlighting/codehighlighting.cpp

605 lines
20 KiB
C++
Raw Normal View History

/*
* This file is part of KDevelop
*
* Copyright 2007-2010 David Nolden <david.nolden.kdevelop@art-master.de>
* Copyright 2006 Hamish Rodda <rodda@kde.org>
* Copyright 2009 Milian Wolff <mail@milianw.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "codehighlighting.h"
#include <KTextEditor/Document>
#include "../../interfaces/icore.h"
#include "../../interfaces/ilanguagecontroller.h"
#include "../../interfaces/icompletionsettings.h"
#include "../../interfaces/foregroundlock.h"
#include "../duchain/declaration.h"
#include "../duchain/types/functiontype.h"
#include "../duchain/types/enumeratortype.h"
#include "../duchain/types/typealiastype.h"
#include "../duchain/types/enumerationtype.h"
#include "../duchain/types/structuretype.h"
#include "../duchain/functiondefinition.h"
#include "../duchain/use.h"
#include "colorcache.h"
#include "configurablecolors.h"
#include <duchain/parsingenvironment.h>
#include <backgroundparser/backgroundparser.h>
#include <ktexteditor/movinginterface.h>
#include <duchain/dumpchain.h>
#include <backgroundparser/urlparselock.h>
using namespace KTextEditor;
static const float highlightingZDepth = -500;
#define ifDebug(x)
namespace KDevelop {
///@todo Don't highlighting everything, only what is visible on-demand
CodeHighlighting::CodeHighlighting( QObject * parent )
: QObject(parent), m_localColorization(true), m_globalColorization(true), m_dataMutex(QMutex::Recursive)
{
qRegisterMetaType<KDevelop::IndexedString>("KDevelop::IndexedString");
adaptToColorChanges();
connect(ColorCache::self(), SIGNAL(colorsGotChanged()),
this, SLOT(adaptToColorChanges()));
}
CodeHighlighting::~CodeHighlighting( )
{
qDeleteAll(m_highlights.values());
}
void CodeHighlighting::adaptToColorChanges()
{
QMutexLocker lock(&m_dataMutex);
// disable local highlighting if the ratio is set to 0
m_localColorization = ICore::self()->languageController()->completionSettings()->localColorizationLevel() > 0;
// disable global highlighting if the ratio is set to 0
m_globalColorization = ICore::self()->languageController()->completionSettings()->globalColorizationLevel() > 0;
m_declarationAttributes.clear();
m_definitionAttributes.clear();
m_depthAttributes.clear();
m_referenceAttributes.clear();
}
KTextEditor::Attribute::Ptr CodeHighlighting::attributeForType( Types type, Contexts context, const QColor &color ) const
{
QMutexLocker lock(&m_dataMutex);
KTextEditor::Attribute::Ptr a;
switch (context) {
case DefinitionContext:
a = m_definitionAttributes[type];
break;
case DeclarationContext:
a = m_declarationAttributes[type];
break;
case ReferenceContext:
a = m_referenceAttributes[type];
break;
}
if ( !a || color.isValid() ) {
a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*ColorCache::self()->defaultColors()->getAttribute(type)));
if ( context == DefinitionContext || context == DeclarationContext ) {
a->setFontBold();
}
if( color.isValid() ) {
a->setForeground(color);
// a->setBackground(QColor(mix(0xffffff-color, backgroundColor(), 255-backgroundTinting)));
} else {
switch (context) {
case DefinitionContext:
m_definitionAttributes.insert(type, a);
break;
case DeclarationContext:
m_declarationAttributes.insert(type, a);
break;
case ReferenceContext:
m_referenceAttributes.insert(type, a);
break;
}
}
}
return a;
}
ColorMap emptyColorMap() {
ColorMap ret(ColorCache::self()->validColorCount()+1, 0);
return ret;
}
CodeHighlightingInstance* CodeHighlighting::createInstance() const
{
return new CodeHighlightingInstance(this);
}
bool CodeHighlighting::hasHighlighting(IndexedString url) const
{
DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url);
if(tracker)
{
QMutexLocker lock(&m_dataMutex);
return m_highlights.contains(tracker) && !m_highlights[tracker]->m_highlightedRanges.isEmpty();
}
return false;
}
void CodeHighlighting::highlightDUChain(ReferencedTopDUContext context)
{
ENSURE_CHAIN_NOT_LOCKED
IndexedString url;
{
DUChainReadLocker lock;
if (!context)
return;
url = context->url();
}
// This prevents the background-parser from updating the top-context while we're working with it
UrlParseLock urlLock(context->url());
DUChainReadLocker lock;
qint64 revision = context->parsingEnvironmentFile()->modificationRevision().revision;
kDebug() << "highlighting du chain" << url.toUrl();
if ( !m_localColorization && !m_globalColorization ) {
kDebug() << "highlighting disabled";
QMetaObject::invokeMethod(this, "clearHighlightingForDocument", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url));
return;
}
CodeHighlightingInstance* instance = createInstance();
lock.unlock();
instance->highlightDUChain(context.data());
DocumentHighlighting* highlighting = new DocumentHighlighting;
highlighting->m_document = url;
highlighting->m_waitingRevision = revision;
highlighting->m_waiting = instance->m_highlight;
qSort(highlighting->m_waiting.begin(), highlighting->m_waiting.end());
QMetaObject::invokeMethod(this, "applyHighlighting", Qt::QueuedConnection, Q_ARG(void*, highlighting));
delete instance;
}
void CodeHighlightingInstance::highlightDUChain(TopDUContext* context)
{
m_contextClasses.clear();
m_useClassCache = true;
//Highlight
highlightDUChain(context, QHash<Declaration*, uint>(), emptyColorMap());
m_functionColorsForDeclarations.clear();
m_functionDeclarationsForColors.clear();
m_useClassCache = false;
m_contextClasses.clear();
}
void CodeHighlightingInstance::highlightDUChain(DUContext* context, QHash<Declaration*, uint> colorsForDeclarations, ColorMap declarationsForColors)
{
DUChainReadLocker lock;
TopDUContext* top = context->topContext();
//Merge the colors from the function arguments
foreach( const DUContext::Import &imported, context->importedParentContexts() ) {
if(!imported.context(top) || (imported.context(top)->type() != DUContext::Other && imported.context(top)->type() != DUContext::Function))
continue;
//For now it's enough simply copying them, because we only pass on colors within function bodies.
if (m_functionColorsForDeclarations.contains(imported.context(top)))
colorsForDeclarations = m_functionColorsForDeclarations[imported.context(top)];
if (m_functionDeclarationsForColors.contains(imported.context(top)))
declarationsForColors = m_functionDeclarationsForColors[imported.context(top)];
}
QList<Declaration*> takeFreeColors;
foreach (Declaration* dec, context->localDeclarations()) {
if (!useRainbowColor(dec)) {
highlightDeclaration(dec, QColor(QColor::Invalid));
continue;
}
//Initially pick a color using the hash, so the chances are good that the same identifier gets the same color always.
uint colorNum = dec->identifier().hash() % ColorCache::self()->validColorCount();
if( declarationsForColors[colorNum] ) {
takeFreeColors << dec; //Use one of the colors that stays free
continue;
}
colorsForDeclarations[dec] = colorNum;
declarationsForColors[colorNum] = dec;
highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum));
}
foreach( Declaration* dec, takeFreeColors ) {
uint colorNum = dec->identifier().hash() % ColorCache::self()->validColorCount();
uint oldColorNum = colorNum;
while( declarationsForColors[colorNum] ) {
colorNum = (colorNum+1) % ColorCache::self()->validColorCount();
if( colorNum == oldColorNum ) {
colorNum = ColorCache::self()->validColorCount();
break;
}
}
if(colorNum != ColorCache::self()->validColorCount()) {
//If no color could be found, use default color,, not black
colorsForDeclarations[dec] = colorNum;
declarationsForColors[colorNum] = dec;
highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum));
}else{
highlightDeclaration(dec, QColor(QColor::Invalid));
}
}
for(int a = 0; a < context->usesCount(); ++a) {
Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[a].m_declarationIndex);
QColor color(QColor::Invalid);
if( colorsForDeclarations.contains(decl) )
color = ColorCache::self()->generatedColor(colorsForDeclarations[decl]);
highlightUse(context, a, color);
}
if(context->type() == DUContext::Other || context->type() == DUContext::Function) {
m_functionColorsForDeclarations[IndexedDUContext(context)] = colorsForDeclarations;
m_functionDeclarationsForColors[IndexedDUContext(context)] = declarationsForColors;
}
QVector< DUContext* > children = context->childContexts();
lock.unlock(); // Periodically release the lock, so that the UI won't be blocked too much
foreach (DUContext* child, children)
highlightDUChain(child, colorsForDeclarations, declarationsForColors );
}
KTextEditor::Attribute::Ptr CodeHighlighting::attributeForDepth(int depth) const
{
while (depth >= m_depthAttributes.count()) {
KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute());
a->setBackground(QColor(Qt::white).dark(100 + (m_depthAttributes.count() * 25)));
a->setBackgroundFillWhitespace(true);
if (depth % 2)
a->setOutline(Qt::red);
m_depthAttributes.append(a);
}
return m_depthAttributes[depth];
}
KDevelop::Declaration* CodeHighlightingInstance::localClassFromCodeContext(KDevelop::DUContext* context) const
{
if(!context)
return 0;
if(m_contextClasses.contains(context))
return m_contextClasses[context];
DUContext* startContext = context;
while( context->parentContext() && context->type() == DUContext::Other && context->parentContext()->type() == DUContext::Other )
{ //Move context to the top context of type "Other". This is needed because every compound-statement creates a new sub-context.
context = context->parentContext();
}
///Step 1: Find the function-declaration for the function we are in
Declaration* functionDeclaration = 0;
if( FunctionDefinition* def = dynamic_cast<FunctionDefinition*>(context->owner()) ) {
if(m_contextClasses.contains(context))
return m_contextClasses[context];
functionDeclaration = def->declaration(startContext->topContext());
}
if( !functionDeclaration && context->owner() )
functionDeclaration = context->owner();
if(!functionDeclaration) {
if(m_useClassCache)
m_contextClasses[context] = 0;
return 0;
}
Declaration* decl = functionDeclaration->context()->owner();
if(m_useClassCache)
m_contextClasses[context] = decl;
return decl;
}
CodeHighlightingInstance::Types CodeHighlightingInstance::typeForDeclaration(Declaration * dec, DUContext* context) const
{
/**
* We highlight in 3 steps by priority:
* 1. Is the item in the local class or an inherited class? If yes, highlight.
* 2. What kind of item is it? If it's a type/function/enumerator, highlight by type.
* 3. Else, highlight by scope.
*
* */
// if(ClassMemberDeclaration* classMember = dynamic_cast<ClassMemberDeclaration*>(dec))
// if(!Cpp::isAccessible(context, classMember))
// return ErrorVariableType;
if(!dec)
return ErrorVariableType;
Types type = LocalVariableType;
if(dec->kind() == Declaration::Namespace)
return NamespaceType;
if (context && dec->context() && dec->context()->type() == DUContext::Class) {
//It is a use.
//Determine the class we're in
Declaration* klass = localClassFromCodeContext(context);
if(klass) {
if (klass->internalContext() == dec->context())
type = LocalClassMemberType; //Using Member of the local class
else if (dec->context()->type() == DUContext::Class && klass->internalContext() && klass->internalContext()->imports(dec->context()))
type = InheritedClassMemberType; //Using Member of an inherited class
}
}
if (type == LocalVariableType) {
if (dec->kind() == Declaration::Type || dec->type<KDevelop::FunctionType>() || dec->type<KDevelop::EnumeratorType>()) {
if (dec->isForwardDeclaration())
type = ForwardDeclarationType;
else if (dec->type<KDevelop::FunctionType>())
type = FunctionType;
else if(dec->type<StructureType>())
type = ClassType;
else if(dec->type<KDevelop::TypeAliasType>())
type = TypeAliasType;
else if(dec->type<EnumerationType>())
type = EnumType;
else if(dec->type<KDevelop::EnumeratorType>())
type = EnumeratorType;
}
}
if (type == LocalVariableType) {
switch (dec->context()->type()) {
case DUContext::Namespace:
type = NamespaceVariableType;
break;
case DUContext::Class:
type = MemberVariableType;
break;
case DUContext::Function:
type = FunctionVariableType;
break;
default:
break;
}
}
return type;
}
bool CodeHighlightingInstance::useRainbowColor(Declaration* dec) const
{
return dec->context()->type() == DUContext::Function || (dec->context()->type() == DUContext::Other && dec->context()->owner());
}
void CodeHighlightingInstance::highlightDeclaration(Declaration * declaration, const QColor &color)
{
HighlightedRange h;
h.range = declaration->range();
h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, 0), DeclarationContext, color);
m_highlight.push_back(h);
}
void CodeHighlightingInstance::highlightUse(DUContext* context, int index, const QColor &color)
{
Types type = ErrorVariableType;
Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[index].m_declarationIndex);
type = typeForDeclaration(decl, context);
if(type != ErrorVariableType || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())
{
HighlightedRange h;
h.range = context->uses()[index].m_range;
h.attribute = m_highlighting->attributeForType(type, ReferenceContext, color);
m_highlight.push_back(h);
}
}
void CodeHighlightingInstance::highlightUses(DUContext* context)
{
for(int a = 0; a < context->usesCount(); ++a)
highlightUse(context, a, QColor(QColor::Invalid));
}
void CodeHighlighting::clearHighlightingForDocument(IndexedString document)
{
VERIFY_FOREGROUND_LOCKED
QMutexLocker lock(&m_dataMutex);
DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document);
if(m_highlights.contains(tracker))
{
disconnect(tracker, SIGNAL(destroyed(QObject*)), this, SLOT(trackerDestroyed(QObject*)));
qDeleteAll(m_highlights[tracker]->m_highlightedRanges);
delete m_highlights[tracker];
m_highlights.remove(tracker);
}
}
void CodeHighlighting::applyHighlighting(void* _highlighting)
{
CodeHighlighting::DocumentHighlighting* highlighting = static_cast<CodeHighlighting::DocumentHighlighting*>(_highlighting);
VERIFY_FOREGROUND_LOCKED
QMutexLocker lock(&m_dataMutex);
DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(highlighting->m_document);
if(!tracker)
{
kDebug() << "no document found for the planned highlighting of" << highlighting->m_document.str();
delete highlighting;
return;
}
QVector< MovingRange* > oldHighlightedRanges;
if(m_highlights.contains(tracker))
{
oldHighlightedRanges = m_highlights[tracker]->m_highlightedRanges;
delete m_highlights[tracker];
}else{
// we newly add this tracker, so add the connection
connect(tracker, SIGNAL(destroyed(QObject*)), SLOT(trackerDestroyed(QObject*)));
connect(tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)),
this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)));
connect(tracker->document(), SIGNAL(aboutToRemoveText(KTextEditor::Range)),
this, SLOT(aboutToRemoveText(KTextEditor::Range)));
}
m_highlights[tracker] = highlighting;
// Now create MovingRanges (match old ones with the incoming ranges)
KTextEditor::Range tempRange;
QVector<MovingRange*>::iterator movingIt = oldHighlightedRanges.begin();
QVector<HighlightedRange>::iterator rangeIt = highlighting->m_waiting.begin();
while(rangeIt != highlighting->m_waiting.end())
{
// Translate the range into the current revision
SimpleRange transformedRange = tracker->transformToCurrentRevision(rangeIt->range, highlighting->m_waitingRevision);
while(movingIt != oldHighlightedRanges.end() &&
((*movingIt)->start().line() < transformedRange.start.line ||
((*movingIt)->start().line() == transformedRange.start.line && (*movingIt)->start().column() < transformedRange.start.column)))
{
delete *movingIt; // Skip ranges that are in front of the current matched range
++movingIt;
}
tempRange.start().setPosition(transformedRange.start.line, transformedRange.start.column);
tempRange.end().setPosition(transformedRange.end.line, transformedRange.end.column);
if(movingIt == oldHighlightedRanges.end() ||
transformedRange.start.line != (*movingIt)->start().line() ||
transformedRange.start.column != (*movingIt)->start().column() ||
transformedRange.end.line != (*movingIt)->end().line() ||
transformedRange.end.column != (*movingIt)->end().column())
{
Q_ASSERT(rangeIt->attribute);
// The moving range is behind or unequal, create a new range
highlighting->m_highlightedRanges.push_back(tracker->documentMovingInterface()->newMovingRange(tempRange));
highlighting->m_highlightedRanges.back()->setAttribute(rangeIt->attribute);
highlighting->m_highlightedRanges.back()->setZDepth(highlightingZDepth);
}
else
{
// Update the existing moving range
(*movingIt)->setAttribute(rangeIt->attribute);
(*movingIt)->setRange(tempRange);
highlighting->m_highlightedRanges.push_back(*movingIt);
++movingIt;
}
++rangeIt;
}
for(; movingIt != oldHighlightedRanges.end(); ++movingIt)
delete *movingIt; // Delete unmatched moving ranges behind
}
void CodeHighlighting::trackerDestroyed(QObject* object)
{
// Called when a document is destroyed
VERIFY_FOREGROUND_LOCKED
QMutexLocker lock(&m_dataMutex);
DocumentChangeTracker* tracker = static_cast<DocumentChangeTracker*>(object);
Q_ASSERT(m_highlights.contains(tracker));
delete m_highlights[tracker]; // No need to care about the individual ranges, as the document is being destroyed
m_highlights.remove(tracker);
}
void CodeHighlighting::aboutToInvalidateMovingInterfaceContent(Document* doc)
{
clearHighlightingForDocument(IndexedString(doc->url()));
}
void CodeHighlighting::aboutToRemoveText( const KTextEditor::Range& range )
{
if (range.onSingleLine()) // don't try to optimize this
return;
VERIFY_FOREGROUND_LOCKED
QMutexLocker lock(&m_dataMutex);
Q_ASSERT(dynamic_cast<KTextEditor::Document*>(sender()));
KTextEditor::Document* doc = static_cast<KTextEditor::Document*>(sender());
DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()
->trackerForUrl(IndexedString(doc->url()));
if(m_highlights.contains(tracker))
{
QVector<MovingRange*>& ranges = m_highlights.value(tracker)->m_highlightedRanges;
QVector<MovingRange*>::iterator it = ranges.begin();
while(it != ranges.end()) {
if (range.contains((*it)->toRange())) {
delete (*it);
it = ranges.erase(it);
} else {
++it;
}
}
}
}
}
#include "moc_codehighlighting.cpp"
// kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2;