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"