diff --git a/languages/clang/duchain/unknowndeclarationproblem.cpp b/languages/clang/duchain/unknowndeclarationproblem.cpp --- a/languages/clang/duchain/unknowndeclarationproblem.cpp +++ b/languages/clang/duchain/unknowndeclarationproblem.cpp @@ -47,9 +47,15 @@ #include #include +#include +#include +#include +#include #include +#include + using namespace KDevelop; namespace { @@ -62,11 +68,187 @@ */ const int maxSuggestions = 5; +static bool interrupted = false; + +class UDPScanEventLoop : public QEventLoop +{ + Q_OBJECT +public: + UDPScanEventLoop(QObject *p=Q_NULLPTR) + : QEventLoop(p) + { + // empty the event queue so no events are pending anymore before we start + // this event loop: + QCoreApplication::sendPostedEvents(); + QCoreApplication::flush(); + QAbstractEventDispatcher *eD = QCoreApplication::eventDispatcher(); + if (eD) { + eD->processEvents(QEventLoop::AllEvents); + } else { + QCoreApplication::processEvents(); + } + mouseBtnPresses = 0; + keyReleases = 0; + } + ~UDPScanEventLoop() + { + qApp->removeEventFilter(this); + } + bool event(QEvent *e) override; + bool eventFilter(QObject *obj, QEvent *e) override; + bool isInterrupt(QEvent *e); + + int mouseBtnPresses; + int keyReleases; +}; + +bool UDPScanEventLoop::isInterrupt(QEvent *e) +{ + bool ret; + switch (e->type()) { + // events that probably justify interrupting and bailing out from an ongoing scan: + case QEvent::ApplicationStateChange: + case QEvent::ContextMenu: + case QEvent::Drop: + case QEvent::FocusAboutToChange: + case QEvent::KeyPress: + case QEvent::MouseButtonDblClick: + case QEvent::TouchBegin: + case QEvent::Wheel: + ret = true; + break; + case QEvent::MouseButtonPress: + // the 1st two events are related to the user action that triggered the solutionAssistant + // so we do not bail out for those events. + mouseBtnPresses += 1; + ret = (mouseBtnPresses > 2); + break; + case QEvent::KeyRelease: + // the 1st KeyRelease event might be the Alt key, if the user triggered the solutionAssistant + // by pressing Alt. Just accept the 1st KeyRelease. + keyReleases += 1; + ret = (keyReleases > 1); + break; + default: + ret = false; + break; + } + return ret; +} + +bool UDPScanEventLoop::event(QEvent *e) +{ + if (!interrupted && isInterrupt(e)) { + interrupted = true; +// qWarning() << Q_FUNC_INFO << "interrupting because caught event" << e; + } + + // hand off the event for the actual processing + return QEventLoop::event(e); +} + +bool UDPScanEventLoop::eventFilter(QObject *obj, QEvent *e) +{ + if (!interrupted && isInterrupt(e)) { + interrupted = true; +// qWarning() << Q_FUNC_INFO << "interrupting because caught event" << e; + } + + // hand off the event for the actual processing + return QEventLoop::eventFilter(obj, e); +} + +class UDPWorkerThread : public QThread +{ + Q_OBJECT +public: + UDPWorkerThread(const QualifiedIdentifier& identifier, const KDevelop::Path& file, + const KDevelop::DocumentRange& docrange, ClangFixits* resultPtr, UDPScanEventLoop* eventLoop) + : QThread(Q_NULLPTR) + , m_identifier(identifier) + , m_file(file) + , m_range(docrange) + , m_results(resultPtr) + , m_eventLoop(eventLoop) + { + interrupted = false; + } + + void requestInterruption() + { + m_results = Q_NULLPTR; + interrupted = true; + if (m_eventLoop) { + m_eventLoop->exit(-1); + } + m_eventLoop = Q_NULLPTR; +// qWarning() << Q_FUNC_INFO; + QThread::requestInterruption(); + } + + ClangFixits fixUnknownDeclaration(); + ClangFixits scanResults() + { + if (m_results) { + return *m_results; + } else { + return {}; + } + } + + void run() override; + +protected: + bool isBlacklisted(const QString& path); + QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ); + QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ); + int sharedPathLevel(const QString& a, const QString& b); + KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile); + KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source); + QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ); + QStringList findMatchingIncludeFiles(const QVector declarations); + ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ); + KDevelop::Path::List includePaths( const KDevelop::Path& file ); + QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file); + ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source); + QVector findMatchingDeclarations(const QVector& identifiers); + +private: + const QualifiedIdentifier m_identifier; + const Path m_file; + const KDevelop::DocumentRange m_range; + ClangFixits *m_results; + UDPScanEventLoop *m_eventLoop; +}; + +UDPWorkerThread *scanThread = Q_NULLPTR; + +void UDPWorkerThread::run() +{ + interrupted = false; + if (m_results) { + ClangFixits result = fixUnknownDeclaration(); + if (!interrupted && m_results) { + *m_results = result; + } + // wipe out the public reference to ourselves; + if (scanThread == this) { + scanThread = NULL; + } + } + if (m_eventLoop) { + m_eventLoop->exit(interrupted || !m_results); + } + // m_results may point to a local variable which will go out of scope when we're done + m_results = Q_NULLPTR; + m_eventLoop = Q_NULLPTR; +} + /** * We don't want anything from the bits directory - * we'd rather prefer forwarding includes, such as */ -bool isBlacklisted(const QString& path) +bool UDPWorkerThread::isBlacklisted(const QString& path) { if (ClangHelpers::isSource(path)) return true; @@ -79,12 +261,29 @@ return false; } -QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ) +QStringList UDPWorkerThread::scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth ) { if (!maxDepth) { return {}; } +#if 0 + QCoreApplication::sendPostedEvents(); + QCoreApplication::flush(); + QAbstractEventDispatcher *eD = QCoreApplication::eventDispatcher(); + if (eD) { + if (eD->processEvents(QEventLoop::AllEvents)) { + qWarning() << Q_FUNC_INFO << "processed events, this run aborted"; + interrupted = true; + return {}; + } + } else { + QCoreApplication::processEvents(); + qWarning() << "We may have processed events; app.eD=" << qApp->eventDispatcher() + << "thread.eD=" << QThread::currentThread()->eventDispatcher(); + } +#endif + QStringList candidates; const auto path = dir.absolutePath(); @@ -99,11 +298,18 @@ clangDebug() << "Found candidate file" << filePath; candidates.append( filePath ); } + if (interrupted) { + return {}; + } } maxDepth--; - for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) + for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) { candidates += scanIncludePaths( identifier, QDir{ path + QLatin1Char('/') + subdir }, maxDepth ); + if (interrupted) { + return {}; + } + } return candidates; } @@ -111,12 +317,15 @@ /** * Find files in dir that match the given identifier. Matches common C++ header file extensions only. */ -QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) +QStringList UDPWorkerThread::scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) { const auto stripped_identifier = identifier.last().toString(); QStringList candidates; for( const auto& include : includes ) { candidates += scanIncludePaths( stripped_identifier, QDir{ include.toLocalFile() } ); + if (interrupted) { + return {}; + } } std::sort( candidates.begin(), candidates.end() ); @@ -133,7 +342,7 @@ * boost/vector * have a shared path of 1 */ -int sharedPathLevel(const QString& a, const QString& b) +int UDPWorkerThread::sharedPathLevel(const QString& a, const QString& b) { int shared = -1; for(auto x = a.begin(), y = b.begin(); *x == *y && x != a.end() && y != b.end() ; ++x, ++y ) { @@ -153,7 +362,7 @@ * * TODO: Implement a fallback scheme */ -KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) +KDevelop::DocumentRange UDPWorkerThread::includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); @@ -185,7 +394,7 @@ return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } -KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) +KDevelop::DocumentRange UDPWorkerThread::forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); @@ -235,7 +444,7 @@ * foo::type * type */ -QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) +QVector UDPWorkerThread::findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( file.toUrl() ); @@ -260,7 +469,7 @@ return declarations; } -QStringList findMatchingIncludeFiles(const QVector declarations) +QStringList UDPWorkerThread::findMatchingIncludeFiles(const QVector declarations) { DUChainReadLocker lock; @@ -318,7 +527,7 @@ /** * Takes a filepath and the include paths and determines what directive to use. */ -ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) +ClangFixit UDPWorkerThread::directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) { const auto sourceFolder = source.parent(); const Path canonicalFile( QFileInfo( includefile ).canonicalFilePath() ); @@ -364,7 +573,7 @@ return ClangFixit{directive + QLatin1Char('\n'), range, QObject::tr("Insert \'%1\'").arg(directive)}; } -KDevelop::Path::List includePaths( const KDevelop::Path& file ) +KDevelop::Path::List UDPWorkerThread::includePaths( const KDevelop::Path& file ) { // Find project's custom include paths const auto source = file.toLocalFile(); @@ -376,7 +585,7 @@ /** * Return a list of header files viable for inclusions. All elements will be unique */ -QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file) +QStringList UDPWorkerThread::includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file) { const auto includes = includePaths( file ); if( includes.isEmpty() ) { @@ -396,7 +605,7 @@ /** * Construct viable forward declarations for the type name. */ -ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source) +ClangFixits UDPWorkerThread::forwardDeclarations(const QVector& matchingDeclarations, const Path& source) { DUChainReadLocker lock; ClangFixits fixits; @@ -441,7 +650,7 @@ /** * Search the persistent symbol table for matching declarations for identifiers @p identifiers */ -QVector findMatchingDeclarations(const QVector& identifiers) +QVector UDPWorkerThread::findMatchingDeclarations(const QVector& identifiers) { DUChainReadLocker lock; @@ -464,17 +673,17 @@ return matchingDeclarations; } -ClangFixits fixUnknownDeclaration( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::DocumentRange& docrange ) +ClangFixits UDPWorkerThread::fixUnknownDeclaration() { ClangFixits fixits; - const CursorInRevision cursor{docrange.start().line(), docrange.start().column()}; + const CursorInRevision cursor{m_range.start().line(), m_range.start().column()}; - const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(identifier, file, cursor); + const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(m_identifier, m_file, cursor); const auto matchingDeclarations = findMatchingDeclarations(possibleIdentifiers); if (ClangSettingsManager::self()->assistantsSettings().forwardDeclare) { - for (const auto& fixit : forwardDeclarations(matchingDeclarations, file)) { + for (const auto& fixit : forwardDeclarations(matchingDeclarations, m_file)) { fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; @@ -482,19 +691,19 @@ } } - const auto includefiles = includeFiles(identifier, matchingDeclarations, file); + const auto includefiles = includeFiles(m_identifier, matchingDeclarations, m_file); if (includefiles.isEmpty()) { // return early as the computation of the include paths is quite expensive return fixits; } - const auto includepaths = includePaths( file ); + const auto includepaths = includePaths( m_file ); /* create fixits for candidates */ for( const auto& includeFile : includefiles ) { - const auto fixit = directiveForFile( includeFile, includepaths, file /* UP */ ); + const auto fixit = directiveForFile( includeFile, includepaths, m_file /* UP */ ); if (!fixit.range.isValid()) { - clangDebug() << "unable to create directive for" << includeFile << "in" << file.toLocalFile(); + clangDebug() << "unable to create directive for" << includeFile << "in" << m_file.toLocalFile(); continue; } @@ -536,6 +745,38 @@ IAssistant::Ptr UnknownDeclarationProblem::solutionAssistant() const { const Path path(finalLocation().document.str()); - const auto fixits = allFixits() + fixUnknownDeclaration(m_identifier, path, finalLocation()); - return IAssistant::Ptr(new ClangFixitAssistant(fixits)); + if (scanThread) { + if (scanThread->isRunning()) { + scanThread->requestInterruption(); + } + } + interrupted = false; + UDPScanEventLoop eventLoop; + ClangFixits unknownDeclFixits; + scanThread = new UDPWorkerThread(m_identifier, path, finalLocation(), &unknownDeclFixits, &eventLoop); + UDPWorkerThread::connect(scanThread, &UDPWorkerThread::finished, scanThread, &QObject::deleteLater); +#if 0 + unknownDeclFixits = scanThread->fixUnknownDeclaration(); + if (!interrupted) { + const auto fixits = allFixits() + unknownDeclFixits; + return IAssistant::Ptr(new ClangFixitAssistant(fixits)); + } +#else + // eventLoop's ctor will have processed all pending events so they those won't interrupt the coming scan + qApp->installEventFilter(&eventLoop); + // launch the scan of all known headerfiles for unknown declaration fixes + scanThread->start(QThread::IdlePriority); + // enter our dedicated event loop. We will exit when the worker thread calls eventLoop.exit(). + if (eventLoop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents) == 0) { + return IAssistant::Ptr(new ClangFixitAssistant(allFixits() + unknownDeclFixits)); + } + if (scanThread) { + qWarning() << Q_FUNC_INFO << "scanThread seems to have outlived its usefulness"; + // this shouldn't happen but better safe than sorry: + scanThread->requestInterruption(); + } +#endif + return static_cast(NULL); } + +#include "unknowndeclarationproblem.moc"