File indexing completed on 2024-06-16 04:23:14
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "abstractnavigationcontext.h" 0008 0009 #include <KLocalizedString> 0010 0011 #include "abstractdeclarationnavigationcontext.h" 0012 #include "abstractnavigationwidget.h" 0013 #include "usesnavigationcontext.h" 0014 #include "../../../interfaces/icore.h" 0015 #include "../../../interfaces/idocumentcontroller.h" 0016 #include "../functiondeclaration.h" 0017 #include "../namespacealiasdeclaration.h" 0018 #include "../types/functiontype.h" 0019 #include "../types/structuretype.h" 0020 #include <debug.h> 0021 #include <interfaces/icontextbrowser.h> 0022 #include <interfaces/idocumentationcontroller.h> 0023 #include <interfaces/iplugincontroller.h> 0024 0025 namespace KDevelop { 0026 class AbstractNavigationContextPrivate 0027 { 0028 public: 0029 QVector<NavigationContextPointer> m_children; //Used to keep alive all children until this is deleted 0030 0031 int m_selectedLink = 0; //The link currently selected 0032 NavigationAction m_selectedLinkAction; //Target of the currently selected link 0033 0034 bool m_shorten = false; 0035 0036 //A counter used while building the html-code to count the used links. 0037 int m_linkCount = -1; 0038 //Something else than -1 if the current position is represented by a line-number, not a link. 0039 int m_currentLine = 0; 0040 int m_currentPositionLine = 0; 0041 QMap<QString, NavigationAction> m_links; 0042 QMap<int, int> m_linkLines; //Holds the line for each link 0043 QMap<int, NavigationAction> m_intLinks; 0044 AbstractNavigationContext* m_previousContext; 0045 TopDUContextPointer m_topContext; 0046 0047 QString m_currentText; //Here the text is built 0048 }; 0049 0050 void AbstractNavigationContext::setTopContext(const TopDUContextPointer& context) 0051 { 0052 Q_D(AbstractNavigationContext); 0053 0054 d->m_topContext = context; 0055 } 0056 0057 TopDUContextPointer AbstractNavigationContext::topContext() const 0058 { 0059 Q_D(const AbstractNavigationContext); 0060 0061 return d->m_topContext; 0062 } 0063 0064 AbstractNavigationContext::AbstractNavigationContext(const TopDUContextPointer& topContext, 0065 AbstractNavigationContext* previousContext) 0066 : d_ptr(new AbstractNavigationContextPrivate) 0067 { 0068 Q_D(AbstractNavigationContext); 0069 0070 d->m_previousContext = previousContext; 0071 d->m_topContext = topContext; 0072 0073 qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor"); 0074 qRegisterMetaType<IDocumentation::Ptr>("IDocumentation::Ptr"); 0075 } 0076 0077 AbstractNavigationContext::~AbstractNavigationContext() 0078 { 0079 } 0080 0081 void AbstractNavigationContext::makeLink(const QString& name, const DeclarationPointer& declaration, 0082 NavigationAction::Type actionType) 0083 { 0084 NavigationAction action(declaration, actionType); 0085 makeLink(name, QString(), action); 0086 } 0087 0088 QString AbstractNavigationContext::createLink(const QString& name, const QString&, const NavigationAction& action) 0089 { 0090 Q_D(AbstractNavigationContext); 0091 0092 if (d->m_shorten) { 0093 //Do not create links in shortened mode, it's only for viewing 0094 return typeHighlight(name.toHtmlEscaped()); 0095 } 0096 0097 // NOTE: Since the by definition in the HTML standard some uri components 0098 // are case-insensitive, we define a new lowercase link-id for each 0099 // link. Otherwise Qt 5 seems to mess up the casing and the link 0100 // cannot be matched when it's executed. 0101 QString hrefId = QStringLiteral("link_%1").arg(d->m_links.count()); 0102 0103 d->m_links[hrefId] = action; 0104 d->m_intLinks[d->m_linkCount] = action; 0105 d->m_linkLines[d->m_linkCount] = d->m_currentLine; 0106 if (d->m_currentPositionLine == d->m_currentLine) { 0107 d->m_currentPositionLine = -1; 0108 d->m_selectedLink = d->m_linkCount; 0109 } 0110 0111 QString str = name.toHtmlEscaped(); 0112 if (d->m_linkCount == d->m_selectedLink) 0113 str = QLatin1String("<font color=\"#880088\">") + str + QLatin1String("</font>"); 0114 0115 QString ret = QLatin1String("<a href=\"") + hrefId + QLatin1Char('\"') + 0116 ((d->m_linkCount == d->m_selectedLink && 0117 d->m_currentPositionLine == 0118 -1) ? QStringLiteral(" name = \"currentPosition\"") : QString()) + QLatin1Char('>') + str + 0119 QLatin1String("</a>"); 0120 0121 if (d->m_selectedLink == d->m_linkCount) 0122 d->m_selectedLinkAction = action; 0123 0124 ++d->m_linkCount; 0125 return ret; 0126 } 0127 0128 void AbstractNavigationContext::makeLink(const QString& name, const QString& targetId, const NavigationAction& action) 0129 { 0130 modifyHtml() += createLink(name, targetId, action); 0131 } 0132 0133 void AbstractNavigationContext::clear() 0134 { 0135 Q_D(AbstractNavigationContext); 0136 0137 d->m_linkCount = 0; 0138 d->m_currentLine = 0; 0139 d->m_currentText.clear(); 0140 d->m_links.clear(); 0141 d->m_intLinks.clear(); 0142 d->m_linkLines.clear(); 0143 } 0144 0145 void AbstractNavigationContext::executeLink(const QString& link) 0146 { 0147 Q_D(AbstractNavigationContext); 0148 0149 const auto actionIt = d->m_links.constFind(link); 0150 if (actionIt == d->m_links.constEnd()) 0151 return; 0152 0153 execute(*actionIt); 0154 } 0155 0156 NavigationContextPointer AbstractNavigationContext::executeKeyAction(const QString& key) 0157 { 0158 Q_UNUSED(key); 0159 return NavigationContextPointer(this); 0160 } 0161 0162 NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) 0163 { 0164 Q_D(AbstractNavigationContext); 0165 0166 if (action.targetContext) 0167 return NavigationContextPointer(action.targetContext); 0168 0169 if (action.type == NavigationAction::ExecuteKey) 0170 return executeKeyAction(action.key); 0171 0172 if (!action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty())) { 0173 qCDebug(LANGUAGE) << "Navigation-action has invalid declaration"; 0174 return NavigationContextPointer(this); 0175 } 0176 0177 switch (action.type) { 0178 case NavigationAction::ExecuteKey: 0179 break; 0180 case NavigationAction::None: 0181 qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget"; 0182 break; 0183 case NavigationAction::NavigateDeclaration: 0184 { 0185 auto ctx = dynamic_cast<AbstractDeclarationNavigationContext*>(d->m_previousContext); 0186 if (ctx && ctx->declaration() == action.decl) 0187 return NavigationContextPointer(d->m_previousContext); 0188 return registerChild(action.decl); 0189 } 0190 case NavigationAction::NavigateUses: 0191 { 0192 auto* browser = ICore::self()->pluginController()->extensionForPlugin<IContextBrowser>(); 0193 if (browser) { 0194 browser->showUses(action.decl); 0195 return NavigationContextPointer(this); 0196 } 0197 [[fallthrough]]; 0198 } 0199 case NavigationAction::ShowUses: { 0200 return registerChild(new UsesNavigationContext(action.decl.data(), this)); 0201 } 0202 case NavigationAction::JumpToSource: 0203 { 0204 QUrl doc = action.document; 0205 KTextEditor::Cursor cursor = action.cursor; 0206 { 0207 DUChainReadLocker lock(DUChain::lock()); 0208 if (action.decl) { 0209 if (doc.isEmpty()) { 0210 doc = action.decl->url().toUrl(); 0211 /* if(action.decl->internalContext()) 0212 cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); 0213 else*/ 0214 cursor = action.decl->rangeInCurrentRevision().start(); 0215 } 0216 0217 action.decl->activateSpecialization(); 0218 } 0219 } 0220 0221 //This is used to execute the slot delayed in the event-loop, so crashes are avoided 0222 QMetaObject::invokeMethod(ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, 0223 Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor)); 0224 break; 0225 } 0226 case NavigationAction::ShowDocumentation: { 0227 auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); 0228 // This is used to execute the slot delayed in the event-loop, so crashes are avoided 0229 // which can happen e.g. due to focus change events resulting in tooltip destruction and thus this object 0230 QMetaObject::invokeMethod( 0231 ICore::self()->documentationController(), "showDocumentation", Qt::QueuedConnection, 0232 Q_ARG(IDocumentation::Ptr, doc)); 0233 } 0234 break; 0235 } 0236 0237 return NavigationContextPointer(this); 0238 } 0239 0240 AbstractNavigationContext* AbstractNavigationContext::previousContext() const 0241 { 0242 Q_D(const AbstractNavigationContext); 0243 0244 return d->m_previousContext; 0245 } 0246 0247 void AbstractNavigationContext::setPreviousContext(AbstractNavigationContext* previous) 0248 { 0249 Q_D(AbstractNavigationContext); 0250 0251 d->m_previousContext = previous; 0252 } 0253 0254 NavigationContextPointer AbstractNavigationContext::registerChild(AbstractNavigationContext* context) 0255 { 0256 Q_D(AbstractNavigationContext); 0257 0258 d->m_children << NavigationContextPointer(context); 0259 return d->m_children.last(); 0260 } 0261 0262 NavigationContextPointer AbstractNavigationContext::registerChild(const DeclarationPointer& declaration) 0263 { 0264 Q_D(AbstractNavigationContext); 0265 0266 // Lock the DUChain for reading, since the createNavigationWidget 0267 // method will eventually need read access to the DUChain and 0268 // does not lock it itself. 0269 DUChainReadLocker lock; 0270 0271 // Check that declaration and its context are still valid 0272 // (they might have been deleted while we were waiting for the lock) 0273 if (!declaration || !declaration->context()) { 0274 return NavigationContextPointer(this); 0275 } 0276 0277 //We create a navigation-widget here, and steal its context.. evil ;) 0278 QScopedPointer<AbstractNavigationWidget> navigationWidget( 0279 declaration->context()->createNavigationWidget(declaration.data())); 0280 if (navigationWidget) { 0281 NavigationContextPointer ret = navigationWidget->context(); 0282 ret->setPreviousContext(this); 0283 d->m_children << ret; 0284 return ret; 0285 } else { 0286 return NavigationContextPointer(this); 0287 } 0288 } 0289 0290 const int lineJump = 3; 0291 0292 bool AbstractNavigationContext::down() 0293 { 0294 Q_D(AbstractNavigationContext); 0295 0296 //Make sure link-count is valid 0297 if (d->m_linkCount == -1) { 0298 DUChainReadLocker lock; 0299 html(); 0300 } 0301 0302 // select first link when we enter via down 0303 if (d->m_selectedLink == -1 && d->m_linkCount) { 0304 d->m_selectedLink = 0; 0305 d->m_currentPositionLine = -1; 0306 return true; 0307 } 0308 0309 int fromLine = d->m_currentPositionLine; 0310 0311 // try to select the next link within our lineJump distance 0312 if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { 0313 if (fromLine == -1) 0314 fromLine = d->m_linkLines[d->m_selectedLink]; 0315 0316 for (int newSelectedLink = d->m_selectedLink + 1; newSelectedLink < d->m_linkCount; ++newSelectedLink) { 0317 if (d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) { 0318 d->m_selectedLink = newSelectedLink; 0319 d->m_currentPositionLine = -1; 0320 return true; 0321 } 0322 } 0323 } 0324 0325 if (fromLine == d->m_currentLine - 1) // nothing to do, we are at the end of the document 0326 return false; 0327 0328 // scroll down by applying the lineJump 0329 if (fromLine == -1) 0330 fromLine = 0; 0331 0332 d->m_currentPositionLine = fromLine + lineJump; 0333 0334 if (d->m_currentPositionLine >= d->m_currentLine) { 0335 d->m_currentPositionLine = d->m_currentLine - 1; 0336 } 0337 return fromLine != d->m_currentPositionLine; 0338 } 0339 0340 bool AbstractNavigationContext::up() 0341 { 0342 Q_D(AbstractNavigationContext); 0343 0344 //Make sure link-count is valid 0345 if (d->m_linkCount == -1) { 0346 DUChainReadLocker lock; 0347 html(); 0348 } 0349 0350 // select last link when we enter via up 0351 if (d->m_selectedLink == -1 && d->m_linkCount) { 0352 d->m_selectedLink = d->m_linkCount - 1; 0353 d->m_currentPositionLine = -1; 0354 return true; 0355 } 0356 0357 int fromLine = d->m_currentPositionLine; 0358 0359 if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { 0360 if (fromLine == -1) 0361 fromLine = d->m_linkLines[d->m_selectedLink]; 0362 0363 for (int newSelectedLink = d->m_selectedLink - 1; newSelectedLink >= 0; --newSelectedLink) { 0364 if (d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) { 0365 d->m_selectedLink = newSelectedLink; 0366 d->m_currentPositionLine = -1; 0367 return true; 0368 } 0369 } 0370 } 0371 0372 if (fromLine == -1) 0373 fromLine = d->m_currentLine - 1; 0374 0375 d->m_currentPositionLine = fromLine - lineJump; 0376 if (d->m_currentPositionLine < 0) 0377 d->m_currentPositionLine = 0; 0378 0379 return fromLine || d->m_currentPositionLine; 0380 } 0381 0382 bool AbstractNavigationContext::nextLink() 0383 { 0384 Q_D(AbstractNavigationContext); 0385 0386 //Make sure link-count is valid 0387 if (d->m_linkCount == -1) { 0388 DUChainReadLocker lock; 0389 html(); 0390 } 0391 0392 if (!d->m_linkCount) 0393 return false; 0394 0395 d->m_currentPositionLine = -1; 0396 0397 d->m_selectedLink++; 0398 if (d->m_selectedLink >= d->m_linkCount) { 0399 d->m_selectedLink = 0; 0400 return false; 0401 } 0402 return true; 0403 } 0404 0405 bool AbstractNavigationContext::previousLink() 0406 { 0407 Q_D(AbstractNavigationContext); 0408 0409 //Make sure link-count is valid 0410 if (d->m_linkCount == -1) { 0411 DUChainReadLocker lock; 0412 html(); 0413 } 0414 0415 if (!d->m_linkCount) 0416 return false; 0417 0418 d->m_currentPositionLine = -1; 0419 0420 d->m_selectedLink--; 0421 if (d->m_selectedLink < 0) { 0422 d->m_selectedLink = d->m_linkCount - 1; 0423 return false; 0424 } 0425 return true; 0426 } 0427 0428 int AbstractNavigationContext::linkCount() const 0429 { 0430 Q_D(const AbstractNavigationContext); 0431 0432 return d->m_linkCount; 0433 } 0434 0435 void AbstractNavigationContext::resetNavigation() 0436 { 0437 Q_D(AbstractNavigationContext); 0438 0439 d->m_currentPositionLine = -1; 0440 d->m_selectedLink = -1; 0441 d->m_selectedLinkAction = {}; 0442 } 0443 0444 NavigationContextPointer AbstractNavigationContext::back() 0445 { 0446 Q_D(AbstractNavigationContext); 0447 0448 if (d->m_previousContext) 0449 return NavigationContextPointer(d->m_previousContext); 0450 else 0451 return NavigationContextPointer(this); 0452 } 0453 0454 NavigationContextPointer AbstractNavigationContext::accept() 0455 { 0456 Q_D(AbstractNavigationContext); 0457 0458 if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { 0459 NavigationAction action = d->m_intLinks[d->m_selectedLink]; 0460 return execute(action); 0461 } 0462 return NavigationContextPointer(this); 0463 } 0464 0465 NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) 0466 { 0467 if (decl.data()) { 0468 NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); 0469 return execute(action); 0470 } else { 0471 return NavigationContextPointer(this); 0472 } 0473 } 0474 0475 NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) 0476 { 0477 Q_D(AbstractNavigationContext); 0478 0479 const auto actionIt = d->m_links.constFind(link); 0480 if (actionIt == d->m_links.constEnd()) { 0481 qCDebug(LANGUAGE) << "Executed unregistered link " << link; 0482 return NavigationContextPointer(this); 0483 } 0484 0485 return execute(*actionIt); 0486 } 0487 0488 NavigationAction AbstractNavigationContext::currentAction() const 0489 { 0490 Q_D(const AbstractNavigationContext); 0491 0492 return d->m_selectedLinkAction; 0493 } 0494 0495 QString AbstractNavigationContext::declarationKind(const DeclarationPointer& decl) 0496 { 0497 const auto* function = dynamic_cast<const AbstractFunctionDeclaration*>(decl.data()); 0498 0499 QString kind; 0500 0501 if (decl->isTypeAlias()) 0502 kind = i18n("Typedef"); 0503 else if (decl->kind() == Declaration::Type) { 0504 if (decl->type<StructureType>()) 0505 kind = i18n("Class"); 0506 } else if (decl->kind() == Declaration::Instance) { 0507 kind = i18n("Variable"); 0508 } else if (decl->kind() == Declaration::Namespace) { 0509 kind = i18n("Namespace"); 0510 } 0511 0512 if (auto* alias = dynamic_cast<NamespaceAliasDeclaration*>(decl.data())) { 0513 if (alias->identifier().isEmpty()) 0514 kind = i18n("Namespace import"); 0515 else 0516 kind = i18n("Namespace alias"); 0517 } 0518 0519 if (function) 0520 kind = i18n("Function"); 0521 0522 if (decl->isForwardDeclaration()) 0523 kind = i18n("Forward Declaration"); 0524 0525 return kind; 0526 } 0527 0528 QString AbstractNavigationContext::html(bool shorten) 0529 { 0530 Q_D(AbstractNavigationContext); 0531 0532 d->m_shorten = shorten; 0533 return QString(); 0534 } 0535 0536 bool AbstractNavigationContext::alreadyComputed() const 0537 { 0538 Q_D(const AbstractNavigationContext); 0539 0540 return !d->m_currentText.isEmpty(); 0541 } 0542 0543 bool AbstractNavigationContext::isWidgetMaximized() const 0544 { 0545 return true; 0546 } 0547 0548 QWidget* AbstractNavigationContext::widget() const 0549 { 0550 return nullptr; 0551 } 0552 0553 ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line 0554 static QStringList splitAndKeep(QString str, const QRegExp& regExp) 0555 { 0556 QStringList ret; 0557 int place = regExp.indexIn(str); 0558 while (place != -1) { 0559 ret << str.left(place + regExp.matchedLength()); 0560 str.remove(0, place + regExp.matchedLength()); 0561 place = regExp.indexIn(str); 0562 } 0563 ret << str; 0564 return ret; 0565 } 0566 0567 void AbstractNavigationContext::addHtml(const QString& html) 0568 { 0569 Q_D(AbstractNavigationContext); 0570 0571 QRegExp newLineRegExp(QStringLiteral("<br>|<br */>|</p>")); 0572 const auto lines = splitAndKeep(html, newLineRegExp); 0573 for (const QString& line : lines) { 0574 d->m_currentText += line; 0575 if (line.indexOf(newLineRegExp) != -1) { 0576 ++d->m_currentLine; 0577 if (d->m_currentLine == d->m_currentPositionLine) { 0578 d->m_currentText += QLatin1String( 0579 "<font color=\"#880088\"> <a name = \"currentPosition\" ><-></a> </font>"); // ><-> is <-> 0580 } 0581 } 0582 } 0583 } 0584 0585 QString AbstractNavigationContext::currentHtml() const 0586 { 0587 Q_D(const AbstractNavigationContext); 0588 0589 return d->m_currentText; 0590 } 0591 0592 QString Colorizer::operator()(const QString& str) const 0593 { 0594 QString ret = QLatin1String("<font color=\"#") + m_color + QLatin1String("\">") + str + QLatin1String("</font>"); 0595 0596 if (m_formatting & Fixed) 0597 ret = QLatin1String("<tt>") + ret + QLatin1String("</tt>"); 0598 if (m_formatting & Bold) 0599 ret = QLatin1String("<b>") + ret + QLatin1String("</b>"); 0600 if (m_formatting & Italic) 0601 ret = QLatin1String("<i>") + ret + QLatin1String("</i>"); 0602 0603 return ret; 0604 } 0605 0606 const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); 0607 const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); 0608 const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); 0609 const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); 0610 const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); 0611 const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); 0612 const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral( 0613 "000000"), Colorizer::Bold | Colorizer::Italic); 0614 const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); 0615 const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); 0616 } 0617 0618 #include "moc_abstractnavigationcontext.cpp"