File indexing completed on 2024-04-28 04:38:07

0001 /*
0002     SPDX-FileCopyrightText: 2013 Olivier de Gaalon <olivier.jg@gmail.com>
0003     SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>
0004     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "clangsupport.h"
0010 
0011 #include "clangparsejob.h"
0012 
0013 #include "util/clangdebug.h"
0014 #include "util/clangtypes.h"
0015 #include "util/clangutils.h"
0016 
0017 #include "codecompletion/model.h"
0018 
0019 #include "clanghighlighting.h"
0020 
0021 #include <interfaces/icore.h>
0022 #include <interfaces/ilanguagecontroller.h>
0023 #include <interfaces/contextmenuextension.h>
0024 #include <interfaces/idocumentcontroller.h>
0025 
0026 #include "codegen/clangrefactoring.h"
0027 #include "codegen/clangclasshelper.h"
0028 #include "codegen/adaptsignatureassistant.h"
0029 #include "duchain/documentfinderhelpers.h"
0030 #include "duchain/navigationwidget.h"
0031 #include "duchain/clangindex.h"
0032 #include "duchain/clanghelpers.h"
0033 #include "duchain/macrodefinition.h"
0034 #include "duchain/clangparsingenvironmentfile.h"
0035 #include "duchain/duchainutils.h"
0036 
0037 #include <language/assistant/staticassistantsmanager.h>
0038 #include <language/assistant/renameassistant.h>
0039 #include <language/backgroundparser/backgroundparser.h>
0040 #include <language/codecompletion/codecompletion.h>
0041 #include <language/highlighting/codehighlighting.h>
0042 #include <language/interfaces/editorcontext.h>
0043 #include <language/duchain/duchainlock.h>
0044 #include <language/duchain/duchain.h>
0045 #include <language/duchain/duchainutils.h>
0046 #include <language/duchain/parsingenvironment.h>
0047 #include <language/duchain/use.h>
0048 #include <language/editor/documentcursor.h>
0049 
0050 #include "clangsettings/sessionsettings/sessionsettings.h"
0051 
0052 #include <KActionCollection>
0053 #include <KPluginFactory>
0054 
0055 #include <KTextEditor/View>
0056 #include <KTextEditor/ConfigInterface>
0057 
0058 #include <QAction>
0059 
0060 K_PLUGIN_FACTORY_WITH_JSON(KDevClangSupportFactory, "kdevclangsupport.json", registerPlugin<ClangSupport>(); )
0061 
0062 using namespace KDevelop;
0063 
0064 namespace {
0065 
0066 QPair<QString, KTextEditor::Range> lineInDocument(const QUrl &url, const KTextEditor::Cursor& position)
0067 {
0068     KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url);
0069     if (!doc || !doc->textDocument() || !ICore::self()->documentController()->activeTextDocumentView()) {
0070         return {};
0071     }
0072 
0073     const int lineNumber = position.line();
0074     const int lineLength = doc->textDocument()->lineLength(lineNumber);
0075     KTextEditor::Range range(lineNumber, 0, lineNumber, lineLength);
0076     QString line = doc->textDocument()->text(range);
0077     return {line, range};
0078 }
0079 
0080 QPair<TopDUContextPointer, KTextEditor::Range> importedContextForPosition(const QUrl &url, const KTextEditor::Cursor& position)
0081 {
0082     auto pair = lineInDocument(url, position);
0083 
0084     const QString line = pair.first;
0085     if (line.isEmpty())
0086         return {{}, KTextEditor::Range::invalid()};
0087 
0088     KTextEditor::Range wordRange = ClangUtils::rangeForIncludePathSpec(line, pair.second);
0089     if (!wordRange.isValid() || !wordRange.contains(position)) {
0090         return {{}, KTextEditor::Range::invalid()};
0091     }
0092 
0093     // Since this is called by the editor while editing, use a fast timeout so the editor stays responsive
0094     DUChainReadLocker lock(nullptr, 100);
0095     if (!lock.locked()) {
0096         clangDebug() << "Failed to lock the du-chain in time";
0097         return {TopDUContextPointer(), KTextEditor::Range::invalid()};
0098     }
0099 
0100     TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
0101     if (line.isEmpty() || !topContext || !topContext->parsingEnvironmentFile()) {
0102         return {TopDUContextPointer(), KTextEditor::Range::invalid()};
0103     }
0104 
0105     // It's an #include, find out which file was included at the given line
0106     const auto importedParentContexts = topContext->importedParentContexts();
0107     for (const DUContext::Import& imported : importedParentContexts) {
0108         auto context = imported.context(nullptr);
0109         if (context) {
0110             if(topContext->transformFromLocalRevision(topContext->importPosition(context)).line() == wordRange.start().line()) {
0111                 if (auto importedTop = dynamic_cast<TopDUContext*>(context))
0112                     return {TopDUContextPointer(importedTop), wordRange};
0113             }
0114         }
0115     }
0116 
0117     // The last resort. Check if the file is already included (maybe recursively from another files).
0118     // This is needed as clang doesn't visit (clang_getInclusions) those inclusions.
0119     // TODO: Maybe create an assistant that'll report whether the file is already included?
0120     auto includeName = line.mid(wordRange.start().column(), wordRange.end().column() - wordRange.start().column());
0121 
0122     if (!includeName.isEmpty()) {
0123         if (includeName.startsWith(QLatin1Char('.'))) {
0124             const Path dir = Path(url).parent();
0125             includeName = Path(dir, includeName).toLocalFile();
0126         }
0127 
0128         const auto recursiveImports = topContext->recursiveImportIndices();
0129         auto iterator = recursiveImports.iterator();
0130         while (iterator) {
0131             const auto str = (*iterator).url().str();
0132             if (str == includeName || (str.endsWith(includeName) && str[str.size()-includeName.size()-1] == QLatin1Char('/'))) {
0133                 return {TopDUContextPointer((*iterator).data()), wordRange};
0134             }
0135             ++iterator;
0136         }
0137     }
0138 
0139     return {{}, KTextEditor::Range::invalid()};
0140 }
0141 
0142 QPair<TopDUContextPointer, Use> macroExpansionForPosition(const QUrl &url, const KTextEditor::Cursor& position)
0143 {
0144     TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
0145     if (topContext) {
0146         int useAt = topContext->findUseAt(topContext->transformToLocalRevision(position));
0147         if (useAt >= 0) {
0148             Use use = topContext->uses()[useAt];
0149             if (dynamic_cast<MacroDefinition*>(use.usedDeclaration(topContext))) {
0150                 return {TopDUContextPointer(topContext), use};
0151             }
0152         }
0153     }
0154     return {{}, Use()};
0155 }
0156 
0157 }
0158 
0159 ClangSupport::ClangSupport(QObject* parent, const QVariantList& )
0160     : IPlugin( QStringLiteral("kdevclangsupport"), parent )
0161     , ILanguageSupport()
0162     , m_highlighting(nullptr)
0163     , m_refactoring(nullptr)
0164     , m_index(nullptr)
0165 {
0166     clangDebug() << "Detected Clang version:" << ClangHelpers::clangVersion();
0167 
0168     {
0169         const auto builtinDir = ClangHelpers::clangBuiltinIncludePath();
0170         if (!ClangHelpers::isValidClangBuiltingIncludePath(builtinDir)) {
0171             setErrorDescription(i18n("The clang builtin include path \"%1\" is invalid (missing cpuid.h header).\n"
0172                                      "Try setting the KDEV_CLANG_BUILTIN_DIR environment variable manually to fix this.\n"
0173                                      "See also: https://bugs.kde.org/show_bug.cgi?id=393779", builtinDir));
0174             return;
0175         }
0176     }
0177 
0178     setXMLFile( QStringLiteral("kdevclangsupport.rc") );
0179 
0180     ClangIntegration::DUChainUtils::registerDUChainItems();
0181 
0182     m_highlighting = new ClangHighlighting(this);
0183     m_refactoring = new ClangRefactoring(this);
0184     m_index.reset(new ClangIndex);
0185 
0186     auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() );
0187     connect(model, &CodeCompletion::registeredToView,
0188             this, &ClangSupport::disableKeywordCompletion);
0189     connect(model, &CodeCompletion::unregisteredFromView,
0190             this, &ClangSupport::enableKeywordCompletion);
0191     const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList();
0192     for (const auto& type : mimeTypes) {
0193         KDevelop::IBuddyDocumentFinder::addFinder(type, this);
0194     }
0195 
0196     auto assistantsManager = core()->languageController()->staticAssistantsManager();
0197     assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this)));
0198     assistantsManager->registerAssistant(StaticAssistant::Ptr(new AdaptSignatureAssistant(this)));
0199 
0200     connect(ICore::self()->documentController(), &IDocumentController::documentActivated,
0201             this, &ClangSupport::documentActivated);
0202 }
0203 
0204 ClangSupport::~ClangSupport()
0205 {
0206     parseLock()->lockForWrite();
0207     // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state
0208     parseLock()->unlock();
0209 
0210     const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList();
0211     for (const auto& type : mimeTypes) {
0212         KDevelop::IBuddyDocumentFinder::removeFinder(type);
0213     }
0214 
0215     ClangIntegration::DUChainUtils::unregisterDUChainItems();
0216 }
0217 
0218 KDevelop::ConfigPage* ClangSupport::configPage(int number, QWidget* parent)
0219 {
0220     return number == 0 ? new SessionSettings(parent) : nullptr;
0221 }
0222 
0223 int ClangSupport::configPages() const
0224 {
0225     return 1;
0226 }
0227 
0228 ParseJob* ClangSupport::createParseJob(const IndexedString& url)
0229 {
0230     return new ClangParseJob(url, this);
0231 }
0232 
0233 QString ClangSupport::name() const
0234 {
0235     return QStringLiteral("clang");
0236 }
0237 
0238 ICodeHighlighting* ClangSupport::codeHighlighting() const
0239 {
0240     return m_highlighting;
0241 }
0242 
0243 BasicRefactoring* ClangSupport::refactoring() const
0244 {
0245     return m_refactoring;
0246 }
0247 
0248 ICreateClassHelper* ClangSupport::createClassHelper() const
0249 {
0250     return new ClangClassHelper;
0251 }
0252 
0253 ClangIndex* ClangSupport::index()
0254 {
0255     return m_index.data();
0256 }
0257 
0258 bool ClangSupport::areBuddies(const QUrl &url1, const QUrl& url2)
0259 {
0260     return DocumentFinderHelpers::areBuddies(url1, url2);
0261 }
0262 
0263 bool ClangSupport::buddyOrder(const QUrl &url1, const QUrl& url2)
0264 {
0265     return DocumentFinderHelpers::buddyOrder(url1, url2);
0266 }
0267 
0268 QVector<QUrl> ClangSupport::potentialBuddies(const QUrl& url) const
0269 {
0270     return DocumentFinderHelpers::potentialBuddies(url);
0271 }
0272 
0273 void ClangSupport::createActionsForMainWindow (Sublime::MainWindow* /*window*/, QString& _xmlFile, KActionCollection& actions)
0274 {
0275     _xmlFile = xmlFile();
0276 
0277     QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration"));
0278     renameDeclarationAction->setText( i18nc("@action", "Rename Declaration") );
0279     renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0280     actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R);
0281     connect(renameDeclarationAction, &QAction::triggered,
0282             m_refactoring, &ClangRefactoring::executeRenameAction);
0283 
0284     QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition"));
0285     moveIntoSourceAction->setText(i18nc("@action", "Move into Source"));
0286     actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S);
0287     connect(moveIntoSourceAction, &QAction::triggered,
0288             m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction);
0289 }
0290 
0291 KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0292 {
0293     ContextMenuExtension cm;
0294     auto *ec = dynamic_cast<KDevelop::EditorContext *>(context);
0295 
0296     if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) {
0297         // It's a C++ file, let's add our context menu.
0298         m_refactoring->fillContextMenu(cm, context, parent);
0299     }
0300     return cm;
0301 }
0302 
0303 KTextEditor::Range ClangSupport::specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position)
0304 {
0305     DUChainReadLocker lock;
0306     const QPair<TopDUContextPointer, Use> macroExpansion = macroExpansionForPosition(url, position);
0307     if (macroExpansion.first) {
0308         return macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range);
0309     }
0310 
0311     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
0312     if(import.first) {
0313         return import.second;
0314     }
0315 
0316     return KTextEditor::Range::invalid();
0317 }
0318 
0319 QPair<QUrl, KTextEditor::Cursor> ClangSupport::specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position)
0320 {
0321     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
0322     DUChainReadLocker lock;
0323     if (import.first) {
0324         return qMakePair(import.first->url().toUrl(), KTextEditor::Cursor(0,0));
0325     }
0326 
0327     return {{}, KTextEditor::Cursor::invalid()};
0328 }
0329 
0330 QPair<QWidget*, KTextEditor::Range> ClangSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position)
0331 {
0332     DUChainReadLocker lock;
0333     const QPair<TopDUContextPointer, Use> macroExpansion = macroExpansionForPosition(url, position);
0334     if (macroExpansion.first) {
0335         Declaration* declaration = macroExpansion.second.usedDeclaration(macroExpansion.first.data());
0336         const MacroDefinition::Ptr macroDefinition(dynamic_cast<MacroDefinition*>(declaration));
0337         Q_ASSERT(macroDefinition);
0338         auto rangeInRevision = macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range.start);
0339         return {
0340             new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)),
0341             macroExpansion.second.m_range.castToSimpleRange()
0342         };
0343     }
0344 
0345     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
0346 
0347     if (import.first) {
0348         return {import.first->createNavigationWidget(), import.second};
0349     }
0350     return {nullptr, KTextEditor::Range::invalid()};
0351 }
0352 
0353 QString ClangSupport::indentationSample() const
0354 {
0355     return QStringLiteral("class C{\n class D {\n void c() {\n int m;\n }\n }\n};\n");
0356 }
0357 
0358 TopDUContext* ClangSupport::standardContext(const QUrl &url, bool /*proxyContext*/)
0359 {
0360     ClangParsingEnvironment env;
0361     return DUChain::self()->chainForDocument(url, &env);
0362 }
0363 
0364 void ClangSupport::documentActivated(IDocument* doc)
0365 {
0366     TopDUContext::Features features;
0367     {
0368         DUChainReadLocker lock;
0369         auto ctx = DUChainUtils::standardContextForUrl(doc->url());
0370         if (!ctx) {
0371             return;
0372         }
0373 
0374         auto file = ctx->parsingEnvironmentFile();
0375         if (!file) {
0376             return;
0377         }
0378 
0379         if (file->type() != CppParsingEnvironment) {
0380             return;
0381         }
0382 
0383         if (file->needsUpdate()) {
0384             return;
0385         }
0386 
0387         features = ctx->features();
0388     }
0389 
0390     const auto indexedUrl = IndexedString(doc->url());
0391 
0392     auto sessionData = ClangIntegration::DUChainUtils::findParseSessionData(indexedUrl, index()->translationUnitForUrl(IndexedString(doc->url())));
0393     if (sessionData) {
0394         return;
0395     }
0396 
0397     if ((features & TopDUContext::AllDeclarationsContextsAndUses) != TopDUContext::AllDeclarationsContextsAndUses) {
0398         // the file was parsed in simplified mode, we need to reparse it to get all data
0399         // now that its opened in the editor
0400         features = TopDUContext::AllDeclarationsContextsAndUses;
0401     } else {
0402         features = static_cast<TopDUContext::Features>(ClangParseJob::AttachASTWithoutUpdating | features);
0403         if (ICore::self()->languageController()->backgroundParser()->isQueued(indexedUrl)) {
0404             // The document is already scheduled for parsing (happens when opening a project with an active document)
0405             // The background parser will optimize the previous request out, so we need to update highlighting
0406             features = static_cast<TopDUContext::Features>(ClangParseJob::UpdateHighlighting | features);
0407         }
0408     }
0409     ICore::self()->languageController()->backgroundParser()->addDocument(indexedUrl, features);
0410 }
0411 
0412 static void setKeywordCompletion(KTextEditor::View* view, bool enabled)
0413 {
0414     if (auto config = qobject_cast<KTextEditor::ConfigInterface*>(view)) {
0415         config->setConfigValue(QStringLiteral("keyword-completion"), enabled);
0416     }
0417 }
0418 
0419 int ClangSupport::suggestedReparseDelayForChange(KTextEditor::Document* /*doc*/, const KTextEditor::Range& /*changedRange*/,
0420                                                  const QString& /*changedText*/, bool /*removal*/) const
0421 {
0422     return ILanguageSupport::DefaultDelay;
0423 }
0424 
0425 void ClangSupport::disableKeywordCompletion(KTextEditor::View* view)
0426 {
0427     setKeywordCompletion(view, false);
0428 }
0429 
0430 void ClangSupport::enableKeywordCompletion(KTextEditor::View* view)
0431 {
0432     setKeywordCompletion(view, true);
0433 }
0434 
0435 #include "clangsupport.moc"
0436 #include "moc_clangsupport.cpp"