diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index 3a3ace5e3..ed0c17ca8 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,769 +1,788 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractdeclarationnavigationcontext.h" #include #include #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../classfunctiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../forwarddeclaration.h" #include "../types/enumeratortype.h" #include "../types/enumerationtype.h" #include "../types/functiontype.h" #include "../duchainutils.h" #include "../types/pointertype.h" #include "../types/referencetype.h" #include "../types/typeutils.h" #include "../types/typesystem.h" #include "../persistentsymboltable.h" #include "util/debug.h" #include #include #include #include #include namespace KDevelop { -AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext( DeclarationPointer decl, TopDUContextPointer topContext, AbstractNavigationContext* previousContext) - : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : nullptr)), previousContext), m_declaration(decl), m_fullBackwardSearch(false) +class AbstractDeclarationNavigationContextPrivate { +public: + DeclarationPointer m_declaration; + bool m_fullBackwardSearch = false; +}; + +AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext(const DeclarationPointer& decl, + const TopDUContextPointer& topContext, + AbstractNavigationContext* previousContext) + : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : nullptr)), previousContext) + , d(new AbstractDeclarationNavigationContextPrivate) +{ + d->m_declaration = decl; + //Jump from definition to declaration if possible - FunctionDefinition* definition = dynamic_cast(m_declaration.data()); + FunctionDefinition* definition = dynamic_cast(d->m_declaration.data()); if(definition && definition->declaration()) - m_declaration = DeclarationPointer(definition->declaration()); + d->m_declaration = DeclarationPointer(definition->declaration()); +} + +AbstractDeclarationNavigationContext::~AbstractDeclarationNavigationContext() +{ } QString AbstractDeclarationNavigationContext::name() const { - if(m_declaration.data()) - return prettyQualifiedIdentifier(m_declaration).toString(); + if(d->m_declaration.data()) + return prettyQualifiedIdentifier(d->m_declaration).toString(); else - return declarationName(m_declaration); + return declarationName(d->m_declaration); } QString AbstractDeclarationNavigationContext::html(bool shorten) { DUChainReadLocker lock(DUChain::lock(), 300); if ( !lock.locked() ) { return {}; } + clear(); - m_shorten = shorten; + AbstractNavigationContext::html(shorten); + modifyHtml() += "

" + fontSizePrefix(shorten); - addExternalHtml(m_prefix); + addExternalHtml(prefix()); - if(!m_declaration.data()) { + if(!d->m_declaration.data()) { modifyHtml() += i18n("
lost declaration
"); return currentHtml(); } - if( m_previousContext ) { - QString link = createLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); + if(auto context = previousContext()) { + const QString link = createLink(context->name(), context->name(), + NavigationAction(context)); modifyHtml() += navigationHighlight(i18n("Back to %1
", link)); } QExplicitlySharedDataPointer doc; if( !shorten ) { - doc = ICore::self()->documentationController()->documentationForDeclaration(m_declaration.data()); + doc = ICore::self()->documentationController()->documentationForDeclaration(d->m_declaration.data()); - const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); + const AbstractFunctionDeclaration* function = dynamic_cast(d->m_declaration.data()); if( function ) { htmlFunction(); - } else if( m_declaration->isTypeAlias() || m_declaration->type() || m_declaration->kind() == Declaration::Instance ) { - if( m_declaration->isTypeAlias() ) + } else if( d->m_declaration->isTypeAlias() || d->m_declaration->type() || d->m_declaration->kind() == Declaration::Instance ) { + if( d->m_declaration->isTypeAlias() ) modifyHtml() += importantHighlight(QStringLiteral("typedef ")); - if(m_declaration->type()) + if(d->m_declaration->type()) modifyHtml() += i18n("enumerator "); - AbstractType::Ptr useType = m_declaration->abstractType(); - if(m_declaration->isTypeAlias()) { + AbstractType::Ptr useType = d->m_declaration->abstractType(); + if(d->m_declaration->isTypeAlias()) { //Do not show the own name as type of typedefs if(useType.cast()) useType = useType.cast()->type(); } eventuallyMakeTypeLinks( useType ); - modifyHtml() += ' ' + identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); + modifyHtml() += ' ' + identifierHighlight(declarationName(d->m_declaration).toHtmlEscaped(), d->m_declaration); - if(auto integralType = m_declaration->type()) { + if(auto integralType = d->m_declaration->type()) { const QString plainValue = integralType->valueAsString(); if (!plainValue.isEmpty()) { modifyHtml() += QStringLiteral(" = ") + plainValue; } } modifyHtml() += QStringLiteral("
"); }else{ - if( m_declaration->kind() == Declaration::Type && m_declaration->abstractType().cast() ) { + if( d->m_declaration->kind() == Declaration::Type && d->m_declaration->abstractType().cast() ) { htmlClass(); } - if ( m_declaration->kind() == Declaration::Namespace ) { - modifyHtml() += i18n("namespace %1 ", identifierHighlight(m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), m_declaration)); - } else if ( m_declaration->kind() == Declaration::NamespaceAlias ) { - modifyHtml() += identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); + if ( d->m_declaration->kind() == Declaration::Namespace ) { + modifyHtml() += i18n("namespace %1 ", identifierHighlight(d->m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), d->m_declaration)); + } else if ( d->m_declaration->kind() == Declaration::NamespaceAlias ) { + modifyHtml() += identifierHighlight(declarationName(d->m_declaration).toHtmlEscaped(), d->m_declaration); } - if(m_declaration->type()) { - EnumerationType::Ptr enumeration = m_declaration->type(); - modifyHtml() += i18n("enumeration %1 ", identifierHighlight(m_declaration->identifier().toString().toHtmlEscaped(), m_declaration)); + if(d->m_declaration->type()) { + EnumerationType::Ptr enumeration = d->m_declaration->type(); + modifyHtml() += i18n("enumeration %1 ", identifierHighlight(d->m_declaration->identifier().toString().toHtmlEscaped(), d->m_declaration)); } - if(m_declaration->isForwardDeclaration()) { - ForwardDeclaration* forwardDec = static_cast(m_declaration.data()); - Declaration* resolved = forwardDec->resolve(m_topContext.data()); + if(d->m_declaration->isForwardDeclaration()) { + ForwardDeclaration* forwardDec = static_cast(d->m_declaration.data()); + Declaration* resolved = forwardDec->resolve(topContext().data()); if(resolved) { modifyHtml() += i18n("(resolved forward-declaration: "); makeLink(resolved->identifier().toString(), DeclarationPointer(resolved), NavigationAction::NavigateDeclaration ); modifyHtml() += i18n(") "); }else{ modifyHtml() += i18n("(unresolved forward-declaration) "); QualifiedIdentifier id = forwardDec->qualifiedIdentifier(); const auto& forwardDecFile = forwardDec->topContext()->parsingEnvironmentFile(); uint count; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(id, count, decls); for(uint a = 0; a < count; ++a) { auto dec = decls[a].data(); if (!dec || dec->isForwardDeclaration()) { continue; } const auto& decFile = forwardDec->topContext()->parsingEnvironmentFile(); if ((static_cast(decFile) != static_cast(forwardDecFile)) || (decFile && forwardDecFile && decFile->language() != forwardDecFile->language())) { // the language of the declarations must match continue; } modifyHtml() += QStringLiteral("
"); makeLink(i18n("possible resolution from"), DeclarationPointer(dec), NavigationAction::NavigateDeclaration); modifyHtml() += ' ' + dec->url().str(); } } } modifyHtml() += QStringLiteral("
"); } }else{ - AbstractType::Ptr showType = m_declaration->abstractType(); + AbstractType::Ptr showType = d->m_declaration->abstractType(); if(showType && showType.cast()) { showType = showType.cast()->returnType(); if(showType) modifyHtml() += labelHighlight(i18n("Returns: ")); }else if(showType) { modifyHtml() += labelHighlight(i18n("Type: ")); } if(showType) { eventuallyMakeTypeLinks(showType); modifyHtml() += QStringLiteral(" "); } } - QualifiedIdentifier identifier = m_declaration->qualifiedIdentifier(); + QualifiedIdentifier identifier = d->m_declaration->qualifiedIdentifier(); if( identifier.count() > 1 ) { - if( m_declaration->context() && m_declaration->context()->owner() ) + if( d->m_declaration->context() && d->m_declaration->context()->owner() ) { - Declaration* decl = m_declaration->context()->owner(); + Declaration* decl = d->m_declaration->context()->owner(); FunctionDefinition* definition = dynamic_cast(decl); if(definition && definition->declaration()) decl = definition->declaration(); if(decl->abstractType().cast()) modifyHtml() += labelHighlight(i18n("Enum: ")); else modifyHtml() += labelHighlight(i18n("Container: ")); makeLink( declarationName(DeclarationPointer(decl)), DeclarationPointer(decl), NavigationAction::NavigateDeclaration ); modifyHtml() += QStringLiteral(" "); } else { QualifiedIdentifier parent = identifier; parent.pop(); modifyHtml() += labelHighlight(i18n("Scope: %1 ", typeHighlight(parent.toString().toHtmlEscaped()))); } } - if( shorten && !m_declaration->comment().isEmpty() ) { - QString comment = QString::fromUtf8(m_declaration->comment()); + if( shorten && !d->m_declaration->comment().isEmpty() ) { + QString comment = QString::fromUtf8(d->m_declaration->comment()); if( comment.length() > 60 ) { comment.truncate(60); comment += QLatin1String("..."); } comment.replace('\n', QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + " "; } - QString access = stringFromAccess(m_declaration); + QString access = stringFromAccess(d->m_declaration); if( !access.isEmpty() ) modifyHtml() += labelHighlight(i18n("Access: %1 ", propertyHighlight(access.toHtmlEscaped()))); ///@todo Enumerations QString detailsHtml; - QStringList details = declarationDetails(m_declaration); + QStringList details = declarationDetails(d->m_declaration); if( !details.isEmpty() ) { bool first = true; foreach( const QString &str, details ) { if( !first ) detailsHtml += QLatin1String(", "); first = false; detailsHtml += propertyHighlight(str); } } - QString kind = declarationKind(m_declaration); + QString kind = declarationKind(d->m_declaration); if( !kind.isEmpty() ) { if( !detailsHtml.isEmpty() ) modifyHtml() += labelHighlight(i18n("Kind: %1 %2 ", importantHighlight(kind.toHtmlEscaped()), detailsHtml)); else modifyHtml() += labelHighlight(i18n("Kind: %1 ", importantHighlight(kind.toHtmlEscaped()))); } - if (m_declaration->isDeprecated()) { + if (d->m_declaration->isDeprecated()) { modifyHtml() += labelHighlight(i18n("Status: %1 ", propertyHighlight(i18n("Deprecated")))); } modifyHtml() += QStringLiteral("
"); if(!shorten) htmlAdditionalNavigation(); if( !shorten ) { - if(dynamic_cast(m_declaration.data())) + if(dynamic_cast(d->m_declaration.data())) modifyHtml() += labelHighlight(i18n( "Def.: " )); else modifyHtml() += labelHighlight(i18n( "Decl.: " )); - makeLink( QStringLiteral("%1 :%2").arg( m_declaration->url().toUrl().fileName() ).arg( m_declaration->rangeInCurrentRevision().start().line()+1 ), m_declaration, NavigationAction::JumpToSource ); + makeLink( QStringLiteral("%1 :%2").arg( d->m_declaration->url().toUrl().fileName() ).arg( d->m_declaration->rangeInCurrentRevision().start().line()+1 ), d->m_declaration, NavigationAction::JumpToSource ); modifyHtml() += QStringLiteral(" "); //modifyHtml() += "
"; - if(!dynamic_cast(m_declaration.data())) { - if( FunctionDefinition* definition = FunctionDefinition::definition(m_declaration.data()) ) { + if(!dynamic_cast(d->m_declaration.data())) { + if( FunctionDefinition* definition = FunctionDefinition::definition(d->m_declaration.data()) ) { modifyHtml() += labelHighlight(i18n( " Def.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->url().toUrl().fileName() ).arg( definition->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition), NavigationAction::JumpToSource ); } } - if( FunctionDefinition* definition = dynamic_cast(m_declaration.data()) ) { + if( FunctionDefinition* definition = dynamic_cast(d->m_declaration.data()) ) { if(definition->declaration()) { modifyHtml() += labelHighlight(i18n( " Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->declaration()->url().toUrl().fileName() ).arg( definition->declaration()->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition->declaration()), NavigationAction::JumpToSource ); } } modifyHtml() += QStringLiteral(" "); //The action name _must_ stay "show_uses", since that is also used from outside - makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(m_declaration, NavigationAction::NavigateUses)); + makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(d->m_declaration, NavigationAction::NavigateUses)); } - QByteArray declarationComment = m_declaration->comment(); + QByteArray declarationComment = d->m_declaration->comment(); if( !shorten && (!declarationComment.isEmpty() || doc) ) { modifyHtml() += QStringLiteral("

"); if(doc) { QString comment = doc->description(); connect(doc.data(), &IDocumentation::descriptionChanged, this, &AbstractDeclarationNavigationContext::contentsChanged); if(!comment.isEmpty()) { modifyHtml() += "

" + commentHighlight(comment) + "

"; } } QString comment = QString::fromUtf8(declarationComment); if(!comment.isEmpty()) { // if the first paragraph does not contain a tag, we assume that this is a plain-text comment if (!Qt::mightBeRichText(comment)) { // still might contain extra html tags for line breaks (this is the case for doxygen-style comments sometimes) // let's protect them from being removed completely comment.replace(QRegExp("
"), QStringLiteral("\n")); comment = comment.toHtmlEscaped(); comment.replace('\n', QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); modifyHtml() += QStringLiteral("

"); } } if(!shorten && doc) { modifyHtml() += "

" + i18n("Show documentation for "); - makeLink(prettyQualifiedName(m_declaration), - m_declaration, NavigationAction::ShowDocumentation); + makeLink(prettyQualifiedName(d->m_declaration), + d->m_declaration, NavigationAction::ShowDocumentation); modifyHtml() += "

"; } //modifyHtml() += "
"; - addExternalHtml(m_suffix); + addExternalHtml(suffix()); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } AbstractType::Ptr AbstractDeclarationNavigationContext::typeToShow(AbstractType::Ptr type) { return type; } void AbstractDeclarationNavigationContext::htmlFunction() { - const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); + const AbstractFunctionDeclaration* function = dynamic_cast(d->m_declaration.data()); Q_ASSERT(function); - const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); - const FunctionType::Ptr type = m_declaration->abstractType().cast(); + const ClassFunctionDeclaration* classFunDecl = dynamic_cast(d->m_declaration.data()); + const FunctionType::Ptr type = d->m_declaration->abstractType().cast(); if( !type ) { modifyHtml() += errorHighlight(QStringLiteral("Invalid type
")); return; } if( !classFunDecl || (!classFunDecl->isConstructor() && !classFunDecl->isDestructor()) ) { // only print return type for global functions and non-ctor/dtor methods eventuallyMakeTypeLinks( type->returnType() ); } - modifyHtml() += ' ' + identifierHighlight(prettyIdentifier(m_declaration).toString().toHtmlEscaped(), m_declaration); + modifyHtml() += ' ' + identifierHighlight(prettyIdentifier(d->m_declaration).toString().toHtmlEscaped(), d->m_declaration); if( type->indexedArgumentsSize() == 0 ) { modifyHtml() += QStringLiteral("()"); } else { modifyHtml() += QStringLiteral("( "); bool first = true; int firstDefaultParam = type->indexedArgumentsSize() - function->defaultParametersSize(); int currentArgNum = 0; QVector decls; - if (DUContext* argumentContext = DUChainUtils::getArgumentContext(m_declaration.data())) { - decls = argumentContext->localDeclarations(m_topContext.data()); + if (DUContext* argumentContext = DUChainUtils::getArgumentContext(d->m_declaration.data())) { + decls = argumentContext->localDeclarations(topContext().data()); } foreach(const AbstractType::Ptr& argType, type->arguments()) { if( !first ) modifyHtml() += QStringLiteral(", "); first = false; eventuallyMakeTypeLinks( argType ); if (currentArgNum < decls.size()) { - modifyHtml() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), m_declaration); + modifyHtml() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), d->m_declaration); } if( currentArgNum >= firstDefaultParam ) modifyHtml() += " = " + function->defaultParameters()[ currentArgNum - firstDefaultParam ].str().toHtmlEscaped(); ++currentArgNum; } modifyHtml() += QStringLiteral(" )"); } modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(DeclarationPointer decl) const { Identifier ret; QualifiedIdentifier q = prettyQualifiedIdentifier(decl); if(!q.isEmpty()) ret = q.last(); return ret; } QualifiedIdentifier AbstractDeclarationNavigationContext::prettyQualifiedIdentifier(DeclarationPointer decl) const { if(decl) return decl->qualifiedIdentifier(); else return QualifiedIdentifier(); } QString AbstractDeclarationNavigationContext::prettyQualifiedName(DeclarationPointer decl) const { const auto qid = prettyQualifiedIdentifier(decl); if (qid.isEmpty()) { return i18nc("An anonymous declaration (class, function, etc.)", ""); } return qid.toString(); } void AbstractDeclarationNavigationContext::htmlAdditionalNavigation() { ///Check if the function overrides or hides another one - const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); + const ClassFunctionDeclaration* classFunDecl = dynamic_cast(d->m_declaration.data()); if(classFunDecl) { - Declaration* overridden = DUChainUtils::getOverridden(m_declaration.data()); + Declaration* overridden = DUChainUtils::getOverridden(d->m_declaration.data()); if(overridden) { modifyHtml() += i18n("Overrides a "); makeLink(i18n("function"), QStringLiteral("jump_to_overridden"), NavigationAction(DeclarationPointer(overridden), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(overridden->context()->owner())), QStringLiteral("jump_to_overridden_container"), NavigationAction(DeclarationPointer(overridden->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); }else{ //Check if this declarations hides other declarations QList decls; - foreach(const DUContext::Import &import, m_declaration->context()->importedParentContexts()) - if(import.context(m_topContext.data())) - decls += import.context(m_topContext.data())->findDeclarations(QualifiedIdentifier(m_declaration->identifier()), - CursorInRevision::invalid(), AbstractType::Ptr(), m_topContext.data(), DUContext::DontSearchInParent); + foreach(const DUContext::Import &import, d->m_declaration->context()->importedParentContexts()) + if(import.context(topContext().data())) + decls += import.context(topContext().data())->findDeclarations(QualifiedIdentifier(d->m_declaration->identifier()), + CursorInRevision::invalid(), AbstractType::Ptr(), topContext().data(), DUContext::DontSearchInParent); uint num = 0; foreach(Declaration* decl, decls) { modifyHtml() += i18n("Hides a "); makeLink(i18n("function"), QStringLiteral("jump_to_hide_%1").arg(num), NavigationAction(DeclarationPointer(decl), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(decl->context()->owner())), QStringLiteral("jump_to_hide_container_%1").arg(num), NavigationAction(DeclarationPointer(decl->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); ++num; } } ///Show all places where this function is overridden if(classFunDecl->isVirtual()) { - Declaration* classDecl = m_declaration->context()->owner(); + Declaration* classDecl = d->m_declaration->context()->owner(); if(classDecl) { - uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; + uint maxAllowedSteps = d->m_fullBackwardSearch ? (uint)-1 : 10; QList overriders = DUChainUtils::getOverriders(classDecl, classFunDecl, maxAllowedSteps); if(!overriders.isEmpty()) { modifyHtml() += i18n("Overridden in "); bool first = true; foreach(Declaration* overrider, overriders) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const auto owner = DeclarationPointer(overrider->context()->owner()); const QString name = prettyQualifiedName(owner); makeLink(name, name, NavigationAction(DeclarationPointer(overrider), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(overriders.isEmpty() ? i18n("Overriders possible, show all") : i18n("More overriders possible, show all")); } } } ///Show all classes that inherit this one - uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; - QList inheriters = DUChainUtils::getInheriters(m_declaration.data(), maxAllowedSteps); + uint maxAllowedSteps = d->m_fullBackwardSearch ? (uint)-1 : 10; + QList inheriters = DUChainUtils::getInheriters(d->m_declaration.data(), maxAllowedSteps); if(!inheriters.isEmpty()) { modifyHtml() += i18n("Inherited by "); bool first = true; foreach(Declaration* importer, inheriters) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const QString importerName = prettyQualifiedName(DeclarationPointer(importer)); makeLink(importerName, importerName, NavigationAction(DeclarationPointer(importer), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(inheriters.isEmpty() ? i18n("Inheriters possible, show all") : i18n("More inheriters possible, show all")); } void AbstractDeclarationNavigationContext::createFullBackwardSearchLink(QString string) { makeLink(string, QStringLiteral("m_fullBackwardSearch=true"), NavigationAction(QStringLiteral("m_fullBackwardSearch=true"))); modifyHtml() += QStringLiteral("
"); } NavigationContextPointer AbstractDeclarationNavigationContext::executeKeyAction( QString key ) { if(key == QLatin1String("m_fullBackwardSearch=true")) { - m_fullBackwardSearch = true; + d->m_fullBackwardSearch = true; clear(); } return NavigationContextPointer(this); } void AbstractDeclarationNavigationContext::htmlClass() { - StructureType::Ptr klass = m_declaration->abstractType().cast(); + StructureType::Ptr klass = d->m_declaration->abstractType().cast(); Q_ASSERT(klass); - ClassDeclaration* classDecl = dynamic_cast(klass->declaration(m_topContext.data())); + ClassDeclaration* classDecl = dynamic_cast(klass->declaration(topContext().data())); if(classDecl) { switch ( classDecl->classType() ) { case ClassDeclarationData::Class: modifyHtml() += QStringLiteral("class "); break; case ClassDeclarationData::Struct: modifyHtml() += QStringLiteral("struct "); break; case ClassDeclarationData::Union: modifyHtml() += QStringLiteral("union "); break; case ClassDeclarationData::Interface: modifyHtml() += QStringLiteral("interface "); break; case ClassDeclarationData::Trait: modifyHtml() += QStringLiteral("trait "); break; default: modifyHtml() += QStringLiteral(" "); break; } eventuallyMakeTypeLinks( klass.cast() ); FOREACH_FUNCTION( const BaseClassInstance& base, classDecl->baseClasses ) { modifyHtml() += ", " + stringFromAccess(base.access) + " " + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + " "; eventuallyMakeTypeLinks(base.baseClass.abstractType()); } } else { /// @todo How can we get here? and should this really be a class? modifyHtml() += QStringLiteral("class "); eventuallyMakeTypeLinks( klass.cast() ); } modifyHtml() += QStringLiteral(" "); } void AbstractDeclarationNavigationContext::htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) { Q_ASSERT(type); Q_ASSERT(idType); - if( Declaration* decl = idType->declaration(m_topContext.data()) ) { + if( Declaration* decl = idType->declaration(topContext().data()) ) { //Remove the last template-identifiers, because we create those directly QualifiedIdentifier id = prettyQualifiedIdentifier(DeclarationPointer(decl)); Identifier lastId = id.last(); id.pop(); lastId.clearTemplateIdentifiers(); id.push(lastId); if(decl->context() && decl->context()->owner()) { //Also create full type-links for the context around AbstractType::Ptr contextType = decl->context()->owner()->abstractType(); IdentifiedType* contextIdType = dynamic_cast(contextType.data()); if(contextIdType && !contextIdType->equals(idType)) { //Create full type information for the context if(!id.isEmpty()) id = id.mid(id.count()-1); htmlIdentifiedType(contextType, contextIdType); modifyHtml() += QStringLiteral("::").toHtmlEscaped(); } } //We leave out the * and & reference and pointer signs, those are added to the end - makeLink(id.toString() , DeclarationPointer(idType->declaration(m_topContext.data())), NavigationAction::NavigateDeclaration ); + makeLink(id.toString() , DeclarationPointer(idType->declaration(topContext().data())), NavigationAction::NavigateDeclaration ); } else { - qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << m_topContext->url().str(); + qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << topContext()->url().str(); modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } void AbstractDeclarationNavigationContext::eventuallyMakeTypeLinks( AbstractType::Ptr type ) { type = typeToShow(type); if( !type ) { modifyHtml() += typeHighlight(QStringLiteral("").toHtmlEscaped()); return; } - AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, m_topContext.data() ); + AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, topContext().data() ); const IdentifiedType* idType = dynamic_cast( target.data() ); qCDebug(LANGUAGE) << "making type-links for" << type->toString(); - if( idType && idType->declaration(m_topContext.data()) ) { + if( idType && idType->declaration(topContext().data()) ) { ///@todo This is C++ specific, move into subclass if(target->modifiers() & AbstractType::ConstModifier) modifyHtml() += typeHighlight(QStringLiteral("const ")); htmlIdentifiedType(target, idType); //We need to exchange the target type, else template-parameters may confuse this SimpleTypeExchanger exchangeTarget(target, AbstractType::Ptr()); AbstractType::Ptr exchanged = exchangeTarget.exchange(type); if(exchanged) { QString typeSuffixString = exchanged->toString(); QRegExp suffixExp("\\&|\\*"); int suffixPos = typeSuffixString.indexOf(suffixExp); if(suffixPos != -1) modifyHtml() += typeHighlight(typeSuffixString.mid(suffixPos)); } } else { if(idType) { qCDebug(LANGUAGE) << "identified type could not be resolved:" << idType->qualifiedIdentifier() << idType->declarationId().isValid() << idType->declarationId().isDirect(); } modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } DeclarationPointer AbstractDeclarationNavigationContext::declaration() const { - return m_declaration; + return d->m_declaration; } QString AbstractDeclarationNavigationContext::identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const { QString ret = nameHighlight(identifier); if (!decl) { return ret; } if (decl->isDeprecated()) { ret = QStringLiteral("") + ret + QStringLiteral(""); } return ret; } QString AbstractDeclarationNavigationContext::stringFromAccess(Declaration::AccessPolicy access) { switch(access) { case Declaration::Private: return QStringLiteral("private"); case Declaration::Protected: return QStringLiteral("protected"); case Declaration::Public: return QStringLiteral("public"); default: break; } return QString(); } QString AbstractDeclarationNavigationContext::stringFromAccess(DeclarationPointer decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( DeclarationPointer decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return "using namespace " + alias->importIdentifier().toString(); else return "namespace " + alias->identifier().toString() + " = " + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(DeclarationPointer decl) { QStringList details; const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { if( memberDecl->isMutable() ) details << QStringLiteral("mutable"); if( memberDecl->isRegister() ) details << QStringLiteral("register"); if( memberDecl->isStatic() ) details << QStringLiteral("static"); if( memberDecl->isAuto() ) details << QStringLiteral("auto"); if( memberDecl->isExtern() ) details << QStringLiteral("extern"); if( memberDecl->isFriend() ) details << QStringLiteral("friend"); } if( decl->isDefinition() ) details << i18nc("tells if a declaration is defining the variable's value", "definition"); if( decl->isExplicitlyDeleted() ) details << QStringLiteral("deleted"); if( memberDecl && memberDecl->isForwardDeclaration() ) details << i18nc("as in c++ forward declaration", "forward"); AbstractType::Ptr t(decl->abstractType()); if( t ) { if( t->modifiers() & AbstractType::ConstModifier ) details << i18nc("a variable that won't change, const", "constant"); if( t->modifiers() & AbstractType::VolatileModifier ) details << QStringLiteral("volatile"); } if( function ) { if( function->isInline() ) details << QStringLiteral("inline"); if( function->isExplicit() ) details << QStringLiteral("explicit"); if( function->isVirtual() ) details << QStringLiteral("virtual"); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl.data()); if( classFunDecl ) { if( classFunDecl->isSignal() ) details << QStringLiteral("signal"); if( classFunDecl->isSlot() ) details << QStringLiteral("slot"); if( classFunDecl->isConstructor() ) details << QStringLiteral("constructor"); if( classFunDecl->isDestructor() ) details << QStringLiteral("destructor"); if( classFunDecl->isConversionFunction() ) details << QStringLiteral("conversion-function"); if( classFunDecl->isAbstract() ) details << QStringLiteral("abstract"); } } return details; } } diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.h b/language/duchain/navigation/abstractdeclarationnavigationcontext.h index de59c7a3a..1a967e51f 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.h +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.h @@ -1,95 +1,97 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTDECLARATIONNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTDECLARATIONNAVIGATIONCONTEXT_H #include "abstractnavigationcontext.h" #include "../declaration.h" #include "../duchainpointer.h" #include "../types/abstracttype.h" namespace KDevelop { +class AbstractDeclarationNavigationContextPrivate; class IdentifiedType; class Identifier; class QualifiedIdentifier; class KDEVPLATFORMLANGUAGE_EXPORT AbstractDeclarationNavigationContext : public AbstractNavigationContext { Q_OBJECT public: - AbstractDeclarationNavigationContext( DeclarationPointer decl, KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext = nullptr ); + AbstractDeclarationNavigationContext(const DeclarationPointer& decl, const TopDUContextPointer& topContext, + AbstractNavigationContext* previousContext = nullptr); + ~AbstractDeclarationNavigationContext() override; QString name() const override; DeclarationPointer declaration() const; ///Execute an action. For example "show_uses" shows the uses of the declaration. ///Returns the context pointer for the new state. NavigationContextPointer executeKeyAction(QString key) override; - protected: QString html(bool shorten = false) override; - DeclarationPointer m_declaration; - + protected: ///Should returns a stripped version of the declarations qualified identifier, with all implicit/redundant parts removed virtual QualifiedIdentifier prettyQualifiedIdentifier(DeclarationPointer decl) const; ///Returns a stripped version of the declarations identifier, using prettyQualifiedIdentifier Identifier prettyIdentifier(DeclarationPointer decl) const; /// @return String version of the qualified identifier of @p decl, '' on an invalid QID QString prettyQualifiedName(DeclarationPointer decl) const; /** * Return a rich-text version of the identifier @p identifier representing the declaration @p decl * * @note In case @p declaration is deprecated, the resulting string will get a special formatting */ QString identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const; static QString stringFromAccess(Declaration::AccessPolicy access); static QString stringFromAccess(DeclarationPointer decl); QString declarationName( DeclarationPointer decl ) const; static QStringList declarationDetails(DeclarationPointer decl); ///This can be used for example to resolve typedefs within the type. ///All types that are visualized in the navigation-context are/should be mangled through this. ///The default-implementation returns the original type. virtual AbstractType::Ptr typeToShow(AbstractType::Ptr type); ///Print the function-signature in a way that return-type and argument can be jumped to virtual void htmlFunction(); ///Navigation for additional less important links, like what function was overloaded etc. virtual void htmlAdditionalNavigation(); virtual void htmlClass(); virtual void htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType); ///Creates and registers a link for the given type that jumps to its declaration and to the template-argument declarations virtual void eventuallyMakeTypeLinks( KDevelop::AbstractType::Ptr type ); ///Creates a link that triggers a recomputation of this context with m_fullBackwardSearch set to true void createFullBackwardSearchLink(QString string); - bool m_fullBackwardSearch; +private: + QScopedPointer d; }; } #endif diff --git a/language/duchain/navigation/abstractincludenavigationcontext.cpp b/language/duchain/navigation/abstractincludenavigationcontext.cpp index 037d72ae6..e12a270f5 100644 --- a/language/duchain/navigation/abstractincludenavigationcontext.cpp +++ b/language/duchain/navigation/abstractincludenavigationcontext.cpp @@ -1,174 +1,174 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractincludenavigationcontext.h" #include #include #include #include namespace KDevelop { AbstractIncludeNavigationContext::AbstractIncludeNavigationContext(const IncludeItem& item, TopDUContextPointer topContext, const ParsingEnvironmentType& type) : AbstractNavigationContext(topContext), m_type(type), m_item(item) {} TopDUContext* pickContextWithData(QList duchains, uint maxDepth, const ParsingEnvironmentType& type, bool forcePick = true) { TopDUContext* duchain = nullptr; foreach(TopDUContext* ctx, duchains) { if(!ctx->parsingEnvironmentFile() || ctx->parsingEnvironmentFile()->type() != type) continue; if(ctx->childContexts().count() != 0 && (duchain == nullptr || ctx->childContexts().count() > duchain->childContexts().count())) { duchain = ctx; } if(ctx->localDeclarations().count() != 0 && (duchain == nullptr || ctx->localDeclarations().count() > duchain->localDeclarations().count())) { duchain = ctx; } } if(!duchain && maxDepth != 0) { if(maxDepth != 0) { foreach(TopDUContext* ctx, duchains) { QList children; foreach(const DUContext::Import &import, ctx->importedParentContexts()) if(import.context(nullptr)) children << import.context(nullptr)->topContext(); duchain = pickContextWithData(children, maxDepth-1, type, false); if(duchain) break; } } } if(!duchain && !duchains.isEmpty() && forcePick) duchain = duchains.first(); return duchain; } QString AbstractIncludeNavigationContext::html(bool shorten) { clear(); modifyHtml() += "

" + fontSizePrefix(shorten); - addExternalHtml(m_prefix); + addExternalHtml(prefix()); QUrl u = m_item.url(); NavigationAction action(u, KTextEditor::Cursor(0,0)); makeLink(u.toDisplayString(QUrl::PreferLocalFile), u.toString(), action); modifyHtml() += QStringLiteral("
"); QList duchains = DUChain::self()->chainsForDocument(u); //Pick the one duchain for this document that has the most child-contexts/declarations. //This prevents picking a context that is empty due to header-guards. TopDUContext* duchain = pickContextWithData(duchains, 2, m_type); if(duchain) { getFileInfo(duchain); if(!shorten) { modifyHtml() += labelHighlight(i18n("Declarations:")) + "
"; bool first = true; QList decs; addDeclarationsFromContext(duchain, first, decs); } }else if(duchains.isEmpty()) { modifyHtml() += i18n("not parsed yet"); } - addExternalHtml(m_suffix); + addExternalHtml(suffix()); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } void AbstractIncludeNavigationContext::getFileInfo(TopDUContext* duchain) { modifyHtml() += QStringLiteral("%1: %2 %3: %4").arg(labelHighlight(i18nc("Files included into this file", "Includes"))).arg(duchain->importedParentContexts().count()).arg(labelHighlight(i18nc("Count of files this file was included into", "Included by"))).arg(duchain->importers().count()); modifyHtml() += QStringLiteral("
"); } QString AbstractIncludeNavigationContext::name() const { return m_item.name; } bool AbstractIncludeNavigationContext::filterDeclaration(Declaration* /*decl*/) { return true; } void AbstractIncludeNavigationContext::addDeclarationsFromContext(KDevelop::DUContext* ctx, bool& first, QList< IdentifierPair > &addedDeclarations, const QString& indent ) { //modifyHtml() += indent + ctx->localScopeIdentifier().toString() + "{
"; QVector children = ctx->childContexts(); QVector declarations = ctx->localDeclarations(); QVector::const_iterator childIterator = children.constBegin(); QVector::const_iterator declarationIterator = declarations.constBegin(); while(childIterator != children.constEnd() || declarationIterator != declarations.constEnd()) { //Show declarations/contexts in the order they appear in the file int currentDeclarationLine = -1; int currentContextLine = -1; if(declarationIterator != declarations.constEnd()) currentDeclarationLine = (*declarationIterator)->rangeInCurrentRevision().start().line(); if(childIterator != children.constEnd()) currentDeclarationLine = (*childIterator)->rangeInCurrentRevision().start().line(); if((currentDeclarationLine <= currentContextLine || currentContextLine == -1 || childIterator == children.constEnd()) && declarationIterator != declarations.constEnd() ) { IdentifierPair id = qMakePair(static_cast((*declarationIterator)->kind()), (*declarationIterator)->qualifiedIdentifier().index()); if(!addedDeclarations.contains(id) && filterDeclaration(*declarationIterator) ) { //Show the declaration if(!first) modifyHtml() += QStringLiteral(", "); else first = false; modifyHtml() += QString(indent + declarationKind(DeclarationPointer(*declarationIterator)) + " ").toHtmlEscaped(); makeLink((*declarationIterator)->qualifiedIdentifier().toString(), DeclarationPointer(*declarationIterator), NavigationAction::NavigateDeclaration); addedDeclarations << id; } ++declarationIterator; } else { //Eventually Recurse into the context if((*childIterator)->type() == DUContext::Global || (*childIterator)->type() == DUContext::Namespace /*|| (*childIterator)->type() == DUContext::Class*/) addDeclarationsFromContext(*childIterator, first, addedDeclarations, indent + ' '); ++childIterator; } } //modifyHtml() += "}
"; } } diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 7cad556d4..48558303a 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,506 +1,555 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationcontext.h" #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include "util/debug.h" #include #include #include namespace KDevelop { -void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { - m_topContext = context; +class AbstractNavigationContextPrivate +{ +public: + QVector m_children; //Used to keep alive all children until this is deleted + + int m_selectedLink = 0; //The link currently selected + NavigationAction m_selectedLinkAction; //Target of the currently selected link + + bool m_shorten = false; + + //A counter used while building the html-code to count the used links. + int m_linkCount = -1; + //Something else than -1 if the current position is represented by a line-number, not a link. + int m_currentLine = 0; + int m_currentPositionLine = 0; + QMap m_links; + QMap m_linkLines; //Holds the line for each link + QMap m_intLinks; + AbstractNavigationContext* m_previousContext; + QString m_prefix, m_suffix; + TopDUContextPointer m_topContext; + + QString m_currentText; //Here the text is built +}; + +void AbstractNavigationContext::setTopContext(const TopDUContextPointer& context) +{ + d->m_topContext = context; } -KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { - return m_topContext; +TopDUContextPointer AbstractNavigationContext::topContext() const +{ + return d->m_topContext; } +AbstractNavigationContext::AbstractNavigationContext(const TopDUContextPointer& topContext, + AbstractNavigationContext* previousContext) + : d(new AbstractNavigationContextPrivate) +{ + d->m_previousContext = previousContext; + d->m_topContext = topContext; +} -AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) - : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), - m_previousContext(previousContext), m_topContext(topContext) +AbstractNavigationContext::~AbstractNavigationContext() { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, QString, const NavigationAction& action) { - if(m_shorten) { + if(d->m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. - QString hrefId = QString("link_%1").arg(m_links.count()); - - m_links[ hrefId ] = action; - m_intLinks[ m_linkCount ] = action; - m_linkLines[ m_linkCount ] = m_currentLine; - if(m_currentPositionLine == m_currentLine) { - m_currentPositionLine = -1; - m_selectedLink = m_linkCount; + QString hrefId = QString("link_%1").arg(d->m_links.count()); + + d->m_links[ hrefId ] = action; + d->m_intLinks[ d->m_linkCount ] = action; + d->m_linkLines[ d->m_linkCount ] = d->m_currentLine; + if(d->m_currentPositionLine == d->m_currentLine) { + d->m_currentPositionLine = -1; + d->m_selectedLink = d->m_linkCount; } QString str = name.toHtmlEscaped(); - if( m_linkCount == m_selectedLink ) + if( d->m_linkCount == d->m_selectedLink ) str = "" + str + ""; - QString ret = "" + str + ""; + QString ret = "m_linkCount == d->m_selectedLink && d->m_currentPositionLine == -1) ? QStringLiteral(" name = \"currentPosition\"") : QString()) + ">" + str + ""; - if( m_selectedLink == m_linkCount ) - m_selectedLinkAction = action; + if( d->m_selectedLink == d->m_linkCount ) + d->m_selectedLinkAction = action; - ++m_linkCount; + ++d->m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { - m_linkCount = 0; - m_currentLine = 0; - m_currentText.clear(); - m_links.clear(); - m_intLinks.clear(); - m_linkLines.clear(); + d->m_linkCount = 0; + d->m_currentLine = 0; + d->m_currentText.clear(); + d->m_links.clear(); + d->m_intLinks.clear(); + d->m_linkLines.clear(); } -NavigationContextPointer AbstractNavigationContext::executeLink (QString link) +void AbstractNavigationContext::executeLink(const QString& link) { - if(!m_links.contains(link)) - return NavigationContextPointer(this); + if(!d->m_links.contains(link)) + return; - return execute(m_links[link]); + execute(d->m_links[link]); } -NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { +NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key){ Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { - AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); + auto ctx = dynamic_cast(d->m_previousContext); if( ctx && ctx->declaration() == action.decl ) - return NavigationContextPointer( m_previousContext ); - return AbstractNavigationContext::registerChild(action.decl); + return NavigationContextPointer(d->m_previousContext); + return registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } - case NavigationAction::ShowUses: + case NavigationAction::ShowUses: { return registerChild(new UsesNavigationContext(action.decl.data(), this)); + } case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } -void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { - m_previousContext = previous; +AbstractNavigationContext* AbstractNavigationContext::previousContext() const +{ + return d->m_previousContext; } -NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { - m_children << NavigationContextPointer(context); - return m_children.last(); +void AbstractNavigationContext::setPreviousContext(AbstractNavigationContext* previous) { + d->m_previousContext = previous; +} + +NavigationContextPointer AbstractNavigationContext::registerChild(AbstractNavigationContext* context) +{ + d->m_children << NavigationContextPointer(context); + return d->m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); - m_children << ret; + d->m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid - if( m_linkCount == -1 ) { + if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } - int fromLine = m_currentPositionLine; + int fromLine = d->m_currentPositionLine; - if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { + if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) - fromLine = m_linkLines[m_selectedLink]; + fromLine = d->m_linkLines[d->m_selectedLink]; - for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { - if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { - m_selectedLink = newSelectedLink; - m_currentPositionLine = -1; + for(int newSelectedLink = d->m_selectedLink+1; newSelectedLink < d->m_linkCount; ++newSelectedLink) { + if(d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) { + d->m_selectedLink = newSelectedLink; + d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; - m_currentPositionLine = fromLine + lineJump; + d->m_currentPositionLine = fromLine + lineJump; - if(m_currentPositionLine > m_currentLine) - m_currentPositionLine = m_currentLine; + if(d->m_currentPositionLine > d->m_currentLine) + d->m_currentPositionLine = d->m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid - if( m_linkCount == -1 ) { + if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } - int fromLine = m_currentPositionLine; + int fromLine = d->m_currentPositionLine; - if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { + if(d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if(fromLine == -1) - fromLine = m_linkLines[m_selectedLink]; + fromLine = d->m_linkLines[d->m_selectedLink]; - for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { - if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { - m_selectedLink = newSelectedLink; - m_currentPositionLine = -1; + for(int newSelectedLink = d->m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { + if(d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) { + d->m_selectedLink = newSelectedLink; + d->m_currentPositionLine = -1; return; } } } if(fromLine == -1) - fromLine = m_currentLine; + fromLine = d->m_currentLine; - m_currentPositionLine = fromLine - lineJump; - if(m_currentPositionLine < 0) - m_currentPositionLine = 0; + d->m_currentPositionLine = fromLine - lineJump; + if(d->m_currentPositionLine < 0) + d->m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid - if( m_linkCount == -1 ) { + if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } - m_currentPositionLine = -1; + d->m_currentPositionLine = -1; - if( m_linkCount > 0 ) - m_selectedLink = (m_selectedLink+1) % m_linkCount; + if( d->m_linkCount > 0 ) + d->m_selectedLink = (d->m_selectedLink+1) % d->m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid - if( m_linkCount == -1 ) { + if( d->m_linkCount == -1 ) { DUChainReadLocker lock; html(); } - m_currentPositionLine = -1; + d->m_currentPositionLine = -1; - if( m_linkCount > 0 ) { - --m_selectedLink; - if( m_selectedLink < 0 ) - m_selectedLink += m_linkCount; + if( d->m_linkCount > 0 ) { + --d->m_selectedLink; + if( d->m_selectedLink < 0 ) + d->m_selectedLink += d->m_linkCount; } - Q_ASSERT(m_selectedLink >= 0); + Q_ASSERT(d->m_selectedLink >= 0); } int AbstractNavigationContext::linkCount() const { - return m_linkCount; + return d->m_linkCount; } -void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { - m_prefix = prefix; - m_suffix = suffix; +QString AbstractNavigationContext::prefix() const +{ + return d->m_prefix; +} + +QString AbstractNavigationContext::suffix() const +{ + return d->m_suffix; +} + +void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) +{ + d->m_prefix = prefix; + d->m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { - if(m_previousContext) - return NavigationContextPointer(m_previousContext); + if(d->m_previousContext) + return NavigationContextPointer(d->m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { - if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) + if( d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount ) { - NavigationAction action = m_intLinks[m_selectedLink]; + NavigationAction action = d->m_intLinks[d->m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { - if( !m_links.contains(link) ) { + if( !d->m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } - return execute(m_links[link]); + return execute(d->m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { - return m_selectedLinkAction; + return d->m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { - m_shorten = shorten; + d->m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { - return !m_currentText.isEmpty(); + return !d->m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { - m_currentText += line; + d->m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { - ++m_currentLine; - if(m_currentLine == m_currentPositionLine) { - m_currentText += QStringLiteral(" <-> "); // ><-> is <-> + ++d->m_currentLine; + if(d->m_currentLine == d->m_currentPositionLine) { + d->m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { - - return m_currentText; + return d->m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/navigation/abstractnavigationcontext.h b/language/duchain/navigation/abstractnavigationcontext.h index 30cad6f94..605dd2cfa 100644 --- a/language/duchain/navigation/abstractnavigationcontext.h +++ b/language/duchain/navigation/abstractnavigationcontext.h @@ -1,195 +1,180 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #include #include #include "../indexeddeclaration.h" #include "navigationaction.h" namespace KDevelop { /** A helper-class for elegant colorization of html-strings . * * Initialize it with a html-color like "990000". and colorize strings * using operator() */ struct KDEVPLATFORMLANGUAGE_EXPORT Colorizer { enum FormattingFlag { Nothing = 0x0, Bold = 0x1, Italic = 0x2, Fixed = 0x4 }; Q_DECLARE_FLAGS(Formatting, FormattingFlag) explicit Colorizer(const QString& color, Formatting formatting = Nothing) : m_color(color), m_formatting(formatting) { } QString operator()(const QString& str) const; QString m_color; Formatting m_formatting; }; class AbstractNavigationContext; typedef QExplicitlySharedDataPointer NavigationContextPointer; +class AbstractNavigationContextPrivate; class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationContext : public QObject, public QSharedData { Q_OBJECT - public: - explicit AbstractNavigationContext( KDevelop::TopDUContextPointer topContext = KDevelop::TopDUContextPointer(), AbstractNavigationContext* previousContext = nullptr ); - ~AbstractNavigationContext() override { - } +public: + explicit AbstractNavigationContext(const TopDUContextPointer& topContext = TopDUContextPointer(), + AbstractNavigationContext* previousContext = nullptr); + ~AbstractNavigationContext() override; void nextLink(); void previousLink(); int linkCount() const; void up(); void down(); + + QString prefix() const; + QString suffix() const; void setPrefixSuffix( const QString& prefix, const QString& suffix ); + NavigationContextPointer accept(); NavigationContextPointer back(); NavigationContextPointer accept(IndexedDeclaration decl); NavigationContextPointer acceptLink(const QString& link); NavigationAction currentAction() const; virtual QString name() const = 0; ///Here the context can return html to be displayed. ///NOTE: The DUChain must be locked while this is called. virtual QString html(bool shorten = false); ///Here the context can return a widget to be displayed. ///The widget stays owned by this navigation-context. ///The widget may have a signal "navigateDeclaration(KDevelop::IndexedDeclaration)". ///If that signal is emitted, the new declaration is navigated in the navigation-wdiget. virtual QWidget* widget() const; ///Whether the widget returned by widget() should take the maximum possible spsace. ///The default implementation returns true. virtual bool isWidgetMaximized() const; ///Returns whether this context's string has already been computed, and is up to date. ///After clear() was called, this returns false again. bool alreadyComputed() const; - void setTopContext(TopDUContextPointer context); - TopDUContextPointer topContext() const; + void setTopContext(const TopDUContextPointer& context); - NavigationContextPointer executeLink(QString link); - + void executeLink(const QString& link); NavigationContextPointer execute(const NavigationAction& action); Q_SIGNALS: void contentsChanged(); protected: - /// Returns the html font-size prefix (aka. <small> or similar) for the given mode QString fontSizePrefix(bool shorten) const; /// Returns the html font-size suffix (aka. <small> or similar) for the given mode QString fontSizeSuffix(bool shorten) const; - virtual void setPreviousContext(AbstractNavigationContext* previous); + AbstractNavigationContext* previousContext() const; + virtual void setPreviousContext(AbstractNavigationContext* previousContext); struct TextHandler { explicit TextHandler(AbstractNavigationContext* c) : context(c) { } void operator+=(const QString& str) const { context->addHtml(str); } AbstractNavigationContext* context; }; ///Override this to execute own key-actions using NavigationAction virtual NavigationContextPointer executeKeyAction(QString key); ///Adds given the text to currentHtml() void addHtml(QString html); ///Returns the html text being built in its current state QString currentHtml() const; ///Returns a convenience object that allows writing "modifyHtml() += "Hallo";" TextHandler modifyHtml() { return TextHandler(this); } //Clears the computed html and links void clear(); void addExternalHtml( const QString& text ); ///Creates and registers a link to the given declaration, labeled by the given name virtual void makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ); ///Creates a link that executes the given action and adds it to the current context void makeLink( const QString& name, QString targetId, const NavigationAction& action); ///Creates a link that executes the given action and returns it QString createLink(const QString& name, QString targetId, const NavigationAction& action); - int m_selectedLink; //The link currently selected - NavigationAction m_selectedLinkAction; //Target of the currently selected link - NavigationContextPointer registerChild(DeclarationPointer /*declaration*/); - NavigationContextPointer registerChild( AbstractNavigationContext* context ); - QList m_children; //Useed to keep alive all children until this is deleted - - bool m_shorten; - - int m_currentLine; - - //A counter used while building the html-code to count the used links. - int m_linkCount; - //Something else than -1 if the current position is represented by a line-number, not a link. - int m_currentPositionLine; - QMap m_links; - QMap m_linkLines; //Holds the line for each link - QMap m_intLinks; - AbstractNavigationContext* m_previousContext; - QString m_prefix, m_suffix; - KDevelop::TopDUContextPointer m_topContext; + NavigationContextPointer registerChild(AbstractNavigationContext* context); virtual QString declarationKind(DeclarationPointer decl); static const Colorizer typeHighlight; static const Colorizer errorHighlight; static const Colorizer labelHighlight; static const Colorizer codeHighlight; static const Colorizer propertyHighlight; static const Colorizer navigationHighlight; static const Colorizer importantHighlight; static const Colorizer commentHighlight; static const Colorizer nameHighlight; - private: - QString m_currentText; //Here the text is built + +private: + QScopedPointer d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::Colorizer::Formatting) #endif diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index 727b3bcac..7de4fe417 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,303 +1,323 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationwidget.h" #include #include #include #include #include #include #include #include "../duchainlock.h" #include "util/debug.h" +namespace { +const int maxNavigationWidgetWidth = 580; +const int maxNavigationWidgetHeight = 400; +} + namespace KDevelop { +class AbstractNavigationWidgetPrivate +{ +public: + AbstractNavigationWidgetPrivate(AbstractNavigationWidget* q) : q(q) {} + + void anchorClicked(const QUrl&); + + AbstractNavigationWidget* q; + + NavigationContextPointer m_startContext; + + QPointer m_browser; + QWidget* m_currentWidget = nullptr; + QString m_currentText; + mutable QSize m_idealTextSize; + AbstractNavigationWidget::DisplayHints m_hints = AbstractNavigationWidget::NoHints; + + NavigationContextPointer m_context; +}; + AbstractNavigationWidget::AbstractNavigationWidget() - : m_browser(nullptr), m_currentWidget(nullptr) + : d(new AbstractNavigationWidgetPrivate(this)) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } -const int maxNavigationWidgetWidth = 800; -const int maxNavigationWidgetHeight = 400; - QSize AbstractNavigationWidget::sizeHint() const { - if(m_browser) { + if(d->m_browser) { updateIdealSize(); - QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), - qMin(m_idealTextSize.height(), maxNavigationWidgetHeight)); - if(m_idealTextSize.height()>=maxNavigationWidgetHeight) { + QSize ret = QSize(qMin(d->m_idealTextSize.width(), maxNavigationWidgetWidth), + qMin(d->m_idealTextSize.height(), maxNavigationWidgetHeight)); + if(d->m_idealTextSize.height()>=maxNavigationWidgetHeight) { //make space for the scrollbar in case it's not fitting - ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } - if(m_currentWidget) { - ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); - if(m_currentWidget->sizeHint().width() > ret.width()) - ret.setWidth(m_currentWidget->sizeHint().width()); + if(d->m_currentWidget) { + ret.setHeight( ret.height() + d->m_currentWidget->sizeHint().height() ); + if(d->m_currentWidget->sizeHint().width() > ret.width()) + ret.setWidth(d->m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); - m_browser = new QTextBrowser; + d->m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); - m_browser->setPalette( p ); + d->m_browser->setPalette( p ); - m_browser->setOpenLinks(false); - m_browser->setOpenExternalLinks(false); + d->m_browser->setOpenLinks(false); + d->m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget(m_browser); + layout->addWidget(d->m_browser); layout->setMargin(0); setLayout(layout); - connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); + connect(d->m_browser.data(), &QTextBrowser::anchorClicked, this, [&](const QUrl& url) { d->anchorClicked(url); }); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { - if(m_currentWidget) - layout()->removeWidget(m_currentWidget); - + if(d->m_currentWidget) + layout()->removeWidget(d->m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { - if(m_browser == nullptr) + if(d->m_browser == nullptr) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } - if(context == m_context && (!context || context->alreadyComputed())) + if(context == d->m_context && (!context || context->alreadyComputed())) return; - if (!m_startContext) - m_startContext = m_context; + if (!d->m_startContext) { + d->m_startContext = context; + } - bool wasInitial = (m_context == m_startContext); + bool wasInitial = (d->m_context == d->m_startContext); - m_context = context; + d->m_context = context; update(); - emit contextChanged(wasInitial, m_context == m_startContext); + emit contextChanged(wasInitial, d->m_context == d->m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { - if(m_context && !m_idealTextSize.isValid()) { + if(d->m_context && !d->m_idealTextSize.isValid()) { QTextDocument doc; - doc.setHtml(m_currentText); + doc.setHtml(d->m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setTextWidth(maxNavigationWidgetWidth); - m_idealTextSize.setWidth(maxNavigationWidgetWidth); + d->m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ - m_idealTextSize.setWidth(doc.idealWidth()); + d->m_idealTextSize.setWidth(doc.idealWidth()); } - m_idealTextSize.setHeight(doc.size().height()); + d->m_idealTextSize.setHeight(doc.size().height()); } } -void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) { - m_hints = hints; +void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) +{ + d->m_hints = hints; } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); - Q_ASSERT( m_context ); + Q_ASSERT( d->m_context ); QString html; { DUChainReadLocker lock; - html = m_context->html(); + html = d->m_context->html(); } if(!html.isEmpty()) { - int scrollPos = m_browser->verticalScrollBar()->value(); + int scrollPos = d->m_browser->verticalScrollBar()->value(); - if ( !(m_hints & EmbeddableWidget)) { + if ( !(d->m_hints & EmbeddableWidget)) { // TODO: Only show that the first time, or the first few times this context is shown? html += QStringLiteral("

"); - if (m_context->linkCount() > 0) { + if (d->m_context->linkCount() > 0) { html += i18n("(Hold 'Alt' to show. Navigate via arrow keys, activate by pressing 'Enter')"); } else { html += i18n("(Hold 'Alt' to show this tooltip)"); } html += QStringLiteral("

"); } - m_browser->setHtml( html ); - m_currentText = html; + d->m_currentText = html; - m_idealTextSize = QSize(); QSize hint = sizeHint(); - if(hint.height() >= m_idealTextSize.height()) { - m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + if(hint.height() >= d->m_idealTextSize.height()) { + d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ - m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + d->m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } - m_browser->verticalScrollBar()->setValue(scrollPos); - m_browser->scrollToAnchor(QStringLiteral("currentPosition")); - m_browser->show(); + d->m_browser->verticalScrollBar()->setValue(scrollPos); + d->m_browser->scrollToAnchor(QStringLiteral("currentPosition")); + d->m_browser->show(); }else{ - m_browser->hide(); + d->m_browser->hide(); } - if(m_currentWidget) { - layout()->removeWidget(m_currentWidget); - m_currentWidget->setParent(nullptr); + if(d->m_currentWidget) { + layout()->removeWidget(d->m_currentWidget); + d->m_currentWidget->setParent(nullptr); } - m_currentWidget = m_context->widget(); + d->m_currentWidget = d->m_context->widget(); - m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_browser->setMaximumHeight(10000); + d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + d->m_browser->setMaximumHeight(10000); - if(m_currentWidget) { - if (m_currentWidget->metaObject() + if(d->m_currentWidget) { + if (d->m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { - connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), + connect(d->m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } - layout()->addWidget(m_currentWidget); - if(m_context->isWidgetMaximized()) { + layout()->addWidget(d->m_currentWidget); + if(d->m_context->isWidgetMaximized()) { //Leave unused room to the widget - m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - m_browser->setMaximumHeight(m_idealTextSize.height()); + d->m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + d->m_browser->setMaximumHeight(d->m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { - return m_context; + return d->m_context; } -void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { - setContext(m_context->accept(decl)); +void AbstractNavigationWidget::navigateDeclaration(const IndexedDeclaration& decl) +{ + setContext(d->m_context->accept(decl)); } -void AbstractNavigationWidget::anchorClicked(const QUrl& url) { +void AbstractNavigationWidgetPrivate::anchorClicked(const QUrl& url) +{ //We may get deleted while the call to acceptLink, so make sure we don't crash in that case - QPointer thisPtr(this); - NavigationContextPointer oldContext = m_context; + QPointer thisPtr(q); NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) - setContext( nextContext ); + q->setContext(nextContext); } void AbstractNavigationWidget::next() { - Q_ASSERT( m_context ); - m_context->nextLink(); + Q_ASSERT( d->m_context ); + d->m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { - Q_ASSERT( m_context ); - m_context->previousLink(); + Q_ASSERT( d->m_context ); + d->m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { - Q_ASSERT( m_context ); + Q_ASSERT( d->m_context ); QPointer thisPtr(this); - NavigationContextPointer oldContext = m_context; - NavigationContextPointer nextContext = m_context->accept(); + NavigationContextPointer nextContext = d->m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { QPointer thisPtr(this); - NavigationContextPointer oldContext = m_context; - NavigationContextPointer nextContext = m_context->back(); + NavigationContextPointer nextContext = d->m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { - m_context->up(); + d->m_context->up(); update(); } void AbstractNavigationWidget::down() { - m_context->down(); + d->m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); } } +#include "moc_abstractnavigationwidget.cpp" diff --git a/language/duchain/navigation/abstractnavigationwidget.h b/language/duchain/navigation/abstractnavigationwidget.h index 3315b339c..b73ea5024 100644 --- a/language/duchain/navigation/abstractnavigationwidget.h +++ b/language/duchain/navigation/abstractnavigationwidget.h @@ -1,110 +1,102 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTNAVIGATIONWIDGET_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONWIDGET_H +#include #include #include #include "../../interfaces/quickopendataprovider.h" -#include #include "abstractnavigationcontext.h" class QTextBrowser; + namespace KDevelop { + class AbstractNavigationWidgetPrivate; + /** * This class deleted itself when its part is deleted, so always use a QPointer when referencing it. * The duchain must be read-locked for most operations * */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationWidget : public QWidget, public QuickOpenEmbeddedWidgetInterface { Q_OBJECT public: enum DisplayHint { NoHints = 0x0, // < Normal display EmbeddableWidget = 0x1, // < Omit parts which are only useful for the navigation popup }; Q_DECLARE_FLAGS(DisplayHints, DisplayHint) - AbstractNavigationWidget(); ~AbstractNavigationWidget() override; void setContext(NavigationContextPointer context, int initBrowser = 400); void setDisplayHints(DisplayHints hints); QSize sizeHint() const override; public slots: ///Keyboard-action "next" void next() override; ///Keyboard-action "previous" void previous() override; ///Keyboard-action "accept" void accept() override; void up() override; void down() override; virtual void back(); ///These are temporarily for gettings these events directly from kate ///@todo Do this through a public interface post 4.2 void embeddedWidgetRight(); ///Keyboard-action "previous" void embeddedWidgetLeft(); ///Keyboard-action "accept" void embeddedWidgetAccept(); void embeddedWidgetUp(); void embeddedWidgetDown(); NavigationContextPointer context(); + void navigateDeclaration(const KDevelop::IndexedDeclaration& decl); + Q_SIGNALS: void sizeHintChanged(); /// Emitted whenever the current navigation-context has changed /// @param wasInitial whether the previous context was the initial context /// @param isInitial whether the current context is the initial context void contextChanged(bool wasInitial, bool isInitial); - public slots: - void navigateDeclaration(KDevelop::IndexedDeclaration decl); - private slots: - void anchorClicked(const QUrl&); + protected: void wheelEvent(QWheelEvent* ) override; void updateIdealSize() const; void initBrowser(int height); void update(); - NavigationContextPointer m_startContext; - - TopDUContextPointer m_topContext; - - QPointer m_browser; - QWidget* m_currentWidget; - QString m_currentText; - mutable QSize m_idealTextSize; - DisplayHints m_hints = NoHints; - private: - NavigationContextPointer m_context; + QScopedPointer d; + + Q_PRIVATE_SLOT(d, void anchorClicked(const QUrl&)) }; } - #endif diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 69ebe8e2e..51d926837 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,270 +1,271 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" #include "util/debug.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString KEY_INVOKE_ACTION(int num) { return QString("invoke_action_%1").arg(num); } QString iconForSeverity(IProblem::Severity severity) { switch (severity) { case IProblem::Hint: return QStringLiteral("dialog-information"); case IProblem::Warning: return QStringLiteral("dialog-warning"); case IProblem::Error: return QStringLiteral("dialog-error"); case IProblem::NoSeverity: return {}; } Q_UNREACHABLE(); return {}; } QString htmlImg(const QString& iconName, KIconLoader::Group group) { KIconLoader loader; const int size = loader.currentSize(group); return QString::fromLatin1("") .arg(size) .arg(loader.iconPath(iconName, group)); } } ProblemNavigationContext::ProblemNavigationContext(const QVector& problems, const Flags flags) : m_problems(problems) , m_flags(flags) , m_widget(nullptr) { // Sort problems vector: // 1) By severity // 2) By sourceString, if severities are equals std::sort(m_problems.begin(), m_problems.end(), [](const IProblem::Ptr a, const IProblem::Ptr b) { if (a->severity() < b->severity()) return true; if (a->severity() > b->severity()) return false; if (a->sourceString() < b->sourceString()) return true; return false; }); } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::escapedHtml(const QString& text) const { static const QString htmlStart = QStringLiteral(""); static const QString htmlEnd = QStringLiteral(""); QString result = text.trimmed(); if (!result.startsWith(htmlStart)) return result.toHtmlEscaped(); result.remove(htmlStart); result.remove(htmlEnd); return result; } void ProblemNavigationContext::html(IProblem::Ptr problem) { auto iconPath = iconForSeverity(problem->severity()); + modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); // BEGIN: right column modifyHtml() += QStringLiteral(""); // END: right column modifyHtml() += QStringLiteral("
%1"); modifyHtml() += i18n("Problem in %1", problem->sourceString()); modifyHtml() += QStringLiteral("
"); if (m_flags & ShowLocation) { modifyHtml() += labelHighlight(i18n("Location: ")); makeLink(QStringLiteral("%1 :%2") .arg(problem->finalLocation().document.toUrl().fileName()) .arg(problem->finalLocation().start().line() + 1), QString(), NavigationAction(problem->finalLocation().document.toUrl(), problem->finalLocation().start()) ); modifyHtml() += QStringLiteral("
"); } QString description = escapedHtml(problem->description()); QString explanation = escapedHtml(problem->explanation()); modifyHtml() += description; // Add only non-empty explanation which differs from the problem description. // Skip this if we have more than one problem. if (m_problems.size() == 1 && !explanation.isEmpty() && explanation != description) modifyHtml() += "

" + explanation + "

"; modifyHtml() += QStringLiteral("
"); auto diagnostics = problem->diagnostics(); if (!diagnostics.isEmpty()) { DUChainReadLocker lock; for (auto diagnostic : diagnostics) { modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += escapedHtml(diagnostic->description()); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()).declaration; if (declaration) { modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } else if (range.start().isValid()) { modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") .arg(url.fileName()) .arg(range.start().line() + 1), url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } modifyHtml() += QStringLiteral("

"); } } auto assistant = problem->solutionAssistant(); if (assistant && !assistant->actions().isEmpty()) { modifyHtml() += QString::fromLatin1("").arg("#b3d4ff"); modifyHtml() += QStringLiteral(""; modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); const int startIndex = m_assistantsActions.size(); int currentIndex = startIndex; foreach (auto assistantAction, assistant->actions()) { m_assistantsActions.append(assistantAction); if (currentIndex != startIndex) modifyHtml() += "
"; makeLink(i18n("Solution (%1)", currentIndex + 1), KEY_INVOKE_ACTION( currentIndex ), NavigationAction(KEY_INVOKE_ACTION( currentIndex ))); modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); ++currentIndex; } modifyHtml() += "
"); } } QString ProblemNavigationContext::html(bool shorten) { - m_shorten = shorten; + AbstractNavigationContext::html(shorten); clear(); m_assistantsActions.clear(); int problemIndex = 0; foreach (auto problem, m_problems) { html(problem); if (++problemIndex != m_problems.size()) modifyHtml() += "
"; } return currentHtml(); } NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) { if (key.startsWith(QLatin1String("invoke_action_"))) { const int index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); executeAction(index); } return {}; } void ProblemNavigationContext::executeAction(int index) { if (index < 0 || index >= m_assistantsActions.size()) return; auto action = m_assistantsActions.at(index); Q_ASSERT(action); if (action) { action->execute(); if ( topContext() ) DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); } else { qCWarning(LANGUAGE()) << "No such action"; return; } } diff --git a/language/duchain/navigation/usesnavigationcontext.cpp b/language/duchain/navigation/usesnavigationcontext.cpp index cfe4ced7c..6acc35e32 100644 --- a/language/duchain/navigation/usesnavigationcontext.cpp +++ b/language/duchain/navigation/usesnavigationcontext.cpp @@ -1,62 +1,64 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "usesnavigationcontext.h" #include "useswidget.h" #include #include #include #include using namespace KDevelop; -UsesNavigationContext::UsesNavigationContext( KDevelop::IndexedDeclaration declaration, AbstractNavigationContext* previousContext ) : AbstractNavigationContext(TopDUContextPointer(), previousContext), m_declaration(declaration) { +UsesNavigationContext::UsesNavigationContext(IndexedDeclaration declaration, AbstractNavigationContext* previousContext ) + : AbstractNavigationContext(TopDUContextPointer(), previousContext), m_declaration(declaration) +{ m_widget = new UsesWidget(m_declaration); } UsesNavigationContext::~UsesNavigationContext() { delete m_widget; } QString UsesNavigationContext::name() const { return QStringLiteral("Uses"); } QString UsesNavigationContext::html(bool shorten) { clear(); modifyHtml() += "

" + fontSizePrefix(shorten); - if( m_previousContext ) { + if(auto context = previousContext()) { modifyHtml() += navigationHighlight(i18n("Uses of ")); - makeLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); + makeLink(context->name(), context->name(), NavigationAction(context)); }else{ KDevelop::DUChainReadLocker lock(DUChain::lock()); if(Declaration* decl = m_declaration.data()) { makeLink( i18n("Uses of %1", decl->toString()), DeclarationPointer(decl), NavigationAction::NavigateDeclaration); } } modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } QWidget* UsesNavigationContext::widget() const { return m_widget; } diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index f9b026b3c..cc2e2508d 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1497 +1,1497 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * 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 "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: explicit ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setAutoRaise(true); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setAutoRaise(true); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { - NavigationContextPointer nextContext = widget->context()->execute( + auto nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { QVector problems; auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); foreach (auto modelData, modelsData) { foreach (auto problem, modelData.model->problems(topContext->url())) { DocumentRange problemRange = problem->finalLocation(); if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) problems += problem; } } return problems; } static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { QVector allProblems; auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); foreach (auto modelData, modelsData) { foreach (auto problem, modelData.model->problems(topContext->url())) { allProblems += problem; } } if (allProblems.isEmpty()) return allProblems; std::sort(allProblems.begin(), allProblems.end(), [position](const KDevelop::IProblem::Ptr a, const KDevelop::IProblem::Ptr b) { const auto aRange = a->finalLocation(); const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); QVector closestProblems; // Show problems, located on the same line foreach (auto problem, allProblems) { auto r = problem->finalLocation(); if (r.onSingleLine() && r.start().line() == position.line()) closestProblems += problem; else break; } // If not, only show it in case there's only whitespace // between the current cursor position and the problem line if (closestProblems.isEmpty()) { foreach (auto problem, allProblems) { auto r = problem->finalLocation(); KTextEditor::Range dist; KTextEditor::Cursor bound(r.start().line(), 0); if (position < r.start()) dist = KTextEditor::Range(position, bound); else { bound.setLine(r.end().line() + 1); dist = KTextEditor::Range(bound, position); } if (view->document()->text(dist).trimmed().isEmpty()) closestProblems += problem; else break; } } return closestProblems; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor const auto problems = findProblemsUnderCursor(topContext, position); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor const auto problems = findProblemsCloseToCursor(topContext, position, view); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc"