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\" >&lt;-&gt;</a> </font>");                        // >&lt;-&gt; 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"