1 /*
2     SPDX-FileCopyrightText: 2013 Olivier de Gaalon <olivier.jg@gmail.com>
3     SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>
4     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "clangsupport.h"
10 
11 #include "clangparsejob.h"
12 
13 #include "util/clangdebug.h"
14 #include "util/clangtypes.h"
15 #include "util/clangutils.h"
16 
17 #include "codecompletion/model.h"
18 
19 #include "clanghighlighting.h"
20 
21 #include <interfaces/icore.h>
22 #include <interfaces/ilanguagecontroller.h>
23 #include <interfaces/contextmenuextension.h>
24 #include <interfaces/idocumentcontroller.h>
25 
26 #include "codegen/clangrefactoring.h"
27 #include "codegen/clangclasshelper.h"
28 #include "codegen/adaptsignatureassistant.h"
29 #include "duchain/documentfinderhelpers.h"
30 #include "duchain/navigationwidget.h"
31 #include "duchain/clangindex.h"
32 #include "duchain/clanghelpers.h"
33 #include "duchain/macrodefinition.h"
34 #include "duchain/clangparsingenvironmentfile.h"
35 #include "duchain/duchainutils.h"
36 
37 #include <language/assistant/staticassistantsmanager.h>
38 #include <language/assistant/renameassistant.h>
39 #include <language/backgroundparser/backgroundparser.h>
40 #include <language/codecompletion/codecompletion.h>
41 #include <language/highlighting/codehighlighting.h>
42 #include <language/interfaces/editorcontext.h>
43 #include <language/duchain/duchainlock.h>
44 #include <language/duchain/duchain.h>
45 #include <language/duchain/duchainutils.h>
46 #include <language/duchain/parsingenvironment.h>
47 #include <language/duchain/use.h>
48 #include <language/editor/documentcursor.h>
49 
50 #include "clangsettings/sessionsettings/sessionsettings.h"
51 
52 #include <KActionCollection>
53 #include <KPluginFactory>
54 
55 #include <KTextEditor/View>
56 #include <KTextEditor/ConfigInterface>
57 
58 #include <QAction>
59 
60 K_PLUGIN_FACTORY_WITH_JSON(KDevClangSupportFactory, "kdevclangsupport.json", registerPlugin<ClangSupport>(); )
61 
62 using namespace KDevelop;
63 
64 namespace {
65 
lineInDocument(const QUrl & url,const KTextEditor::Cursor & position)66 QPair<QString, KTextEditor::Range> lineInDocument(const QUrl &url, const KTextEditor::Cursor& position)
67 {
68     KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url);
69     if (!doc || !doc->textDocument() || !ICore::self()->documentController()->activeTextDocumentView()) {
70         return {};
71     }
72 
73     const int lineNumber = position.line();
74     const int lineLength = doc->textDocument()->lineLength(lineNumber);
75     KTextEditor::Range range(lineNumber, 0, lineNumber, lineLength);
76     QString line = doc->textDocument()->text(range);
77     return {line, range};
78 }
79 
importedContextForPosition(const QUrl & url,const KTextEditor::Cursor & position)80 QPair<TopDUContextPointer, KTextEditor::Range> importedContextForPosition(const QUrl &url, const KTextEditor::Cursor& position)
81 {
82     auto pair = lineInDocument(url, position);
83 
84     const QString line = pair.first;
85     if (line.isEmpty())
86         return {{}, KTextEditor::Range::invalid()};
87 
88     KTextEditor::Range wordRange = ClangUtils::rangeForIncludePathSpec(line, pair.second);
89     if (!wordRange.isValid() || !wordRange.contains(position)) {
90         return {{}, KTextEditor::Range::invalid()};
91     }
92 
93     // Since this is called by the editor while editing, use a fast timeout so the editor stays responsive
94     DUChainReadLocker lock(nullptr, 100);
95     if (!lock.locked()) {
96         clangDebug() << "Failed to lock the du-chain in time";
97         return {TopDUContextPointer(), KTextEditor::Range::invalid()};
98     }
99 
100     TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
101     if (line.isEmpty() || !topContext || !topContext->parsingEnvironmentFile()) {
102         return {TopDUContextPointer(), KTextEditor::Range::invalid()};
103     }
104 
105     // It's an #include, find out which file was included at the given line
106     const auto importedParentContexts = topContext->importedParentContexts();
107     for (const DUContext::Import& imported : importedParentContexts) {
108         auto context = imported.context(nullptr);
109         if (context) {
110             if(topContext->transformFromLocalRevision(topContext->importPosition(context)).line() == wordRange.start().line()) {
111                 if (auto importedTop = dynamic_cast<TopDUContext*>(context))
112                     return {TopDUContextPointer(importedTop), wordRange};
113             }
114         }
115     }
116 
117     // The last resort. Check if the file is already included (maybe recursively from another files).
118     // This is needed as clang doesn't visit (clang_getInclusions) those inclusions.
119     // TODO: Maybe create an assistant that'll report whether the file is already included?
120     auto includeName = line.mid(wordRange.start().column(), wordRange.end().column() - wordRange.start().column());
121 
122     if (!includeName.isEmpty()) {
123         if (includeName.startsWith(QLatin1Char('.'))) {
124             const Path dir = Path(url).parent();
125             includeName = Path(dir, includeName).toLocalFile();
126         }
127 
128         const auto recursiveImports = topContext->recursiveImportIndices();
129         auto iterator = recursiveImports.iterator();
130         while (iterator) {
131             const auto str = (*iterator).url().str();
132             if (str == includeName || (str.endsWith(includeName) && str[str.size()-includeName.size()-1] == QLatin1Char('/'))) {
133                 return {TopDUContextPointer((*iterator).data()), wordRange};
134             }
135             ++iterator;
136         }
137     }
138 
139     return {{}, KTextEditor::Range::invalid()};
140 }
141 
macroExpansionForPosition(const QUrl & url,const KTextEditor::Cursor & position)142 QPair<TopDUContextPointer, Use> macroExpansionForPosition(const QUrl &url, const KTextEditor::Cursor& position)
143 {
144     TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
145     if (topContext) {
146         int useAt = topContext->findUseAt(topContext->transformToLocalRevision(position));
147         if (useAt >= 0) {
148             Use use = topContext->uses()[useAt];
149             if (dynamic_cast<MacroDefinition*>(use.usedDeclaration(topContext))) {
150                 return {TopDUContextPointer(topContext), use};
151             }
152         }
153     }
154     return {{}, Use()};
155 }
156 
157 }
158 
ClangSupport(QObject * parent,const QVariantList &)159 ClangSupport::ClangSupport(QObject* parent, const QVariantList& )
160     : IPlugin( QStringLiteral("kdevclangsupport"), parent )
161     , ILanguageSupport()
162     , m_highlighting(nullptr)
163     , m_refactoring(nullptr)
164     , m_index(nullptr)
165 {
166     clangDebug() << "Detected Clang version:" << ClangHelpers::clangVersion();
167 
168     {
169         const auto builtinDir = ClangHelpers::clangBuiltinIncludePath();
170         if (!ClangHelpers::isValidClangBuiltingIncludePath(builtinDir)) {
171             setErrorDescription(i18n("The clang builtin include path \"%1\" is invalid (missing cpuid.h header).\n"
172                                      "Try setting the KDEV_CLANG_BUILTIN_DIR environment variable manually to fix this.\n"
173                                      "See also: https://bugs.kde.org/show_bug.cgi?id=393779", builtinDir));
174             return;
175         }
176     }
177 
178     setXMLFile( QStringLiteral("kdevclangsupport.rc") );
179 
180     ClangIntegration::DUChainUtils::registerDUChainItems();
181 
182     m_highlighting = new ClangHighlighting(this);
183     m_refactoring = new ClangRefactoring(this);
184     m_index.reset(new ClangIndex);
185 
186     auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() );
187     connect(model, &CodeCompletion::registeredToView,
188             this, &ClangSupport::disableKeywordCompletion);
189     connect(model, &CodeCompletion::unregisteredFromView,
190             this, &ClangSupport::enableKeywordCompletion);
191     const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList();
192     for (const auto& type : mimeTypes) {
193         KDevelop::IBuddyDocumentFinder::addFinder(type, this);
194     }
195 
196     auto assistantsManager = core()->languageController()->staticAssistantsManager();
197     assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this)));
198     assistantsManager->registerAssistant(StaticAssistant::Ptr(new AdaptSignatureAssistant(this)));
199 
200     connect(ICore::self()->documentController(), &IDocumentController::documentActivated,
201             this, &ClangSupport::documentActivated);
202 }
203 
~ClangSupport()204 ClangSupport::~ClangSupport()
205 {
206     parseLock()->lockForWrite();
207     // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state
208     parseLock()->unlock();
209 
210     const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList();
211     for (const auto& type : mimeTypes) {
212         KDevelop::IBuddyDocumentFinder::removeFinder(type);
213     }
214 
215     ClangIntegration::DUChainUtils::unregisterDUChainItems();
216 }
217 
configPage(int number,QWidget * parent)218 KDevelop::ConfigPage* ClangSupport::configPage(int number, QWidget* parent)
219 {
220     return number == 0 ? new SessionSettings(parent) : nullptr;
221 }
222 
configPages() const223 int ClangSupport::configPages() const
224 {
225     return 1;
226 }
227 
createParseJob(const IndexedString & url)228 ParseJob* ClangSupport::createParseJob(const IndexedString& url)
229 {
230     return new ClangParseJob(url, this);
231 }
232 
name() const233 QString ClangSupport::name() const
234 {
235     return QStringLiteral("clang");
236 }
237 
codeHighlighting() const238 ICodeHighlighting* ClangSupport::codeHighlighting() const
239 {
240     return m_highlighting;
241 }
242 
refactoring() const243 BasicRefactoring* ClangSupport::refactoring() const
244 {
245     return m_refactoring;
246 }
247 
createClassHelper() const248 ICreateClassHelper* ClangSupport::createClassHelper() const
249 {
250     return new ClangClassHelper;
251 }
252 
index()253 ClangIndex* ClangSupport::index()
254 {
255     return m_index.data();
256 }
257 
areBuddies(const QUrl & url1,const QUrl & url2)258 bool ClangSupport::areBuddies(const QUrl &url1, const QUrl& url2)
259 {
260     return DocumentFinderHelpers::areBuddies(url1, url2);
261 }
262 
buddyOrder(const QUrl & url1,const QUrl & url2)263 bool ClangSupport::buddyOrder(const QUrl &url1, const QUrl& url2)
264 {
265     return DocumentFinderHelpers::buddyOrder(url1, url2);
266 }
267 
potentialBuddies(const QUrl & url) const268 QVector<QUrl> ClangSupport::potentialBuddies(const QUrl& url) const
269 {
270     return DocumentFinderHelpers::potentialBuddies(url);
271 }
272 
createActionsForMainWindow(Sublime::MainWindow *,QString & _xmlFile,KActionCollection & actions)273 void ClangSupport::createActionsForMainWindow (Sublime::MainWindow* /*window*/, QString& _xmlFile, KActionCollection& actions)
274 {
275     _xmlFile = xmlFile();
276 
277     QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration"));
278     renameDeclarationAction->setText( i18nc("@action", "Rename Declaration") );
279     renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
280     actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R);
281     connect(renameDeclarationAction, &QAction::triggered,
282             m_refactoring, &ClangRefactoring::executeRenameAction);
283 
284     QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition"));
285     moveIntoSourceAction->setText(i18nc("@action", "Move into Source"));
286     actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S);
287     connect(moveIntoSourceAction, &QAction::triggered,
288             m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction);
289 }
290 
contextMenuExtension(KDevelop::Context * context,QWidget * parent)291 KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
292 {
293     ContextMenuExtension cm;
294     auto *ec = dynamic_cast<KDevelop::EditorContext *>(context);
295 
296     if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) {
297         // It's a C++ file, let's add our context menu.
298         m_refactoring->fillContextMenu(cm, context, parent);
299     }
300     return cm;
301 }
302 
specialLanguageObjectRange(const QUrl & url,const KTextEditor::Cursor & position)303 KTextEditor::Range ClangSupport::specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position)
304 {
305     DUChainReadLocker lock;
306     const QPair<TopDUContextPointer, Use> macroExpansion = macroExpansionForPosition(url, position);
307     if (macroExpansion.first) {
308         return macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range);
309     }
310 
311     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
312     if(import.first) {
313         return import.second;
314     }
315 
316     return KTextEditor::Range::invalid();
317 }
318 
specialLanguageObjectJumpCursor(const QUrl & url,const KTextEditor::Cursor & position)319 QPair<QUrl, KTextEditor::Cursor> ClangSupport::specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position)
320 {
321     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
322     DUChainReadLocker lock;
323     if (import.first) {
324         return qMakePair(import.first->url().toUrl(), KTextEditor::Cursor(0,0));
325     }
326 
327     return {{}, KTextEditor::Cursor::invalid()};
328 }
329 
specialLanguageObjectNavigationWidget(const QUrl & url,const KTextEditor::Cursor & position)330 QPair<QWidget*, KTextEditor::Range> ClangSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position)
331 {
332     DUChainReadLocker lock;
333     const QPair<TopDUContextPointer, Use> macroExpansion = macroExpansionForPosition(url, position);
334     if (macroExpansion.first) {
335         Declaration* declaration = macroExpansion.second.usedDeclaration(macroExpansion.first.data());
336         const MacroDefinition::Ptr macroDefinition(dynamic_cast<MacroDefinition*>(declaration));
337         Q_ASSERT(macroDefinition);
338         auto rangeInRevision = macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range.start);
339         return {
340             new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)),
341             macroExpansion.second.m_range.castToSimpleRange()
342         };
343     }
344 
345     const QPair<TopDUContextPointer, KTextEditor::Range> import = importedContextForPosition(url, position);
346 
347     if (import.first) {
348         return {import.first->createNavigationWidget(), import.second};
349     }
350     return {nullptr, KTextEditor::Range::invalid()};
351 }
352 
standardContext(const QUrl & url,bool)353 TopDUContext* ClangSupport::standardContext(const QUrl &url, bool /*proxyContext*/)
354 {
355     ClangParsingEnvironment env;
356     return DUChain::self()->chainForDocument(url, &env);
357 }
358 
documentActivated(IDocument * doc)359 void ClangSupport::documentActivated(IDocument* doc)
360 {
361     TopDUContext::Features features;
362     {
363         DUChainReadLocker lock;
364         auto ctx = DUChainUtils::standardContextForUrl(doc->url());
365         if (!ctx) {
366             return;
367         }
368 
369         auto file = ctx->parsingEnvironmentFile();
370         if (!file) {
371             return;
372         }
373 
374         if (file->type() != CppParsingEnvironment) {
375             return;
376         }
377 
378         if (file->needsUpdate()) {
379             return;
380         }
381 
382         features = ctx->features();
383     }
384 
385     const auto indexedUrl = IndexedString(doc->url());
386 
387     auto sessionData = ClangIntegration::DUChainUtils::findParseSessionData(indexedUrl, index()->translationUnitForUrl(IndexedString(doc->url())));
388     if (sessionData) {
389         return;
390     }
391 
392     if ((features & TopDUContext::AllDeclarationsContextsAndUses) != TopDUContext::AllDeclarationsContextsAndUses) {
393         // the file was parsed in simplified mode, we need to reparse it to get all data
394         // now that its opened in the editor
395         features = TopDUContext::AllDeclarationsContextsAndUses;
396     } else {
397         features = static_cast<TopDUContext::Features>(ClangParseJob::AttachASTWithoutUpdating | features);
398         if (ICore::self()->languageController()->backgroundParser()->isQueued(indexedUrl)) {
399             // The document is already scheduled for parsing (happens when opening a project with an active document)
400             // The background parser will optimize the previous request out, so we need to update highlighting
401             features = static_cast<TopDUContext::Features>(ClangParseJob::UpdateHighlighting | features);
402         }
403     }
404     ICore::self()->languageController()->backgroundParser()->addDocument(indexedUrl, features);
405 }
406 
setKeywordCompletion(KTextEditor::View * view,bool enabled)407 static void setKeywordCompletion(KTextEditor::View* view, bool enabled)
408 {
409     if (auto config = qobject_cast<KTextEditor::ConfigInterface*>(view)) {
410         config->setConfigValue(QStringLiteral("keyword-completion"), enabled);
411     }
412 }
413 
suggestedReparseDelayForChange(KTextEditor::Document *,const KTextEditor::Range &,const QString &,bool) const414 int ClangSupport::suggestedReparseDelayForChange(KTextEditor::Document* /*doc*/, const KTextEditor::Range& /*changedRange*/,
415                                                  const QString& /*changedText*/, bool /*removal*/) const
416 {
417     return ILanguageSupport::DefaultDelay;
418 }
419 
disableKeywordCompletion(KTextEditor::View * view)420 void ClangSupport::disableKeywordCompletion(KTextEditor::View* view)
421 {
422     setKeywordCompletion(view, false);
423 }
424 
enableKeywordCompletion(KTextEditor::View * view)425 void ClangSupport::enableKeywordCompletion(KTextEditor::View* view)
426 {
427     setKeywordCompletion(view, true);
428 }
429 
430 #include "clangsupport.moc"
431