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